From bae0edbc4ac8c14f963b16177e5ec36916bc55a8 Mon Sep 17 00:00:00 2001 From: Neil Dewhurst Date: Thu, 17 Nov 2022 18:23:28 +0000 Subject: [PATCH 1/5] Migration from other repos --- antora.yml | 6 +- modules/ROOT/content-nav.adoc | 36 +- .../csv-files/artists-fieldterminator.csv | 4 + .../csv-files/artists-with-escaped-char.csv | 1 + .../csv-files/artists-with-headers.csv | 5 + .../dev/ql/load-csv/csv-files/artists.csv | 4 + ...eges.png => grant-privileges-database.png} | Bin modules/ROOT/images/grant-privileges-dbms.png | Bin 0 -> 84496 bytes ..._syntax.png => grant-privileges-graph.png} | Bin ...ntax.png => grant-privileges-overview.png} | Bin modules/ROOT/images/graph1.svg | 81 - modules/ROOT/images/graph2.svg | 51 - modules/ROOT/images/graph3.svg | 98 - modules/ROOT/images/graph4.svg | 91 - modules/ROOT/images/graph5.svg | 149 - modules/ROOT/images/graph6.svg | 92 - .../images/graph_aggregating_functions.svg | 109 - .../images/graph_call_subquery_clause.svg | 63 - modules/ROOT/images/graph_delete_clause.svg | 62 - modules/ROOT/images/graph_foreach_clause.svg | 66 - modules/ROOT/images/graph_limit_clause.svg | 71 - modules/ROOT/images/graph_list_functions.svg | 91 - modules/ROOT/images/graph_match_clause.svg | 123 - .../images/graph_match_clause_backtick.svg | 130 - .../graph_match_clause_variable_length.svg | 171 - modules/ROOT/images/graph_merge_clause.svg | 135 - .../ROOT/images/graph_numeric_functions.svg | 99 - .../images/graph_optional_match_clause.svg | 125 - modules/ROOT/images/graph_order_by_clause.svg | 50 - .../ROOT/images/graph_predicate_functions.svg | 107 - modules/ROOT/images/graph_remove_clause.svg | 54 - modules/ROOT/images/graph_return_clause.svg | 41 - .../ROOT/images/graph_scalar_functions.svg | 91 - modules/ROOT/images/graph_set_clause.svg | 63 - modules/ROOT/images/graph_skip_clause.svg | 71 - .../ROOT/images/graph_spatial_functions.svg | 40 - modules/ROOT/images/graph_union_clause.svg | 66 - modules/ROOT/images/graph_where_clause.svg | 122 - modules/ROOT/images/graph_with_clause.svg | 85 - ...e.png => privilege-hierarchy-database.png} | Bin .../ROOT/images/privilege-hierarchy-dbms.png | Bin 0 -> 103987 bytes ...rchy.png => privilege-hierarchy-graph.png} | Bin ..._grant_and_deny_syntax_dbms_privileges.png | Bin 112745 -> 0 bytes .../ROOT/images/privileges_hierarchy_dbms.png | Bin 133936 -> 0 bytes .../pages/access-control/built-in-roles.adoc | 80 +- .../database-administration.adoc | 846 +-- .../access-control/dbms-administration.adoc | 827 +-- modules/ROOT/pages/access-control/index.adoc | 91 +- .../pages/access-control/limitations.adoc | 223 +- .../access-control/manage-privileges.adoc | 341 +- .../pages/access-control/manage-roles.adoc | 236 +- .../pages/access-control/manage-users.adoc | 287 +- .../access-control/privileges-reads.adoc | 148 +- .../access-control/privileges-writes.adoc | 273 +- modules/ROOT/pages/aliases.adoc | 1378 ----- modules/ROOT/pages/clauses/call-subquery.adoc | 477 +- modules/ROOT/pages/clauses/call.adoc | 305 +- modules/ROOT/pages/clauses/create.adoc | 286 +- modules/ROOT/pages/clauses/delete.adoc | 152 +- modules/ROOT/pages/clauses/foreach.adoc | 84 +- modules/ROOT/pages/clauses/index.adoc | 333 +- modules/ROOT/pages/clauses/limit.adoc | 220 +- .../ROOT/pages/clauses/listing-functions.adoc | 358 +- .../pages/clauses/listing-procedures.adoc | 327 +- modules/ROOT/pages/clauses/load-csv.adoc | 194 +- modules/ROOT/pages/clauses/match.adoc | 1162 +++- modules/ROOT/pages/clauses/merge.adoc | 919 +++- .../ROOT/pages/clauses/optional-match.adoc | 205 +- modules/ROOT/pages/clauses/order-by.adoc | 235 +- modules/ROOT/pages/clauses/remove.adoc | 129 +- modules/ROOT/pages/clauses/return.adoc | 247 +- modules/ROOT/pages/clauses/set.adoc | 482 +- modules/ROOT/pages/clauses/skip.adoc | 161 +- .../pages/clauses/transaction-clauses.adoc | 333 -- modules/ROOT/pages/clauses/union.adoc | 114 +- modules/ROOT/pages/clauses/unwind.adoc | 162 +- modules/ROOT/pages/clauses/use.adoc | 36 +- modules/ROOT/pages/clauses/where.adoc | 974 +++- modules/ROOT/pages/clauses/with.adoc | 243 +- modules/ROOT/pages/constraints/examples.adoc | 1078 +--- modules/ROOT/pages/constraints/index.adoc | 33 +- modules/ROOT/pages/constraints/syntax.adoc | 235 +- modules/ROOT/pages/databases.adoc | 914 ++-- ...ions-additions-removals-compatibility.adoc | 1532 +----- .../ROOT/pages/execution-plans/db-hits.adoc | 98 +- modules/ROOT/pages/execution-plans/index.adoc | 28 +- .../execution-plans/operator-summary.adoc | 332 +- .../ROOT/pages/execution-plans/operators.adoc | 4435 +++++++--------- .../shortestpath-planning.adoc | 444 +- modules/ROOT/pages/functions/aggregating.adoc | 1131 ++-- modules/ROOT/pages/functions/index.adoc | 842 +-- modules/ROOT/pages/functions/list.adoc | 860 +-- modules/ROOT/pages/functions/load-csv.adoc | 55 +- .../functions/mathematical-logarithmic.adoc | 248 +- .../pages/functions/mathematical-numeric.adoc | 578 +- .../functions/mathematical-trigonometric.adoc | 617 +-- modules/ROOT/pages/functions/predicate.adoc | 625 ++- modules/ROOT/pages/functions/scalar.adoc | 1270 +++-- modules/ROOT/pages/functions/spatial.adoc | 713 +-- modules/ROOT/pages/functions/string.adoc | 751 ++- .../pages/functions/temporal/duration.adoc | 515 +- .../ROOT/pages/functions/temporal/index.adoc | 4664 +++++++---------- .../ROOT/pages/functions/user-defined.adoc | 93 +- modules/ROOT/pages/index.adoc | 67 +- .../pages/indexes-for-full-text-search.adoc | 363 +- .../pages/indexes-for-search-performance.adoc | 3252 ++++++------ modules/ROOT/pages/introduction/index.adoc | 109 +- .../introduction/neo4j-databases-graphs.adoc | 62 +- .../quering-updating-administering.adoc | 18 +- .../ROOT/pages/introduction/transactions.adoc | 42 +- .../ROOT/pages/introduction/uniqueness.adoc | 68 +- modules/ROOT/pages/keyword-glossary.adoc | 1755 ++----- .../pages/query-tuning/advanced-example.adoc | 257 +- .../pages/query-tuning/basic-example.adoc | 200 +- .../how-do-i-profile-a-query.adoc | 414 ++ modules/ROOT/pages/query-tuning/index.adoc | 405 +- modules/ROOT/pages/query-tuning/indexes.adoc | 1106 ++-- .../pages/query-tuning/query-options.adoc | 254 +- .../pages/query-tuning/query-profile.adoc | 25 - modules/ROOT/pages/query-tuning/using.adoc | 1026 ++-- modules/ROOT/pages/styleguide.adoc | 131 +- modules/ROOT/pages/syntax/comments.adoc | 15 +- modules/ROOT/pages/syntax/expressions.adoc | 296 +- modules/ROOT/pages/syntax/index.adoc | 157 +- modules/ROOT/pages/syntax/lists.adoc | 612 ++- modules/ROOT/pages/syntax/maps.adoc | 196 +- modules/ROOT/pages/syntax/naming.adoc | 11 +- modules/ROOT/pages/syntax/operators.adoc | 755 ++- modules/ROOT/pages/syntax/parameters.adoc | 107 +- modules/ROOT/pages/syntax/patterns.adoc | 194 +- modules/ROOT/pages/syntax/reserved.adoc | 9 +- modules/ROOT/pages/syntax/spatial.adoc | 305 +- modules/ROOT/pages/syntax/temporal.adoc | 1088 ++-- modules/ROOT/pages/syntax/values.adoc | 36 +- modules/ROOT/pages/syntax/variables.adoc | 14 +- .../ROOT/pages/syntax/working-with-null.adoc | 42 +- package-lock.json | 104 +- package.json | 3 +- preview.yml | 4 +- scripts/dot.js | 22 + 140 files changed, 23308 insertions(+), 27828 deletions(-) create mode 100644 modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists-fieldterminator.csv create mode 100644 modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists-with-escaped-char.csv create mode 100644 modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists-with-headers.csv create mode 100644 modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists.csv rename modules/ROOT/images/{privileges_grant_and_deny_syntax_database_privileges.png => grant-privileges-database.png} (100%) create mode 100644 modules/ROOT/images/grant-privileges-dbms.png rename modules/ROOT/images/{privileges_on_graph_syntax.png => grant-privileges-graph.png} (100%) rename modules/ROOT/images/{privileges_grant_and_deny_syntax.png => grant-privileges-overview.png} (100%) delete mode 100644 modules/ROOT/images/graph1.svg delete mode 100644 modules/ROOT/images/graph2.svg delete mode 100644 modules/ROOT/images/graph3.svg delete mode 100644 modules/ROOT/images/graph4.svg delete mode 100644 modules/ROOT/images/graph5.svg delete mode 100644 modules/ROOT/images/graph6.svg delete mode 100644 modules/ROOT/images/graph_aggregating_functions.svg delete mode 100644 modules/ROOT/images/graph_call_subquery_clause.svg delete mode 100644 modules/ROOT/images/graph_delete_clause.svg delete mode 100644 modules/ROOT/images/graph_foreach_clause.svg delete mode 100644 modules/ROOT/images/graph_limit_clause.svg delete mode 100644 modules/ROOT/images/graph_list_functions.svg delete mode 100644 modules/ROOT/images/graph_match_clause.svg delete mode 100644 modules/ROOT/images/graph_match_clause_backtick.svg delete mode 100644 modules/ROOT/images/graph_match_clause_variable_length.svg delete mode 100644 modules/ROOT/images/graph_merge_clause.svg delete mode 100644 modules/ROOT/images/graph_numeric_functions.svg delete mode 100644 modules/ROOT/images/graph_optional_match_clause.svg delete mode 100644 modules/ROOT/images/graph_order_by_clause.svg delete mode 100644 modules/ROOT/images/graph_predicate_functions.svg delete mode 100644 modules/ROOT/images/graph_remove_clause.svg delete mode 100644 modules/ROOT/images/graph_return_clause.svg delete mode 100644 modules/ROOT/images/graph_scalar_functions.svg delete mode 100644 modules/ROOT/images/graph_set_clause.svg delete mode 100644 modules/ROOT/images/graph_skip_clause.svg delete mode 100644 modules/ROOT/images/graph_spatial_functions.svg delete mode 100644 modules/ROOT/images/graph_union_clause.svg delete mode 100644 modules/ROOT/images/graph_where_clause.svg delete mode 100644 modules/ROOT/images/graph_with_clause.svg rename modules/ROOT/images/{privileges_hierarchy_database.png => privilege-hierarchy-database.png} (100%) create mode 100644 modules/ROOT/images/privilege-hierarchy-dbms.png rename modules/ROOT/images/{privileges_hierarchy.png => privilege-hierarchy-graph.png} (100%) delete mode 100644 modules/ROOT/images/privileges_grant_and_deny_syntax_dbms_privileges.png delete mode 100644 modules/ROOT/images/privileges_hierarchy_dbms.png delete mode 100644 modules/ROOT/pages/aliases.adoc delete mode 100644 modules/ROOT/pages/clauses/transaction-clauses.adoc create mode 100644 modules/ROOT/pages/query-tuning/how-do-i-profile-a-query.adoc delete mode 100644 modules/ROOT/pages/query-tuning/query-profile.adoc create mode 100644 scripts/dot.js diff --git a/antora.yml b/antora.yml index 2c565f86f..3c6b07bbd 100644 --- a/antora.yml +++ b/antora.yml @@ -1,10 +1,10 @@ name: cypher-manual title: Cypher Manual -version: '4.4' +version: '4.3' start_page: ROOT:index.adoc nav: - modules/ROOT/content-nav.adoc asciidoc: attributes: - neo4j-version: '4.4' - neo4j-version-exact: '4.4.9' + neo4j-version: '4.3' + neo4j-version-exact: '4.3.21' diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index c18f6595d..5bb9f3737 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -1,6 +1,7 @@ * xref:index.adoc[] * xref:introduction/index.adoc[] +// ** xref:introduction/cypher-introduction.adoc[] ** xref:introduction/neo4j-databases-graphs.adoc[] ** xref:introduction/quering-updating-administering.adoc[] ** xref:introduction/transactions.adoc[] @@ -45,8 +46,6 @@ ** xref:clauses/load-csv.adoc[] ** xref:clauses/listing-functions.adoc[] ** xref:clauses/listing-procedures.adoc[] -** xref:clauses/transaction-clauses.adoc#query-listing-transactions[SHOW TRANSACTIONS] -** xref:clauses/transaction-clauses.adoc#query-terminate-transactions[TERMINATE TRANSACTIONS] * xref:functions/index.adoc[] ** xref:functions/predicate.adoc[] @@ -58,20 +57,41 @@ ** xref:functions/mathematical-trigonometric.adoc[] ** xref:functions/string.adoc[] ** xref:functions/temporal/index.adoc[] +*** xref:functions/temporal/index.adoc#functions-date[date()] +*** xref:functions/temporal/index.adoc#functions-datetime[datetime()] +*** xref:functions/temporal/index.adoc#functions-localdatetime[localdatetime()] +*** xref:functions/temporal/index.adoc#functions-localtime[localtime()] +*** xref:functions/temporal/index.adoc#functions-time[time()] ** xref:functions/temporal/duration.adoc[] ** xref:functions/spatial.adoc[] ** xref:functions/load-csv.adoc[] ** xref:functions/user-defined.adoc[] * xref:indexes-for-search-performance.adoc[] +** xref:indexes-for-search-performance.adoc#administration-indexes-types[Indexes (types and limitations)] +** xref:indexes-for-search-performance.adoc#administration-indexes-examples[Creating indexes] +** xref:indexes-for-search-performance.adoc#administration-indexes-list-indexes[Listing indexes] +** xref:indexes-for-search-performance.adoc#administration-indexes-drop-indexes[Deleting indexes] +** xref:indexes-for-search-performance.adoc#administration-indexes-examples-deprecated-syntax[Deprecated syntax] + + * xref:indexes-for-full-text-search.adoc[] +** xref:indexes-for-full-text-search.adoc#administration-indexes-fulltext-search-manage[Full-text search procedures] +** xref:indexes-for-full-text-search.adoc#administration-indexes-fulltext-search-create-and-configure[Create and configure full-text indexes] +** xref:indexes-for-full-text-search.adoc#administration-indexes-fulltext-search-query[Query full-text indexes] +** xref:indexes-for-full-text-search.adoc#administration-indexes-fulltext-search-drop[Drop full-text indexes] * xref:constraints/index.adoc[] ** xref:constraints/syntax.adoc[] ** xref:constraints/examples.adoc[] * xref:databases.adoc[] -* xref:aliases.adoc[] +** xref:databases.adoc#administration-databases-show-databases[Listing databases] +** xref:databases.adoc#administration-databases-create-database[Creating databases] +** xref:databases.adoc#administration-databases-stop-database[Stopping databases] +** xref:databases.adoc#administration-databases-start-database[Starting databases] +** xref:databases.adoc#administration-databases-drop-database[Deleting databases] +** xref:databases.adoc#administration-wait-nowait[Wait options] * xref:access-control/index.adoc[] ** xref:access-control/manage-users.adoc[] @@ -85,22 +105,22 @@ ** xref:access-control/limitations.adoc[] * xref:query-tuning/index.adoc[] -** xref:query-tuning/query-options.adoc[] -** xref:query-tuning/query-profile.adoc[] +** xref:query-tuning/query-options.adoc#cypher-query-options[Query options] +** xref:query-tuning/how-do-i-profile-a-query.adoc#how-do-i-profile-a-query[Profiling a query] ** xref:query-tuning/indexes.adoc[] ** xref:query-tuning/basic-example.adoc[] ** xref:query-tuning/advanced-example.adoc[] ** xref:query-tuning/using.adoc[] * xref:execution-plans/index.adoc[] -** xref:execution-plans/db-hits.adoc[] -** xref:execution-plans/operator-summary.adoc[] +** xref:execution-plans/operator-summary.adoc[Execution plan operators] +** xref:execution-plans/db-hits.adoc[Database hits] ** xref:execution-plans/operators.adoc[] ** xref:execution-plans/shortestpath-planning.adoc[] * xref:deprecations-additions-removals-compatibility.adoc[] + * xref:keyword-glossary.adoc[] .Appendix * xref:styleguide.adoc[] - diff --git a/modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists-fieldterminator.csv b/modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists-fieldterminator.csv new file mode 100644 index 000000000..be3b6eabf --- /dev/null +++ b/modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists-fieldterminator.csv @@ -0,0 +1,4 @@ +1;ABBA;1992 +2;Roxette;1986 +3;Europe;1979 +4;The Cardigans;1992 diff --git a/modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists-with-escaped-char.csv b/modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists-with-escaped-char.csv new file mode 100644 index 000000000..1cb1fe23e --- /dev/null +++ b/modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists-with-escaped-char.csv @@ -0,0 +1 @@ +"1","The ""Symbol""","1992" diff --git a/modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists-with-headers.csv b/modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists-with-headers.csv new file mode 100644 index 000000000..52814fe62 --- /dev/null +++ b/modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists-with-headers.csv @@ -0,0 +1,5 @@ +Id,Name,Year +1,ABBA,1992 +2,Roxette,1986 +3,Europe,1979 +4,The Cardigans,1992 diff --git a/modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists.csv b/modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists.csv new file mode 100644 index 000000000..9ef4ddf39 --- /dev/null +++ b/modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/load-csv/csv-files/artists.csv @@ -0,0 +1,4 @@ +1,ABBA,1992 +2,Roxette,1986 +3,Europe,1979 +4,The Cardigans,1992 diff --git a/modules/ROOT/images/privileges_grant_and_deny_syntax_database_privileges.png b/modules/ROOT/images/grant-privileges-database.png similarity index 100% rename from modules/ROOT/images/privileges_grant_and_deny_syntax_database_privileges.png rename to modules/ROOT/images/grant-privileges-database.png diff --git a/modules/ROOT/images/grant-privileges-dbms.png b/modules/ROOT/images/grant-privileges-dbms.png new file mode 100644 index 0000000000000000000000000000000000000000..1548b524a4cec8485ac07074824da202310883e4 GIT binary patch literal 84496 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 literal 0 HcmV?d00001 diff --git a/modules/ROOT/images/privileges_on_graph_syntax.png b/modules/ROOT/images/grant-privileges-graph.png similarity index 100% rename from modules/ROOT/images/privileges_on_graph_syntax.png rename to modules/ROOT/images/grant-privileges-graph.png diff --git a/modules/ROOT/images/privileges_grant_and_deny_syntax.png b/modules/ROOT/images/grant-privileges-overview.png similarity index 100% rename from modules/ROOT/images/privileges_grant_and_deny_syntax.png rename to modules/ROOT/images/grant-privileges-overview.png diff --git a/modules/ROOT/images/graph1.svg b/modules/ROOT/images/graph1.svg deleted file mode 100644 index a1374a53e..000000000 --- a/modules/ROOT/images/graph1.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - -L - - - -N0 - -Person - -name = 'John' - - - -N3 - -Person - -name = 'Sara' - - - -N0->N3 - - -FRIEND - - - -N1 - -Person - -name = 'Joe' - - - -N0->N1 - - -FRIEND - - - -N4 - -Person - -name = 'Maria' - - - -N3->N4 - - -FRIEND - - - -N2 - -Person - -name = 'Steve' - - - -N1->N2 - - -FRIEND - - - diff --git a/modules/ROOT/images/graph2.svg b/modules/ROOT/images/graph2.svg deleted file mode 100644 index 104947143..000000000 --- a/modules/ROOT/images/graph2.svg +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - -L - - - -N0 - -User - -name = 'Adam' - - - -N1 - -User - -name = 'Pernilla' - - - -N0->N1 - - -FRIEND - - - -N2 - -User - -name = 'David' - - - -N1->N2 - - -FRIEND - - - 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/graph4.svg b/modules/ROOT/images/graph4.svg deleted file mode 100644 index 91242d043..000000000 --- a/modules/ROOT/images/graph4.svg +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - -L - - - -N0 - -name = 'Anders' - - - -N3 - -name = 'Dilshad' - - - -N0->N3 - - -KNOWS - - - -N2 - -name = 'Cesar' - - - -N0->N2 - - -KNOWS - - - -N1 - -name = 'Becky' - - - -N0->N1 - - -KNOWS - - - -N5 - -name = 'Filipa' - - - -N3->N5 - - -KNOWS - - - -N4 - -name = 'George' - - - -N2->N4 - - -KNOWS - - - -N1->N4 - - -KNOWS - - - diff --git a/modules/ROOT/images/graph5.svg b/modules/ROOT/images/graph5.svg deleted file mode 100644 index bdd240952..000000000 --- a/modules/ROOT/images/graph5.svg +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - -L - - - -N0 - -Person - -name = 'Keanu Reeves' - - - -N8 - -Movie - -title = 'The Matrix Resurrections' -released = 2021 - - - -N0->N8 - - -ACTED_IN - - - -N7 - -Movie - -title = 'The Devils Advocate' -released = 1997 - - - -N0->N7 - - -ACTED_IN - - - -N4 - -Movie - -title = 'The Matrix Reloaded' -released = 2003 - - - -N0->N4 - - -ACTED_IN - - - -N3 - -Movie - -title = 'The Matrix Revolutions' -released = 2003 - - - -N0->N3 - - -ACTED_IN - - - -N5 - -Movie - -title = 'The Replacements' -released = 2000 - - - -N0->N5 - - -ACTED_IN - - - -N6 - -Movie - -title = 'The Matrix' -released = 1999 - - - -N0->N6 - - -ACTED_IN - - - -N2 - -Movie - -title = 'Somethings Gotta Give' -released = 2003 - - - -N0->N2 - - -ACTED_IN - - - -N1 - -Movie - -title = 'Johnny Mnemonic' -released = 1995 - - - -N0->N1 - - -ACTED_IN - - - diff --git a/modules/ROOT/images/graph6.svg b/modules/ROOT/images/graph6.svg deleted file mode 100644 index 0fd4be2d0..000000000 --- a/modules/ROOT/images/graph6.svg +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - -L - - - -N0 - -Person - -realName = 'Carlos Irwin Estévez' -name = 'Charlie Sheen' - - - -N4 - -Movie - -year = 1979 -title = 'Apocalypse Now' - - - -N0->N4 - - -ACTED_IN - - - -N3 - -Movie - -year = 1984 -title = 'Red Dawn' - - - -N0->N3 - - -ACTED_IN - - - -N2 - -Movie - -year = 1987 -title = 'Wall Street' - - - -N0->N2 - - -ACTED_IN - - - -N1 - -Person - -name = 'Martin Sheen' - - - -N1->N4 - - -ACTED_IN - - - -N1->N2 - - -ACTED_IN - - - diff --git a/modules/ROOT/images/graph_aggregating_functions.svg b/modules/ROOT/images/graph_aggregating_functions.svg deleted file mode 100644 index 624e6cfe2..000000000 --- a/modules/ROOT/images/graph_aggregating_functions.svg +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - -L - - - -N0 - -Person - -age = 13 -name = 'A' - - - -N1 - -Person - -eyes = 'blue' -age = 33 -name = 'B' - - - -N0->N1 - - -KNOWS - - - -N2 - -Person - -eyes = 'blue' -age = 44 -name = 'C' - - - -N0->N2 - - -KNOWS - - - -N3 - -Person - -eyes = 'brown' -name = 'D' - - - -N0->N3 - - -KNOWS - - - -N5 - -Book - -name = 'Cypher' - - - -N0->N5 - - -READS - - - -N4 - -Person - -name = 'D' - - - -N1->N4 - - -KNOWS - - - -N2->N4 - - -KNOWS - - - 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 e22a70669..000000000 --- a/modules/ROOT/images/graph_call_subquery_clause.svg +++ /dev/null @@ -1,63 +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' - - - diff --git a/modules/ROOT/images/graph_delete_clause.svg b/modules/ROOT/images/graph_delete_clause.svg deleted file mode 100644 index 0ccbc1b16..000000000 --- a/modules/ROOT/images/graph_delete_clause.svg +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - -L - - - -N0 - -Person - -age = 36 -name = 'Andy' - - - -N1 - -Person - -age = 25 -name = 'Timothy' - - - -N0->N1 - - -KNOWS - - - -N2 - -Person - -age = 34 -name = 'Peter' - - - -N0->N2 - - -KNOWS - - - -N3 - -Person - -name = 'UNKNOWN' - - - diff --git a/modules/ROOT/images/graph_foreach_clause.svg b/modules/ROOT/images/graph_foreach_clause.svg deleted file mode 100644 index 58550883c..000000000 --- a/modules/ROOT/images/graph_foreach_clause.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - -L - - - -N0 - -Person - -name = 'A' - - - -N1 - -Person - -name = 'B' - - - -N0->N1 - - -KNOWS - - - -N2 - -Person - -name = 'C' - - - -N1->N2 - - -KNOWS - - - -N3 - -Person - -name = 'D' - - - -N2->N3 - - -KNOWS - - - diff --git a/modules/ROOT/images/graph_limit_clause.svg b/modules/ROOT/images/graph_limit_clause.svg deleted file mode 100644 index 10d6ef05f..000000000 --- a/modules/ROOT/images/graph_limit_clause.svg +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - -L - - - -N0 - -name = 'A' - - - -N4 - -name = 'E' - - - -N0->N4 - - -KNOWS - - - -N3 - -name = 'D' - - - -N0->N3 - - -KNOWS - - - -N2 - -name = 'C' - - - -N0->N2 - - -KNOWS - - - -N1 - -name = 'B' - - - -N0->N1 - - -KNOWS - - - diff --git a/modules/ROOT/images/graph_list_functions.svg b/modules/ROOT/images/graph_list_functions.svg deleted file mode 100644 index 88b6c8672..000000000 --- a/modules/ROOT/images/graph_list_functions.svg +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - -L - - - -N0 - -Person, Developer - -name = 'Alice' -age = 38 -eyes = 'brown' - - - -N2 - -name = 'Charlie' -age = 53 -eyes = 'green' - - - -N0->N2 - - -KNOWS - - - -N1 - -name = 'Bob' -age = 25 -eyes = 'blue' - - - -N0->N1 - - -KNOWS - - - -N3 - -name = 'Daniel' -age = 54 -eyes = 'brown' - - - -N2->N3 - - -KNOWS - - - -N1->N3 - - -KNOWS - - - -N4 - -eyes = 'blue' -array = ['one', 'two', 'three'] -name = 'Eskil' -age = 41 - - - -N1->N4 - - -MARRIED - - - diff --git a/modules/ROOT/images/graph_match_clause.svg b/modules/ROOT/images/graph_match_clause.svg deleted file mode 100644 index b8fea68e9..000000000 --- a/modules/ROOT/images/graph_match_clause.svg +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - -L - - - -N0 - -Person - -name = 'Charlie Sheen' - - - -N5 - -Movie - -title = 'Wall Street' - - - -N0->N5 - - -ACTED_IN -role = 'Bud Fox' - - - -N1 - -Person - -name = 'Martin Sheen' - - - -N1->N5 - - -ACTED_IN -role = 'Carl Fox' - - - -N6 - -Movie - -title = 'The American President' - - - -N1->N6 - - -ACTED_IN -role = 'A.J. MacInerney' - - - -N2 - -Person - -name = 'Michael Douglas' - - - -N2->N5 - - -ACTED_IN -role = 'Gordon Gekko' - - - -N2->N6 - - -ACTED_IN -role = 'President Andrew Shepherd' - - - -N3 - -Person - -name = 'Oliver Stone' - - - -N3->N5 - - -DIRECTED - - - -N4 - -Person - -name = 'Rob Reiner' - - - -N4->N6 - - -DIRECTED - - - diff --git a/modules/ROOT/images/graph_match_clause_backtick.svg b/modules/ROOT/images/graph_match_clause_backtick.svg deleted file mode 100644 index a224c5d9c..000000000 --- a/modules/ROOT/images/graph_match_clause_backtick.svg +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - -L - - - -N0 - -Person - -name = 'Charlie Sheen' - - - -N5 - -Movie - -title = 'Wall Street' - - - -N0->N5 - - -ACTED_IN -role = 'Bud Fox' - - - -N1 - -Person - -name = 'Martin Sheen' - - - -N1->N5 - - -ACTED_IN -role = 'Carl Fox' - - - -N6 - -Movie - -title = 'The American President' - - - -N1->N6 - - -ACTED_IN -role = 'A.J. MacInerney' - - - -N2 - -Person - -name = 'Michael Douglas' - - - -N2->N5 - - -ACTED_IN -role = 'Gordon Gekko' - - - -N2->N6 - - -ACTED_IN -role = 'President Andrew Shepherd' - - - -N3 - -Person - -name = 'Oliver Stone' - - - -N3->N5 - - -DIRECTED - - - -N4 - -Person - -name = 'Rob Reiner' - - - -N4->N0 - - -TYPE INCLUDING A SPACE - - - -N4->N6 - - -DIRECTED - - - diff --git a/modules/ROOT/images/graph_match_clause_variable_length.svg b/modules/ROOT/images/graph_match_clause_variable_length.svg deleted file mode 100644 index 3fcf069f4..000000000 --- a/modules/ROOT/images/graph_match_clause_variable_length.svg +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - -L - - - -N0 - -Person - -name = 'Charlie Sheen' - - - -N7 - -UNBLOCKED - - - - - -N0->N7 - - -X -blocked = false - - - -N8 - -BLOCKED - - - - - -N0->N8 - - -X -blocked = true - - - -N5 - -Movie - -title = 'Wall Street' - - - -N0->N5 - - -ACTED_IN -role = 'Bud Fox' - - - -N1 - -Person - -name = 'Martin Sheen' - - - -N1->N7 - - -X -blocked = false - - - -N1->N8 - - -X -blocked = false - - - -N1->N5 - - -ACTED_IN -role = 'Carl Fox' - - - -N6 - -Movie - -title = 'The American President' - - - -N1->N6 - - -ACTED_IN -role = 'A.J. MacInerney' - - - -N2 - -Person - -name = 'Michael Douglas' - - - -N2->N5 - - -ACTED_IN -role = 'Gordon Gekko' - - - -N2->N6 - - -ACTED_IN -role = 'President Andrew Shepherd' - - - -N3 - -Person - -name = 'Oliver Stone' - - - -N3->N5 - - -DIRECTED - - - -N4 - -Person - -name = 'Rob Reiner' - - - -N4->N6 - - -DIRECTED - - - diff --git a/modules/ROOT/images/graph_merge_clause.svg b/modules/ROOT/images/graph_merge_clause.svg deleted file mode 100644 index 2de22114c..000000000 --- a/modules/ROOT/images/graph_merge_clause.svg +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - -L - - - -N0 - -Person - -bornIn = 'New York' -chauffeurName = 'John Brown' -name = 'Charlie Sheen' - - - -N1 - -Person - -bornIn = 'Ohio' -chauffeurName = 'Bob Brown' -name = 'Martin Sheen' - - - -N0->N1 - - -FATHER - - - -N5 - -Movie - -title = 'Wall Street' - - - -N0->N5 - - -ACTED_IN - - - -N1->N5 - - -ACTED_IN - - - -N6 - -Movie - -title = 'The American President' - - - -N1->N6 - - -ACTED_IN - - - -N2 - -Person - -name = 'Michael Douglas' -chauffeurName = 'John Brown' -bornIn = 'New Jersey' - - - -N2->N5 - - -ACTED_IN - - - -N2->N6 - - -ACTED_IN - - - -N3 - -Person - -bornIn = 'New York' -chauffeurName = 'Bill White' -name = 'Oliver Stone' - - - -N3->N5 - - -ACTED_IN - - - -N4 - -Person - -bornIn = 'New York' -chauffeurName = 'Ted Green' -name = 'Rob Reiner' - - - -N4->N6 - - -ACTED_IN - - - diff --git a/modules/ROOT/images/graph_numeric_functions.svg b/modules/ROOT/images/graph_numeric_functions.svg deleted file mode 100644 index 78b904b14..000000000 --- a/modules/ROOT/images/graph_numeric_functions.svg +++ /dev/null @@ -1,99 +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' -age = 54 -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_optional_match_clause.svg b/modules/ROOT/images/graph_optional_match_clause.svg deleted file mode 100644 index dfb87f679..000000000 --- a/modules/ROOT/images/graph_optional_match_clause.svg +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - -L - - - -N0 - -Person - -name = 'Charlie Sheen' - - - -N1 - -Person - -name = 'Martin Sheen' - - - -N0->N1 - - -FATHER - - - -N5 - -Movie - -title = 'Wall Street' - - - -N0->N5 - - -ACTED_IN - - - -N1->N5 - - -ACTED_IN - - - -N6 - -Movie - -title = 'The American President' - - - -N1->N6 - - -ACTED_IN - - - -N2 - -Person - -name = 'Michael Douglas' - - - -N2->N5 - - -ACTED_IN - - - -N2->N6 - - -ACTED_IN - - - -N3 - -Person - -name = 'Oliver Stone' - - - -N3->N5 - - -DIRECTED - - - -N4 - -Person - -name = 'Rob Reiner' - - - -N4->N6 - - -DIRECTED - - - diff --git a/modules/ROOT/images/graph_order_by_clause.svg b/modules/ROOT/images/graph_order_by_clause.svg deleted file mode 100644 index 71ffc6faf..000000000 --- a/modules/ROOT/images/graph_order_by_clause.svg +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - -L - - - -N0 - -name = 'A' -age = 34 -length = 170 - - - -N1 - -name = 'B' -age = 36 - - - -N0->N1 - - -KNOWS - - - -N2 - -name = 'C' -age = 32 -length = 185 - - - -N1->N2 - - -KNOWS - - - diff --git a/modules/ROOT/images/graph_predicate_functions.svg b/modules/ROOT/images/graph_predicate_functions.svg deleted file mode 100644 index fdad380b8..000000000 --- a/modules/ROOT/images/graph_predicate_functions.svg +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - -L - - - -N0 - -name = 'Alice' -age = 38 -eyes = 'brown' - - - -N2 - -name = 'Charlie' -age = 53 -eyes = 'green' - - - -N0->N2 - - -KNOWS - - - -N1 - -name = 'Bob' -age = 25 -eyes = 'blue' - - - -N0->N1 - - -KNOWS - - - -N3 - -eyes = 'brown' -liked_colors = [] -name = 'Daniel' -age = 54 - - - -N2->N3 - - -KNOWS - - - -N1->N3 - - -KNOWS - - - -N4 - -eyes = 'blue' -liked_colors = ['pink', 'yellow', 'black'] -name = 'Eskil' -age = 41 - - - -N1->N4 - - -MARRIED - - - -N5 - -eyes = '' -liked_colors = ['blue', 'green'] -alias = 'Frank' -age = 61 - - - -N6 - -Person - - - - - diff --git a/modules/ROOT/images/graph_remove_clause.svg b/modules/ROOT/images/graph_remove_clause.svg deleted file mode 100644 index fccb1b2c7..000000000 --- a/modules/ROOT/images/graph_remove_clause.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - -L - - - -N0 - -Swedish - -age = 36 -name = 'Andy' - - - -N2 - -Swedish, German - -age = 34 -name = 'Peter' - - - -N0->N2 - - -KNOWS - - - -N1 - -Swedish - -age = 25 -name = 'Timothy' - - - -N0->N1 - - -KNOWS - - - diff --git a/modules/ROOT/images/graph_return_clause.svg b/modules/ROOT/images/graph_return_clause.svg deleted file mode 100644 index 0607c9c1c..000000000 --- a/modules/ROOT/images/graph_return_clause.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - -L - - - -N0 - -name = 'A' -age = 55 -happy = 'Yes!' - - - -N1 - -name = 'B' - - - -N0->N1 - - -BLOCKS - - - -N0->N1 - - -KNOWS - - - diff --git a/modules/ROOT/images/graph_scalar_functions.svg b/modules/ROOT/images/graph_scalar_functions.svg deleted file mode 100644 index 549084a40..000000000 --- a/modules/ROOT/images/graph_scalar_functions.svg +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - -L - - - -N0 - -Developer - -name = 'Alice' -age = 38 -eyes = 'brown' - - - -N2 - -name = 'Charlie' -age = 53 -eyes = 'green' - - - -N0->N2 - - -KNOWS - - - -N1 - -name = 'Bob' -age = 25 -eyes = 'blue' - - - -N0->N1 - - -KNOWS - - - -N3 - -name = 'Daniel' -age = 54 -eyes = 'brown' - - - -N2->N3 - - -KNOWS - - - -N1->N3 - - -KNOWS - - - -N4 - -eyes = 'blue' -liked_colors = ['pink', 'yellow', 'black'] -name = 'Eskil' -age = 41 - - - -N1->N4 - - -MARRIED - - - diff --git a/modules/ROOT/images/graph_set_clause.svg b/modules/ROOT/images/graph_set_clause.svg deleted file mode 100644 index 35e27e288..000000000 --- a/modules/ROOT/images/graph_set_clause.svg +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - -L - - - -N0 - -Swedish - -name = 'Andy' -age = 36 -hungry = true - - - -N2 - -name = 'Peter' -age = 34 - - - -N0->N2 - - -KNOWS - - - -N1 - -name = 'Stefan' - - - -N1->N0 - - -KNOWS - - - -N3 - -name = 'George' - - - -N3->N2 - - -KNOWS - - - diff --git a/modules/ROOT/images/graph_skip_clause.svg b/modules/ROOT/images/graph_skip_clause.svg deleted file mode 100644 index 10d6ef05f..000000000 --- a/modules/ROOT/images/graph_skip_clause.svg +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - -L - - - -N0 - -name = 'A' - - - -N4 - -name = 'E' - - - -N0->N4 - - -KNOWS - - - -N3 - -name = 'D' - - - -N0->N3 - - -KNOWS - - - -N2 - -name = 'C' - - - -N0->N2 - - -KNOWS - - - -N1 - -name = 'B' - - - -N0->N1 - - -KNOWS - - - diff --git a/modules/ROOT/images/graph_spatial_functions.svg b/modules/ROOT/images/graph_spatial_functions.svg deleted file mode 100644 index b9d6b4db3..000000000 --- a/modules/ROOT/images/graph_spatial_functions.svg +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - -L - - - -N0 - -TrainStation - -latitude = 55.672874 -longitude = 12.56459 -city = 'Copenhagen' - - - -N1 - -Office - -latitude = 55.611784 -longitude = 12.994341 -city = 'Malmo' - - - -N0->N1 - - -TRAVEL_ROUTE - - - diff --git a/modules/ROOT/images/graph_union_clause.svg b/modules/ROOT/images/graph_union_clause.svg deleted file mode 100644 index 5555ace33..000000000 --- a/modules/ROOT/images/graph_union_clause.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - -L - - - -N0 - -Actor - -name = 'Anthony Hopkins' - - - -N3 - -Movie - -title = 'Hitchcock' - - - -N0->N3 - - -ACTS_IN - - - -N1 - -Actor - -name = 'Helen Mirren' - - - -N0->N1 - - -KNOWS - - - -N1->N3 - - -ACTS_IN - - - -N2 - -Actor - -name = 'Hitchcock' - - - diff --git a/modules/ROOT/images/graph_where_clause.svg b/modules/ROOT/images/graph_where_clause.svg deleted file mode 100644 index b7f52981f..000000000 --- a/modules/ROOT/images/graph_where_clause.svg +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - -L - - - -N0 - -Swedish, Person - -name = 'Andy' -age = 36 -belt = 'white' - - - -N3 - -Dog - -name = 'Andy' - - - -N0->N3 - - -HAS_DOG -since = 2016 - - - -N2 - -Person - -name = 'Peter' -email = 'peter_n@example.com' -age = 35 - - - -N0->N2 - - -KNOWS -since = 1999 - - - -N1 - -Person - -age = 25 -address = 'Sweden/Malmo' -name = 'Timothy' - - - -N0->N1 - - -KNOWS -since = 2012 - - - -N4 - -Dog - -name = 'Fido' - - - -N2->N4 - - -HAS_DOG -since = 2010 - - - -N5 - -Dog - -name = 'Ozzy' - - - -N2->N5 - - -HAS_DOG -since = 2018 - - - -N6 - -Toy - -name = 'Banana' - - - -N4->N6 - - -HAS_TOY - - - diff --git a/modules/ROOT/images/graph_with_clause.svg b/modules/ROOT/images/graph_with_clause.svg deleted file mode 100644 index 79b2ca715..000000000 --- a/modules/ROOT/images/graph_with_clause.svg +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - -L - - - -N0 - -name = 'Anders' - - - -N2 - -name = 'Caesar' - - - -N0->N2 - - -BLOCKS - - - -N1 - -name = 'Bossman' - - - -N0->N1 - - -KNOWS - - - -N4 - -name = 'George' - - - -N2->N4 - - -KNOWS - - - -N1->N4 - - -KNOWS - - - -N3 - -name = 'David' - - - -N1->N3 - - -BLOCKS - - - -N3->N0 - - -KNOWS - - - diff --git a/modules/ROOT/images/privileges_hierarchy_database.png b/modules/ROOT/images/privilege-hierarchy-database.png similarity index 100% rename from modules/ROOT/images/privileges_hierarchy_database.png rename to modules/ROOT/images/privilege-hierarchy-database.png diff --git a/modules/ROOT/images/privilege-hierarchy-dbms.png b/modules/ROOT/images/privilege-hierarchy-dbms.png new file mode 100644 index 0000000000000000000000000000000000000000..c4361d84952d8c0fe10949b21175d2036d8a0c8b GIT binary patch literal 103987 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%0D67f3VWf2TdXz-M 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 Password and user recovery]. +If the built-in admin role has been altered or dropped, and needs to be restored to its original state, see link:{neo4j-docs-base-uri}/operations-manual/{page-version}/configuration/password-and-user-recovery[Operations Manual -> Password and user recovery]. [[access-control-built-in-roles-admin-recreate]] === Recreating the `admin` role To restore the role to its original capabilities two steps are needed. -First, execute `DROP ROLE admin`. -Secondly, run these queries: +First, if not already done, execute `DROP ROLE admin`. +Secondly, the following queries must be run in order to set up the privileges: [source, cypher, role=noplay] ---- @@ -413,4 +407,4 @@ GRANT ALL ON DATABASE * TO admin The resulting `admin` role now has the same privileges as the original built-in `admin` role. -Additional information about restoring the `admin` role can be found at link:{neo4j-docs-base-uri}/operations-manual/{page-version}/configuration/password-and-user-recovery#recover-admin-role[Operations Manual -> Recover the admin role]. +Additional information about restoring the admin role can be found in the link:{neo4j-docs-base-uri}/operations-manual/{page-version}/configuration/password-and-user-recovery#recover-admin-role[Operations Manual -> Recover the admin role]. diff --git a/modules/ROOT/pages/access-control/database-administration.adoc b/modules/ROOT/pages/access-control/database-administration.adoc index 4c11dd512..aa5a4d99d 100644 --- a/modules/ROOT/pages/access-control/database-administration.adoc +++ b/modules/ROOT/pages/access-control/database-administration.adoc @@ -1,26 +1,19 @@ -:description: How to use Cypher to manage Neo4j database administrative privileges. - [role=enterprise-edition] [[access-control-database-administration]] = Database administration +:description: This section explains how to use Cypher to manage Neo4j database administrative privileges. -[abstract] --- -This section explains how to use Cypher to manage Neo4j database administrative privileges. --- - -Administrators can use the following Cypher commands to manage Neo4j database administrative rights. - +The administrators can use the following Cypher commands to manage Neo4j database administrative rights. The components of the database privilege commands are: -* _command_: +* the command: ** `GRANT` – gives privileges to roles. ** `DENY` – denies privileges to roles. -** `REVOKE` – removes granted or denied privileges from roles. +** `REVOKE` – removes granted or denied privilege from roles. * _database-privilege_ -** `ACCESS` - allows access to a specific database or remote database alias. +** `ACCESS` - allows access to a specific database. ** `START` - allows the specified database to be started. ** `STOP` - allows the specified database to be stopped. ** `CREATE INDEX` - allows indexes to be created on the specified database. @@ -31,11 +24,11 @@ The components of the database privilege commands are: ** `DROP CONSTRAINT` - allows constraints to be deleted on the specified database. ** `SHOW CONSTRAINT` - allows constraints to be listed on the specified database. ** `CONSTRAINT [MANAGEMENT]` - allows constraints to be created, deleted, and listed on the specified database. -** `CREATE NEW [NODE] LABEL` - allows new node labels to be created. -** `CREATE NEW [RELATIONSHIP] TYPE` - allows new relationship types to be created. -** `CREATE NEW [PROPERTY] NAME` - allows property names to be created, so that nodes and relationships can have properties assigned with these names. +** `CREATE NEW [NODE] LABEL` - allows labels to be created so that future nodes can be assigned them. +** `CREATE NEW [RELATIONSHIP] TYPE` - allows relationship types to be created, so that future relationships can be created with these types. +** `CREATE NEW [PROPERTY] NAME` - allows property names to be created, so that nodes and relationships can have properties with these names assigned. ** `NAME [MANAGEMENT]` - allows all of the name management capabilities: node labels, relationship types, and property names. -** `ALL [[DATABASE] PRIVILEGES]` - allows access, index, constraint, and name management for the specified database or remote database alias. +** `ALL [[DATABASE] PRIVILEGES]` - allows access, index, constraint, and name management for the specified database. ** `SHOW TRANSACTION` - allows listing transactions and queries for the specified users on the specified database. ** `TERMINATE TRANSACTION` - allows ending transactions and queries for the specified users on the specified database. ** `TRANSACTION [MANAGEMENT]` - allows listing and ending transactions and queries for the specified users on the specified database. @@ -45,7 +38,7 @@ The components of the database privilege commands are: + [NOTE] ==== -If you delete a database and create a new one with the same name, the new one will NOT have the same privileges previously assigned to the deleted one. +If you delete a database and create a new one with the same name, the new one will NOT have the privileges assigned to the deleted database. ==== ** The _name_ component can be `+*+`, which means all databases. Databases created after this command execution will also be associated with these privileges. @@ -57,448 +50,164 @@ This can be quite powerful as it allows permissions to be switched from one data * _role[, ...]_ ** The role or roles to associate the privilege with, comma-separated. - -.General grant +ON DATABASE+ privilege syntax -[cols="<15s,<85"] +.General database privilege command syntax +[options="header", width="100%", cols="3a,2"] |=== +| Command | Description -| Command -m| +GRANT ... ON ... TO ...+ +| [source, cypher, role=noplay] +GRANT database-privilege ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} TO role[, ...] +| Grant a privilege to one or multiple roles. -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -GRANT database-privilege ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } TO role[, ...] ----- - -| Description -| Grants a privilege to one or multiple roles. - -|=== - - -.General deny +ON DATABASE+ privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +DENY ... ON ... TO ...+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -DENY database-privilege ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } TO role[, ...] ----- - -| Description -| Denies a privilege to one or multiple roles. +| [source, cypher, role=noplay] +DENY database-privilege ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} TO role[, ...] +| Deny a privilege to one or multiple roles. -|=== - - -.General revoke +ON DATABASE+ privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +REVOKE GRANT ... ON ... FROM ...+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -REVOKE GRANT database-privilege ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } FROM role[, ...] ----- - -| Description +| [source, cypher, role=noplay] +REVOKE GRANT database-privilege ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} FROM role[, ...] | Revoke a granted privilege from one or multiple roles. -|=== - +| [source, cypher, role=noplay] +REVOKE DENY database-privilege ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} FROM role[, ...] +| Revoke a denied privilege from one or multiple roles. -.General revoke +ON DATABASE+ privilege syntax -[cols="<15s,<85"] +| [source, cypher, role=noplay] +REVOKE database-privilege ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} FROM role[, ...] +| Revoke a granted or denied privilege from one or multiple roles. |=== -| Command -m| +REVOKE DENY ... ON ... FROM ...+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -REVOKE DENY database-privilege ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } FROM role[, ...] ----- - -| Description -| Revokes a denied privilege from one or multiple roles. - -|=== - - -.General revoke +ON DATABASE+ privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +REVOKE ... ON ... FROM ...+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -REVOKE database-privilege ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } FROM role[, ...] ----- - -| Description -| Revokes a granted or denied privilege from one or multiple roles. - -|=== - - [NOTE] ==== -`DENY` does *not* erase a granted privilege. +`DENY` does NOT erase a granted privilege; they both exist. Use `REVOKE` if you want to remove a privilege. ==== The hierarchy between the different database privileges is shown in the image below. -image::privileges_hierarchy_database.png[title="Database privileges hierarchy"] +image::privilege-hierarchy-database.png[title="Database privileges hierarchy"] - - - -.Database privilege syntax -[cols="<15s,<85"] +.Database privilege command syntax +[options="header", width="100%", cols="3a,2"] |=== +| Command | Description -| Command -m| +GRANT ACCESS+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- +| [source, cypher, role=noplay] GRANT ACCESS - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - -| Description -a| -Grants the specified roles the privilege to access: - -* The home database. -* Specific database(s) or remote database alias(es). -* All databases and remote database aliases. - -|=== - - -.Database privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT { START \| STOP }+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -GRANT { START \| STOP } - ON { HOME DATABASE \| DATABASE[S] {* \| name[, ...] } } - TO role[, ...] ----- - -| Description -| Grants the specified roles the privilege to start or stop the home database, specific database(s), or all databases. - -|=== - - -.Database privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT { CREATE \| DROP \| SHOW } INDEX+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -GRANT { CREATE \| DROP \| SHOW } INDEX[ES] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - -| Description -| Grants the specified roles the privilege to create, delete, or show indexes on the home database, specific database(s), or all databases. - -|=== - - -.Database privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT INDEX+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Grant the specified roles the privilege to access the home database, specific database(s), or all databases. + +| [source, cypher, role=noplay] +GRANT {START \| STOP} + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Grant the specified roles the privilege to start and stop the home database, specific database(s), or all databases. + +| [source, cypher, role=noplay] +GRANT {CREATE \| DROP \| SHOW} INDEX[ES] + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Grant the specified roles the privilege to create, delete, or show indexes on the home database, specific database(s), or all databases. + +| [source, cypher, role=noplay] GRANT INDEX[ES] [MANAGEMENT] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Grant the specified roles the privilege to manage indexes on the home database, specific database(s), or all databases. -| Description -| Grants the specified roles the privilege to manage indexes on the home database, specific database(s), or all databases. +| [source, cypher, role=noplay] +GRANT {CREATE \| DROP \| SHOW} CONSTRAINT[S] + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Grant the specified roles the privilege to create, delete, or show constraints on the home database, specific database(s), or all databases. -|=== - - -.Database privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT { CREATE \| DROP \| SHOW } CONSTRAINT+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -GRANT { CREATE \| DROP \| SHOW } CONSTRAINT[S] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - -| Description -| Grants the specified roles the privilege to create, delete, or show constraints on the home database, specific database(s), or all databases. - -|=== - - -.Database privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT CONSTRAINT+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- +| [source, cypher, role=noplay] GRANT CONSTRAINT[S] [MANAGEMENT] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Grant the specified roles the privilege to manage constraints on the home database, specific database(s), or all databases. -| Description -| Grants the specified roles the privilege to manage constraints on the home database, specific database(s), or all databases. - -|=== - - -.Database privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT CREATE NEW LABEL+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- +| [source, cypher, role=noplay] GRANT CREATE NEW [NODE] LABEL[S] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - -| Description -| Grants the specified roles the privilege to create new node labels in the home database, specific database(s), or all databases. - -|=== - - -.Database privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT CREATE NEW TYPE+ + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Grant the specified roles the privilege to create new node labels in the home database, specific database(s), or all databases. -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- +| [source, cypher, role=noplay] GRANT CREATE NEW [RELATIONSHIP] TYPE[S] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - -| Description -| Grants the specified roles the privilege to create new relationship types in the home database, specific database(s), or all databases. - -|=== - - -.Database privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT CREATE NEW NAME+ + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Grant the specified roles the privilege to create new relationships types in the home database, specific database(s), or all databases. -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- +| [source, cypher, role=noplay] GRANT CREATE NEW [PROPERTY] NAME[S] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - -| Description -| Grants the specified roles the privilege to create new property names in the home database, specific database(s), or all databases. - -|=== - - -.Database privilege syntax -[cols="<15s,<85"] -|=== + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Grant the specified roles the privilege to create new property names in the home database, specific database(s), or all databases. -| Command -m| +GRANT NAME+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- +| [source, cypher, role=noplay] GRANT NAME [MANAGEMENT] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - -| Description -| Grants the specified roles the privilege to manage new labels, relationship types, and property names in the home database, specific database(s), or all databases. - -|=== - - -.Database privilege syntax -[cols="<15s,<85"] -|=== + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Grant the specified roles the privilege to manage new labels, relationship types, and property names in the home database, specific database(s), or all databases. -| Command -m| +GRANT ALL+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- +| [source, cypher, role=noplay] GRANT ALL [[DATABASE] PRIVILEGES] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - -| Description -| Grants the specified roles all privileges for the home, a specific, or all databases and remote database aliases. + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Grant the specified roles all privileges for the home database, specific database(s), or all databases. -|=== +| [source, cypher, role=noplay] +GRANT {SHOW \| TERMINATE} TRANSACTION[S] [( {* \| user[, ...]} )] +ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} +TO role[, ...] +| Grant the specified roles the privilege to list and end the transactions and queries of all users or a particular user(s) in the home database, specific database(s), or all databases. +| [source, cypher, role=noplay] +GRANT TRANSACTION [MANAGEMENT] [( {* \| user[, ...]} )] +ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} +TO role[, ...] +| Grant the specified roles the privilege to manage the transactions and queries of all users or a particular user(s) in the home database, specific database(s), or all databases. -.Database privilege syntax -[cols="<15s,<85"] |=== -| Command -m| +GRANT { SHOW \| TERMINATE } TRANSACTION+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -GRANT { SHOW \| TERMINATE } TRANSACTION[S] [( { * \| user[, ...] } )] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - -| Description -| Grants the specified roles the privilege to list and end the transactions and queries of all users or a particular user(s) in the home database, specific database(s), or all databases. - -|=== - - -.Database privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT TRANSACTION+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -GRANT TRANSACTION [MANAGEMENT] [( { * \| user[, ...] } )] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - -| Description -| Grants the specified roles the privilege to manage the transactions and queries of all users or a particular user(s) in the home database, specific database(s), or all databases. - -|=== - - -image::privileges_grant_and_deny_syntax_database_privileges.png[title="Syntax of GRANT and DENY Database Privileges"] +image::grant-privileges-database.png[title="Syntax of GRANT and DENY Database Privileges"] [[access-control-database-administration-access]] == The database `ACCESS` privilege -The `ACCESS` privilege enables users to connect to a database or a remote database alias. -With `ACCESS` you can run calculations, for example, `+RETURN 2 * 5 AS answer+` or call functions `RETURN timestamp() AS time`. +The `ACCESS` privilege enables users to connect to a database. +With `ACCESS` you can run calculations, for example, `RETURN 2*5 AS answer` or call functions `RETURN timestamp() AS time`. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- GRANT ACCESS - ON { HOME DATABASE | DATABASE[S] { * | name[, ...] } } - TO role[, ...] + ON {HOME DATABASE | DATABASE[S] {* | name[, ...]}} + TO role[, ...] ---- -For example, to grant the role `regularUsers` the ability to access the database `neo4j`, use: +For example, granting the ability to access the database `neo4j` to the role `regularUsers` is done using the following query. [source, cypher, role=noplay] ---- GRANT ACCESS ON DATABASE neo4j TO regularUsers ---- -The `ACCESS` privilege can also be denied: +The `ACCESS` privilege can also be denied. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- DENY ACCESS - ON { HOME DATABASE | DATABASE[S] { * | name[, ...] } } - TO role[, ...] + ON {HOME DATABASE | DATABASE[S] {* | name[, ...]}} + TO role[, ...] ---- -For example, to deny the role `regularUsers` the ability to access to the remote database alias `remote-db`, use: +For example, denying the ability to access to the database `neo4j` to the role `regularUsers` is done using the following query. [source, cypher, role=noplay] ---- -DENY ACCESS ON DATABASE `remote-db` TO regularUsers +DENY ACCESS ON DATABASE neo4j TO regularUsers ---- The privileges granted can be seen using the `SHOW PRIVILEGES` command: @@ -512,7 +221,7 @@ SHOW ROLE regularUsers PRIVILEGES AS COMMANDS [options="header,footer", width="100%", cols="m"] |=== |command -|"DENY ACCESS ON DATABASE `remote-db` TO `regularUsers`" +|"DENY ACCESS ON DATABASE `neo4j` TO `regularUsers`" |"GRANT ACCESS ON DATABASE `neo4j` TO `regularUsers`" a|Rows: 2 |=== @@ -521,64 +230,64 @@ a|Rows: 2 [[access-control-database-administration-startstop]] == The database `START`/`STOP` privileges -The `START` privilege can be used to enable the ability to start a database: +The `START` privilege can be used to enable the ability to start a database. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- GRANT START - ON { HOME DATABASE | DATABASE[S] { * | name[, ...] } } - TO role[, ...] + ON {HOME DATABASE | DATABASE[S] {* | name[, ...]}} + TO role[, ...] ---- -For example, to grant the role `regularUsers` the ability to start the database `neo4j`, use: +For example, granting the ability to start the database `neo4j` to the role `regularUsers` is done using the following query. [source, cypher, role=noplay] ---- GRANT START ON DATABASE neo4j TO regularUsers ---- -The `START` privilege can also be denied: +The `START` privilege can also be denied. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- DENY START - ON { HOME DATABASE | DATABASE[S] { * | name[, ...] } } - TO role[, ...] + ON {HOME DATABASE | DATABASE[S] {* | name[, ...]}} + TO role[, ...] ---- -For example, to deny the role `regularUsers` the ability to start to the database `neo4j`, use: +For example, denying the ability to start to the database `neo4j` to the role `regularUsers` is done using the following query. [source, cypher, role=noplay] ---- DENY START ON DATABASE system TO regularUsers ---- -The `STOP` privilege can be used to enable the ability to stop a database: +The `STOP` privilege can be used to enable the ability to stop a database. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- GRANT STOP - ON { HOME DATABASE | DATABASE[S] { * | name[, ...] } } - TO role[, ...] + ON {HOME DATABASE | DATABASE[S] {* | name[, ...]}} + TO role[, ...] ---- -For example, to grant the role `regularUsers` the ability to stop the database `neo4j`, use: +For example, granting the ability to stop the database `neo4j` to the role `regularUsers` is done using the following query. [source, cypher, role=noplay] ---- GRANT STOP ON DATABASE neo4j TO regularUsers ---- -The `STOP` privilege can also be denied: +The `STOP` privilege can also be denied. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- DENY STOP - ON { HOME DATABASE | DATABASE[S] { * | name[, ...] } } - TO role[, ...] + ON {HOME DATABASE | DATABASE[S] {* | name[, ...]}} + TO role[, ...] ---- -For example, to deny the role `regularUsers` the ability to stop the database `neo4j`, use: +For example, denying the ability to stop to the database `neo4j` to the role `regularUsers` is done using the following query. [source, cypher, role=noplay] ---- @@ -596,7 +305,7 @@ SHOW ROLE regularUsers PRIVILEGES AS COMMANDS [options="header,footer", width="100%", cols="m"] |=== |command -|"DENY ACCESS ON DATABASE `remote-db` TO `regularUsers`" +|"DENY ACCESS ON DATABASE `neo4j` TO `regularUsers`" |"DENY START ON DATABASE `system` TO `regularUsers`" |"DENY STOP ON DATABASE `system` TO `regularUsers`" |"GRANT ACCESS ON DATABASE `neo4j` TO `regularUsers`" @@ -607,7 +316,7 @@ a|Rows: 6 [NOTE] ==== -Note that `START` and `STOP` privileges are not included in the xref::access-control/database-administration.adoc#access-control-database-administration-all[`ALL DATABASE PRIVILEGES`]. +Note that `START` and `STOP` privileges are not included in the xref:access-control/database-administration.adoc#access-control-database-administration-all[`ALL DATABASE PRIVILEGES`]. ==== @@ -618,62 +327,33 @@ Indexes can be created, deleted, or listed with the `CREATE INDEX`, `DROP INDEX` The privilege to do this can be granted with `GRANT CREATE INDEX`, `GRANT DROP INDEX`, and `GRANT SHOW INDEX` commands. The privilege to do all three can be granted with `GRANT INDEX MANAGEMENT` command. - - - -.Index management privilege syntax -[cols="<15s,<85"] +.Index management command syntax +[options="header", width="100%", cols="3a,2"] |=== - | Command -m| +GRANT { CREATE \| DROP \| SHOW } INDEX+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -GRANT { CREATE \| DROP \| SHOW } INDEX[ES] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - | Description -| Enables the specified roles to create, delete, or show indexes in the home database, specific database(s), or all databases. - -|=== +| [source, cypher, role=noplay] +GRANT {CREATE \| DROP \| SHOW} INDEX[ES] + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Enable the specified roles to create, delete, or show indexes in the home database, specific database(s), or all databases. - -.Index management privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT INDEX+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- +| [source, cypher, role=noplay] GRANT INDEX[ES] [MANAGEMENT] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - -| Description -| Enables the specified roles to manage indexes in the home database, specific database(s), or all databases. - + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Enable the specified roles to manage indexes in the home database, specific database(s), or all databases. |=== - -For example, to grant the role `regularUsers` the ability to create indexes on the database `neo4j`, use: +For example, granting the ability to create indexes on the database `neo4j` to the role `regularUsers` is done using the following query. [source, cypher, role=noplay] ---- GRANT CREATE INDEX ON DATABASE neo4j TO regularUsers ---- -The `SHOW INDEXES` privilege only affects the xref::indexes-for-search-performance.adoc#administration-indexes-list-indexes[`SHOW INDEXES` command], and not the older procedures for listing indexes, such as `db.indexes`. +The `SHOW INDEXES` privilege only affects the xref:indexes-for-search-performance.adoc#administration-indexes-list-indexes[`SHOW INDEXES` command], and not the older procedures for listing indexes, such as `db.indexes`. [[access-control-database-administration-constraints]] @@ -683,174 +363,95 @@ Constraints can be created, deleted, or listed with the `CREATE CONSTRAINT`, `DR The privilege to do this can be granted with `GRANT CREATE CONSTRAINT`, `GRANT DROP CONSTRAINT`, `GRANT SHOW CONSTRAINT` commands. The privilege to do all three can be granted with `GRANT CONSTRAINT MANAGEMENT` command. - -.Constraint management privilege syntax -[cols="<15s,<85"] +.Constraint management command syntax +[options="header", width="100%", cols="3a,2"] |=== - | Command -m| +GRANT { CREATE \| DROP \| SHOW } CONSTRAINT+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -GRANT { CREATE \| DROP \| SHOW } CONSTRAINT[S] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - | Description -| Enables the specified roles to create, delete, or show constraints on the home database, specific database(s), or all databases. - -|=== - -.Constraint management privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT CONSTRAINT+ +| [source, cypher, role=noplay] +GRANT {CREATE \| DROP \| SHOW} CONSTRAINT[S] + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Enable the specified roles to create, delete, or show constraints on the home database, specific database(s), or all databases. -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- +| [source, cypher, role=noplay] GRANT CONSTRAINT[S] [MANAGEMENT] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - -| Description + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] | Enable the specified roles to manage constraints on the home database, specific database(s), or all databases. - |=== - -For example, to grant the role `regularUsers` the ability to create constraints on the database `neo4j`, use: +For example, granting the ability to create constraints on the database `neo4j` to the role `regularUsers` is done using the following query. [source, cypher, role=noplay] ---- GRANT CREATE CONSTRAINT ON DATABASE neo4j TO regularUsers ---- -The `SHOW CONSTRAINTS` privilege only affects the xref::constraints/syntax.adoc#administration-constraints-syntax-list[`SHOW CONSTRAINTS` command], and not the older procedures for listing constraints, such as `db.constraints`. +The `SHOW CONSTRAINTS` privilege only affects the xref:constraints/syntax.adoc#administration-constraints-syntax-list[`SHOW CONSTRAINTS` command], and not the older procedures for listing constraints, such as `db.constraints`. [[access-control-database-administration-tokens]] == The `NAME MANAGEMENT` privileges The right to create new labels, relationship types, and property names is different from the right to create nodes, relationships, and properties. -The latter is managed using database `WRITE` privileges, while the former is managed using specific `+GRANT/DENY CREATE NEW ...+` commands for each type. - +The latter is managed using database `WRITE` privileges, while the former is managed using specific `GRANT/DENY CREATE NEW ...` commands for each type. -.Node label management privileges syntax -[cols="<15s,<85"] +.Label, relationship type and property name management command syntax +[options="header", width="100%", cols="3a,2"] |=== - | Command -m| +GRANT CREATE NEW LABEL+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -GRANT CREATE NEW [NODE] LABEL[S] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - | Description -| Enables the specified roles to create new node labels in the home database, specific database(s), or all databases. - -|=== - - -.Relationship type management privileges syntax -[cols="<15s,<85"] -|=== -| Command -m| +GRANT CREATE NEW TYPE+ +| [source, cypher, role=noplay] +GRANT CREATE NEW [NODE] LABEL[S] + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Enable the specified roles to create new node labels in the home database, specific database(s), or all databases. -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- +| [source, cypher, role=noplay] GRANT CREATE NEW [RELATIONSHIP] TYPE[S] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Enable the specified roles to create new relationship types in the home database, specific database(s), or all databases. -| Description -| Enables the specified roles to create new relationship types in the home database, specific database(s), or all databases. - -|=== - - -.Property name management privileges syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT CREATE NEW NAME+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- +| [source, cypher, role=noplay] GRANT CREATE NEW [PROPERTY] NAME[S] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - -| Description -| Enables the specified roles to create new property names in the home database, specific database(s), or all databases. - -|=== - - -.Node label, relationship type, and property name privileges management syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT NAME+ + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Enable the specified roles to create new property names in the home database, specific database(s), or all databases. -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- +| [source, cypher, role=noplay] GRANT NAME [MANAGEMENT] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - -| Description -| Enables the specified roles to create new labels, relationship types, and property names in the home database, specific database(s), or all databases. - + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Enable the specified roles to create new labels, relationship types, and property names in the home database, specific database(s), or all databases. |=== - -For example, to grant the role `regularUsers` the ability to create new properties on nodes or relationships on the database `neo4j`, use: +For example, granting the ability to create new properties on nodes or relationships in the database `neo4j` to the role `regularUsers` is done using the following query. [source, cypher, role=noplay] ---- GRANT CREATE NEW PROPERTY NAME ON DATABASE neo4j TO regularUsers ---- +[NOTE] +==== +The `SHOW PRIVILEGES` commands return the `NAME MANAGEMENT` privilege as the action `token`, when not using `AS COMMANDS`. +==== + [[access-control-database-administration-all]] == Granting `ALL DATABASE PRIVILEGES` The right to access a database, create and drop indexes and constraints and create new labels, relationship types or property names can be achieved with a single command: -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- GRANT ALL [[DATABASE] PRIVILEGES] - ON { HOME DATABASE | DATABASE[S] { * | name[, ...] } } - TO role[, ...] + ON {HOME DATABASE | DATABASE[S] {* | name[, ...]}} + TO role[, ...] ---- [NOTE] @@ -885,81 +486,40 @@ a|Rows: 1 [[access-control-database-administration-transaction]] == Granting `TRANSACTION MANAGEMENT` privileges -The right to run the commands `SHOW TRANSACTIONS`, `TERMINATE TRANSACTIONS`, and the deprecated procedures `dbms.listTransactions`, `dbms.listQueries`, `dbms.killQuery`, `dbms.killQueries`, `dbms.killTransaction` and `dbms.killTransactions` is now managed through the `SHOW TRANSACTION` and `TERMINATE TRANSACTION` privileges. - - -.Database privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT SHOW TRANSACTION+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -GRANT SHOW TRANSACTION[S] [( { * \| user[, ...] } )] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - -| Description -| Enables the specified roles to list transactions and queries for user(s) or all users in the home database, specific database(s), or all databases. +The right to run the procedures `dbms.listTransactions`, `dbms.listQueries`, `dbms.killQuery`, `dbms.killQueries`, +`dbms.killTransaction` and `dbms.killTransactions` are managed through the `SHOW TRANSACTION` and `TERMINATE TRANSACTION` privileges. +.Transaction management command syntax +[options="header", width="100%", cols="3a,2"] |=== - - -.Database privilege syntax -[cols="<15s,<85"] -|=== - | Command -m| +GRANT TERMINATE TRANSACTION+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -GRANT TERMINATE TRANSACTION[S] [( { * \| user[, ...] } )] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- - | Description -| Enables the specified roles to end running transactions and queries for user(s) or all users in the home database, specific database(s), or all databases. - -|=== - - -.Database privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT TRANSACTION+ -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -GRANT TRANSACTION [MANAGEMENT] [( { * \| user[, ...] } )] - ON { HOME DATABASE \| DATABASE[S] { * \| name[, ...] } } - TO role[, ...] ----- +| [source, cypher, role=noplay] +GRANT SHOW TRANSACTION[S] [( {* \| user[, ...]} )] + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Enable the specified roles to list transactions and queries for user(s) or all users in the home database, specific database(s), or all databases. -| Description -| Enables the specified roles to manage transactions and queries for user(s) or all users in the home database, specific database(s), or all databases. +| [source, cypher, role=noplay] +GRANT TERMINATE TRANSACTION[S] [( {* \| user[, ...]} )] + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Enable the specified roles to end running transactions and queries for user(s) or all users in the home database, specific database(s), or all databases. +| [source, cypher, role=noplay] +GRANT TRANSACTION [MANAGEMENT] [( {* \| user[, ...]} )] + ON {HOME DATABASE \| DATABASE[S] {* \| name[, ...]}} + TO role[, ...] +| Enable the specified roles to manage transactions and queries for user(s) or all users in the home database, specific database(s), or all databases. |=== - [NOTE] ==== -Note that the `TRANSACTION MANAGEMENT` privileges are not included in the xref::access-control/database-administration.adoc#access-control-database-administration-all[`ALL DATABASE PRIVILEGES`]. +Note that the `TRANSACTION MANAGEMENT` privileges are not included in the xref:access-control/database-administration.adoc#access-control-database-administration-all[`ALL DATABASE PRIVILEGES`]. ==== -For example, to grant the role `regularUsers` the ability to list transactions for user `jake` on the database `neo4j`, use: +For example, granting the ability to list transactions for user `jake` in the database `neo4j` to the role `regularUsers` is done using the following query. [source, cypher, role=noplay] ---- diff --git a/modules/ROOT/pages/access-control/dbms-administration.adoc b/modules/ROOT/pages/access-control/dbms-administration.adoc index 9cdeac9f1..0a76d21bc 100644 --- a/modules/ROOT/pages/access-control/dbms-administration.adoc +++ b/modules/ROOT/pages/access-control/dbms-administration.adoc @@ -1,60 +1,53 @@ -:description: How to use Cypher to manage Neo4j DBMS administrative privileges. - [role=enterprise-edition] [[access-control-dbms-administration]] = DBMS administration - -[abstract] --- -This section explains how to use Cypher to manage Neo4j DBMS administrative privileges. --- +:description: This section explains how to use Cypher to manage Neo4j DBMS administrative privileges. All DBMS privileges are relevant system-wide. Like user management, they do not belong to one specific database or graph. -For more details on the differences between graphs, databases and the DBMS, refer to xref::introduction/neo4j-databases-graphs.adoc[]. +For more details on the differences between graphs, databases and the DBMS, refer to xref:introduction/neo4j-databases-graphs.adoc[]. -image::privileges_grant_and_deny_syntax_dbms_privileges.png[title="Syntax of GRANT and DENY DBMS Privileges"] +image::grant-privileges-dbms.png[title="Syntax of GRANT and DENY DBMS Privileges"] -image::privileges_hierarchy_dbms.png[title="DBMS privileges hierarchy"] +image::privilege-hierarchy-dbms.png[title="DBMS privileges hierarchy"] -The xref::access-control/built-in-roles.adoc#access-control-built-in-roles-admin[`admin` role] has a number of built-in privileges. +The xref:access-control/built-in-roles.adoc#access-control-built-in-roles-admin[`admin` role] has a number of built-in privileges. These include: -* Create, delete, and modify databases and aliases. +* Create and drop databases. * Change configuration parameters. * Manage transactions. * Manage users and roles. * Manage sub-graph privileges. * Manage procedure security. -To enable a user to perform these tasks, you can grant them the `admin` role, but it is also possible to make a custom role with a subset of these privileges. -All privileges are also assignable using Cypher commands. -For more details, see the following sections: +The easiest way to enable a user to perform these tasks is to grant them the `admin` role. +All of these privileges are also assignable using Cypher commands. +See the sections on xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[role management], +xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[user management], +xref:access-control/dbms-administration.adoc#access-control-dbms-administration-database-management[database management], +xref:access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[privilege management], +xref:access-control/database-administration.adoc#access-control-database-administration-transaction[transaction management] and +xref:access-control/dbms-administration.adoc#access-control-dbms-administration-execute[procedure and user defined function security] for details. +It is possible to make a custom role with a subset of these privileges. -* xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[Role management] -* xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[User management] -* xref::access-control/dbms-administration.adoc#access-control-dbms-administration-impersonation[Impersonation privileges management] -* xref::access-control/dbms-administration.adoc#access-control-dbms-administration-database-management[Database management] -* xref::access-control/dbms-administration.adoc#access-control-dbms-administration-alias-management[Alias management] -* xref::access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[Privilege management] -* xref::access-control/database-administration.adoc#access-control-database-administration-transaction[Transaction management] -* xref::access-control/dbms-administration.adoc#access-control-dbms-administration-execute[Procedure and user-defined function security] [[access-control-dbms-administration-custom]] == Using a custom role to manage DBMS privileges -In order to have an administrator role with a subset of privileges that includes all DBMS privileges, but not all database privileges, you can copy the `admin` role and revoke or deny the unwanted privileges. +If it is desired to have an administrator with a subset of privileges that includes all DBMS privileges, but not all database privileges, this can be achieved in multiple ways. +One way is to copy the `admin` role and revoking or denying the unwanted privileges. A second option is to build a custom administrator from scratch by granting the wanted privileges instead. -As an example, an administrator role can be created to only manage users and roles by using the second option: +As an example, let's create an administrator that can only manage users and roles by using the latter option. -. First, create the new role: +. First we create the new role: + [source, cypher, role=noplay] ---- CREATE ROLE usermanager ---- -. Then grant the privilege to manage users: +. Then we grant the privilege to manage users: + [source, cypher, role=noplay] ---- @@ -67,14 +60,15 @@ GRANT USER MANAGEMENT ON DBMS TO usermanager GRANT ROLE MANAGEMENT ON DBMS TO usermanager ---- -The resulting role has privileges that only allow user and role management. -To list all privileges for the role `usermanager` as commands, run this query: +The resulting role has privileges that only allow user and role management: [source, cypher, role=noplay] ---- SHOW ROLE usermanager PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `usermanager`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -84,46 +78,45 @@ SHOW ROLE usermanager PRIVILEGES AS COMMANDS a|Rows: 2 |=== -Note that this role does not allow all DBMS capabilities. -For example, the role is missing privileges for management, creation and drop of databases as well as execution of `admin` procedures. -To create a more powerful administrator, you can grant a different set of privileges. +However, this role doesn't allow all DBMS capabilities. +For example, the role is missing privilege management, creating and dropping databases as well as executing admin procedures. +We can make a more powerful administrator by granting a different set of privileges. +Let's create an administrator that can perform almost all DBMS capabilities, excluding database management, but also with some limited database capabilities, such as managing transactions: -In the following example, a new administrator role is created to perform almost all DBMS capabilities, excluding database management. -However, the role still has some limited database capabilities, such as managing transactions: - -. Again, start by creating a new role: +. Again, we start by creating a new role: + [source, cypher, role=noplay] ---- CREATE ROLE customAdministrator ---- -. Then grant the privilege for all DBMS capabilities: +. Then we grant the privilege for all DBMS capabilities: + [source, cypher, role=noplay] ---- GRANT ALL DBMS PRIVILEGES ON DBMS TO customAdministrator ---- -. And explicitly deny the privilege to manage databases and aliases: +. And explicitly deny the privilege to manage databases: + [source, cypher, role=noplay] ---- DENY DATABASE MANAGEMENT ON DBMS TO customAdministrator ---- -. Next, grant the transaction management privilege: +. Thereafter we grant the transaction management privilege: + [source, cypher, role=noplay] ---- GRANT TRANSACTION MANAGEMENT (*) ON DATABASE * TO customAdministrator ---- -The resulting role has privileges that include all DBMS privileges except creating, dropping, and modifying databases and aliases, as well as managing transactions. -Use the following query to list all privileges for the role `customAdministrator` as commands: +The resulting role has privileges that allow all DBMS privileges except creating and dropping databases, as well as managing transactions: [source, cypher, role=noplay] ---- SHOW ROLE customAdministrator PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `customAdministrator`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -150,62 +143,63 @@ They can be granted, denied and revoked like other privileges. GRANT CREATE ROLE ON DBMS TO role[, ...] -| Enables the specified roles to create new roles. +| Enable the specified roles to create new roles. | [source, cypher, role=noplay] GRANT RENAME ROLE ON DBMS TO role[, ...] -| Enables the specified roles to change the name of roles. +| Enable the specified roles to change the name of roles. | [source, cypher, role=noplay] GRANT DROP ROLE ON DBMS TO role[, ...] -| Enables the specified roles to delete roles. +| Enable the specified roles to delete roles. | [source, cypher, role=noplay] GRANT ASSIGN ROLE ON DBMS TO role[, ...] -| Enables the specified roles to assign roles to users. +| Enable the specified roles to assign roles to users. | [source, cypher, role=noplay] GRANT REMOVE ROLE ON DBMS TO role[, ...] -| Enables the specified roles to remove roles from users. +| Enable the specified roles to remove roles from users. | [source, cypher, role=noplay] GRANT SHOW ROLE ON DBMS TO role[, ...] -| Enables the specified roles to list roles. +| Enable the specified roles to list roles. | [source, cypher, role=noplay] GRANT ROLE MANAGEMENT ON DBMS TO role[, ...] -| Enables the specified roles to create, delete, assign, remove, and list roles. +| Enable the specified roles to create, delete, assign, remove, and list roles. |=== The ability to add roles can be granted via the `CREATE ROLE` privilege. -See an example: +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT CREATE ROLE ON DBMS TO roleAdder ---- -The resulting role has privileges that only allow adding roles. -List all privileges for the role `roleAdder` as commands by using the following query: +The resulting role has privileges that only allow adding roles: [source, cypher, role=noplay] ---- -SHOW ROLE roleAdder PRIVILEGES AS COMMANDS +SHOW ROLE roleAdder PRIVILEGES AS COMMANDS" ---- +Lists all privileges for role `roleAdder`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -214,22 +208,22 @@ SHOW ROLE roleAdder PRIVILEGES AS COMMANDS a|Rows: 1 |=== -The ability to rename roles can be granted via the `RENAME ROLE` privilege. -See an example: +The ability to rename roles can be granted via the `RENAME ROLE` privilege. The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT RENAME ROLE ON DBMS TO roleNameModifier ---- -The resulting role has privileges that only allow renaming roles. -List all privileges for the role `roleNameModifier` using the following query: +The resulting role has privileges that only allow renaming roles: [source, cypher, role=noplay] ---- SHOW ROLE roleNameModifier PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `roleNameModifier`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -239,21 +233,22 @@ a|Rows: 1 |=== The ability to delete roles can be granted via the `DROP ROLE` privilege. -See an example: +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT DROP ROLE ON DBMS TO roleDropper ---- -The resulting role has privileges that only allow deleting roles. -List all privileges for the role `roleDropper` by using the following query: +The resulting role has privileges that only allow deleting roles: [source, cypher, role=noplay] ---- SHOW ROLE roleDropper PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `roleDropper`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -263,21 +258,22 @@ a|Rows: 1 |=== The ability to assign roles to users can be granted via the `ASSIGN ROLE` privilege. -See an example: +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT ASSIGN ROLE ON DBMS TO roleAssigner ---- -The resulting role has privileges that only allow assigning/granting roles. -List all privileges for the role `roleAssigner` as commands by using the following query: +The resulting role has privileges that only allow assigning/granting roles: [source, cypher, role=noplay] ---- -SHOW ROLE roleAssigner PRIVILEGES AS COMMANDS +SHOW ROLE roleAssigner PRIVILEGES AS COMMANDS" ---- +Lists all privileges for role `roleAssigner`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -287,21 +283,22 @@ a|Rows: 1 |=== The ability to remove roles from users can be granted via the `REMOVE ROLE` privilege. -See an example: +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT REMOVE ROLE ON DBMS TO roleRemover ---- -The resulting role has privileges that only allow removing/revoking roles. -List all privileges for the role `roleRemover` as commands by using the following query: +The resulting role has privileges that only allow removing/revoking roles: [source, cypher, role=noplay] ---- SHOW ROLE roleRemover PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `roleRemover`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -311,26 +308,24 @@ a|Rows: 1 |=== The ability to show roles can be granted via the `SHOW ROLE` privilege. -A role with this privilege is allowed to execute the `SHOW ROLES` and `SHOW POPULATED ROLES` administration commands. +A user with this privilege is allowed to execute the `SHOW ROLES` and `SHOW POPULATED ROLES` administration commands. For the `SHOW ROLES WITH USERS` and `SHOW POPULATED ROLES WITH USERS` administration commands, both this privilege and the `SHOW USER` privilege are required. The following query shows an example of how to grant the `SHOW ROLE` privilege: -In order to use `SHOW ROLES WITH USERS` and `SHOW POPULATED ROLES WITH USERS` administration commands, both the `SHOW ROLE` and the `SHOW USER` privileges are required. -See an example of how to grant the `SHOW ROLE` privilege: - [source, cypher, role=noplay] ---- GRANT SHOW ROLE ON DBMS TO roleShower ---- -The resulting role has privileges that only allow showing roles. -List all privileges for the role `roleShower` as commands by using the following query: +The resulting role has privileges that only allow showing roles: [source, cypher, role=noplay] ---- SHOW ROLE roleShower PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `roleShower`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -340,21 +335,22 @@ a|Rows: 1 |=== The privileges to create, rename, delete, assign, remove, and list roles can be granted via the `ROLE MANAGEMENT` privilege. -See an example: +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT ROLE MANAGEMENT ON DBMS TO roleManager ---- -The resulting role has all privileges to manage roles. -List all privileges for the role `roleManager` as commands by using the following query: +The resulting role has all privileges to manage roles: [source, cypher, role=noplay] ---- SHOW ROLE roleManager PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `roleManager`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -367,7 +363,7 @@ a|Rows: 1 [[access-control-dbms-administration-user-management]] == The DBMS `USER MANAGEMENT` privileges -The DBMS privileges for user management can be assigned using Cypher administrative commands. +The DBMS privileges for user management are assignable using Cypher administrative commands. They can be granted, denied and revoked like other privileges. .User management privileges command syntax @@ -379,74 +375,75 @@ They can be granted, denied and revoked like other privileges. GRANT CREATE USER ON DBMS TO role[, ...] -| Enables the specified roles to create new users. +| Enable the specified roles to create new users. | [source, cypher, role=noplay] GRANT RENAME USER ON DBMS TO role[, ...] -| Enables the specified roles to change the name of users. +| Enable the specified roles to change the name of users. | [source, cypher, role=noplay] GRANT ALTER USER ON DBMS TO role[, ...] -| Enables the specified roles to modify users. +| Enable the specified roles to modify users. | [source, cypher, role=noplay] GRANT SET PASSWORD[S] ON DBMS TO role[, ...] -| Enables the specified roles to modify users' passwords and whether those passwords must be changed upon first login. +| Enable the specified roles to modify users' passwords and whether those passwords must be changed upon first login. | [source, cypher, role=noplay] GRANT SET USER HOME DATABASE ON DBMS TO role[, ...] -| Enables the specified roles to modify users' home database. +| Enable the specified roles to modify users' home database. | [source, cypher, role=noplay] GRANT SET USER STATUS ON DBMS TO role[, ...] -| Enables the specified roles to modify the account status of users. +| Enable the specified roles to modify the account status of users. | [source, cypher, role=noplay] GRANT DROP USER ON DBMS TO role[, ...] -| Enables the specified roles to delete users. +| Enable the specified roles to delete users. | [source, cypher, role=noplay] GRANT SHOW USER ON DBMS TO role[, ...] -| Enables the specified roles to list users. +| Enable the specified roles to list users. | [source, cypher, role=noplay] GRANT USER MANAGEMENT ON DBMS TO role[, ...] -| Enables the specified roles to create, delete, modify, and list users. +| Enable the specified roles to create, delete, modify, and list users. |=== The ability to add users can be granted via the `CREATE USER` privilege. -See an example: +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT CREATE USER ON DBMS TO userAdder ---- -The resulting role has privileges that only allow adding users. -List all privileges for the role `userAdder` as commands by using this query: +The resulting role has privileges that only allow adding users: [source, cypher, role=noplay] ---- SHOW ROLE userAdder PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `userAdder`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -481,21 +478,22 @@ a|Rows: 1 |=== The ability to modify users can be granted via the `ALTER USER` privilege. -See an example: +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT ALTER USER ON DBMS TO userModifier ---- -The resulting role has privileges that only allow modifying users. -List all privileges for the role `userModifier` as commands by using the following query: +The resulting role has privileges that only allow modifying users: [source, cypher, role=noplay] ---- SHOW ROLE userModifier PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `userModifier`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -504,7 +502,7 @@ SHOW ROLE userModifier PRIVILEGES AS COMMANDS a|Rows: 1 |=== -A user that is granted the `ALTER USER` privilege is allowed to run the `ALTER USER` administration command with one or several of the `SET PASSWORD`, `SET PASSWORD CHANGE [NOT] REQUIRED` and `SET STATUS` parts: +A user that is granted `ALTER USER` is allowed to run the `ALTER USER` administration command with one or several of the `SET PASSWORD`, `SET PASSWORD CHANGE [NOT] REQUIRED` and `SET STATUS` parts: [source, cypher, role=noplay] ---- @@ -512,21 +510,22 @@ ALTER USER jake SET PASSWORD 'secret' SET STATUS SUSPENDED ---- The ability to modify users' passwords and whether those passwords must be changed upon first login can be granted via the `SET PASSWORDS` privilege. -See an example: +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT SET PASSWORDS ON DBMS TO passwordModifier ---- -The resulting role has privileges that only allow modifying users' passwords and whether those passwords must be changed upon first login. -List all privileges for the role `passwordModifier` as commands by using the following query: +The resulting role has privileges that only allow modifying users' passwords and whether those passwords must be changed upon first login: [source, cypher, role=noplay] ---- SHOW ROLE passwordModifier PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `passwordModifier`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -535,7 +534,7 @@ SHOW ROLE passwordModifier PRIVILEGES AS COMMANDS a|Rows: 1 |=== -A user that is granted the `SET PASSWORDS` privilege is allowed to run the `ALTER USER` administration command with one or both of the `SET PASSWORD` and `SET PASSWORD CHANGE [NOT] REQUIRED` parts: +A user that is granted `SET PASSWORDS` is allowed to run the `ALTER USER` administration command with one or both of the `SET PASSWORD` and `SET PASSWORD CHANGE [NOT] REQUIRED` parts: [source, cypher, role=noplay] ---- @@ -543,21 +542,22 @@ ALTER USER jake SET PASSWORD 'abc123' CHANGE NOT REQUIRED ---- The ability to modify the account status of users can be granted via the `SET USER STATUS` privilege. -See an example: +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT SET USER STATUS ON DBMS TO statusModifier ---- -The resulting role has privileges that only allow modifying the account status of users. -List all privileges for the role `statusModifier` as commands by using the following query: +The resulting role has privileges that only allow modifying the account status of users: [source, cypher, role=noplay] ---- SHOW ROLE statusModifier PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `statusModifier`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -566,29 +566,30 @@ SHOW ROLE statusModifier PRIVILEGES AS COMMANDS a|Rows: 1 |=== -A user that is granted the `SET USER STATUS` privilege is allowed to run the `ALTER USER` administration command with only the `SET STATUS` part: +A user that is granted `SET USER STATUS` is allowed to run the `ALTER USER` administration command with only the `SET STATUS` part: [source, cypher, role=noplay] ---- ALTER USER jake SET STATUS ACTIVE ---- -In order to be able to modify the home database of users, grant the `SET USER HOME DATABASE` privilege. -See an example: +The ability to modify the home database of users can be granted via the `SET USER HOME DATABASE` privilege. +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT SET USER HOME DATABASE ON DBMS TO statusModifier ---- -The resulting role has privileges that only allow modifying the home database of users. -List all privileges for the role `statusModifier` as commands by using the following query: +The resulting role has privileges that only allow modifying the home database of users: [source, cypher, role=noplay] ---- SHOW ROLE statusModifier PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `statusModifier`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -598,7 +599,7 @@ SHOW ROLE statusModifier PRIVILEGES AS COMMANDS a|Rows: 2 |=== -A user that is granted the `SET USER HOME DATABASE` privilege is allowed to run the `ALTER USER` administration command with only the `SET HOME DATABASE` or `REMOVE HOME DATABASE` part: +A user that is granted `SET USER HOME DATABASE` is allowed to run the `ALTER USER` administration command with only the `SET HOME DATABASE` or `REMOVE HOME DATABASE` part: [source, cypher, role=noplay] ---- @@ -616,21 +617,22 @@ Note that the combination of the `SET PASSWORDS`, `SET USER STATUS`, and the `SE ==== The ability to delete users can be granted via the `DROP USER` privilege. -See an example: +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT DROP USER ON DBMS TO userDropper ---- -The resulting role has privileges that only allow deleting users. -List all privileges for the role `userDropper` as commands by using the following query: +The resulting role has privileges that only allow deleting users: [source, cypher, role=noplay] ---- SHOW ROLE userDropper PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `userDropper`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -640,21 +642,22 @@ a|Rows: 1 |=== The ability to show users can be granted via the `SHOW USER` privilege. -See an example: +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT SHOW USER ON DBMS TO userShower ---- -The resulting role has privileges that only allow showing users. -List all privileges for the role `userShower` as commands by using the following query: +The resulting role has privileges that only allow showing users: [source, cypher, role=noplay] ---- SHOW ROLE userShower PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `userShower`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -664,106 +667,25 @@ a|Rows: 1 |=== The privileges to create, rename, modify, delete, and list users can be granted via the `USER MANAGEMENT` privilege. -See an example: +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT USER MANAGEMENT ON DBMS TO userManager ---- -The resulting role has all privileges to manage users. -List all privileges for the role `userManager` as commands by using the following query: +The resulting role has all privileges to manage users: [source, cypher, role=noplay] ---- SHOW ROLE userManager PRIVILEGES AS COMMANDS ---- -.Result -[options="header,footer", width="100%", cols="m"] -|=== -|command -|"GRANT SHOW USER ON DBMS TO `userManager`" -a|Rows: 1 -|=== - -[[access-control-dbms-administration-impersonation]] -== The DBMS `IMPERSONATE` privileges - -The DBMS privileges for impersonation can be assigned through Cypher administrative commands. -They can be granted, denied, and revoked like other privileges. - -Impersonation is the ability of a user to assume another user's roles (and therefore privileges), with the restriction of not being able to execute updating `admin` commands as the impersonated user (i.e. they would still be able to use `SHOW` commands). - -The ability to impersonate users can be granted via the `IMPERSONATE` privilege. - -.Impersonation privileges command syntax -[options="header", width="100%", cols="3a,2"] -|=== -| Command | Description - -| [source, cypher, role=noplay] -GRANT IMPERSONATE [(*)] - ON DBMS - TO role[, ...] -| Enables the specified roles to impersonate any user. - -| [source, cypher, role=noplay] -GRANT IMPERSONATE (user[, ...]) - ON DBMS - TO role[, ...] -| Enables the specified roles to impersonate the specified users. - -|=== - -The following query shows an example of this. -Note that `userImpersonator` must be an existing role in order to make this query work: - -.Query -[source, cypher, role=noplay] ----- -GRANT IMPERSONATE (*) ON DBMS TO userImpersonator ----- - -The resulting role has privileges that allow impersonating all users: - -.Query -[source, cypher, role=noplay] ----- -SHOW ROLE userImpersonator PRIVILEGES AS COMMANDS ----- - -.Result -[options="header,footer", width="100%", cols="m"] -|=== -| command -| "GRANT IMPERSONATE (*) ON DBMS TO `userImpersonator`" -a|Rows: 1 -|=== - -It is also possible to deny and revoke that privilege. -See an example which shows of how the `userImpersonator` user would be able to impersonate all users, except `alice`: - -.Query -[source, cypher, role=noplay] ----- -DENY IMPERSONATE (alice) ON DBMS TO userImpersonator ----- - -To grant (or revoke) the permissions to impersonate a specific user or a subset of users, you can first list them with this query: - -.Query -[source, cypher, role=noplay] ----- -GRANT IMPERSONATE (alice, bob) ON DBMS TO userImpersonator ----- - [[access-control-dbms-administration-database-management]] == The DBMS `DATABASE MANAGEMENT` privileges -The DBMS privileges for database management can be assigned by using Cypher administrative commands. -They can be granted, denied and revoked like other privileges. +The DBMS privileges for database management are assignable using Cypher administrative commands. They can be granted, denied and revoked like other privileges. .Database management privileges command syntax [options="header", width="100%", cols="3a,2"] @@ -774,51 +696,40 @@ They can be granted, denied and revoked like other privileges. GRANT CREATE DATABASE ON DBMS TO role[, ...] -| Enables the specified roles to create new databases and aliases. +| Enable the specified roles to create new databases. | [source, cypher, role=noplay] GRANT DROP DATABASE ON DBMS TO role[, ...] -| Enables the specified roles to delete databases and aliases. - -| [source, cypher, role=noplay] -GRANT ALTER DATABASE - ON DBMS - TO role[, ...] -| Enables the specified roles to modify databases and aliases. - -| [source, cypher, role=noplay] -GRANT SET DATABASE ACCESS - ON DBMS - TO role[, ...] -| Enables the specified roles to modify access of databases. +| Enable the specified roles to delete databases. | [source, cypher, role=noplay] GRANT DATABASE MANAGEMENT ON DBMS TO role[, ...] -| Enables the specified roles to create, delete, and modify databases and aliases. +| Enable the specified roles to create and delete databases. |=== -The ability to create databases and aliases can be granted via the `CREATE DATABASE` privilege. -See an example: +The ability to create databases can be granted via the `CREATE DATABASE` privilege. +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT CREATE DATABASE ON DBMS TO databaseAdder ---- -The resulting role has privileges that only allow creating databases and aliases. -List all privileges for the role `databaseAdder` as commands by using the following query: +The resulting role has privileges that only allow creating databases: [source, cypher, role=noplay] ---- SHOW ROLE databaseAdder PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `databaseAdder`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -827,270 +738,62 @@ SHOW ROLE databaseAdder PRIVILEGES AS COMMANDS a|Rows: 1 |=== -The ability to delete databases and aliases can be granted via the `DROP DATABASE` privilege. -See an example: +The ability to delete databases can be granted via the `DROP DATABASE` privilege. +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT DROP DATABASE ON DBMS TO databaseDropper ---- -The resulting role has privileges that only allow deleting databases and aliases. -List all privileges for the role `databaseDropper` as commands by using the following query: +The resulting role has privileges that only allow deleting databases: [source, cypher, role=noplay] ---- SHOW ROLE databaseDropper PRIVILEGES AS COMMANDS ---- -.Result -[options="header,footer", width="100%", cols="m"] -|=== -|command -|"GRANT DROP DATABASE ON DBMS TO `databaseDropper`" -a|Rows: 1 -|=== - -The ability to modify databases and aliases can be granted via the `ALTER DATABASE` privilege. -See an example: - -[source, cypher, role=noplay] ----- -GRANT ALTER DATABASE ON DBMS TO databaseModifier ----- - -The resulting role has privileges that only allow modifying databases and aliases. -List all privileges for the role `databaseModifier` as commands by using the following query: - -[source, cypher, role=noplay] ----- -SHOW ROLE databaseModifier PRIVILEGES AS COMMANDS ----- +Lists all privileges for role `databaseDropper`: .Result [options="header,footer", width="100%", cols="m"] |=== |command -|"GRANT ALTER DATABASE ON DBMS TO `databaseModifier`" +|"GRANT DROP DATABASE ON DBMS TO `databaseDropper`" a|Rows: 1 |=== -The ability to modify access of databases can be granted via the `SET DATABASE ACCESS` privilege. -See an example: - -[source, cypher, role=noplay] ----- -GRANT SET DATABASE ACCESS ON DBMS TO accessModifier ----- - -The resulting role has privileges that only allow modifying access of databases. -List all privileges for the role `accessModifier` as commands by using the following query: - -[source, cypher, role=noplay] ----- -SHOW ROLE accessModifier PRIVILEGES AS COMMANDS ----- - -.Result -[options="header,footer", width="100%", cols="m"] -|=== -|command -|"GRANT SET DATABASE ACCESS ON DBMS TO `accessModifier`" -a|Rows: 1 -|=== -The privileges to create, delete, and modify databases and aliases can be granted via the `DATABASE MANAGEMENT` privilege. -See an example: +The privileges to create and delete databases can be granted via the `DATABASE MANAGEMENT` privilege. +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT DATABASE MANAGEMENT ON DBMS TO databaseManager ---- -The resulting role has all privileges to manage databases and aliases. -List all privileges for the role `databaseManager` as commands by using the following query: +The resulting role has all privileges to manage databases: [source, cypher, role=noplay] ---- SHOW ROLE databaseManager PRIVILEGES AS COMMANDS ---- -.Result -[options="header,footer", width="100%", cols="m"] -|=== -|command -|"GRANT DATABASE MANAGEMENT ON DBMS TO `databaseManager`" -a|Rows: 1 -|=== - -[[access-control-dbms-administration-alias-management]] -== The DBMS `ALIAS MANAGEMENT` privileges - -The DBMS privileges for alias management can be assigned by using Cypher administrative commands and can be applied to both local and remote aliases. -They can be granted, denied and revoked like other privileges. -It is also possible to manage aliases with xref::access-control/dbms-administration.adoc#access-control-dbms-administration-database-management[database management commands]. - -.Alias management privileges command syntax -[options="header", width="100%", cols="3a,2"] -|=== -| Command | Description - -| [source, cypher, role=noplay] -GRANT CREATE ALIAS -ON DBMS -TO role[, ...] -| Enables the specified roles to create new aliases. - -| [source, cypher, role=noplay] -GRANT DROP ALIAS -ON DBMS -TO role[, ...] -| Enables the specified roles to delete aliases. - -| [source, cypher, role=noplay] -GRANT ALTER ALIAS -ON DBMS -TO role[, ...] -| Enables the specified roles to modify aliases. - -| [source, cypher, role=noplay] -GRANT SHOW ALIAS -ON DBMS -TO role[, ...] -| Enables the specified roles to list aliases. - -| [source, cypher, role=noplay] -GRANT ALIAS MANAGEMENT -ON DBMS -TO role[, ...] -| Enables the specified roles to list, create, delete, and modify aliases. - -|=== - -The ability to create aliases can be granted via the `CREATE ALIAS` privilege. -See an example: - -[source, cypher, role=noplay] ----- -GRANT CREATE ALIAS ON DBMS TO aliasAdder ----- - -The resulting role has privileges that only allow creating aliases. -List all privileges for the role `aliasAdder` as commands by using the following query: - -[source, cypher, role=noplay] ----- -SHOW ROLE aliasAdder PRIVILEGES AS COMMANDS ----- - -.Result -[options="header,footer", width="100%", cols="m"] -|=== -|command -|"GRANT CREATE ALIAS ON DBMS TO `aliasAdder`" -a|Rows: 1 -|=== - -The ability to delete aliases can be granted via the `DROP ALIAS` privilege. -See an example: - -[source, cypher, role=noplay] ----- -GRANT DROP ALIAS ON DBMS TO aliasDropper ----- - -The resulting role has privileges that only allow deleting aliases. -See all privileges for the role `aliasDropper` as commands by using the following query: - -[source, cypher, role=noplay] ----- -SHOW ROLE aliasDropper PRIVILEGES AS COMMANDS ----- +Lists all privileges for role `databaseManager`: .Result [options="header,footer", width="100%", cols="m"] |=== |command -|"GRANT DROP ALIAS ON DBMS TO `aliasDropper`" -a|Rows: 1 -|=== - -The ability to modify aliases can be granted via the `ALTER ALIAS` privilege. -See an example: - -[source, cypher, role=noplay] ----- -GRANT ALTER ALIAS ON DBMS TO aliasModifier ----- - -The resulting role has privileges that only allow modifying aliases. -List all privileges for the role `aliasModifier` as commands by using the following query: - -[source, cypher, role=noplay] ----- -SHOW ROLE aliasModifier PRIVILEGES AS COMMANDS ----- - -.Result -[options="header,footer", width="100%", cols="m"] -|=== -|command -|"GRANT ALTER ALIAS ON DBMS TO `aliasModifier`" -a|Rows: 1 -|=== - -The ability to list aliases can be granted via the `SHOW ALIAS` privilege. -See an example: - -[source, cypher, role=noplay] ----- -GRANT SHOW ALIAS ON DBMS TO aliasLister ----- - -The resulting role has privileges that only allow modifying aliases. -List all privileges for the role `aliasLister` as commands by using the following query: - -[source, cypher, role=noplay] ----- -SHOW ROLE aliasLister PRIVILEGES AS COMMANDS ----- - -.Result -[options="header,footer", width="100%", cols="m"] -|=== -|command -|"GRANT SHOW ALIAS ON DBMS TO `aliasLister`" +|"GRANT DATABASE MANAGEMENT ON DBMS TO `databaseManager`" a|Rows: 1 |=== -The privileges to list, create, delete, and modify aliases can be granted via the `ALIAS MANAGEMENT` privilege. -See an example: - -[source, cypher, role=noplay] ----- -GRANT ALIAS MANAGEMENT ON DBMS TO aliasManager ----- - -The resulting role has all privileges to manage aliases. -List all privileges for the role `aliasManager` as commands by using the following query: - -[source, cypher, role=noplay] ----- -SHOW ROLE aliasManager PRIVILEGES AS COMMANDS ----- - -.Result -[options="header,footer", width="100%", cols="m"] -|=== -|command -|"GRANT ALIAS MANAGEMENT ON DBMS TO `aliasManager`" -a|Rows: 1 -|=== [[access-control-dbms-administration-privilege-management]] == The DBMS `PRIVILEGE MANAGEMENT` privileges -The DBMS privileges for privilege management can be assigned by using Cypher administrative commands. +The DBMS privileges for privilege management are assignable using Cypher administrative commands. They can be granted, denied and revoked like other privileges. .Privilege management privileges command syntax @@ -1102,31 +805,30 @@ They can be granted, denied and revoked like other privileges. GRANT SHOW PRIVILEGE ON DBMS TO role[, ...] -| Enables the specified roles to list privileges. +| Enable the specified roles to list privileges. | [source, cypher, role=noplay] GRANT ASSIGN PRIVILEGE ON DBMS TO role[, ...] -| Enables the specified roles to assign privileges using the `GRANT` and `DENY` commands. +| Enable the specified roles to assign privileges using the `GRANT` and `DENY` commands. | [source, cypher, role=noplay] GRANT REMOVE PRIVILEGE ON DBMS TO role[, ...] -| Enables the specified roles to remove privileges using the `REVOKE` command. +| Enable the specified roles to remove privileges using the `REVOKE` command. | [source, cypher, role=noplay] GRANT PRIVILEGE MANAGEMENT ON DBMS TO role[, ...] -| Enables the specified roles to list, assign, and remove privileges. +| Enable the specified roles to list, assign, and remove privileges. |=== The ability to list privileges can be granted via the `SHOW PRIVILEGE` privilege. - -A user with this privilege is allowed to execute the `SHOW PRIVILEGES` and `SHOW ROLE roleName PRIVILEGES` administration commands. -To execute the `SHOW USER username PRIVILEGES` administration command, both this privilege and the `SHOW USER` privilege are required. +A user with this privilege is allowed to execute the `SHOW PRIVILEGES` and `SHOW ROLE roleName PRIVILEGES` administration commands. " + +For the `SHOW USER username PRIVILEGES` administration command, both this privilege and the `SHOW USER` privilege are required. The following query shows an example of how to grant the `SHOW PRIVILEGE` privilege: [source, cypher, role=noplay] @@ -1134,14 +836,15 @@ The following query shows an example of how to grant the `SHOW PRIVILEGE` privil GRANT SHOW PRIVILEGE ON DBMS TO privilegeShower ---- -The resulting role has privileges that only allow showing privileges. -List all privileges for the role `privilegeShower` as commands by using the following query: +The resulting role has privileges that only allow showing privileges: [source, cypher, role=noplay] ---- SHOW ROLE privilegeShower PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `privilegeShower`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -1152,29 +855,30 @@ a|Rows: 1 [NOTE] ==== -Note that no specific privileges are required for showing the current user's privileges through the `SHOW USER _username_ PRIVILEGES` or `SHOW USER PRIVILEGES` commands. +Note that no specific privileges are required for showing the current user's privileges using either `SHOW USER _username_ PRIVILEGES`, or `SHOW USER PRIVILEGES`. -In addition, note that if a non-native auth provider like LDAP is in use, `SHOW USER PRIVILEGES` will only work with a limited capacity by making it only possible for a user to show their own privileges. +Please note that if a non-native auth provider like LDAP is in use, `SHOW USER PRIVILEGES` will only work in a limited capacity; It is only possible for a user to show their own privileges. Other users' privileges cannot be listed when using a non-native auth provider. ==== The ability to assign privileges to roles can be granted via the `ASSIGN PRIVILEGE` privilege. -A user with this privilege is allowed to execute `GRANT` and `DENY` administration commands. -See an example of how to grant this privilege: +A user with this privilege is allowed to execute GRANT and DENY administration commands. +The following query shows an example of how to grant this privilege: [source, cypher, role=noplay] ---- GRANT ASSIGN PRIVILEGE ON DBMS TO privilegeAssigner ---- -The resulting role has privileges that only allow assigning privileges. -List all privileges for the role `privilegeAssigner` as commands by using the following query: +The resulting role has privileges that only allow assigning privileges: [source, cypher, role=noplay] ---- SHOW ROLE privilegeAssigner PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `privilegeAssigner`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -1184,23 +888,23 @@ a|Rows: 1 |=== The ability to remove privileges from roles can be granted via the `REMOVE PRIVILEGE` privilege. - -A user with this privilege is allowed to execute `REVOKE` administration commands. -See an example of how to grant this privilege: +A user with this privilege is allowed to execute REVOKE administration commands. +The following query shows an example of how to grant this privilege: [source, cypher, role=noplay] ---- GRANT REMOVE PRIVILEGE ON DBMS TO privilegeRemover ---- -The resulting role has privileges that only allow removing privileges. -List all privileges for the role `privilegeRemover` as commands by using the following query: +The resulting role has privileges that only allow removing privileges: [source, cypher, role=noplay] ---- SHOW ROLE privilegeRemover PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `privilegeRemover`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -1210,21 +914,22 @@ a|Rows: 1 |=== The privileges to list, assign, and remove privileges can be granted via the `PRIVILEGE MANAGEMENT` privilege. -See an example: +The following query shows an example of this: [source, cypher, role=noplay] ---- GRANT PRIVILEGE MANAGEMENT ON DBMS TO privilegeManager ---- -The resulting role has all privileges to manage privileges. -List all privileges for the role `privilegeManager` as commands by using the following query: +The resulting role has all privileges to manage privileges: [source, cypher, role=noplay] ---- SHOW ROLE privilegeManager PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `privilegeManager`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -1237,7 +942,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 are assignable using Cypher administrative commands. They can be granted, denied and revoked like other privileges. .Execute privileges command syntax @@ -1250,35 +955,35 @@ They can be granted, denied and revoked like other privileges. GRANT EXECUTE PROCEDURE[S] name-globbing[, ...] ON DBMS TO role[, ...] -| Enables the specified roles to execute the given procedures. +| Enable the specified roles to execute the given procedures. | [source, cypher, role=noplay] GRANT EXECUTE BOOSTED PROCEDURE[S] name-globbing[, ...] ON DBMS TO role[, ...] -| Enables the specified roles to execute the given procedures with elevated privileges. +| Enable the specified roles to execute the given procedures with elevated privileges. | [source, cypher, role=noplay] GRANT EXECUTE ADMIN[ISTRATOR] PROCEDURES ON DBMS TO role[, ...] -| Enables the specified roles to execute procedures annotated with `@Admin`. The procedures are executed with elevated privileges. +| Enable the specified roles to execute procedures annotated with `@Admin`. The procedures are executed with elevated privileges. | [source, cypher, role=noplay] GRANT EXECUTE [USER [DEFINED]] FUNCTION[S] name-globbing[, ...] ON DBMS TO role[, ...] -| Enables the specified roles to execute the given user defined functions. +| Enable the specified roles to execute the given user defined functions. | [source, cypher, role=noplay] GRANT 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. +| Enable the specified roles to execute the given user defined functions with elevated privileges. |=== 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. +The configuration parameters are still honoured as a set of temporary privileges. These cannot be revoked, but will be updated on each restart with the current configuration values. @@ -1286,7 +991,7 @@ These cannot be revoked, but will be updated on each restart with the current co === The `EXECUTE PROCEDURE` privilege The ability to execute a procedure can be granted via the `EXECUTE PROCEDURE` privilege. -A role with this privilege is allowed to execute the procedures matched by the xref::access-control/dbms-administration.adoc#access-control-name-globbing[name-globbing]. +A user with this privilege is allowed to execute the procedures matched by the xref:access-control/dbms-administration.adoc#access-control-name-globbing[name-globbing]. The following query shows an example of how to grant this privilege: [source, cypher, role=noplay] @@ -1294,17 +999,17 @@ The following query shows an example of how to grant this privilege: GRANT EXECUTE PROCEDURE db.schema.* ON DBMS TO procedureExecutor ---- -Users with the role `procedureExecutor` can then run any procedure in the `db.schema` namespace. +Users with the role 'procedureExecutor' can then run any procedure in the `db.schema` namespace. The procedure is run using the user's own privileges. - -The resulting role has privileges that only allow executing procedures in the `db.schema` namespace. -List all privileges for the role `procedureExecutor` as commands by using the following query: +The resulting role has privileges that only allow executing procedures in the `db.schema` namespace: [source, cypher, role=noplay] ---- SHOW ROLE procedureExecutor PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `procedureExecutor`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -1313,8 +1018,8 @@ SHOW ROLE procedureExecutor PRIVILEGES AS COMMANDS 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`: +If we want to allow executing all but a few procedures, we can grant `EXECUTE PROCEDURES *` and deny the unwanted procedures. +For example, the following queries allow for executing all procedures, except those starting with `dbms.killTransaction`: [source, cypher, role=noplay] ---- @@ -1326,14 +1031,15 @@ GRANT EXECUTE PROCEDURE * ON DBMS TO deniedProcedureExecutor DENY EXECUTE PROCEDURE dbms.killTransaction* ON DBMS TO deniedProcedureExecutor ---- -The resulting role has privileges that only allow executing all procedures except those starting with `dbms.killTransaction`. -List all privileges for the role `deniedProcedureExecutor` as commands by using the following query: +The resulting role has privileges that only allow executing all procedures except those starting with `dbms.killTransaction`: [source, cypher, role=noplay] ---- SHOW ROLE deniedProcedureExecutor PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `deniedProcedureExecutor`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -1343,15 +1049,14 @@ SHOW ROLE deniedProcedureExecutor PRIVILEGES AS COMMANDS 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.killTransaction` and `dbms.killTransactions` are blocked, as well as any other procedures starting with `dbms.killTransaction`. [[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::access-control/dbms-administration.adoc#access-control-name-globbing[name-globbing] without the execution being restricted to their other privileges. - +A user with this privilege is allowed to execute the procedures matched by the xref:access-control/dbms-administration.adoc#access-control-name-globbing[name-globbing] without the execution being restricted to their other privileges. 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: @@ -1361,17 +1066,17 @@ The following query shows an example of how to grant this privilege: 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 then run `db.labels` and `db.relationshipTypes` with full privileges, seeing everything in the graph not just the labels and types that the user has `TRAVERSE` privilege on. -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 only allow executing procedures `db.labels` and `db.relationshipTypes`, but with elevated execution: [source, cypher, role=noplay] ---- SHOW ROLE boostedProcedureExecutor PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `boostedProcedureExecutor`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -1381,9 +1086,9 @@ SHOW ROLE boostedProcedureExecutor PRIVILEGES AS COMMANDS a|Rows: 2 |=== -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. +Granting `EXECUTE BOOSTED PROCEDURE` on its own allows the procedure to be both executed (because of the implicit `EXECUTE PROCEDURE` grant) and given elevated privileges during the execution. +A denied `EXECUTE BOOSTED PROCEDURE` on its own behaves slightly differently, and only denies the elevation and not the execution of the procedure. +However, a role with only a granted `EXECUTE BOOSTED PROCEDURE` and a denied `EXECUTE BOOSTED PROCEDURE` will deny the execution as well. This is explained through the following examples: .Grant `EXECUTE PROCEDURE` and deny `EXECUTE BOOSTED PROCEDURE` @@ -1399,17 +1104,16 @@ GRANT EXECUTE PROCEDURE * ON DBMS TO deniedBoostedProcedureExecutor1 DENY EXECUTE BOOSTED PROCEDURE db.labels ON DBMS TO deniedBoostedProcedureExecutor1 ---- -The resulting role has privileges that allow the execution of all procedures using the user's own privileges. -It also prevents the `db.labels` procedure from being elevated. -Still, the denied `EXECUTE BOOSTED PROCEDURE` does not block execution of `db.labels`. - -To list all privileges for role `deniedBoostedProcedureExecutor1` as commands, use the following query: +The resulting role has privileges that allow executing all procedures using the user's own privileges, as well as blocking `db.labels` from being elevated. +The deny `EXECUTE BOOSTED PROCEDURE` does not block execution of `db.labels`. [source, cypher, role=noplay] ---- SHOW ROLE deniedBoostedProcedureExecutor1 PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `deniedBoostedProcedureExecutor1`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -1433,14 +1137,15 @@ 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. -List all privileges for the role `deniedBoostedProcedureExecutor2` as commands by using the following query: +The resulting role has privileges that allow executing all procedures with elevated privileges except `db.labels` which is not allowed to execute at all: [source, cypher, role=noplay] ---- SHOW ROLE deniedBoostedProcedureExecutor2 PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `deniedBoostedProcedureExecutor2`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -1464,14 +1169,15 @@ 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. -List all privileges for the role `deniedBoostedProcedureExecutor3` as commands by using the following query: +The resulting role has privileges that allow executing all procedures with elevated privileges except `db.labels` which is not allowed to execute at all: [source, cypher, role=noplay] ---- SHOW ROLE deniedBoostedProcedureExecutor3 PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `deniedBoostedProcedureExecutor3`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -1500,8 +1206,7 @@ GRANT EXECUTE BOOSTED PROCEDURE * ON DBMS TO deniedBoostedProcedureExecutor4 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: +The resulting role has privileges that allow executing all procedures with elevated privileges except `db.labels` which is only allowed to execute using the user's own privileges: [source, cypher, role=noplay] ---- @@ -1519,14 +1224,14 @@ 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 4 affect the output of a procedure? [example] ==== -Assume there is a procedure called `myProc`. +Let's assume there exists 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, let's adapt the privileges in examples 1 to 4 to apply 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. @@ -1535,7 +1240,7 @@ With the privileges from example 3, granted `EXECUTE BOOSTED PROCEDURE *` and de With the privileges from example 4, granted `EXECUTE PROCEDURE myProc` and `EXECUTE BOOSTED PROCEDURE *` and denied `EXECUTE BOOSTED PROCEDURE myProc`, the `myProc` procedure returns the result `A` and `B`. -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. +For comparison, when only granted `EXECUTE BOOSTED PROCEDURE myProc`, the `myProc` procedure returns the result `A`, `B` and `C`, without needing to be granted the `EXECUTE PROCEDURE myProc` privilege. ==== @@ -1543,8 +1248,8 @@ For comparison, when only `EXECUTE BOOSTED PROCEDURE myProc` is granted, the `my === 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::access-control/dbms-administration.adoc#access-control-execute-boosted-procedure[`EXECUTE BOOSTED PROCEDURE` privilege] on each of the admin procedures. -Any newly added `admin` procedure is automatically included in this privilege. +This privilege is equivalent with granting the xref:access-control/dbms-administration.adoc#access-control-execute-boosted-procedure[`EXECUTE BOOSTED PROCEDURE` privilege] on each of the admin procedures. +Any new admin procedures that gets added are automatically included in this privilege. The following query shows an example of how to grant this privilege: [source, cypher, role=noplay] @@ -1552,16 +1257,17 @@ The following query shows an example of how to grant this privilege: GRANT EXECUTE ADMIN PROCEDURES ON DBMS TO adminProcedureExecutor ---- -Users with the role `adminProcedureExecutor` can then run any `admin` procedure with elevated privileges. +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: +The resulting role has privileges that allow executing all admin procedures: [source, cypher, role=noplay] ---- SHOW ROLE adminProcedureExecutor PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `adminProcedureExecutor`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -1570,29 +1276,30 @@ SHOW ROLE adminProcedureExecutor PRIVILEGES AS COMMANDS 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. +To compare this with the `EXECUTE PROCEDURE` and `EXECUTE BOOSTED PROCEDURE` privileges, let's revisit the `myProc` procedure. +This time as an admin procedure, which gives 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. +Let's start with a user only granted the `EXECUTE 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 `EXECUTE BOOSTED PROCEDURE myProc` or `EXECUTE ADMIN PROCEDURES`, 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. +Any denied execute privilege results in the procedure not being allowed to execute. +It does not matter whether `EXECUTE PROCEDURE`, `EXECUTE BOOSTED PROCEDURE` or `EXECUTE ADMIN PROCEDURES` is denied. [[access-control-execute-user-defined-function]] === The `EXECUTE USER DEFINED FUNCTION` privilege //EXECUTE [USER [DEFINED]] FUNCTION[S] -The ability to execute a user-defined function (UDF) can be granted via the `EXECUTE USER DEFINED FUNCTION` privilege. -A role with this privilege is allowed to execute the UDFs matched by the xref::access-control/dbms-administration.adoc#access-control-name-globbing[name-globbing]. +The ability to execute a user defined function (UDF) can be granted via the `EXECUTE USER DEFINED FUNCTION` privilege. +A user with this privilege is allowed to execute the UDFs matched by the xref:access-control/dbms-administration.adoc#access-control-name-globbing[name-globbing]. [IMPORTANT] ==== The `EXECUTE USER DEFINED FUNCTION` privilege does not apply to built-in functions, which are always executable. ==== -.Execute user-defined function +.Execute user defined function ====== The following query shows an example of how to grant this privilege: @@ -1608,17 +1315,18 @@ Or in short form: GRANT EXECUTE FUNCTION apoc.coll.* ON DBMS TO functionExecutor ---- -Users with the role `functionExecutor` can thus run any UDF in the `apoc.coll` namespace. -The function here is run using the user's own privileges. +Users with the role `functionExecutor` can then run any UDF in the `apoc.coll` namespace. +The function is run using the user's own privileges. -The resulting role has privileges that only allow executing UDFs in the `apoc.coll` namespace. -List all privileges for the role `functionExecutor` as commands by using the following query: +The resulting role has privileges that only allow executing UDFs in the `apoc.coll` namespace: [source,cypher,role=noplay] ---- SHOW ROLE functionExecutor PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `functionExecutor`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -1628,11 +1336,11 @@ a|Rows: 1 |=== ====== -To allow the execution of all but a few UDFs, you can grant `+EXECUTE USER DEFINED FUNCTIONS *+` and deny the unwanted functions. +If you want to allow executing all but a few UDFs, you can grant `EXECUTE USER DEFINED FUNCTIONS *` and deny the unwanted functions. -.Execute user-defined functions +.Execute user defined functions ====== -The following queries allow the execution of all UDFs except those starting with `apoc.any.prop`: +The following queries allow for executing all UDFs except those starting with `apoc.any.prop`: [source, cypher, role=noplay] ---- @@ -1656,14 +1364,15 @@ GRANT EXECUTE FUNCTIONS * ON DBMS TO deniedFunctionExecutor DENY EXECUTE FUNCTION apoc.any.prop* ON DBMS TO deniedFunctionExecutor ---- -The resulting role has privileges that only allow the execution of all procedures except those starting with `apoc.any.prop`. -List all privileges for the role `deniedFunctionExecutor` as commands by using the following query: +The resulting role has privileges that only allow executing all procedures except those starting with `apoc.any.prop`: [source, cypher, role=noplay] ---- SHOW ROLE deniedFunctionExecutor PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `deniedFunctionExecutor`: + .Result [options="header,footer", width="100%", cols="m"] |=== @@ -1673,30 +1382,29 @@ SHOW ROLE deniedFunctionExecutor PRIVILEGES AS COMMANDS a|Rows: 2 |=== -The `apoc.any.property` and `apoc.any.properties` are blocked, as well as any other procedures starting with `apoc.any.prop`. +The `apoc.any.property` and `apoc.any.properties` is blocked, as well as any other procedures starting with `apoc.any.prop`. ====== [[access-control-execute-boosted-user-defined-function]] === 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::access-control/dbms-administration.adoc#access-control-name-globbing[name-globbing] without the execution being restricted to their other privileges. - -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. +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:access-control/dbms-administration.adoc#access-control-name-globbing[name-globbing] without the execution being restricted to their other privileges. +There is no need to grant an individual `EXECUTE USER DEFINED FUNCTION` privilege for the functions either, as granting the `EXECUTE BOOSTED USER DEFINED FUNCTION` includes an implicit `EXECUTE USER DEFINED FUNCTION` grant for them. +A denied `EXECUTE USER DEFINED FUNCTION` still denies executing the function. [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::access-control/dbms-administration.adoc#access-control-execute-boosted-procedure[`EXECUTE BOOSTED PROCEDURE` privilege]. +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 given elevated privileges during the execution. +A denied `EXECUTE BOOSTED USER DEFINED FUNCTION` on its own behaves slightly differently, and 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` denies the execution as well. +This is the same behavior as for the xref:access-control/dbms-administration.adoc#access-control-execute-boosted-procedure[`EXECUTE BOOSTED PROCEDURE` privilege]. -.Execute boosted user-defined function +.Execute boosted user defined function ====== The following query shows an example of how to grant the `EXECUTE BOOSTED USER DEFINED FUNCTION` privilege: @@ -1712,16 +1420,17 @@ Or in short form: GRANT EXECUTE BOOSTED FUNCTION apoc.any.properties ON DBMS TO boostedFunctionExecutor ---- -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. +Users with the role `boostedFunctionExecutor` can then run `apoc.any.properties` with full privileges, seeing every property on the node/relationship not just the properties that the user has `READ` privilege on. -The resulting role has privileges that only allow executing of the UDF `apoc.any.properties`, but with elevated execution. -List all privileges for the role `boostedFunctionExecutor` as commands by using the following query: +The resulting role has privileges that only allow executing the UDF `apoc.any.properties`, but with elevated execution: [source,cypher,role=noplay] ---- SHOW ROLE boostedFunctionExecutor PRIVILEGES AS COMMANDS ---- +Lists all privileges for role `boostedFunctionExecutor`: + .Result [options="header,footer",width="100%",cols="m"] |=== @@ -1734,21 +1443,20 @@ a|Rows: 1 [[access-control-name-globbing]] === Procedure and user-defined function name-globbing -The name-globbing for procedure and user defined function 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. +The name-globbing for procedure and user defined function names is a simplified version of globbing for filename expansions, only allowing two wildcard characters; `+*+` and `?`. +They are used for multiple and single character matches, where `+*+` means 0 or more characters and `?` matches exactly one character. [NOTE] ==== -The name-globbing is subject to the xref::syntax/naming.adoc[standard Cypher restrictions on valid identifiers], +The name-globbing is subject to the xref:syntax/naming.adoc[standard Cypher restrictions on valid identifiers], with the exception that it may include dots, stars, and question marks without the need for escaping using backticks. - Each part of the name-globbing separated by dots may be individually escaped, for example, `++mine.`procedureWith%`++` but not `++mine.procedure`With%`++`. -It is also good to keep in mind that wildcard characters behave as wildcards even when escaped. +Also good to keep in mind is that the wildcard characters behave as wildcards even when escaped. As an example, using `++`*`++` is equivalent to using `+*+`, and thus allows executing all functions or procedures and not only the procedure or function named `+*+`. ==== -The examples below only use procedures, but the same rules apply to user defined function names: +The examples below only use procedures but the same rules apply to user defined function names. +For the examples below, assume we have the following procedures: * `mine.public.exampleProcedure` * `mine.public.exampleProcedure1` @@ -1765,47 +1473,46 @@ The examples below only use procedures, but the same rules apply to user defined GRANT EXECUTE PROCEDURE * ON DBMS TO globbing1 ---- -Users with the role `globbing1` can thus run all the procedures. +Users with the role `globbing1` can then run procedures all the procedures. [source, cypher, role=noplay] ---- GRANT EXECUTE PROCEDURE mine.*.exampleProcedure ON DBMS TO globbing2 ---- -Users with the role `globbing2` can thus run procedures `mine.public.exampleProcedure` and `mine.private.exampleProcedure`, but none of the others. +Users with the role `globbing2` can then run procedures `mine.public.exampleProcedure` and `mine.private.exampleProcedure`, but none of the others. [source, cypher, role=noplay] ---- GRANT EXECUTE PROCEDURE mine.*.exampleProcedure? ON DBMS TO globbing3 ---- -Users with the role `globbing3` can thus run procedures `mine.public.exampleProcedure1`, `mine.private.exampleProcedure1` and `mine.private.exampleProcedure2`, but none of the others. +Users with the role `globbing3` can then run procedures `mine.public.exampleProcedure1`, `mine.private.exampleProcedure1` and `mine.private.exampleProcedure2`, but none of the others. [source, cypher, role=noplay] ---- GRANT EXECUTE PROCEDURE *.exampleProcedure ON DBMS TO globbing4 ---- -Users with the role `globbing4` can thus run procedures `your.exampleProcedure`, `mine.public.exampleProcedure` and `mine.private.exampleProcedure`, but none of the others. +Users with the role `globbing4` can then run procedures `your.exampleProcedure`, `mine.public.exampleProcedure` and `mine.private.exampleProcedure`, but none of the others. [source, cypher, role=noplay] ---- GRANT EXECUTE PROCEDURE mine.public.exampleProcedure* ON DBMS TO globbing5 ---- -Users with the role `globbing5` can thus run procedures `mine.public.exampleProcedure`, `mine.public.exampleProcedure1` and `mine.public.exampleProcedure42`, but none of the others. +Users with the role `globbing5` can then run procedures `mine.public.exampleProcedure`, `mine.public.exampleProcedure1` and `mine.public.exampleProcedure42`, but none of the others. [source, cypher, role=noplay] ---- GRANT EXECUTE PROCEDURE `mine.public.with#*§Characters`, mine.private.`with#Spec???§Characters` ON DBMS TO globbing6 ---- -Users with the role `globbing6` can thus run procedures `mine.public.with#Special§Characters` and `mine.private.with#Special§Characters`, but none of the others. +Users with the role `globbing6` can then run procedures `mine.public.with#Special§Characters` and `mine.private.with#Special§Characters`, but none of the others. [NOTE] ==== -The name-globbing may be fully or partially escaped. -Both `+*+` and `+?+` are interpreted as wildcards either way. +The name-globbing may be fully or partially escaped, and both the `+*+` and `+?+` are interpreted as wildcards either way. ==== @@ -1814,12 +1521,22 @@ Both `+*+` and `+?+` are interpreted as wildcards either way. The right to perform the following privileges can be achieved with a single command: -* Create, drop, assign, remove, and show roles. -* Create, alter, drop, show, and impersonate users. -* Create, alter, and drop databases. -* Show, assign, and remove privileges. -* Execute all procedures with elevated privileges. -* Execute all user defined functions with elevated privileges. +* create roles +* drop roles +* assign roles +* remove roles +* show roles +* create users +* alter users +* drop users +* show users +* create databases +* drop databases +* show privileges +* assign privileges +* remove privileges +* execute all procedures with elevated privileges +* execute all user defined functions with elevated privileges [source, cypher, role=noplay] ---- @@ -1828,7 +1545,7 @@ GRANT ALL [[DBMS] PRIVILEGES] TO role[, ...] ---- -For example, to grant the role `dbmsManager` the abilities above, use the following query: +For example, granting the abilities above to the role `dbmsManager` is done using the following query. [source, cypher, role=noplay] ---- diff --git a/modules/ROOT/pages/access-control/index.adoc b/modules/ROOT/pages/access-control/index.adoc index fa3037b6f..2ae37e8ca 100644 --- a/modules/ROOT/pages/access-control/index.adoc +++ b/modules/ROOT/pages/access-control/index.adoc @@ -1,37 +1,32 @@ -:description: Neo4j role-based access control and fine-grained security. - [[access-control]] = Access control +:description: This chapter explains how to manage Neo4j role-based access control and fine-grained security. -[abstract] --- -This section explains how to manage Neo4j role-based access control and fine-grained security. --- - -Neo4j has a complex security model stored in the system graph, which is maintained on a special database called the `system` database. -All administrative commands need to be executed against the `system` database. -When connected to the DBMS over `bolt`, administrative commands are automatically routed to the `system` database. -For more information on how to manage multiple databases, refer to the section on xref::databases.adoc[administering databases]. +Neo4j has a complex security model stored in the system graph, maintained in a special database called the `system` database. +All administrative commands need to be executing against the `system` database. +When connected to the DBMS over bolt, administrative commands are automatically routed to the `system` database. +For more information on how to manage multiple databases, refer to the section on xref:databases.adoc[administering databases]. -The concept of _role-based access control_ was introduced in Neo4j 3.1. -Since then, it has been possible to create users and assign them to roles to control whether users can read, write and administer the database. +Neo4j 3.1 introduced the concept of _role-based access control_. +It was possible to create users and assign them to roles to control whether the users could read, write and administer the database. In Neo4j 4.0 this model was enhanced significantly with the addition of _privileges_, which are the underlying access-control rules by which the users rights are defined. -The original built-in roles still exist with almost the exact same access rights, but they are no-longer statically defined (see xref::access-control/built-in-roles.adoc[Built-in roles]). -Instead, they are defined in terms of their underlying _privileges_, and they can be modified by adding or removing these access rights. +The original built-in roles still exist with almost the exact same access rights, but they are no-longer statically defined (see xref:access-control/built-in-roles.adoc[Built-in roles]). +Instead they are defined in terms of their underlying _privileges_ and they can be modified by adding an removing these access rights. -In addition, any newly created roles can be assigned to any combination of _privileges_, so that you may set specific access controls for them. -Another new major capability is the _sub-graph_ access control, through which read access to the graph can be limited to specific combinations of labels, relationship types, and properties. +In addition, any new roles created can by assigned any combination of _privileges_ to create the specific access control desired. +A major additional capability is _sub-graph_ access control whereby read-access to the graph can be limited to specific combinations of label, relationship-type and property. [[access-control-syntax]] == Syntax summaries -Almost all administration commands have variations. -The most common are parts of the command that are optional or that can have multiple values. +Almost all administration commands have variations in the commands. +Parts of the command that are optional or can have multiple values are most common. +To show all versions of a command, a summary of the syntax will be presented. +These summaries will use some special characters to indicate such variations. -See below a summary of the syntax to check all versions of a command. -These summaries use some special characters to indicate such variations. +The special characters and their meaning are as follows: .Special characters in syntax summaries [options="header", width="100%", cols="1a,3a,3a"] @@ -39,59 +34,57 @@ These summaries use some special characters to indicate such variations. | Character | Meaning | Example | `\|` -| -Used to indicate alternative parts of a command (i.e. `or`). +| Or, used to indicate alternative parts of a command. Needs to be part of a grouping. -| If the syntax needs to specify either a name or `+*+`, this can be indicated with `+* \| name+`. +| If the syntax needs to specify either a name or `+*+`, this can be indicated with `* \| name`. -| `+{+` and `+}+` -| Used to group parts of the command. Commonly found together with `\|`. -| In order to use the `or` in the syntax summary, it needs to be in a group: `+{* \| name}+`. +| `{` and `}` +| Used to group parts of the command, common together with `\|`. +| To use the `or` in the syntax summary it needs to be in a group, `{* \| name}`. | `[` and `]` | Used to indicate an optional part of the command. It also groups alternatives together, when there can be either of the alternatives or nothing. | If a keyword in the syntax can either be in singular or plural, we can indicate that the `S` is optional with `GRAPH[S]`. -| `+...+` -| -Repeated pattern. -Related to the command part immediately before this is repeated. -| A comma separated list of names would be `+name[, ...]+`. +| `...` +| Repeated pattern, the command part immediately before this is repeated. +| A comma separated list of names would be `name[, ...]`. | `"` | When a special character is part of the syntax itself, we surround it with `"` to indicate this. -| -To include `+{+` in the syntax use `+"{" { * \| name } "}"+`. -In this case, you will get either `+{ * }+` or `+{ name }+`. +| To include `{` in the syntax use `"{" { * \| name } "}"`, here we get either `{*}` or `\{name}`. |=== The special characters in the table above are the only ones that need to be escaped using `"` in the syntax summaries. -Here is an example that uses all special characters is granting the `READ` privilege: +An example that uses all special characters is granting the `READ` privilege: -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- GRANT READ - "{" { * | property[, ...] } "}" - ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} - [ ELEMENT[S] { * | label-or-rel-type[, ...] } - | NODE[S] { * | label[, ...] } - | RELATIONSHIP[S] { * | rel-type[, ...] }] - TO role[, ...] + "{" { * | property[, ...] } "}" + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + [ + ELEMENT[S] { * | label-or-rel-type[, ...] } + | NODE[S] { * | label[, ...] } + | RELATIONSHIP[S] { * | rel-type[, ...] } + ] + TO role[, ...] ---- -Note that this command includes `+{+` and `+}+` in the syntax, and between them there can be a grouping of properties or the character `+*+`. +Some things to notice about this command is that it includes `{` and `}` in the syntax, and between them has a grouping of either a list of properties or the character `*`. It also has multiple optional parts, including the entity part of the command which is the grouping following the graph name. -However, there is no need to escape any characters when creating a constraint for a node property. -This is because `(` and `)` are not special characters, and `[` and `]` indicate that the constraint name is optional, and therefore not part of the command. +In difference, there is no need to escape any characters in the node property existence constraint creation command. +This is because `(` and `)` are not special characters, and the `[` and `]` indicate that the constraint name is optional, and are not part of the command. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- CREATE CONSTRAINT [constraint_name] [IF NOT EXISTS] -FOR (n:LabelName) -REQUIRE n.propertyName IS NOT NULL +ON (n:LabelName) +ASSERT n.propertyName IS NOT NULL ---- + diff --git a/modules/ROOT/pages/access-control/limitations.adoc b/modules/ROOT/pages/access-control/limitations.adoc index d5114141b..fe5bdfbf9 100644 --- a/modules/ROOT/pages/access-control/limitations.adoc +++ b/modules/ROOT/pages/access-control/limitations.adoc @@ -1,110 +1,100 @@ -:description: Known limitations and implications of Neo4js role-based access control security. - [[access-control-limitations]] = Limitations - -[abstract] --- -This section lists the known limitations and implications of Neo4js role-based access control security. --- +:description: This section explains known limitations and implications of Neo4js role-based access control security. [[access-control-limitations-indexes]] == Security and Indexes -As described in xref::indexes-for-search-performance.adoc[Indexes for search performance], Neo4j {neo4j-version} supports the creation and use of indexes to improve the performance of Cypher queries. - -Note that the Neo4j security model impacts the results of queries, regardless if the indexes are used or not. +As described in xref:indexes-for-search-performance.adoc[Indexes for search performance], Neo4j {neo4j-version} supports the creation and use of indexes to improve the performance of Cypher queries. +The Neo4j security model will impact the results of queries (regardless if the indexes are used). When using non full-text Neo4j indexes, a Cypher query will always return the same results it would have if no index existed. -This means that, if the security model causes fewer results to be returned due to restricted read access in xref::access-control/manage-privileges.adoc[Graph and sub-graph access control], +This means that if the security model causes fewer results to be returned due to restricted read access in xref:access-control/manage-privileges.adoc[Graph and sub-graph access control], the index will also return the same fewer results. -However, this rule is not fully obeyed by xref::indexes-for-full-text-search.adoc[Indexes for full-text search]. -These specific indexes are backed by _Lucene_ internally. -It is therefore not possible to know for certain whether a security violation has affected each specific entry returned from the index. -In face of this, Neo4j will return zero results from full-text indexes in case it is determined that any result might be violating the security privileges active for that query. +However, this rule is not fully obeyed by xref:indexes-for-full-text-search.adoc[Indexes for full-text search]. +These specific indexes are backed by Lucene internally. +It is therefore not possible to know for certain whether a security violation occurred for each specific entry returned from the index. +As a result, Neo4j will return zero results from full-text indexes if it is determined that any result might violate the security privileges active for that query. -Since full-text indexes are not automatically used by Cypher, they do not lead to the case where the same Cypher query would return different results simply because such an index was created. +Since full-text indexes are not automatically used by Cypher, this does not lead to the case where the same Cypher query would return different results simply because such an index got created. Users need to explicitly call procedures to use these indexes. -The problem is only that, if this behavior is not known by the user, they might expect the full-text index to return the same results that a different, but semantically similar, Cypher query does. +The problem is only that if this behavior is not understood by the user, they might expect the full text index to return the same results that a different, but semantically similar, Cypher query does. === Example with denied properties Consider the following example. -The database has nodes with labels `:User` and `:Person`, and they have properties `name` and `surname`. -There are indexes on both properties: +The database has nodes with labels `:User` and `:Person`, and these have properties `name` and `surname`. +We have indexes on both properties: [source, cypher] ---- -CREATE INDEX singleProp FOR (n:User) ON (n.name) -CREATE INDEX composite FOR (n:User) ON (n.name, n.surname) -CREATE FULLTEXT INDEX userNames FOR (n:User|Person) ON EACH [n.name, n.surname] +CREATE INDEX singleProp FOR (n:User) ON (n.name); +CREATE INDEX composite FOR (n:User) ON (n.name, n.surname); +CREATE FULLTEXT INDEX userNames FOR (n:User|Person) ON EACH [n.name, n.surname]; ---- [NOTE] -==== Full-text indexes support multiple labels. -See xref::indexes-for-full-text-search.adoc[Indexes for full-text search] for more details on creating and using full-text indexes. -==== +See xref:indexes-for-full-text-search.adoc[Indexes for full-text search] for more details on creating and using full-text indexes. After creating these indexes, it would appear that the latter two indexes accomplish the same thing. However, this is not completely accurate. -The composite and full-text indexes behave in different ways and are focused on different use cases. -A key difference is that full-text indexes are backed by _Lucene_, and will use the _Lucene_ syntax for querying. +The composite and fulltext indexes behave in different ways and are focused on different use cases. +A key difference is that full-text indexes are backed by Lucene, and will use the Lucene syntax for querying the index. This has consequences for users restricted on the labels or properties involved in the indexes. -Ideally, if the labels and properties in the index are denied, they can correctly return zero results from both native indexes and full-text indexes. +Ideally, if the labels and properties in the index are denied, we can correctly return zero results from both native indexes and full-text indexes. However, there are borderline cases where this is not as simple. Imagine the following nodes were added to the database: [source, cypher] ---- -CREATE (:User {name: 'Sandy'}) -CREATE (:User {name: 'Mark', surname: 'Andy'}) -CREATE (:User {name: 'Andy', surname: 'Anderson'}) -CREATE (:User:Person {name: 'Mandy', surname: 'Smith'}) -CREATE (:User:Person {name: 'Joe', surname: 'Andy'}) +CREATE (:User {name:'Sandy'}); +CREATE (:User {name:'Mark', surname:'Andy'}); +CREATE (:User {name:'Andy', surname:'Anderson'}); +CREATE (:User:Person {name:'Mandy', surname:'Smith'}); +CREATE (:User:Person {name:'Joe', surname:'Andy'}); ---- -Consider denying the label `:Person`: +Consider denying the label `:Person`. [source, cypher] ---- -DENY TRAVERSE Person ON GRAPH * TO users +DENY TRAVERSE Person ON GRAPH * TO users; ---- If the user runs a query that uses the native single property index on `name`: [source, cypher] ---- -MATCH (n:User) WHERE n.name CONTAINS 'ndy' RETURN n.name +MATCH (n:User) WHERE n.name CONTAINS 'ndy' RETURN n.name; ---- This query performs several checks: -* Scans the index to create a stream of results of nodes with the `name` property, which leads to five results. -* Filters the results to include only nodes where `n.name CONTAINS 'ndy'`, filtering out `Mark` and `Joe`, which leads to three results. -* Filters the results to exclude nodes that also have the denied label `:Person`, filtering out `Mandy`, which leads to two results. +* do a scan on the index to create a stream of results of nodes with the `name` property, which leads to five results +* filter the results to include only nodes where `n.name CONTAINS 'ndy'`, filtering out `Mark` and `Joe` so we have three results +* filter the results to exclude nodes that also have the denied label `:Person`, filtering out `Mandy` so we have two results -Two results will be returned from this dataset and only one of them has the `surname` property. +For the above dataset, we can see we will get two results and that only one of these has the `surname` property. -In order to use the native composite index on `name` and `surname`, the query needs to include a predicate on the `surname` property as well: +To use the native composite index on `name` and `surname`, the query needs to include a predicate on the `surname` property as well: [source, cypher] ---- -MATCH (n:User) -WHERE n.name CONTAINS 'ndy' AND n.surname IS NOT NULL -RETURN n.name +MATCH (n:User) WHERE n.name CONTAINS 'ndy' AND n.surname IS NOT NULL RETURN n.name; ---- -This query performs several checks, which are almost identical to the single property index query: +This query performs several checks, almost identical to the single property index query: -* Scans the index to create a stream of results of nodes with the `name` and `surname` property, which leads to four results. -* Filters the results to include only nodes where `n.name CONTAINS 'ndy'`, filtering out `Mark` and `Joe`, which leads to two results. -* Filters the results to exclude nodes that also have the denied label `:Person`, filtering out `Mandy`, which leads to only one result. +* do a scan on the index to create a stream of results of nodes with the `name` and `surname` property, which leads to four results +* filter the results to include only nodes where `n.name CONTAINS 'ndy'`, filtering out `Mark` and `Joe` so we have two results +* filter the results to exclude nodes that also have the denied label `:Person`, filtering out `Mandy` so we only have one result -Only one result was returned from the above dataset. -What if this query with the full-text index was used instead: +For the above dataset, we can see we will get one result. + +What if we query this with the full-text index: [source, cypher] ---- @@ -112,13 +102,13 @@ CALL db.index.fulltext.queryNodes("userNames", "ndy") YIELD node, score RETURN node.name ---- -The problem now is that it is not certain whether the results provided by the index were achieved due to a match to the `name` or the `surname` property. +The problem now is that we do not know if the results provided by the index were because of a match to the `name` or the `surname` property. The steps taken by the query engine would be: -* Run a _Lucene_ query on the full-text index to produce results containing `ndy` in either property, leading to five results. -* Filter the results to exclude nodes that also have the label `:Person`, filtering out `Mandy` and `Joe`, leading to three results. +* run a _Lucene_ query on the full-text index to produce results containing `ndy` in either property, leading to five results. +* filter the results to exclude nodes that also have the label `:Person`, filtering out `Mandy` and `Joe` so we have three results. -This difference in results is caused by the `OR` relationship between the two properties in the index creation. +This difference in results is due to the `OR` relationship between the two properties in the index creation. === Denying properties @@ -126,27 +116,23 @@ Now consider denying access on properties, like the `surname` property: [source, cypher] ---- -DENY READ {surname} ON GRAPH * TO users +DENY READ {surname} ON GRAPH * TO users; ---- -For that, run the same queries again: +Now we run the same queries again: [source, cypher] ---- -MATCH (n:User) -WHERE n.name CONTAINS 'ndy' -RETURN n.name +MATCH (n:User) WHERE n.name CONTAINS 'ndy' RETURN n.name; ---- -This query operates exactly as before, returning the same two results, because nothing in it relates to the denied property. +This query operates exactly as before, returning the same two results, because nothing in this query relates to the denied property. -However, this is not the same for the query targeting the composite index: +However, for the query targeting the composite index, things have changed. [source, cypher] ---- -MATCH (n:User) -WHERE n.name CONTAINS 'ndy' AND n.surname IS NOT NULL -RETURN n.name +MATCH (n:User) WHERE n.name CONTAINS 'ndy' AND n.surname IS NOT NULL RETURN n.name; ---- Since the `surname` property is denied, it will appear to always be `null` and the composite index empty. Therefore, the query returns no result. @@ -159,18 +145,17 @@ CALL db.index.fulltext.queryNodes("userNames", "ndy") YIELD node, score RETURN node.name ---- -The problem remains, since it is not certain whether the results provided by the index were returned due to a match on the `name` or the `surname` property. -Results from the `surname` property now need to be excluded by the security rules, because they require that the user is unable to see any `surname` properties. -However, the security model is not able to introspect the _Lucene_ query in order to know what it will actually do, whether it works only on the allowed `name` property, or also on the disallowed `surname` property. -What is known is that the earlier query returned a match for `Joe Andy` which should now be filtered out. -Therefore, in order to never return results the user should not be able to see, all results need to be blocked. +The problem remains, we do not know if the results provided by the index were because of a match on the `name` or the `surname` property. +Results from the surname now need to be excluded by the security rules, because they require that the user cannot see any `surname` properties. +However, the security model is not able to introspect the _Lucene_ query to know what it will actually do, whether it works only on the allowed `name` property, or also on the disallowed `surname` property. +We know that the earlier query returned a match for `Joe Andy` which should now be filtered out. +So, in order to never return results the user should not be able to see, we have to block all results. The steps taken by the query engine would be: -* Determine if the full-text index includes denied properties. -* If yes, return an empty results stream. -Otherwise, it will process as described before. +* Determine if the full-text index includes denied properties +* If yes, return an empty results stream, otherwise process as before -In this case, the query will return zero results rather than simply returning the results `Andy` and `Sandy`, which might have been expected. +The query will therefore return zero results in this case, rather than simply returning the results `Andy` and `Sandy` which might be expected. [[access-control-limitations-labels]] @@ -178,123 +163,116 @@ In this case, the query will return zero results rather than simply returning th === Traversing the graph with multi-labeled nodes -The general influence of access control privileges on graph traversal is described in detail in xref::access-control/manage-privileges.adoc[Graph and sub-graph access control]. -The following section will only focus on nodes due to their ability to have multiple labels. -Relationships can only have one type of label and thus they do not exhibit the behavior this section aims to clarify. +The general influence of access control privileges on graph traversal is described in detail in xref:access-control/manage-privileges.adoc[Graph and sub-graph access control]. +The following section will only focus on nodes because of their ability to have multiple labels. Relationships can only ever have one type +and thus they do not exhibit the behavior this section aims to clarify. While this section will not mention relationships further, the general function of the traverse privilege also applies to them. For any node that is traversable, due to `GRANT TRAVERSE` or `GRANT MATCH`, -the user can get information about the attached labels by calling the built-in `labels()` function. -In the case of nodes with multiple labels, they can be returned to users that weren't directly granted access to. +the user can get information about the labels attached to the node by calling the built-in `labels()` function. +In the case of nodes with multiple labels, this can seemingly result in labels being returned to which the user +wasn't directly granted access to. -To give an illustrative example, imagine a graph with three nodes: one labeled `:A`, another labeled `:B` and one with the labels `:A` and `:B`. -In this case, there is a user with the role `custom` defined by: +To give an illustrative example, imagine a graph with three nodes: one labeled `:A`, one labeled `:B` and one with `:A :B`. +We also have a user with a role `custom` as defined by: [source, cypher] ---- -GRANT TRAVERSE ON GRAPH * NODES A TO custom +GRANT TRAVERSE ON GRAPH * NODES A TO custom; ---- If that user were to execute [source, cypher] ---- -MATCH (n:A) -RETURN n, labels(n) +MATCH (n:A) RETURN n, labels(n); ---- -They would get a result with two nodes: the node that was labeled with `:A` and the node with labels `:A :B`. +they would be returned two nodes: the node that was labeled with `:A` and the node with labels `:A :B`. In contrast, executing [source, cypher] ---- -MATCH (n:B) -RETURN n, labels(n) +MATCH (n:B) RETURN n, labels(n); ---- -This will return only the one node that has both labels: `:A` and `:B`. -Even though `:B` did not have access to traversals, there is one node with that label accessible in the dataset due to the allow-listed label `:A` that is attached to the same node. +will return only the one node that has both labels: `:A :B`. Even though `:B` was not allowed access for traversal, there is one +node with that label accessible in the data because of the allowlisted label `:A` that is attached to the same node. -If a user is denied to traverse on a label they will never get results from any node that has this label attached to it. -Thus, the label name will never show up for them. -As an example, this can be done by executing: +If a user is denied traverse on a label they will never get results from any node that has this label +attached to it. Thus, the label name will never show up for them. For our example this can be done by executing: [source, cypher] ---- -DENY TRAVERSE ON GRAPH * NODES B TO custom +DENY TRAVERSE ON GRAPH * NODES B TO custom; ---- The query [source, cypher] ---- -MATCH (n:A) -RETURN n, labels(n) +MATCH (n:A) RETURN n, labels(n); ---- will now return the node only labeled with `:A`, while the query [source, cypher] ---- -MATCH (n:B) -RETURN n, labels(n) +MATCH (n:B) RETURN n, labels(n); ---- will now return no nodes. === The db.labels() procedure -In contrast to the normal graph traversal described in the previous section, the built-in `db.labels()` procedure is not processing the data graph itself, but the security rules defined on the system graph. +In contrast to the normal graph traversal described in the previous section, the built-in `db.labels()` procedure +is not processing the data graph itself but the security rules defined on the system graph. That means: -* If a label is explicitly whitelisted (granted), it will be returned by this procedure. -* If a label is denied or isn't explicitly allowed, it will not be returned by this procedure. +* if a label is explicitly whitelisted (granted), it will be returned by this procedure. +* if a label is denied or isn't explicitly allowed it will not be returned by this procedure. -Reusing the previous example, imagine a graph with three nodes: one labeled `:A`, another labeled `:B` and one with the labels `:A` and `:B`. -In this case, there is a user with the role `custom` defined by: +To reuse the example of the previous section: imagine a graph with three nodes: one labeled `:A`, one labeled `:B` and one with `:A :B`. +We also have a user with a role `custom` as defined by: [source, cypher] ---- -GRANT TRAVERSE ON GRAPH * NODES A TO custom +GRANT TRAVERSE ON GRAPH * NODES A TO custom; ---- -This means that only label `:A` is explicitly allow-listed. +This means that only label `:A` is explicitly allowlisted. Thus, executing [source, cypher] ---- -CALL db.labels() +CALL db.labels(); ---- -will only return label `:A`, because that is the only label for which traversal was granted. +will only return label `:A` because that is the only label for which traversal was granted. [[access-control-limitations-db-operations]] == Security and count store operations The rules of a security model may impact some of the database operations. -This means extra security checks are necessary to incur additional data accesses, especially in the case of count store operations. -These are, however, usually very fast lookups and the difference might be noticeable. +This comes down to necessary additional security checks that incur additional data accesses. +Especially in regards to count store operations, as they are usually very fast lookups, the difference might be noticeable. -See the following security rules that set up a `restricted` and a `free` role as an example: +Let's look at the following security rules that set up a `restricted` and a `free` role as an example: ----- -GRANT TRAVERSE ON GRAPH * NODES Person TO restricted -DENY TRAVERSE ON GRAPH * NODES Customer TO restricted -GRANT TRAVERSE ON GRAPH * ELEMENTS * TO free ----- + GRANT TRAVERSE ON GRAPH * NODES Person TO restricted; + DENY TRAVERSE ON GRAPH * NODES Customer TO restricted; + GRANT TRAVERSE ON GRAPH * ELEMENTS * TO free; Now, let's look at what the database needs to do in order to execute the following query: ----- -MATCH (n:Person) -RETURN count(n) ----- + MATCH (n:Person) RETURN count(n); For both roles the execution plan will look like this: ----- +[listing] +.... +--------------------------+ | Operator | +--------------------------+ @@ -302,10 +280,9 @@ For both roles the execution plan will look like this: | | + | +NodeCountFromCountStore | +--------------------------+ ----- +.... -Internally, however, very different operations need to be executed. -The following table illustrates the difference: +Internally however, very different operations need to be executed. The following table illustrates the difference. [%header,cols=2*] |=== @@ -316,8 +293,8 @@ The following table illustrates the difference: This is a very quick operation. -|The database cannot access the count store because it must make sure that only traversable nodes with the desired label `:Person` are counted. -Due to this, each node with the `:Person` label needs to be accessed and examined to make sure that they do not have a deny-listed label, such as `:Customer`. +|The database cannot just access the count store because it must make sure that only traversable nodes with the desired label `:Person` are counted. +Due to this, each node with the `:Person` label needs to be accessed and examined to make sure that it does not also have a denylisted label, such as `:Customer`. Due to the additional data accesses that the security checks need to do, this operation will be slower compared to executing the query as an unrestricted user. diff --git a/modules/ROOT/pages/access-control/manage-privileges.adoc b/modules/ROOT/pages/access-control/manage-privileges.adoc index 8c773ed8b..778c70e52 100644 --- a/modules/ROOT/pages/access-control/manage-privileges.adoc +++ b/modules/ROOT/pages/access-control/manage-privileges.adoc @@ -1,34 +1,28 @@ -:description: This section explains how to use Cypher to manage privileges for Neo4j role-based access control and fine-grained security. [[access-control-manage-privileges]] - = Managing privileges - -[abstract] --- -This section explains how to use Cypher to manage privileges for Neo4j role-based access control and fine-grained security. --- +:description: This section explains how to use Cypher to manage privileges for Neo4j role-based access control and fine-grained security. Privileges control the access rights to graph elements using a combined allowlist/denylist mechanism. -It is possible to grant or deny access, or use a combination of the two. -The user will be able to access the resource if they have a `GRANT` (allowlist) and do not have a `DENY` (denylist) relevant to that resource. +It is possible to grant access, or deny access, or a combination of the two. +The user will be able to access the resource if they have a grant (whitelist) and do not have a deny (blacklist) relevant to that resource. All other combinations of `GRANT` and `DENY` will result in the matching path being inaccessible. -What this means in practice depends on whether we are talking about a xref::access-control/privileges-reads.adoc[read privilege] or a xref::access-control/privileges-writes.adoc[write privilege]: +What this means in practice depends on whether we are talking about a xref:access-control/privileges-reads.adoc[read privilege] or a xref:access-control/privileges-writes.adoc[write privilege]. -* If an entity is not accessible due to xref::access-control/privileges-reads.adoc[read privileges], the data will become invisible. -It will appear to the user as if they had a smaller database (smaller graph). -* If an entity is not accessible due to xref::access-control/privileges-writes.adoc[write privileges], an error will occur on any attempt to write that data. +* If a entity is not accessible due to xref:access-control/privileges-reads.adoc[read privileges], the data will become invisible to attempts to read it. +It will appear to the user as if they have a smaller database (smaller graph). +* If an entity is not accessible due to xref:access-control/privileges-writes.adoc[write privileges], an error will occur on any attempt to write that data. [NOTE] ==== In this document we will often use the terms _'allows'_ and _'enables'_ in seemingly identical ways. However, there is a subtle difference. -We will use _'enables'_ to refer to the consequences of xref::access-control/privileges-reads.adoc[read privileges] where a restriction will not cause an error, only a reduction in the apparent graph size. -We will use _'allows'_ to refer to the consequence of xref::access-control/privileges-writes.adoc[write privileges] where a restriction can result in an error. +We will use _'enables'_ to refer to the consequences of xref:access-control/privileges-reads.adoc[read privileges] where a restriction will not cause an error, only a reduction in the apparent graph size. +We will use _'allows'_ to refer to the consequence of xref:access-control/privileges-writes.adoc[write privileges] where a restriction can result in an error. ==== [NOTE] ==== -If a user was not also provided with the database `ACCESS` privilege, then access to the entire database will be denied. -Information about the database access privilege can be found in xref::access-control/database-administration.adoc#access-control-database-administration-access[The ACCESS privilege]. +If a user was not also provided with the database `ACCESS` privilege then access to the entire database will be denied. +Information about the database access privilege can be found in xref:access-control/database-administration.adoc#access-control-database-administration-access[The ACCESS privilege]. ==== @@ -42,26 +36,25 @@ The components of the graph privilege commands are: * the command: ** `GRANT` – gives privileges to roles. ** `DENY` – denies privileges to roles. -** `REVOKE` – removes granted or denied privileges from roles. +** `REVOKE` – removes granted or denied privilege from roles. * _graph-privilege_ -** Can be either a xref::access-control/privileges-reads.adoc[read privilege] or xref::access-control/privileges-writes.adoc[write privilege]. +** Can be either a xref:access-control/privileges-reads.adoc[read privilege] or xref:access-control/privileges-writes.adoc[write privilege]. * _name_ ** The graph or graphs to associate the privilege with. -Because in Neo4j {neo4j-version} you can have only one graph per database, this command uses the database name or alias to refer to that graph. -When using an alias, the command will be executed on the resolved graph. +Because in Neo4j {neo4j-version} you can have only one graph per database, this command uses the database name to refer to that graph. + [NOTE] ==== -If you delete a database and create a new one with the same name, the new one will _NOT_ have the privileges previously assigned to the deleted graph. +If you delete a database and create a new one with the same name, the new one will _NOT_ have the privileges assigned to the deleted graph. ==== -** It can be `+*+`, which means all graphs. +** It can be `+*+` which means all graphs. Graphs created after this command execution will also be associated with these privileges. ** `HOME GRAPH` refers to the graph associated with the home database for that user. -The default database will be used as home database if a user does not have one configured. -If the user's home database changes for any reason after privileges have been created, then these privileges will be associated with the graph attached to the new database. +The default database will be used as home database if a user does not have a home database configured. +If the user's home database changes for any reason after privileges have been created then these privileges will be associated with the graph attached to the new database. This can be quite powerful as it allows permissions to be switched from one graph to another simply by changing a user's home database. * _entity_ @@ -69,105 +62,37 @@ This can be quite powerful as it allows permissions to be switched from one grap *** `NODES` label (nodes with the specified label(s)). *** `RELATIONSHIPS` type (relationships of the specific type(s)). *** `ELEMENTS` label (both nodes and relationships). -** The label or type can be referred with `+*+`, which means all labels or types. +** The label or type can be `+*+` which means all labels or types. ** Multiple labels or types can be specified, comma-separated. ** Defaults to `ELEMENTS` `+*+` if omitted. -** Some of the commands for write privileges do not allow an _entity_ part. -See xref::access-control/privileges-writes.adoc[Write privileges] for details. +** Some of the commands for write privileges do not allow an _entity_ part, see xref:access-control/privileges-writes.adoc[Write privileges] for details. * _role[, ...]_ ** The role or roles to associate the privilege with, comma-separated. -.General grant +ON GRAPH+ privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +GRANT ... ON ... TO ...+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -GRANT graph-privilege ON { HOME GRAPH \| GRAPH[S] { * \| name[, ...] } } [entity] TO role[, ...] ----- - -| Description -a| Grants a privilege to one or multiple roles. - -|=== - -.General deny +ON GRAPH+ privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +DENY ... ON ... TO ...+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -DENY graph-privilege ON { HOME GRAPH \| GRAPH[S] { * \| name[, ...] } } [entity] TO role[, ...] ----- - -| Description -a| Denies a privilege to one or multiple roles. - +.General graph privilege command syntax +[options="header", width="100%", cols="2a,1a"] |=== +| Command | Description -.General revoke +ON GRAPH+ privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +REVOKE GRANT ... ON ... FROM ...+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -REVOKE GRANT graph-privilege ON { HOME GRAPH \| GRAPH[S] { * \| name[, ...] } } [entity] FROM role[, ...] ----- -| Description -a| Revokes a granted privilege from one or multiple roles. - -|=== - -.General revoke +ON GRAPH+ privilege syntax -[cols="<15s,<85"] -|=== +| [source, cypher, role=noplay] +GRANT graph-privilege ON {HOME GRAPH \| GRAPH[S] {* \| name[, ...]}} [entity] TO role[, ...] +| Grant a privilege to one or multiple roles. -| Command -m| +REVOKE DENY ... ON ... FROM ...+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -REVOKE DENY graph-privilege ON { HOME GRAPH \| GRAPH[S] {* \| name[, ...] } } [entity] FROM role[, ...] ----- +| [source, cypher, role=noplay] +DENY graph-privilege ON {HOME GRAPH \| GRAPH[S] {* \| name[, ...]}} [entity] TO role[, ...] +| Deny a privilege to one or multiple roles. -| Description -a| Revokes a denied privilege from one or multiple roles. +| [source, cypher, role=noplay] +REVOKE GRANT graph-privilege ON {HOME GRAPH \| GRAPH[S] {* \| name[, ...]}} [entity] FROM role[, ...] +| Revoke a granted privilege from one or multiple roles. -|=== +| [source, cypher, role=noplay] +REVOKE DENY graph-privilege ON {HOME GRAPH \| GRAPH[S] {* \| name[, ...]}} [entity] FROM role[, ...] +| Revoke a denied privilege from one or multiple roles. -.General revoke +ON GRAPH+ privilege syntax -[cols="<15s,<85"] -|=== - -| Command -m| +REVOKE ... ON ... FROM ...+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- -REVOKE graph-privilege ON { HOME GRAPH \| GRAPH[S] { * \| name[, ...] } } [entity] FROM role[, ...] ----- - -| Description -| Revokes a granted or denied privilege from one or multiple roles. +| [source, cypher, role=noplay] +REVOKE graph-privilege ON {HOME GRAPH \| GRAPH[S] {* \| name[, ...]}} [entity] FROM role[, ...] +| Revoke a granted or denied privilege from one or multiple roles. |=== [NOTE] @@ -176,87 +101,51 @@ REVOKE graph-privilege ON { HOME GRAPH \| GRAPH[S] { * \| name[, ...] } } [entit Use `REVOKE` if you want to remove a privilege. ==== -The general `GRANT` and `DENY` syntaxes are illustrated in the following image: +The general grant and deny syntax is illustrated in the following image: -image::privileges_grant_and_deny_syntax.png[title="GRANT and DENY Syntax"] +image::grant-privileges-overview.png[title="GRANT and DENY Syntax"] -A more detailed syntax illustration for graph privileges would be the following: +A more detailed syntax illustration would be the following image for graph privileges: -image::privileges_on_graph_syntax.png[title="Syntax of GRANT and DENY Graph Privileges. The `{` and `}` are part of the syntax and not used for grouping."] +image::grant-privileges-graph.png[title="Syntax of GRANT and DENY Graph Privileges. The `{` and `}` are part of the syntax and not used for grouping."] -The following image shows the hierarchy between different graph privileges: +The following image shows the hierarchy between the different graph privileges: -image::privileges_hierarchy.png[title="Graph privileges hierarchy"] +image::privilege-hierarchy-graph.png[title="Graph privileges hierarchy"] [role=enterprise-edition] [[access-control-list-privileges]] == Listing privileges -Available privileges can be displayed using the different `SHOW PRIVILEGE[S]` commands. +Available privileges can be displayed using the different `SHOW PRIVILEGES` commands. .Show privileges command syntax -[cols="<15s,<85"] +[options="header", width="100%", cols="3a,2a"] |=== - | Command -m| +SHOW PRIVILEGE+ +| Description -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- +| [source, cypher, role=noplay] SHOW [ALL] PRIVILEGE[S] [AS [REVOKE] COMMAND[S]] - [YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] - [WHERE expression] - [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ----- -| Description + [YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] + [WHERE expression] + [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] | List all privileges. -|=== - -.Show role privileges syntax -[cols="<15s,<85"] -|=== - -| Command -m| +SHOW ROLE ... PRIVILEGE+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- +| [source, cypher, role=noplay] SHOW ROLE[S] name[, ...] PRIVILEGE[S] [AS [REVOKE] COMMAND[S]] - [YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] - [WHERE expression] - [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ----- - -| Description -| Lists privileges for a specific role. - -|=== - -.Show user privileges syntax -[cols="<15s,<85"] -|=== + [YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] + [WHERE expression] + [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] +| List privileges for a specific role. -| Command -m| +SHOW USER ... PRIVILEGE+ - -| Syntax -a| -[source, syntax, role="noheader", indent=0] ----- +| [source, cypher, role=noplay] SHOW USER[S] [name[, ...]] PRIVILEGE[S] [AS [REVOKE] COMMAND[S]] - [YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] - [WHERE expression] - [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ----- - -| Description -| Lists privileges for a specific user, or the current user. + [YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] + [WHERE expression] + [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] +| List privileges for a specific user, or the current user. [NOTE] ==== @@ -267,37 +156,37 @@ Other users' privileges cannot be listed when using a non-native auth provider. ==== |=== -When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. +When using the `RETURN` clause, the `YIELD` clause is mandatory and may not be omitted. -For an easy overview of the existing privileges, it is recommended to use the `AS COMMANDS` version of the `SHOW` command. +For an easy overview of the existing privileges, it is recommended to use the `AS COMMANDS` version of the show command. This returns the privileges as the commands that are granted or denied. -When omitting the `AS COMMANDS` clause, results will include multiple columns describing privileges: +Omitting the `AS COMMANDS` clause instead gives the result as multiple columns describing the privilege: * `access`: whether the privilege is granted or denied. -* `action`: which type of privilege this is, for example traverse, read, index management or role management. -* `resource`: what type of scope this privilege applies to, i.e. the entire DBMS, a specific database, a graph or sub-graph access. +* `action`: which type of privilege this is, for example traverse, read, index management, or role management. +* `resource`: what type of scope this privilege applies to: the entire dbms, a database, a graph or sub-graph access. * `graph`: the specific database or graph this privilege applies to. -* `segment`: when applicable, this privilege applies to labels, relationship types, procedures, functions or transactions. -* `role`: the role a privilege is granted to. +* `segment`: when applicable, the scope this privilege applies to: labels, relationship types, procedures, functions, or transactions. +* `role`: the role the privilege is granted to. [role=enterprise-edition] [[access-control-list-all-privileges]] === Examples for listing all privileges -Available privileges can be displayed using the different `SHOW PRIVILEGE[S]` commands. +Available privileges can be displayed using the different `SHOW PRIVILEGES` commands. .Command syntax [source, cypher, role=noplay] ---- SHOW [ALL] PRIVILEGE[S] [AS [REVOKE] COMMAND[S]] - [WHERE expression] + [WHERE expression] SHOW [ALL] PRIVILEGE[S] [AS [REVOKE] COMMAND[S]] - YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n] - [WHERE expression] - [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] + YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n] + [WHERE expression] + [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ---- [source, cypher, role=noplay] @@ -593,13 +482,16 @@ Lists all privileges for all roles: 6+a|Rows: 39 |=== +[NOTE] +==== +The `token` action corresponds to the `NAME MANAGEMENT` privilege. +==== + It is also possible to filter and sort the results by using `YIELD`, `ORDER BY` and `WHERE`: [source, cypher, role=noplay] ---- -SHOW PRIVILEGES YIELD role, access, action, segment -ORDER BY action -WHERE role = 'admin' +SHOW PRIVILEGES YIELD role, access, action, segment ORDER BY action WHERE role = 'admin' ---- In this example: @@ -682,12 +574,16 @@ In this example: 4+a|Rows: 12 |=== -`WHERE` can also be used without `YIELD`: +[NOTE] +==== +The `token` action corresponds to the `NAME MANAGEMENT` privilege. +==== + +`WHERE` can be used without `YIELD`: [source, cypher, role=noplay] ---- -SHOW PRIVILEGES -WHERE graph <> '*' +SHOW PRIVILEGES WHERE graph <> '*' ---- In this example, the `WHERE` clause is used to filter privileges down to those that target specific graphs only. @@ -727,11 +623,11 @@ In this example, the `WHERE` clause is used to filter privileges down to those t |=== Aggregations in the `RETURN` clause can be used to group privileges. -In this case, by user and `GRANTED` or `DENIED`: +In this case, by user and granted / denied: [source, cypher, role=noplay] ---- -SHOW PRIVILEGES YIELD * RETURN role, access, collect([graph, resource, segment, action]) AS privileges +SHOW PRIVILEGES YIELD * RETURN role, access, collect([graph, resource, segment, action]) as privileges ---- .Result @@ -776,6 +672,11 @@ SHOW PRIVILEGES YIELD * RETURN role, access, collect([graph, resource, segment, 3+a|Rows: 8 |=== +[NOTE] +==== +The `token` action corresponds to the `NAME MANAGEMENT` privilege. +==== + The `RETURN` clause can also be used to order and paginate the results, which is useful when combined with `YIELD` and `WHERE`. In this example the query returns privileges for display five-per-page, and skips the first five to display the second page. @@ -832,7 +733,7 @@ SHOW PRIVILEGES YIELD * RETURN * ORDER BY role SKIP 5 LIMIT 5 6+a|Rows: 5 |=== -Available privileges can also be displayed as Cypher commands by adding `AS COMMAND[S]`: +Available privileges can also be output as Cypher commands, by appending `AS COMMAND[S]` to the show command: [source, cypher, role=noplay] ---- @@ -885,8 +786,7 @@ Like other `SHOW` commands, the output can also be processed using `YIELD` / `WH [source, cypher, role=noplay] ---- -SHOW PRIVILEGES AS COMMANDS -WHERE command CONTAINS 'MANAGEMENT' +SHOW PRIVILEGES AS COMMANDS WHERE command CONTAINS 'MANAGEMENT' ---- .Result @@ -904,7 +804,7 @@ WHERE command CONTAINS 'MANAGEMENT' a|Rows: 8 |=== -It is also possible to get the privileges listed as revoking commands instead of granting or denying: +It is also possible to get the privilege commands formatted for revoking instead of granting or denying the privileges: [source, cypher, role=noplay] ---- @@ -953,24 +853,24 @@ SHOW PRIVILEGES AS REVOKE COMMANDS a|Rows: 35 |=== -For more info about revoking privileges, please see xref::access-control/manage-privileges.adoc#access-control-revoke-privileges[The REVOKE command]. +For more info about revoking privileges, please see xref:access-control/manage-privileges.adoc#access-control-revoke-privileges[The REVOKE command]. [role=enterprise-edition] [[access-control-list-privileges-role]] === Examples for listing privileges for specific roles -Available privileges for specific roles can be displayed using `SHOW ROLE name PRIVILEGE[S]`: +Available privileges for specific roles can be displayed using `SHOW ROLE name PRIVILEGES`. [source, cypher, role=noplay] ---- SHOW ROLE[S] name[, ...] PRIVILEGE[S] [AS [REVOKE] COMMAND[S]] - [WHERE expression] + [WHERE expression] SHOW ROLE[S] name[, ...] PRIVILEGE[S] [AS [REVOKE] COMMAND[S]] - YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n] - [WHERE expression] - [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] + YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n] + [WHERE expression] + [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ---- [source, cypher, role=noplay] @@ -1034,7 +934,7 @@ Lists all privileges for roles `regularUsers` and `noAccessUsers`. 6+a|Rows: 2 |=== -Similar to the other `SHOW PRIVILEGES` commands, the available privileges for roles can also be listed as Cypher commands with the optional `AS COMMAND[S]`. +Similar to the other show privilege commands, the available privileges for roles can also be output as Cypher commands with the optional `AS COMMAND[S]`. .Result [options="header,footer", width="100%", cols="m"] @@ -1054,7 +954,7 @@ Similar to the other `SHOW PRIVILEGES` commands, the available privileges for ro a|Rows: 11 |=== -The output can be processed using `YIELD` / `WHERE` / `RETURN` here as well: +The output can be processed using `YIELD` / `WHERE` / `RETURN` here as well. [source, cypher, role=noplay] ---- @@ -1070,8 +970,8 @@ SHOW ROLE architect PRIVILEGES AS COMMANDS WHERE command CONTAINS 'MATCH' |Rows: 2 |=== -Again, it is possible to get the privileges listed as revoking commands instead of granting or denying. -For more info about revoking privileges, please see xref::access-control/manage-privileges.adoc#access-control-revoke-privileges[The REVOKE command]. +Again, is it possible to get the privilege commands formatted for revoking instead of granting or denying the privileges. +For more info about revoking privileges, please see xref:access-control/manage-privileges.adoc#access-control-revoke-privileges[The REVOKE command]. [source, cypher, role=noplay] ---- @@ -1097,19 +997,20 @@ Available privileges for specific users can be displayed using `SHOW USER name P [NOTE] ==== -Note that if a non-native auth provider like LDAP is in use, `SHOW USER PRIVILEGES` will only work with a limited capacity as it is only possible for a user to show their own privileges. +Please note that if a non-native auth provider like LDAP is in use, `SHOW USER PRIVILEGES` will only work in a limited capacity; +It is only possible for a user to show their own privileges. Other users' privileges cannot be listed when using a non-native auth provider. ==== [source, cypher, role=noplay] ---- SHOW USER[S] [name[, ...]] PRIVILEGE[S] [AS [REVOKE] COMMAND[S]] - [WHERE expression] + [WHERE expression] SHOW USER[S] [name[, ...]] PRIVILEGE[S] [AS [REVOKE] COMMAND[S]] - YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n] - [WHERE expression] - [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] + YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n] + [WHERE expression] + [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ---- [source, cypher, role=noplay] @@ -1251,18 +1152,18 @@ Lists all privileges for users `jake` and `joe`. |=== The same command can be used at all times to review available privileges for the current user. -For this purpose, there is a shorter form of the command: `SHOW USER PRIVILEGES`: +For this purpose, a shorter form of the the command also exists: `SHOW USER PRIVILEGES` [source, cypher, role=noplay] ---- SHOW USER PRIVILEGES ---- -As for the other privilege commands, available privileges for users can also be listed as Cypher commands with the optional `AS COMMAND[S]`. +As for the other privilege commands, available privileges for users can also be output as Cypher commands with the optional `AS COMMAND[S]`. [NOTE] ==== -When showing user privileges as commands, the roles in the Cypher commands are replaced with a parameter. +When showing _user_ privileges as commands, the roles in the Cypher commands are replaced with a parameter. This can be used to quickly create new roles based on the privileges of specific users. ==== @@ -1287,8 +1188,7 @@ Additionally, similar to the other show privilege commands, it is also possible [source, cypher, role=noplay] ---- -SHOW USER jake PRIVILEGES AS REVOKE COMMANDS -WHERE command CONTAINS 'EXECUTE' +SHOW USER jake PRIVILEGES AS REVOKE COMMANDS WHERE command CONTAINS 'EXECUTE' ---- .Result @@ -1301,7 +1201,6 @@ a|Rows: 2 |=== - [role=enterprise-edition] [[access-control-revoke-privileges]] == Revoking privileges @@ -1311,8 +1210,8 @@ Privileges that were granted or denied earlier can be revoked using the `REVOKE` [source, cypher, role=noplay] ---- REVOKE - [ GRANT | DENY ] graph-privilege - FROM role[, ...] + [ GRANT | DENY ] graph-privilege + FROM role[, ...] ---- An example usage of the `REVOKE` command is given here: @@ -1322,8 +1221,8 @@ An example usage of the `REVOKE` command is given here: REVOKE GRANT TRAVERSE ON HOME GRAPH NODES Post FROM regularUsers ---- -While it can be explicitly specified that `REVOKE` should remove a `GRANT` or `DENY`, it is also possible to `REVOKE` both by not specifying them at all, as the next example demonstrates. -Because of this, if there happens to be a `GRANT` and a `DENY` for the same privilege, it would remove both. +While it can be explicitly specified that revoke should remove a `GRANT` or `DENY`, it is also possible to revoke either one by not specifying at all as the next example demonstrates. +Because of this, if there happen to be a `GRANT` and a `DENY` on the same privilege, it would remove both. [source, cypher, role=noplay] ---- diff --git a/modules/ROOT/pages/access-control/manage-roles.adoc b/modules/ROOT/pages/access-control/manage-roles.adoc index 9b29d9011..cbe5e4fec 100644 --- a/modules/ROOT/pages/access-control/manage-roles.adoc +++ b/modules/ROOT/pages/access-control/manage-roles.adoc @@ -1,17 +1,11 @@ -:description: This section explains how to use Cypher to manage roles in Neo4j. - [role=enterprise-edition] [[access-control-manage-roles]] = Managing roles - -[abstract] --- -This section explains how to use Cypher to manage roles in Neo4j. --- +:description: This section explains how to use Cypher to manage roles in Neo4j. Roles can be created and managed using a set of Cypher administration commands executed against the `system` database. -When connected to the DBMS over `bolt`, administration commands are automatically routed to the `system` database. +When connected to the DBMS over bolt, administration commands are automatically routed to the `system` database. [[access-control-role-syntax]] @@ -19,13 +13,12 @@ When connected to the DBMS over `bolt`, administration commands are automaticall [cols="<15s,<85"] |=== - | Command m| SHOW ROLES | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- SHOW [ALL\|POPULATED] ROLES [YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] @@ -34,34 +27,26 @@ SHOW [ALL\|POPULATED] ROLES ---- | Description -a| -Lists roles. +a| List roles. -When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. +When using the `RETURN` clause, the `YIELD` clause is mandatory and may not be omitted. -For more information, see xref::access-control/manage-roles.adoc#access-control-list-roles[Listing roles]. +For more information, see xref:access-control/manage-roles.adoc#access-control-list-roles[Listing roles]. | Required privilege -a| -[source, privilege, role="noheader"] ----- -GRANT SHOW ROLE ----- - +a| `GRANT SHOW ROLE` -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]). +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]) |=== - [cols="<15s,<85"] |=== - | Command m| SHOW ROLES WITH USERS | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- SHOW [ALL\|POPULATED] ROLES WITH USERS [YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] @@ -70,44 +55,30 @@ SHOW [ALL\|POPULATED] ROLES WITH USERS ---- | Description -a| -Lists roles and users assigned to them. +a| List roles and users assigned to them. -When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. +When using the `RETURN` clause, the `YIELD` clause is mandatory and may not be omitted. -For more information, see xref::access-control/manage-roles.adoc#access-control-list-roles[Listing roles]. +For more information, see xref:access-control/manage-roles.adoc#access-control-list-roles[Listing roles]. | Required privilege -a| -[source, privilege, role="noheader"] ----- -GRANT SHOW ROLE ----- +a| `GRANT SHOW ROLE` -[source, privilege, role="noheader"] -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]) +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]) +`GRANT SHOW USER` -[source, privilege, role="noheader"] ----- -GRANT SHOW USER ----- - - -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) - +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) |=== - [cols="<15s,<85"] |=== - | Command m| SHOW ROLE PRIVILEGES | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- SHOW ROLE[S] name[, ...] PRIVILEGE[S] [AS [REVOKE] COMMAND[S]] [YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] @@ -116,54 +87,39 @@ SHOW ROLE[S] name[, ...] PRIVILEGE[S] [AS [REVOKE] COMMAND[S]] ---- | Description -a| -Lists the privileges granted to the specified roles. +a| List the privileges granted to the specified roles. -When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. +When using the `RETURN` clause, the `YIELD` clause is mandatory and may not be omitted. -For more information, see xref::access-control/manage-privileges.adoc#access-control-list-privileges[Listing privileges]. +For more information, see xref:access-control/manage-privileges.adoc#access-control-list-privileges[Listing privileges]. | Required privilege -a| -[source, privilege, role="noheader"] ----- -GRANT SHOW PRIVILEGE ----- - -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[DBMS PRIVILEGE MANAGEMENT privileges]) +a| GRANT SHOW PRIVILEGE +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[DBMS PRIVILEGE MANAGEMENT privileges]) |=== - [cols="<15s,<85"] |=== - - | Command m| CREATE ROLE | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- CREATE ROLE name [IF NOT EXISTS] [AS COPY OF otherName] ---- | Description -a| -Creates a new role. +a| Create a new role. -For more information, see xref::access-control/manage-roles.adoc#access-control-create-roles[Creating roles]. +For more information, see xref:access-control/manage-roles.adoc#access-control-create-roles[Creating roles]. | Required privilege -a| -[source, privilege, role="noheader"] ----- -GRANT CREATE ROLE ----- - -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]) +a| `GRANT CREATE ROLE` +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]) |=== [cols="<15s,<85"] @@ -173,154 +129,112 @@ m| CREATE OR REPLACE ROLE | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- CREATE OR REPLACE ROLE name [AS COPY OF otherName] ---- | Description -a| -Creates a new role, or if a role with the same name exists, replace it. +a| Create a new role, or if a role with the same name exists, replace it. -For more information, see xref::access-control/manage-roles.adoc#access-control-create-roles[Creating roles]. +For more information, see xref:access-control/manage-roles.adoc#access-control-create-roles[Creating roles]. | Required privilege -a| -[source, privilege, role="noheader"] ----- -GRANT CREATE ROLE ----- - -[source, privilege, role="noheader"] ----- -GRANT DROP ROLE ----- - -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]) +a| `GRANT CREATE ROLE` and `GRANT DROP ROLE` +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]) |=== - [cols="<15s,<85"] |=== - | Command m| RENAME ROLE | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- RENAME ROLE name [IF EXISTS] TO otherName ---- | Description -a| -Changes the name of a role. +a| Change the name of a role. -For more information, see xref::access-control/manage-roles.adoc#access-control-rename-roles[Renaming roles]. +For more information, see xref:access-control/manage-roles.adoc#access-control-rename-roles[Renaming roles]. | Required privilege -a| -[source, privilege, role="noheader"] ----- -GRANT RENAME ROLE ----- - -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]) +a| `GRANT RENAME ROLE` +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]) |=== - [cols="<15s,<85"] |=== - | Command m| DROP ROLE | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- DROP ROLE name [IF EXISTS] ---- | Description -a| -Removes a role. +a| Remove a role. -For more information, see xref::access-control/manage-roles.adoc#access-control-drop-roles[Deleting roles]. +For more information, see xref:access-control/manage-roles.adoc#access-control-drop-roles[Deleting roles]. | Required privilege -[source, privilege, role="noheader"] ----- -GRANT DROP ROLE ----- - -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]) +a| `GRANT DROP ROLE` +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]) |=== - [cols="<15s,<85"] |=== - | Command m| GRANT ROLE TO | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- GRANT ROLE[S] name[, ...] TO user[, ...] ---- | Description -a| -Assigns roles to users. +a| Assign roles to users. -For more information, see xref::access-control/manage-roles.adoc#access-control-assign-roles[Assigning roles to users]. +For more information, see xref:access-control/manage-roles.adoc#access-control-assign-roles[Assigning roles to users]. | Required privilege -a| -[source, privilege, role="noheader"] ----- -GRANT ASSIGN ROLE ----- - -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]) +a| `GRANT ASSIGN ROLE` +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]) |=== - [cols="<15s,<85"] |=== - | Command m| REVOKE ROLE | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- REVOKE ROLE[S] name[, ...] FROM user[, ...] ---- | Description -a| -Removes roles from users. +a| Remove roles from users. -For more information, see xref::access-control/manage-roles.adoc#access-control-revoke-roles[Revoking roles from users]. +For more information, see xref:access-control/manage-roles.adoc#access-control-revoke-roles[Revoking roles from users]. | Required privilege -a| -[source, privilege, role="noheader"] ----- -GRANT REMOVE ROLE ----- - -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]) +a| `GRANT REMOVE ROLE` +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DBMS ROLE MANAGEMENT privileges]) |=== @@ -336,15 +250,14 @@ SHOW ROLES This is the same command as `SHOW ALL ROLES`. -When first starting a Neo4j DBMS, there are a number of built-in roles: +When first starting a Neo4j DBMS there are a number of built-in roles: -* `PUBLIC` - a role that all users have granted. -By default it gives access to the home database and to execute privileges for procedures and functions. -* `reader` - can perform traverse and read operations in all databases except `system`. -* `editor` - can perform traverse, read, and write operations in all databases except `system`, but cannot create new labels or relationship types. +* `PUBLIC` - a role that all users have granted, by default it gives access to the home database and execute privileges for procedures and functions. +* `reader` - can perform traverse and read operations on all databases except `system`. +* `editor` - can perform traverse, read, and write operations on all databases except `system`, but cannot make new labels or relationship types. * `publisher` - can do the same as `editor`, but also create new labels and relationship types. * `architect` - can do the same as `publisher` as well as create and manage indexes and constraints. -* `admin` - can do the same as all the above, as well as manage databases, aliases, users, roles, and privileges. +* `admin` - can do the same as all the above, as well as manage databases, users, roles, and privileges. .Result [options="header,footer", width="100%", cols="m"] @@ -361,12 +274,12 @@ By default it gives access to the home database and to execute privileges for pr 1+a|Rows: 6 |=== -More information about the built-in roles can be found in link:{neo4j-docs-base-uri}/operations-manual/{page-version}/authentication-authorization/built-in-roles[Operations Manual -> Built-in roles]. +More information about the built-in roles can be found in link:{neo4j-docs-base-uri}/operations-manual/{page-version}/authentication-authorization/built-in-roles[Operations Manual -> Built-in roles] There are multiple versions of this command, the default being `SHOW ALL ROLES`. To only show roles that are assigned to users, the command is `SHOW POPULATED ROLES`. -To see which users are assigned to roles, `WITH USERS` can be added to the command. -This will give a result with one row for each user, so if a role is assigned to two users, then it will show up twice. +To see which users are assigned to roles `WITH USERS` can be appended to the commands. +This will give one result row for each user, so if a role is assigned to two users then it will show up twice in the result. [source, cypher, role=noplay] ---- @@ -406,15 +319,13 @@ It is also possible to filter and sort the results by using `YIELD`, `ORDER BY` [source, cypher, role=noplay] ---- -SHOW ROLES YIELD role -ORDER BY role -WHERE role ENDS WITH 'r' +SHOW ROLES YIELD role ORDER BY role WHERE role ENDS WITH 'r' ---- In this example: * The results have been filtered to only return the roles ending in 'r'. -* The results are ordered by the `action` column using `ORDER BY`. +* The results are ordered by the 'action' column using `ORDER BY`. It is also possible to use `SKIP` and `LIMIT` to paginate the results. @@ -432,21 +343,21 @@ It is also possible to use `SKIP` and `LIMIT` to paginate the results. [NOTE] ==== -The `SHOW ROLE name PRIVILEGES` command is found in xref::access-control/manage-privileges.adoc#access-control-list-privileges[Listing privileges]. +The `SHOW ROLE name PRIVILEGES` command is found in xref:access-control/manage-privileges.adoc#access-control-list-privileges[Listing privileges]. ==== [[access-control-create-roles]] == Creating roles -Roles can be created using `CREATE ROLE`: +Roles can be created using `CREATE ROLE`. [source, cypher, role=noplay] ---- CREATE ROLE name [IF NOT EXISTS] [AS COPY OF otherName] ---- -Roles can be created or replaced by using `CREATE OR REPLACE ROLE`: +Roles can be created or replaced by using `CREATE OR REPLACE ROLE`. [source, cypher, role=noplay] ---- @@ -500,35 +411,28 @@ SHOW ROLES ====== The `CREATE ROLE` command is optionally idempotent, with the default behavior to throw an exception if the role already exists. -Adding `IF NOT EXISTS` to the `CREATE ROLE` command will ensure that no exception is thrown and nothing happens should the role already exist. +Appending `IF NOT EXISTS` to the `CREATE ROLE` command will ensure that no exception is thrown and nothing happens should the role already exist. .Create role if not exists ====== - [source, cypher, role=noplay] ---- CREATE ROLE myrole IF NOT EXISTS ---- - ====== - The `CREATE OR REPLACE ROLE` command will result in any existing role being deleted and a new one created. - .Create or replace role ====== - [source, cypher, role=noplay] ---- CREATE OR REPLACE ROLE myrole ---- This is equivalent to running `DROP ROLE myrole IF EXISTS` followed by `CREATE ROLE myrole`. - ====== - [NOTE] ==== * The `CREATE OR REPLACE ROLE` command does not allow you to use the `IF NOT EXISTS`. @@ -583,7 +487,7 @@ Users can be given access rights by assigning them roles using `GRANT ROLE`: GRANT ROLE myrole TO bob ---- -The roles assigned to each user can be seen on the list provided by `SHOW USERS`: +The roles assigned to each user can be seen in the list provided by `SHOW USERS`: [source, cypher, role=noplay] ---- @@ -697,7 +601,7 @@ Users can lose access rights by revoking their role using `REVOKE ROLE`: REVOKE ROLE myrole FROM bob ---- -The roles revoked from users can no longer be seen on the list provided by `SHOW USERS`: +The roles revoked from users can no longer be seen in the list provided by `SHOW USERS`: [source, cypher, role=noplay] ---- @@ -787,8 +691,8 @@ SHOW ROLES 1+a|Rows: 8 |=== -This command is optionally idempotent, with the default behavior to throw an exception if the role does not exist. -Adding `IF EXISTS` to the command will ensure that no exception is thrown and nothing happens should the role not exist: +This command is optionally idempotent, with the default behavior to throw an exception if the role does not exists. +Appending `IF EXISTS` to the command will ensure that no exception is thrown and nothing happens should the role not exist: [source, cypher, role=noplay] ---- diff --git a/modules/ROOT/pages/access-control/manage-users.adoc b/modules/ROOT/pages/access-control/manage-users.adoc index 8c15df491..dcff1dd44 100644 --- a/modules/ROOT/pages/access-control/manage-users.adoc +++ b/modules/ROOT/pages/access-control/manage-users.adoc @@ -1,12 +1,6 @@ -:description: This section explains how to use Cypher to manage users in Neo4j. - [[access-control-manage-users]] = Managing users - -[abstract] --- -This section explains how to use Cypher to manage users in Neo4j. --- +:description: This section explains how to use Cypher to manage users in Neo4j. Users can be created and managed using a set of Cypher administration commands executed against the `system` database. When connected to the DBMS over `bolt`, administration commands are automatically routed to the `system` database. @@ -17,13 +11,12 @@ When connected to the DBMS over `bolt`, administration commands are automaticall [cols="<15s,<85"] |=== - | Command m| SHOW CURRENT USER | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- SHOW CURRENT USER [YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] @@ -32,28 +25,24 @@ SHOW CURRENT USER ---- | Description -a| -Lists the current user. +a| Lists the current user. -When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. +When using the `RETURN` clause, the `YIELD` clause is mandatory and may not be omitted. -For more information, see xref::access-control/manage-users.adoc#access-control-current-users[Listing current user]. +For more information, see xref:access-control/manage-users.adoc#access-control-current-users[Listing current user]. | Required privilege a| None - |=== - [cols="<15s,<85"] |=== - | Command m| SHOW USERS | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- SHOW USERS [YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] @@ -62,25 +51,18 @@ SHOW USERS ---- | Description -a| -Lists all users. +a| List all users. -When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. +When using the `RETURN` clause, the `YIELD` clause is mandatory and may not be omitted. -For more information, see xref::access-control/manage-users.adoc#access-control-list-users[Listing users]. +For more information, see xref:access-control/manage-users.adoc#access-control-list-users[Listing users]. | Required privilege -a| -[source, privilege, role="noheader"] ----- -GRANT SHOW USER ----- - -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) +a| `GRANT SHOW USER` +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) |=== - [cols="<15s,<85"] |=== | Command @@ -88,7 +70,7 @@ m| SHOW USER PRIVILEGES | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- SHOW USER[S] [name[, ...]] PRIVILEGE[S] [AS [REVOKE] COMMAND[S]] [YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] @@ -97,28 +79,20 @@ SHOW USER[S] [name[, ...]] PRIVILEGE[S] [AS [REVOKE] COMMAND[S]] ---- | Description -a| -Lists the privileges granted to the specified users or the current user if no user is specified. +a| List the privileges granted to the specified users or the current user if no user is specified. -When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. +When using the `RETURN` clause, the `YIELD` clause is mandatory and may not be omitted. -For more information, see xref::access-control/manage-privileges.adoc#access-control-list-privileges[Listing privileges]. +For more information, see xref:access-control/manage-privileges.adoc#access-control-list-privileges[Listing privileges]. | Required privilege -a| -[source, privilege, role="noheader"] ----- -GRANT SHOW PRIVILEGE ----- +a| `GRANT SHOW PRIVILEGE` -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[DBMS PRIVILEGE MANAGEMENT privileges]) +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[DBMS PRIVILEGE MANAGEMENT privileges]) -[source, privilege, role="noheader"] ----- -GRANT SHOW USER ----- +`GRANT SHOW USER` -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) |=== [cols="<15s,<85"] @@ -128,7 +102,7 @@ m| CREATE USER | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- CREATE USER name [IF NOT EXISTS] SET [PLAINTEXT \| ENCRYPTED] PASSWORD 'password' @@ -138,20 +112,14 @@ CREATE USER name [IF NOT EXISTS] ---- | Description -a| -Creates a new user. +a| Create a new user. -For more information, see xref::access-control/manage-users.adoc#access-control-create-users[Creating users]. +For more information, see xref:access-control/manage-users.adoc#access-control-create-users[Creating users]. | Required privilege -a| -[source, privilege, role="noheader"] ----- -GRANT CREATE USER ----- - -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) +a| `GRANT CREATE USER` +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) |=== [cols="<15s,<85"] @@ -161,7 +129,7 @@ m| CREATE OR REPLACE USER | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- CREATE OR REPLACE USER name SET [PLAINTEXT \| ENCRYPTED] PASSWORD 'password' @@ -171,27 +139,14 @@ CREATE OR REPLACE USER name ---- | Description -a| -Creates a new user, or if a user with the same name exists, replace it. +a| Create a new user, or if a user with the same name exists, replace it. -For more information, see xref::access-control/manage-users.adoc#access-control-create-users[Creating users]. +For more information, see xref:access-control/manage-users.adoc#access-control-create-users[Creating users]. | Required privilege -a| -[source, privilege, role="noheader"] ----- -GRANT CREATE USER ----- - -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) - -[source, privilege, role="noheader"] ----- -GRANT DROP USER ----- - -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) +a| `GRANT CREATE USER` and `GRANT DROP USER` +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) |=== [cols="<15s,<85"] @@ -201,26 +156,20 @@ m| RENAME USER | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- RENAME USER name [IF EXISTS] TO otherName ---- | Description -a| -Changes the name of a user. +a| Change the name of a user. -For more information, see xref::access-control/manage-users.adoc#access-control-rename-users[Renaming users]. +For more information, see xref:access-control/manage-users.adoc#access-control-rename-users[Renaming users]. | Required privilege -a| -[source, privilege, role="noheader"] ----- -GRANT RENAME USER ----- - -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) +a| `GRANT RENAME USER` +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) |=== [cols="<15s,<85"] @@ -230,7 +179,7 @@ m| ALTER USER | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- ALTER USER name [IF EXISTS] [SET [PLAINTEXT \| ENCRYPTED] PASSWORD 'password'] @@ -241,88 +190,58 @@ ALTER USER name [IF EXISTS] ---- | Description -a| -Modifies the settings for an existing user. -At least one `SET` or `REMOVE` clause is required. -`SET` and `REMOVE` clauses cannot be combined in the same command. +a| Modify the settings for an existing user. At least one `SET` or `REMOVE` clause is required. `SET` and `REMOVE` clauses cannot be combined in the same command. -For more information, see xref::access-control/manage-users.adoc#access-control-alter-users[Modifying users]. +For more information, see xref:access-control/manage-users.adoc#access-control-alter-users[Modifying users]. | Required privilege -a| -[source, privilege, role="noheader"] ----- -GRANT SET PASSWORD ----- - -[source, privilege, role="noheader" ----- -GRANT SET USER STATUS ----- - -[source, privilege, role="noheader"] ----- -GRANT SET USER HOME DATABASE ----- - -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) +a| `GRANT SET PASSWORD`, `GRANT SET USER STATUS`, and/or `GRANT SET USER HOME DATABASE` +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) |=== - [cols="<15s,<85"] |=== - | Command m| ALTER CURRENT USER SET PASSWORD | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- ALTER CURRENT USER SET PASSWORD FROM 'oldPassword' TO 'newPassword' ---- | Description -a| -Changes the current user's password. +a| Change the current user's password. -For more information, see xref::access-control/manage-users.adoc#access-control-alter-password[Changing the current user's password]. +For more information, see xref:access-control/manage-users.adoc#access-control-alter-password[Changing the current user's password]. | Required privilege a| None - |=== - [cols="<15s,<85"] |=== - | Command m| DROP USER | Syntax a| -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- DROP USER name [IF EXISTS] ---- | Description -a| -Removes an existing user. +a| Remove an existing user. -For more information, see xref::access-control/manage-users.adoc#access-control-drop-users[Delete users]. +For more information, see xref:access-control/manage-users.adoc#access-control-drop-users[Delete users]. | Required privilege -a| -[source, privilege, role="noheader"] ----- -GRANT DROP USER ----- - -(see xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) +a| `GRANT DROP USER` +(see xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DBMS USER MANAGEMENT privileges]) |=== @@ -335,7 +254,7 @@ The `SHOW USER[S] PRIVILEGES` command is only available in Neo4j Enterprise Edit [[access-control-current-users]] == Listing current user -The currently logged-in user can be seen using `SHOW CURRENT USER`, which will produce a table with the following columns: +The currently logged-in user can be seen using `SHOW CURRENT USER` which will produce a table with the following columns: [options="header", width="100%", cols="2a,4,^.^,^.^"] |=== @@ -365,8 +284,8 @@ The currently logged-in user can be seen using `SHOW CURRENT USER`, which will p | {check-mark} | home -| The home database configured by the user, or `null` if no home database has been configured. -If this database is unavailable and the user does not specify a database to use, they will not be able to log in. +| The home database configured for the user, or `null` if no home database has been configured. +If this database is unavailable, and the user does not specify a database to use they will not be able to log in. | {cross-mark} | {check-mark} |=== @@ -403,7 +322,7 @@ This command is only supported for a logged-in user and will return an empty res [[access-control-list-users]] == Listing users -Available users can be seen using `SHOW USERS`, which will produce a table of users with the following columns: +Available users can be seen using `SHOW USERS` which will produce a table of users with the following columns: [options="header", width="100%", cols="2a,4,^.^,^.^"] |=== @@ -433,9 +352,8 @@ Available users can be seen using `SHOW USERS`, which will produce a table of us | {check-mark} | home -| The home database configured by the user, or `null` if no home database has been configured. -A home database will be resolved if it is either pointing to a database or a database alias. -If this database is unavailable and the user does not specify a database to use, they will not be able to log in. +| The home database configured for the user, or `null` if no home database has been configured. +If this database is unavailable, and the user does not specify a database to use they will not be able to log in. | {cross-mark} | {check-mark} |=== @@ -468,12 +386,12 @@ It is possible to set the initial password using link:{neo4j-docs-base-uri}/oper .Show user ====== -This example shows how to: +This example show how: * Reorder the columns using a `YIELD` clause. * Filter the results using a `WHERE` clause. -[source, cypher, role=noplay] +[source,cypher,role=noplay] ---- SHOW USERS YIELD user, suspended, passwordChangeRequired, roles, home WHERE user = 'jake' @@ -495,7 +413,7 @@ RETURN user AS adminUser [NOTE] ==== -The `SHOW USER name PRIVILEGES` command is described in xref::access-control/manage-privileges.adoc#access-control-list-privileges[Listing privileges]. +The `SHOW USER name PRIVILEGES` command is described in xref:access-control/manage-privileges.adoc#access-control-list-privileges[Listing privileges]. ==== @@ -504,7 +422,7 @@ The `SHOW USER name PRIVILEGES` command is described in xref::access-control/man Users can be created using `CREATE USER`. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- CREATE USER name [IF NOT EXISTS] SET [PLAINTEXT | ENCRYPTED] PASSWORD 'password' @@ -515,7 +433,7 @@ CREATE USER name [IF NOT EXISTS] Users can be created or replaced using `CREATE OR REPLACE USER`. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- CREATE OR REPLACE USER name SET [PLAINTEXT | ENCRYPTED] PASSWORD 'password' @@ -527,20 +445,19 @@ CREATE OR REPLACE USER name * For `SET PASSWORD`: ** The `password` can either be a string value or a string parameter. ** All passwords are encrypted (hashed) when stored in the Neo4j `system` database. -`PLAINTEXT` and `ENCRYPTED` just refer to the format of the password in the Cypher command, i.e. whether Neo4j needs to hash it or it has already been hashed. -Consequently, it is never possible to get the plaintext of a password back out of the database. -A password can be set in either fashion at any time. +`PLAINTEXT` and `ENCRYPTED` just refer to the format of the password in the Cypher command, i.e., whether Neo4j needs to hash it or it has already been hashed. +Therefore, it is never possible to get the plaintext of a password back out of the database. +A password can be set in either fashion at any time. ** The optional `PLAINTEXT` in `SET PLAINTEXT PASSWORD` has the same behavior as `SET PASSWORD`. ** The optional `ENCRYPTED` is used to recreate an existing user when the plaintext password is unknown, but the encrypted password is available in the _data/scripts/databasename/restore_metadata.cypher_ file of a database backup. See link:{neo4j-docs-base-uri}/operations-manual/{page-version}/backup-restore/restore-backup#restore-backup-example[Operations Manual -> Restore a database backup -> Example]. + -With `ENCRYPTED`, the password string is expected to be in the format of ``, `` or ``, where, for example: +With `ENCRYPTED`, the password string is expected to be in the format of `,,`, where, for example: *** `0` is the first version and refers to the `SHA-256` cryptographic hash function with iterations `1`. *** `1` is the second version and refers to the `SHA-256` cryptographic hash function with iterations `1024`. * If the optional `SET PASSWORD CHANGE [NOT] REQUIRED` is omitted, the default is `CHANGE REQUIRED`. The `SET PASSWORD` part is only optional if it directly follows the `SET PASSWORD` clause. * The default for `SET STATUS` is `ACTIVE`. * `SET HOME DATABASE` can be used to configure a home database for a user. -A home database will be resolved if it is either pointing to a database or a database alias. If no home database is set, the DBMS default database is used as the home database for the user. * The `SET PASSWORD CHANGE [NOT] REQUIRED`, `SET STATUS`, and `SET HOME DATABASE` clauses can be applied in any order. @@ -550,13 +467,12 @@ User names are case sensitive. The created user will appear on the list provided by `SHOW USERS`. * In Neo4j Community Edition there are no roles, but all users have implied administrator privileges. -* In Neo4j Enterprise Edition all users are automatically assigned the xref::access-control/built-in-roles.adoc#access-control-built-in-roles-public[`PUBLIC` role], giving them a base set of privileges. +* In Neo4j Enterprise Edition all users are automatically assigned the xref:access-control/built-in-roles.adoc#access-control-built-in-roles-public[`PUBLIC` role], giving them a base set of privileges. ==== - .Create user ====== -For example, you can create the user `jake` in a suspended state, with the home database `anotherDb`, and the requirement to change the password by using the command: +For example, you can create the user `jake` in a suspended state, with the home database `anotherDb`, and the requirement to change the password, using the command: [source,cypher,role=noplay] ---- @@ -565,13 +481,11 @@ SET PASSWORD 'abc' CHANGE REQUIRED SET STATUS SUSPENDED SET HOME DATABASE anotherDb ---- - ====== - .Create user ====== -Or you can recreate the user `jake` in an active state, with an encrypted password (taken from the _data/scripts/databasename/restore_metadata.cypher_ of a database backup), and the requirement to not change the password by running: +Or, you can recreate the user `jake` in an active state, with an encrypted password (taken from the _data/scripts/databasename/restore_metadata.cypher_ of a database backup), and the requirement not to change the password, by running: [source,cypher,role=noplay] ---- @@ -579,7 +493,6 @@ CREATE USER jake SET ENCRYPTED PASSWORD '1,6d57a5e0b3317055454e455f96c98c750c77fb371f3f0634a1b8ff2a55c5b825,190ae47c661e0668a0c8be8a21ff78a4a34cdf918cae3c407e907b73932bd16c' CHANGE NOT REQUIRED SET STATUS ACTIVE ---- - ====== [NOTE] @@ -590,7 +503,6 @@ The `SET STATUS {ACTIVE | SUSPENDED}` and `SET HOME DATABASE` parts of the comma The `CREATE USER` command is optionally idempotent, with the default behavior to throw an exception if the user already exists. Appending `IF NOT EXISTS` to the `CREATE USER` command will ensure that no exception is thrown and nothing happens should the user already exist. - .Create user if not exists ====== [source,cypher,role=noplay] @@ -598,12 +510,10 @@ Appending `IF NOT EXISTS` to the `CREATE USER` command will ensure that no excep CREATE USER jake IF NOT EXISTS SET PLAINTEXT PASSWORD 'xyz' ---- - ====== The `CREATE OR REPLACE USER` command will result in any existing user being deleted and a new one created. - .Create or replace user ====== [source,cypher,role=noplay] @@ -613,19 +523,18 @@ SET PLAINTEXT PASSWORD 'xyz' ---- This is equivalent to running `DROP USER jake IF EXISTS` followed by `CREATE USER jake SET PASSWORD 'xyz'`. - ====== [NOTE] ==== -The `CREATE OR REPLACE USER` command does not allow the use of `IF NOT EXISTS`. +The `CREATE OR REPLACE USER` command does not allow you to use the `IF NOT EXISTS`. ==== [[access-control-rename-users]] == Renaming users -Users can be renamed with the `RENAME USER` command. +Users can be renamed using the `RENAME USER` command. [source, cypher, role=noplay] ---- @@ -640,7 +549,11 @@ SHOW USERS .Result [options="header,footer", width="100%", cols="2m,3m,3m,2m,2m"] |=== -|user |roles |passwordChangeRequired |suspended |home +|user +|roles +|passwordChangeRequired +|suspended +|home |"bob" |["PUBLIC"] @@ -655,7 +568,6 @@ SHOW USERS | 5+a|Rows: 2 - |=== [NOTE] @@ -667,9 +579,9 @@ The `RENAME USER` command is only available when using native authentication and [[access-control-alter-users]] == Modifying users -Users can be modified with `ALTER USER`. +Users can be modified using `ALTER USER`. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- ALTER USER name [IF EXISTS] [SET [PLAINTEXT | ENCRYPTED] PASSWORD 'password'] @@ -682,24 +594,23 @@ ALTER USER name [IF EXISTS] * At least one `SET` or `REMOVE` clause is required for the command. * `SET` and `REMOVE` clauses cannot be combined in the same command. * The `SET PASSWORD CHANGE [NOT] REQUIRED`, `SET STATUS`, and `SET HOME DATABASE` clauses can be applied in any order. -The `SET PASSWORD` clause must come first, if used. +The `SET PASSWORD` clause must come first if used. * For `SET PASSWORD`: ** The `password` can either be a string value or a string parameter. ** All passwords are encrypted (hashed) when stored in the Neo4j `system` database. -`PLAINTEXT` and `ENCRYPTED` just refer to the format of the password in the Cypher command, i.e. whether Neo4j needs to hash it or it has already been hashed. -Consequently, it is never possible to get the plaintext of a password back out of the database. -A password can be set in either fashion at any time. +`PLAINTEXT` and `ENCRYPTED` just refer to the format of the password in the Cypher command, i.e., whether Neo4j needs to hash it or it has already been hashed. +Therefore, it is never possible to get the plaintext of a password back out of the database. +A password can be set in either fashion at any time. ** The optional `PLAINTEXT` in `SET PLAINTEXT PASSWORD` has the same behavior as `SET PASSWORD`. ** The optional `ENCRYPTED` is used to update an existing user's password when the plaintext password is unknown, but the encrypted password is available in the _data/scripts/databasename/restore_metadata.cypher_ file of a database backup. See link:{neo4j-docs-base-uri}/operations-manual/{page-version}/backup-restore/restore-backup#restore-backup-example[Operations Manual -> Restore a database backup -> Example]. + -With `ENCRYPTED`, the password string is expected to be in the format of ``,`` or ``, where, for example: +With `ENCRYPTED`, the password string is expected to be in the format of `,,`, where, for example: *** `0` is the first version and refers to the `SHA-256` cryptographic hash function with iterations `1`. *** `1` is the second version and refers to the `SHA-256` cryptographic hash function with iterations `1024`. * If the optional `SET PASSWORD CHANGE [NOT] REQUIRED` is omitted, the default is `CHANGE REQUIRED`. The `SET PASSWORD` part is only optional if it directly follows the `SET PASSWORD` clause. * For `SET PASSWORD CHANGE [NOT] REQUIRED`, the `SET PASSWORD` is only optional if it directly follows the `SET PASSWORD` clause. * `SET HOME DATABASE` can be used to configure a home database for a user. -A home database will be resolved if it is either pointing to a database or a database alias. If no home database is set, the DBMS default database is used as the home database for the user. * `REMOVE HOME DATABASE` is used to unset the home database for a user. This results in the DBMS default database being used as the home database for the user. @@ -708,30 +619,26 @@ For example, you can modify the user `bob` with a new password and active status [source, cypher, role=noplay] ---- -ALTER USER bob -SET PASSWORD 'abc123' CHANGE NOT REQUIRED -SET STATUS ACTIVE +ALTER USER bob SET PASSWORD 'abc123' CHANGE NOT REQUIRED SET STATUS ACTIVE ---- -Or you may decide to assign the user `bob` a different home database: +Or, you may decide to assign the user `bob` a different home database: [source, cypher, role=noplay] ---- -ALTER USER bob -SET HOME DATABASE anotherDbOrAlias +ALTER USER bob SET HOME DATABASE anotherDb ---- -Or remove the home database from the user `bob`: +Or, remove the home database from the user `bob`: [source, cypher, role=noplay] ---- -ALTER USER bob -REMOVE HOME DATABASE +ALTER USER bob REMOVE HOME DATABASE ---- [NOTE] ==== -When altering a user, it is only necessary to specify the changes required. +When altering a user it is only necessary to specify the changes required. For example, leaving out the `CHANGE [NOT] REQUIRED` part of the query will leave that unchanged. ==== @@ -750,7 +657,11 @@ SHOW USERS .Result [options="header,footer", width="100%", cols="2m,3m,3m,2m,2m"] |=== -|user |roles |passwordChangeRequired |suspended |home +|user +|roles +|passwordChangeRequired +|suspended +|home |"bob" |["PUBLIC"] @@ -765,12 +676,10 @@ SHOW USERS | 5+a|Rows: 2 - |=== The default behavior of this command is to throw an exception if the user does not exist. -Adding an optional parameter `IF EXISTS` to the command makes it idempotent and ensures that no exception is thrown. -Nothing happens should the user not exist. +Appending an optional parameter `IF EXISTS` to the command makes it idempotent and ensures that no exception is thrown and nothing happens should the user not exist. [source, cypher, role=noplay] ---- @@ -787,8 +696,7 @@ When a user executes this command it will change their password as well as set t [source, cypher, role=noplay] ---- -ALTER CURRENT USER -SET PASSWORD FROM 'abc123' TO '123xyz' +ALTER CURRENT USER SET PASSWORD FROM 'abc123' TO '123xyz' ---- [NOTE] @@ -800,7 +708,7 @@ This command works only for a logged-in user and cannot be run with auth disable [[access-control-drop-users]] == Delete users -Users can be deleted with `DROP USER`. +Users can be deleted using `DROP USER`. [source, cypher, role=noplay] ---- @@ -809,7 +717,7 @@ DROP USER bob Deleting a user will not automatically terminate associated connections, sessions, transactions, or queries. -However, when a user has been deleted, it will no longer appear on the list provided by `SHOW USERS`: +When a user has been deleted, it will no longer appear on the list provided by `SHOW USERS`: [source, cypher, role=noplay] ---- @@ -819,7 +727,11 @@ SHOW USERS .Result [options="header,footer", width="100%", cols="2m,3m,3m,2m,2m"] |=== -|user |roles |passwordChangeRequired |suspended |home +|user +|roles +|passwordChangeRequired +|suspended +|home |"neo4j" |["admin","PUBLIC"] @@ -828,5 +740,4 @@ SHOW USERS | 5+a|Rows: 1 - |=== diff --git a/modules/ROOT/pages/access-control/privileges-reads.adoc b/modules/ROOT/pages/access-control/privileges-reads.adoc index 9cf03287e..ac2343eb8 100644 --- a/modules/ROOT/pages/access-control/privileges-reads.adoc +++ b/modules/ROOT/pages/access-control/privileges-reads.adoc @@ -1,19 +1,13 @@ -:description: How to use Cypher to manage read privileges on graphs. - [role=enterprise-edition] [[access-control-privileges-reads]] = Read privileges - -[abstract] --- -This section explains how to use Cypher to manage read privileges on graphs. --- +:description: This section explains how to use Cypher to manage read privileges on graphs. There are three separate read privileges: -* xref::access-control/privileges-reads.adoc#access-control-privileges-reads-traverse[`TRAVERSE`] - enables the specified entities to be found. -* xref::access-control/privileges-reads.adoc#access-control-privileges-reads-read[`READ`] - enables the specified properties of the found entities to be read. -* xref::access-control/privileges-reads.adoc#access-control-privileges-reads-match[`MATCH`] - combines both `TRAVERSE` and `READ`, enabling an entity to be found and its properties read. +* xref:access-control/privileges-reads.adoc#access-control-privileges-reads-traverse[`TRAVERSE`] - enables the specified entities to be found. +* xref:access-control/privileges-reads.adoc#access-control-privileges-reads-read[`READ`] - enables the specified properties on the found entities to be read. +* xref:access-control/privileges-reads.adoc#access-control-privileges-reads-match[`MATCH`] - combines both `TRAVERSE` and `READ`, enabling an entity to be found and its properties read. [[access-control-privileges-reads-traverse]] @@ -21,19 +15,19 @@ There are three separate read privileges: Users can be granted the right to find nodes and relationships using the `GRANT TRAVERSE` privilege. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- GRANT TRAVERSE - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - [ - ELEMENT[S] { * | label-or-rel-type[, ...] } - | NODE[S] { * | label[, ...] } - | RELATIONSHIP[S] { * | rel-type[, ...] } - ] - TO role[, ...] + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + [ + ELEMENT[S] { * | label-or-rel-type[, ...] } + | NODE[S] { * | label[, ...] } + | RELATIONSHIP[S] { * | rel-type[, ...] } + ] + TO role[, ...] ---- -For example, we can enable the user `jake`, who has the role 'regularUsers' to find all nodes with the label `Post`: +For example, we can enable the user `jake`, who has role 'regularUsers' to find all nodes with the label `Post`. [source, cypher, role=noplay] ---- @@ -42,19 +36,19 @@ GRANT TRAVERSE ON GRAPH neo4j NODES Post TO regularUsers The `TRAVERSE` privilege can also be denied. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- DENY TRAVERSE - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - [ - ELEMENT[S] { * | label-or-rel-type[, ...] } - | NODE[S] { * | label[, ...] } - | RELATIONSHIP[S] { * | rel-type[, ...] } - ] - TO role[, ...] + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + [ + ELEMENT[S] { * | label-or-rel-type[, ...] } + | NODE[S] { * | label[, ...] } + | RELATIONSHIP[S] { * | rel-type[, ...] } + ] + TO role[, ...] ---- -For example, we can disable the user `jake`, who has the role 'regularUsers' from finding all nodes with the label `Payments`: +For example, we can disable the user `jake`, who has role 'regularUsers' from finding all nodes with the label `Payments`. [source, cypher, role=noplay] ---- @@ -68,20 +62,21 @@ DENY TRAVERSE ON HOME GRAPH NODES Payments TO regularUsers Users can be granted the right to do property reads on nodes and relationships using the `GRANT READ` privilege. It is very important to note that users can only read properties on entities that they are enabled to find in the first place. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- -GRANT READ "{" { * | property[, ...] } "}" - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - [ - ELEMENT[S] { * | label-or-rel-type[, ...] } - | NODE[S] { * | label[, ...] } - | RELATIONSHIP[S] { * | rel-type[, ...] } - ] - TO role[, ...] +GRANT READ + "{" { * | property[, ...] } "}" + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + [ + ELEMENT[S] { * | label-or-rel-type[, ...] } + | NODE[S] { * | label[, ...] } + | RELATIONSHIP[S] { * | rel-type[, ...] } + ] + TO role[, ...] ---- -For example, we can enable the user `jake`, who has the role 'regularUsers' to read all properties on nodes with the label `Post`. -The `+*+` implies that the ability to read all properties also extends to properties that might be added in the future. +For example, we can enable the user `jake`, who has role 'regularUsers' to read all properties on nodes with the label `Post`. +The `*` implies that the ability to read all properties also extends to properties that might be added in the future. [source, cypher, role=noplay] ---- @@ -96,20 +91,21 @@ For example, if there is also a `DENY TRAVERSE` present on the same entity as a The `READ` privilege can also be denied. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- -DENY READ "{" { * | property[, ...] } "}" - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - [ - ELEMENT[S] { * | label-or-rel-type[, ...] } - | NODE[S] { * | label[, ...] } - | RELATIONSHIP[S] { * | rel-type[, ...] } - ] - TO role[, ...] +DENY READ + "{" { * | property[, ...] } "}" + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + [ + ELEMENT[S] { * | label-or-rel-type[, ...] } + | NODE[S] { * | label[, ...] } + | RELATIONSHIP[S] { * | rel-type[, ...] } + ] + TO role[, ...] ---- -Although we just granted the user `jake` the right to read all properties, we may want to hide the `secret` property. -The following example shows how to do that: +Although we just granted the user 'jake' the right to read all properties, we may want to hide the `secret` property. +The following example shows how to do that. [source, cypher, role=noplay] ---- @@ -120,22 +116,23 @@ DENY READ { secret } ON GRAPH neo4j NODES Post TO regularUsers [[access-control-privileges-reads-match]] == The `MATCH` privilege -Users can be granted the right to find and do property reads on nodes and relationships using the `GRANT MATCH` privilege. +Users can be granted the right to find and do property reads on nodes and relationships using the `GRANT MATCH` privilege This is semantically the same as having both `TRAVERSE` and `READ` privileges. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- -GRANT MATCH "{" { * | property[, ...] } "}" - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - [ - ELEMENT[S] { * | label-or-rel-type[, ...] } - | NODE[S] { * | label[, ...] } - | RELATIONSHIP[S] { * | rel-type[, ...] } - ] - TO role[, ...] +GRANT MATCH + "{" { * | property[, ...] } "}" + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + [ + ELEMENT[S] { * | label-or-rel-type[, ...] } + | NODE[S] { * | label[, ...] } + | RELATIONSHIP[S] { * | rel-type[, ...] } + ] + TO role[, ...] ---- -For example if you want to grant the ability to read the properties `language` and `length` for nodes with the label `Message`, as well as the ability to find these nodes to the role `regularUsers`, you can use the following `GRANT MATCH` query: +For example if you want to grant the ability to read the properties `language` and `length` for nodes with the label `Message`, as well as the ability to find these nodes, to a role `regularUsers` you can use the following `GRANT MATCH` query. [source, cypher, role=noplay] ---- @@ -144,25 +141,26 @@ GRANT MATCH { language, length } ON GRAPH neo4j NODES Message TO regularUsers Like all other privileges, the `MATCH` privilege can also be denied. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- -DENY MATCH "{" { * | property[, ...] } "}" - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - [ - ELEMENT[S] { * | label-or-rel-type[, ...] } - | NODE[S] { * | label[, ...] } - | RELATIONSHIP[S] { * | rel-type[, ...] } - ] - TO role[, ...] +DENY MATCH + "{" { * | property[, ...] } "}" + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + [ + ELEMENT[S] { * | label-or-rel-type[, ...] } + | NODE[S] { * | label[, ...] } + | RELATIONSHIP[S] { * | rel-type[, ...] } + ] + TO role[, ...] ---- -Please note that the effect of denying a `MATCH` privilege depends on whether concrete property keys are specified or are `+*+`. -If you specify concrete property keys, then `DENY MATCH` will only deny reading those properties. +Please note that the effect of denying a `MATCH` privilege depends on whether concrete property keys are specified or a `+*+`. +If you specify concrete property keys then `DENY MATCH` will only deny reading those properties. Finding the elements to traverse would still be enabled. -If you specify `+*+` instead, then both traversal of the element and all property reads will be disabled. +If you specify `+*+` instead then both traversal of the element and all property reads will be disabled. The following queries will show examples for this. -Denying to read the property `content` on nodes with the label `Message` for the role `regularUsers` would look like the following query. +Denying to read the property ´content´ on nodes with the label `Message` for the role `regularUsers` would look like the following query. Although not being able to read this specific property, nodes with that label can still be traversed (and, depending on other grants, other properties on it could still be read). [source, cypher, role=noplay] @@ -170,7 +168,7 @@ Although not being able to read this specific property, nodes with that label ca DENY MATCH { content } ON GRAPH neo4j NODES Message TO regularUsers ---- -The following query exemplifies how it would look if you wanted to deny both reading all properties and traversing nodes labeled with `Account`: +The following query exemplifies how it would look like if you want to deny both reading all properties and traversing nodes labeled with `Account`. [source, cypher, role=noplay] ---- diff --git a/modules/ROOT/pages/access-control/privileges-writes.adoc b/modules/ROOT/pages/access-control/privileges-writes.adoc index 8b1b5adfd..9101f8f64 100644 --- a/modules/ROOT/pages/access-control/privileges-writes.adoc +++ b/modules/ROOT/pages/access-control/privileges-writes.adoc @@ -1,69 +1,61 @@ -:description: How to use Cypher to manage write privileges on graphs. - [role=enterprise-edition] [[access-control-privileges-writes]] = Write privileges - -[abstract] --- -This section explains how to use Cypher to manage write privileges on graphs. --- +:description: This section explains how to use Cypher to manage write privileges on graphs. Write privileges are defined for different parts of the graph: -* xref::access-control/privileges-writes.adoc#access-control-privileges-writes-create[`CREATE`] - allows creating nodes and relationships. -* xref::access-control/privileges-writes.adoc#access-control-privileges-writes-delete[`DELETE`] - allows deleting nodes and relationships. -* xref::access-control/privileges-writes.adoc#access-control-privileges-writes-set-label[`SET LABEL`] - allows setting the specified node labels using the `SET` clause. -* xref::access-control/privileges-writes.adoc#access-control-privileges-writes-remove-label[`REMOVE LABEL`] - allows removing the specified node labels using the `REMOVE` clause. -* xref::access-control/privileges-writes.adoc#access-control-privileges-writes-set-property[`SET PROPERTY`] - allows setting properties on nodes and relationships. +* xref:access-control/privileges-writes.adoc#access-control-privileges-writes-create[`CREATE`] - allows creating nodes and relationships. +* xref:access-control/privileges-writes.adoc#access-control-privileges-writes-delete[`DELETE`] - allows deleting nodes and relationships. +* xref:access-control/privileges-writes.adoc#access-control-privileges-writes-set-label[`SET LABEL`] - allows setting the specified node labels using the `SET` clause. +* xref:access-control/privileges-writes.adoc#access-control-privileges-writes-remove-label[`REMOVE LABEL`] - allows removing the specified node labels using the `REMOVE` clause. +* xref:access-control/privileges-writes.adoc#access-control-privileges-writes-set-property[`SET PROPERTY`] - allows setting properties on nodes and relationships. There are also compound privileges which combine the above specific privileges: -* xref::access-control/privileges-writes.adoc#access-control-privileges-writes-merge[`MERGE`] - allows `MATCH`, `CREATE` and `SET PROPERTY` to apply the `MERGE` command. -* xref::access-control/privileges-writes.adoc#access-control-privileges-writes-write[`WRITE`] - allows all `WRITE` operations on an entire graph. -* xref::access-control/privileges-writes.adoc#access-control-privileges-writes-all[`ALL GRAPH PRIVILEGES`] - allows all `READ` and `WRITE` operations on an entire graph. +* xref:access-control/privileges-writes.adoc#access-control-privileges-writes-merge[`MERGE`] - allows match, create and set property to permit the `MERGE` command. +* xref:access-control/privileges-writes.adoc#access-control-privileges-writes-write[`WRITE`] - allows all write operations on an entire graph. +* xref:access-control/privileges-writes.adoc#access-control-privileges-writes-all[`ALL GRAPH PRIVILEGES`] - allows all read and write operation on an entire graph. [[access-control-privileges-writes-create]] == The `CREATE` privilege -The `CREATE` privilege allows a user to create new node and relationship elements on a graph. -See the Cypher xref::clauses/create.adoc[CREATE] clause. +The `CREATE` privilege allows a user to create new node and relationship elements in a graph. +See the Cypher xref:clauses/create.adoc[CREATE] clause. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- -GRANT CREATE - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } +GRANT CREATE ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} [ - ELEMENT[S] { * | label-or-rel-type[, ...] } - | NODE[S] { * | label[, ...] } - | RELATIONSHIP[S] { * | rel-type[, ...] } + ELEMENT[S] { * | label-or-rel-type[, ...] } + | NODE[S] { * | label[, ...] } + | RELATIONSHIP[S] { * | rel-type[, ...] } ] - TO role[, ...] + TO role[, ...] ---- -For example, to grant the role `regularUsers` the ability to `CREATE` elements on the graph `neo4j`, use: +For example, granting the ability to create elements on the graph `neo4j` to the role `regularUsers` would be achieved using: [source, cypher, role=noplay] ---- GRANT CREATE ON GRAPH neo4j ELEMENTS * TO regularUsers ---- -The `CREATE` privilege can also be denied: +The `CREATE` privilege can also be denied. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- -DENY CREATE - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } +DENY CREATE ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} [ - ELEMENT[S] { * | label-or-rel-type[, ...] } - | NODE[S] { * | label[, ...] } - | RELATIONSHIP[S] { * | rel-type[, ...] } + ELEMENT[S] { * | label-or-rel-type[, ...] } + | NODE[S] { * | label[, ...] } + | RELATIONSHIP[S] { * | rel-type[, ...] } ] - TO role[, ...] + TO role[, ...] ---- -For example, to deny the role `regularUsers` the ability to `CREATE` nodes with the label `foo` on all graphs, use: +For example, denying the ability to create nodes with the label `foo` on all graphs to the role `regularUsers` would be achieved using: [source, cypher, role=noplay] ---- @@ -72,51 +64,49 @@ DENY CREATE ON GRAPH * NODES foo TO regularUsers [NOTE] ==== -If the user attempts to create nodes with a label that does not already exist on the database, then the user must also possess the xref::access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW LABEL] privilege. -The same applies to new relationships: the xref::access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW RELATIONSHIP TYPE] privilege is required. +If the user attempts to create nodes with a label that does not already exist in the database, then the user must also possess the xref:access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW LABEL] privilege. +The same applies to new relationships - the xref:access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW RELATIONSHIP TYPE] privilege is required. ==== [[access-control-privileges-writes-delete]] == The `DELETE` privilege -The `DELETE` privilege allows a user to delete node and relationship elements on a graph. -See the Cypher xref::clauses/delete.adoc[DELETE] clause. +The `DELETE` privilege allows a user to delete node and relationship elements in a graph. +See the Cypher xref:clauses/delete.adoc[DELETE] clause. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- -GRANT DELETE - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } +GRANT DELETE ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} [ - ELEMENT[S] { * | label-or-rel-type[, ...] } - | NODE[S] { * | label[, ...] } - | RELATIONSHIP[S] { * | rel-type[, ...] } + ELEMENT[S] { * | label-or-rel-type[, ...] } + | NODE[S] { * | label[, ...] } + | RELATIONSHIP[S] { * | rel-type[, ...] } ] - TO role[, ...] + TO role[, ...] ---- -For example, to grant the role `regularUsers` the ability to `DELETE` elements on the graph `neo4j`, use: +For example, granting the ability to delete elements on the graph `neo4j` to the role `regularUsers` would be achieved using: [source, cypher, role=noplay] ---- GRANT DELETE ON GRAPH neo4j ELEMENTS * TO regularUsers ---- -The `DELETE` privilege can also be denied: +The `DELETE` privilege can also be denied. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- -DENY DELETE - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } +DENY DELETE ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} [ - ELEMENT[S] { * | label-or-rel-type[, ...] } - | NODE[S] { * | label[, ...] } - | RELATIONSHIP[S] { * | rel-type[, ...] } + ELEMENT[S] { * | label-or-rel-type[, ...] } + | NODE[S] { * | label[, ...] } + | RELATIONSHIP[S] { * | rel-type[, ...] } ] - TO role[, ...] + TO role[, ...] ---- -For example, to deny the role `regularUsers` the ability to `DELETE` relationships with the relationship type `bar` on all graphs, use: +For example, denying the ability to delete relationships with the relationship type `bar` on all graphs to the role `regularUsers` would be achieved using: [source, cypher, role=noplay] ---- @@ -133,16 +123,16 @@ See link:{neo4j-docs-base-uri}/operations-manual/{page-version}/authentication-a [[access-control-privileges-writes-set-label]] == The `SET LABEL` privilege -The `SET LABEL` privilege allows you to set labels on a node using the xref::clauses/set.adoc#set-set-a-label-on-a-node[SET clause]: +The `SET LABEL` privilege allows you to set labels on a node using the xref:clauses/set.adoc#set-set-a-label-on-a-node[SET clause]. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- GRANT SET LABEL { * | label[, ...] } - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - TO role[, ...] + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + TO role[, ...] ---- -For example, to grant the role `regularUsers` the ability to `SET` any label on nodes of the graph `neo4j`, use: +For example, granting the ability to set any label on nodes of the graph `neo4j` to the role `regularUsers` would be achieved using: [source, cypher, role=noplay] ---- @@ -151,19 +141,19 @@ GRANT SET LABEL * ON GRAPH neo4j TO regularUsers [NOTE] ==== -Unlike many of the other `READ` and `WRITE` privileges, it is not possible to restrict the `SET LABEL` privilege to specific +ELEMENTS+, +NODES+ or +RELATIONSHIPS+. +Unlike many of the other read and write privileges, it is not possible to restrict the `SET LABEL` privilege to specific ELEMENTS, NODES or RELATIONSHIPS. ==== -The `SET LABEL` privilege can also be denied: +The `SET LABEL` privilege can also be denied. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- DENY SET LABEL { * | label[, ...] } - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - TO role[, ...] + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + TO role[, ...] ---- -For example, to deny the role `regularUsers` the ability to `SET` the label `foo` on nodes of all graphs, use: +For example, denying the ability to set the label `foo` on nodes of all graphs to the role `regularUsers` would be achieved using: [source, cypher, role=noplay] ---- @@ -172,23 +162,23 @@ DENY SET LABEL foo ON GRAPH * TO regularUsers [NOTE] ==== -If no instances of this label exist on the database, then the xref::access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW LABEL] privilege is also required. +If no instances of this label exist in the database, then the xref:access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW LABEL] privilege is also required. ==== [[access-control-privileges-writes-remove-label]] == The `REMOVE LABEL` privilege -The `REMOVE LABEL` privilege allows you to remove labels from a node by using the xref::clauses/remove.adoc#remove-remove-a-label-from-a-node[REMOVE clause]: +The `REMOVE LABEL` privilege allows you to remove labels from a node using the xref:clauses/remove.adoc#remove-remove-a-label-from-a-node[REMOVE clause]. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- GRANT REMOVE LABEL { * | label[, ...] } - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - TO role[, ...] + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + TO role[, ...] ---- -For example, to grant the role `regularUsers` the ability to `REMOVE` any label from nodes of the graph `neo4j`, use: +For example, granting the ability to remove any label from nodes of the graph `neo4j` to the role `regularUsers` would be achieved using: [source, cypher, role=noplay] ---- @@ -197,19 +187,19 @@ GRANT REMOVE LABEL * ON GRAPH neo4j TO regularUsers [NOTE] ==== -Unlike many of the other `READ` and `WRITE` privileges, it is not possible to restrict the `REMOVE LABEL` privilege to specific +ELEMENTS+, +NODES+ or +RELATIONSHIPS+. +Unlike many of the other read and write privileges, it is not possible to restrict the `REMOVE LABEL` privilege to specific ELEMENTS, NODES or RELATIONSHIPS. ==== -The `REMOVE LABEL` privilege can also be denied: +The `REMOVE LABEL` privilege can also be denied. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- DENY REMOVE LABEL { * | label[, ...] } - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - TO role[, ...] + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + TO role[, ...] ---- -For example, denying the role `regularUsers` the ability to remove the label `foo` from nodes of all graphs, use: +For example, denying the ability to remove the label `foo` from nodes of all graphs to the role `regularUsers` would be achieved using: [source, cypher, role=noplay] ---- @@ -220,42 +210,42 @@ DENY REMOVE LABEL foo ON GRAPH * TO regularUsers [[access-control-privileges-writes-set-property]] == The `SET PROPERTY` privilege -The `SET PROPERTY` privilege allows a user to set a property on a node or relationship element in a graph by using the xref::clauses/set.adoc#set-set-a-property[SET clause]: +The `SET PROPERTY` privilege allows a user to set a property on a node or relationship element in a graph using the xref:clauses/set.adoc#set-set-a-property[SET clause]. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- GRANT SET PROPERTY "{" { * | property[, ...] } "}" - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - [ - ELEMENT[S] { * | label-or-rel-type[, ...] } - | NODE[S] { * | label[, ...] } - | RELATIONSHIP[S] { * | rel-type[, ...] } - ] - TO role[, ...] + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + [ + ELEMENT[S] { * | label-or-rel-type[, ...] } + | NODE[S] { * | label[, ...] } + | RELATIONSHIP[S] { * | rel-type[, ...] } + ] + TO role[, ...] ---- -For example, to grant the role `regularUsers` the ability to `SET` any property on all elements of the graph `neo4j`, use: +For example, granting the ability to set any property on all elements of the graph `neo4j` to the role `regularUsers` would be achieved using: [source, cypher, role=noplay] ---- GRANT SET PROPERTY {*} ON HOME GRAPH ELEMENTS * TO regularUsers ---- -The `SET PROPERTY` privilege can also be denied: +The `SET PROPERTY` privilege can also be denied. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- DENY SET PROPERTY "{" { * | property[, ...] } "}" - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - [ - ELEMENT[S] { * | label-or-rel-type[, ...] } - | NODE[S] { * | label[, ...] } - | RELATIONSHIP[S] { * | rel-type[, ...] } - ] - TO role[, ...] + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + [ + ELEMENT[S] { * | label-or-rel-type[, ...] } + | NODE[S] { * | label[, ...] } + | RELATIONSHIP[S] { * | rel-type[, ...] } + ] + TO role[, ...] ---- -For example, to deny the role `regularUsers` the ability to `SET` the property `foo` on nodes with the label `bar` on all graphs, use: +For example, denying the ability to set the property `foo` on nodes with the label `bar` on all graphs to the role `regularUsers` would be achieved using: [source, cypher, role=noplay] ---- @@ -264,7 +254,7 @@ DENY SET PROPERTY { foo } ON GRAPH * NODES bar TO regularUsers [NOTE] ==== -If the user attempts to set a property with a property name that does not already exist on the database, the user must also possess the xref::access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW PROPERTY NAME] privilege. +If the users attempts to set a property with a property name that does not already exist in the database the user must also possess the xref:access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW PROPERTY NAME] privilege. ==== @@ -272,21 +262,21 @@ If the user attempts to set a property with a property name that does not alread == The `MERGE` privilege The `MERGE` privilege is a compound privilege that combines `TRAVERSE` and `READ` (i.e. `MATCH`) with `CREATE` and `SET PROPERTY`. -This is intended to enable the use of xref::clauses/merge.adoc[the MERGE command], but it is also applicable to all reads and writes that require these privileges. +This is intended to permit use of xref:clauses/merge.adoc[the MERGE command] but is applicable to all reads and writes that require these privileges. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- GRANT MERGE "{" { * | property[, ...] } "}" - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - [ - ELEMENT[S] { * | label-or-rel-type[, ...] } - | NODE[S] { * | label[, ...] } - | RELATIONSHIP[S] { * | rel-type[, ...] } - ] - TO role[, ...] + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + [ + ELEMENT[S] { * | label-or-rel-type[, ...] } + | NODE[S] { * | label[, ...] } + | RELATIONSHIP[S] { * | rel-type[, ...] } + ] + TO role[, ...] ---- -For example, to grant the role `regularUsers` the ability to `MERGE` on all elements of the graph `neo4j`, use: +For example, granting `MERGE` on all elements of the graph `neo4j` to the role `regularUsers` would be achieved using: [source, cypher, role=noplay] ---- @@ -294,31 +284,31 @@ GRANT MERGE {*} ON GRAPH neo4j ELEMENTS * TO regularUsers ---- It is not possible to deny the `MERGE` privilege. -If you wish to prevent a user from creating elements and setting properties: use xref::access-control/privileges-writes.adoc#access-control-privileges-writes-create[DENY CREATE] or xref::access-control/privileges-writes.adoc#access-control-privileges-writes-set-property[DENY SET PROPERTY]. +If it is desirable to prevent a users from creating elements and setting properties, use xref:access-control/privileges-writes.adoc#access-control-privileges-writes-create[DENY CREATE] or xref:access-control/privileges-writes.adoc#access-control-privileges-writes-set-property[DENY SET PROPERTY]. [NOTE] ==== -If the user attempts to create nodes with a label that does not already exist on the database, the user must also possess the -xref::access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW LABEL] privilege. +If the users attempts to create nodes with a label that does not already exist in the database the user must also possess the +xref:access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW LABEL] privilege. The same applies to new relationships and properties - the -xref::access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW RELATIONSHIP TYPE] or -xref::access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW PROPERTY NAME] privileges are required. +xref:access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW RELATIONSHIP TYPE] or +xref:access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW PROPERTY NAME] privileges are required. ==== [[access-control-privileges-writes-write]] == The `WRITE` privilege -The `WRITE` privilege allows the user to execute any `WRITE` command on a graph. +The `WRITE` privilege allows the user to execute any write command on a graph. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- GRANT WRITE - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - TO role[, ...] + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + TO role[, ...] ---- -For example, to grant the role `regularUsers` the ability to `WRITE` on the graph `neo4j`, use: +For example, granting the ability to write on the graph `neo4j` to the role `regularUsers` would be achieved using: [source, cypher, role=noplay] ---- @@ -327,20 +317,20 @@ GRANT WRITE ON GRAPH neo4j TO regularUsers [NOTE] ==== -Unlike the more specific `WRITE` commands, it is not possible to restrict `WRITE` privileges to specific +ELEMENTS+, +NODES+ or +RELATIONSHIPS+. -If you wish to prevent a user from writing to a subset of database objects, a `GRANT WRITE` can be combined with more specific `DENY` commands to target these elements. +Unlike the more specific write commands, it is not possible to restrict `WRITE` privileges to specific ELEMENTS, NODES or RELATIONSHIPS. +If it is desirable to prevent a user from writing to a subset of database objects, a `GRANT WRITE` can be combined with more specific `DENY` commands to target these elements. ==== -The `WRITE` privilege can also be denied: +The `WRITE` privilege can also be denied. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- DENY WRITE - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - TO role[, ...] + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + TO role[, ...] ---- -For example, to deny the role `regularUsers` the ability to `WRITE` on the graph `neo4j`, use: +For example, denying the ability to write on the graph `neo4j` to the role `regularUsers` would be achieved using: [source, cypher, role=noplay] ---- @@ -357,16 +347,16 @@ See link:{neo4j-docs-base-uri}/operations-manual/{page-version}/authentication-a [[access-control-privileges-writes-all]] == The `ALL GRAPH PRIVILEGES` privilege -The `ALL GRAPH PRIVILEGES` privilege allows the user to execute any command on a graph: +The `ALL GRAPH PRIVILEGES` privilege allows the user to execute any command on a graph. -source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- GRANT ALL [ [ GRAPH ] PRIVILEGES ] - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - TO role[, ...] + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + TO role[, ...] ---- -For example, to grant the role `regularUsers` `ALL GRAPH PRIVILEGES` on the graph `neo4j`, use: +For example, granting all graph privileges on the graph `neo4j` to the role `regularUsers` would be achieved using: [source, cypher, role=noplay] ---- @@ -375,20 +365,27 @@ GRANT ALL GRAPH PRIVILEGES ON GRAPH neo4j TO regularUsers [NOTE] ==== -Unlike the more specific `READ` and `WRITE` commands, it is not possible to restrict `ALL GRAPH PRIVILEGES` to specific +ELEMENTS, +NODES+ or +RELATIONSHIPS+. -If you wish to prevent a user from reading or writing to a subset of database objects, a `GRANT ALL GRAPH PRIVILEGES` can be combined with more specific `DENY` commands to target these elements. +Unlike the more specific read and write commands, it is not possible to restrict `ALL GRAPH PRIVILEGES` privileges to specific ELEMENTS, +NODES or RELATIONSHIPS. +If it is desirable to prevent a user from reading or writing to a subset of database objects, a `GRANT ALL GRAPH PRIVILEGES` can be combined with more specific `DENY` commands to target these elements. +==== + +[NOTE] +==== +The `ALL GRAPH PRIVILEGES` privilege does not allow creating new labels, relationship types, or property names. +These are instead managed by the `NAME MANAGEMENT` privileges. ==== -The `ALL GRAPH PRIVILEGES` privilege can also be denied: +The `ALL GRAPH PRIVILEGES` privilege can also be denied. -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- DENY ALL [ [ GRAPH ] PRIVILEGES ] - ON { HOME GRAPH | GRAPH[S] { * | name[, ...] } } - TO role[, ...] + ON {HOME GRAPH | GRAPH[S] { * | name[, ...] }} + TO role[, ...] ---- -For example, to deny the role `regularUsers` all graph privileges on the graph `neo4j`, use: +For example, denying all graph privileges on the graph `neo4j` to the role `regularUsers` would be achieved using: [source, cypher, role=noplay] ---- diff --git a/modules/ROOT/pages/aliases.adoc b/modules/ROOT/pages/aliases.adoc deleted file mode 100644 index 0625e489e..000000000 --- a/modules/ROOT/pages/aliases.adoc +++ /dev/null @@ -1,1378 +0,0 @@ -:description: How to use Cypher to manage database aliases in Neo4j. - -[[alias-management]] -= Database alias management - -[abstract] --- -This section explains how to use Cypher to manage database aliases in Neo4j. --- - -There are two kinds of aliases, local database aliases and remote database aliases. -A local database alias can only target a database within the same DBMS. -A remote alias may target a database from another Neo4j DBMS. -When a query is run against an alias, it will be redirected to the target database. -The home database for users can be set to an alias, which will be resolved to the target database on use. - -A local alias can be used in all other Cypher commands in place of the target database. -Please note that the local alias will be resolved while executing the command. -Privileges are defined on the database, and not the local alias. - -A remote alias can be used for connecting to a database of a remote Neo4j DBMS, use clauses, setting a user's home database and defining the access privileges to the remote database. -Remote aliases requires configuration to safely connect to the remote target, which is described in link:{neo4j-docs-base-uri}/operations-manual/{page-version}/manage-databases/remote-alias[Connecting remote databases]. -It is not possible to impersonate a user on the remote database or to execute an administration command on the remote database via a remote alias. - -Aliases can be created and managed using a set of Cypher administration commands executed against the `system` database. -The required privileges are described xref::access-control/dbms-administration.adoc#access-control-dbms-administration-alias-management[here]. -When connected to the DBMS over Bolt, administration commands are automatically routed to the `system` database. - -The syntax of the alias management commands is as follows: - -.Alias management command syntax -[options="header", width="100%", cols="1m,5a"] -|=== -| Command | Syntax - -| SHOW ALIASES -| -Lists both local and remote database aliases. - -[source, syntax, role="noheader"] ------ -SHOW ALIASES FOR DATABASE[S] -[WHERE expression] ------ - -[source, syntax, role="noheader"] ------ -SHOW ALIASES FOR DATABASE[S] -YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n] -[WHERE expression] -[RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ------ - - -| CREATE ALIAS -| -Create a local database alias. - -[source, syntax, role="noheader"] ------ -CREATE ALIAS name [IF NOT EXISTS] FOR DATABASE targetName ------ - -[source, syntax, role="noheader"] ------ -CREATE OR REPLACE ALIAS name FOR DATABASE targetName ------ - -| +CREATE ALIAS ... AT+ -| -Create a remote database alias. - -[source, syntax, role="noheader"] ------ -CREATE ALIAS name [IF NOT EXISTS] FOR DATABASE targetName -AT 'url' USER username PASSSWORD 'password' -[DRIVER "{" setting: value[, ...] "}"] ------ - -[source, syntax, role="noheader"] ------ -CREATE OR REPLACE ALIAS name FOR DATABASE targetName -AT 'url' USER username PASSSWORD 'password' -[DRIVER "{" setting: value[, ...] "}"] ------ - -| ALTER ALIAS -| -Alter a local database alias. - -[source, syntax, role="noheader"] ------ -ALTER ALIAS name [IF EXISTS] SET DATABASE TARGET targetName ------ - -| +ALTER ALIAS ...+ -| -Alter a remote database alias. - -[source, syntax, role="noheader"] ------ -ALTER ALIAS name [IF EXISTS] SET DATABASE -[TARGET targetName AT 'url'] -[USER username] -[PASSWORD 'password'] -[DRIVER "{" setting: value[, ...] "}"] ------ - -| DROP ALIAS -| -Drop either a local or remote database alias. - -[source, syntax, role="noheader"] ------ -DROP ALIAS name [IF EXISTS] FOR DATABASE ------ - -|=== - - -This is the list of the allowed driver settings for remote aliases. - -[[remote-alias-driver-settings]] -.ssl_enforced -[width="100%", cols="1s, 4a"] -|=== -| Description -| -SSL for remote alias drivers is configured through the target url scheme. -If `ssl_enforced` is set to true, a secure url scheme is enforced. -This will be validated when the command is executed. - -| Valid values -| Boolean - -| Default value -| true - -|=== - -.connection_timeout -[width="100%", cols="1s, 4a"] -|=== - -| Description -| -Socket connection timeout. -A timeout of zero is treated as an infinite timeout and will be bound by the timeout configured on the operating system level. - -| Valid values -| Duration - -| Default value -| link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_dbms.routing.driver.connection.connect_timeout[dbms.routing.driver.connection.connect_timeout] - -|=== - -.connection_max_lifetime -[width="100%", cols="1s, 4a"] -|=== - -| Description -| -Pooled connections older than this threshold will be closed and removed from the pool. -Setting this option to a low value will cause a high connection churn and might result in a performance hit. -It is recommended to set maximum lifetime to a slightly smaller value than the one configured in network equipment (load balancer, proxy, firewall, etc. can also limit maximum connection lifetime). - -| Valid values -| Duration. - -Zero and negative values result in lifetime not being checked. - -| Default value -| link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_dbms.routing.driver.connection.max_lifetime[dbms.routing.driver.connection.max_lifetime] - -|=== - -.connection_pool_acquisition_timeout -[width="100%", cols="1s, 4a"] -|=== -| Description -| -Maximum amount of time spent attempting to acquire a connection from the connection pool. -This timeout only kicks in when all existing connections are being used and no new connections can be created because maximum connection pool size has been reached. -Error is raised when connection can’t be acquired within configured time. - -| Valid values -| Duration. - -Negative values are allowed and result in unlimited acquisition timeout. -Value of `0` is allowed and results in no timeout and immediate failure when connection is unavailable. - -| Default value -| link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_dbms.routing.driver.connection.pool.acquisition_timeout[dbms.routing.driver.connection.pool.acquisition_timeout] - -|=== - -.connection_pool_max_size -[width="100%", cols="1s, 4a"] -|=== - -| Description -| -Maximum total number of connections to be managed by a connection pool. -The limit is enforced for a combination of a host and user. - -| Valid values -| Integer. - -Negative values are allowed and result in unlimited pool. -Value of `0` is not allowed. - -| Default value -| link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_dbms.routing.driver.connection.pool.max_size[dbms.routing.driver.connection.pool.max_size] - -|=== - -.logging_level -[width="100%", cols="1s, 4a"] -|=== - -| Description -| Sets level for driver internal logging. - -| Valid values -| org.neo4j.logging.Level. - -One of `DEBUG`, `INFO`, `WARN`, `ERROR`, or `NONE`. - -| Default value -| link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_dbms.routing.driver.logging.level[dbms.routing.driver.logging.level] - -|=== - - -[NOTE] -==== -If transaction modifies an alias, other transactions concurrently executing against that alias may be aborted and rolled back for safety. -This prevents issues such as a transaction executing against multiple target databases for the same alias. -==== - - -[role=enterprise-edition] -[[alias-management-show-alias]] -== Listing database aliases - -Available database aliases can be seen using `SHOW ALIASES FOR DATABASE`. -The required privileges are described xref::access-control/dbms-administration.adoc#access-control-dbms-administration-alias-management[here]. - -`SHOW ALIASES FOR DATABASE` will produce a table of database aliases with the following columns: - -[options="header" cols="2m,4a"] -|=== -| Column | Description - -| name -| The name of the database alias. label:default-output[] - -| database -| The names of the target database. label:default-output[] - -| location -| The location of the database, either `local` or `remote`. label:default-output[] - -| url -| Target location or `null` if the target is local. label:default-output[] - -| user -| User connecting to the remote database or `null` if the target database is local. label:default-output[] - -| driver -| -The driver options for connection to the remote database or `null` if the target database is local or if no driver settings are added. -List of xref::aliases.adoc#remote-alias-driver-settings[driver settings] allowed for remote database aliases. - -|=== - -The detailed information for a particular database alias can be displayed using the command `SHOW ALIASES FOR DATABASE YIELD *`. -When a `YIELD *` clause is provided, the full set of columns is returned. - - -.+SHOW ALIASES FOR DATABASE+ -====== - -A summary of all available databases alias can be displayed using the command `SHOW ALIASES FOR DATABASE`. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" -DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -SHOW ALIASES FOR DATABASE ----- - -.Result -[role="queryresult",options="header,footer",cols="5*+ | ++ -| +"motion pictures"+ | +"movies"+ | +"local"+ | ++ | ++ -| +"movie scripts"+ | +"scripts"+ | +"remote"+ | +"neo4j+s://location:7687"+ | +"alice"+ -5+d|Rows: 3 - -|=== - -====== - - -.+SHOW ALIASES FOR DATABASE+ -====== - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -SHOW ALIASES FOR DATABASE YIELD * ----- - -.Result -[role="queryresult",options="header,footer",cols="6*+ | ++ | ++ -| +"motion pictures"+ | +"movies"+ | +"local"+ | ++ | ++ | ++ -| +"movie scripts"+ | +"scripts"+ | +"remote"+ | +"neo4j+s://location:7687"+ | +"alice"+ | +{connection_pool_max_size -> 10, connection_pool_idle_test -> PT2M, connection_pool_acquisition_timeout -> PT1M, connection_max_lifetime -> PT1H, logging_level -> "INFO", ssl_enforced -> true, connection_timeout -> PT5S}+ -6+d|Rows: 3 - -|=== - -====== - - -.+SHOW ALIASES FOR DATABASE+ -====== - -The number of database aliases can be seen using a `count()` aggregation with `YIELD` and `RETURN`. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -SHOW ALIASES FOR DATABASE YIELD * -RETURN count(*) as count ----- - -.Result -[role="queryresult",options="header,footer",cols="1*+ | +"movies"+ -| +"movie scripts"+ | +"neo4j+s://location:7687"+ | +"scripts"+ -3+d|Rows: 2 -|=== - -====== - - -[role=enterprise-edition] -[[alias-management-create-database-alias]] -== Creating database aliases - -Aliases can be created using `CREATE ALIAS`. - -The required privileges are described xref::access-control/dbms-administration.adoc#access-control-dbms-administration-alias-management[here]. - -This command is optionally idempotent, with the default behavior to fail with an error if the database alias already exists. -Inserting `IF NOT EXISTS` after the alias name ensures that no error is returned and nothing happens should a database alias with that name already exist. -Adding `OR REPLACE` to the command will result in any existing database alias being deleted and a new one created. -`CREATE OR REPLACE ALIAS` will fail if there is an existing database with the same name. - -[NOTE] -==== -The `IF NOT EXISTS` and `OR REPLACE` parts of this command cannot be used together. -==== - -[NOTE] -==== -Alias names are subject to the xref::syntax/naming.adoc[standard Cypher restrictions on valid identifiers]. - -The following naming rules apply: - -* A name is a valid identifier, additionally allowing dots e.g. `main.alias` for local aliases. -* Name length can be up to 65534 characters. -* Names cannot end with dots. -* Names that begin with an underscore or with the prefix `system` are reserved for internal use. -* Non-alphabetic characters, including numbers, symbols and whitespace characters, can be used in names, but must be escaped using backticks. -==== - - -[role=enterprise-edition] -[[database-management-create-local-database-alias]] -=== Creating local database aliases - -Local aliases are created with a target database. - - -.+CREATE ALIAS+ -====== - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -CREATE ALIAS `northwind` FOR DATABASE `northwind-graph-2021` ----- - -[source, result, role="noheader"] ----- -System updates: 1 -Rows: 0 ----- - -====== - - -.+SHOW DATABASE+ -====== - -When a local database alias has been created, it will show up in the aliases column provided by the command `SHOW DATABASES` and in the `SHOW ALIASES FOR DATABASE` command. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -SHOW DATABASE `northwind` ----- - -.Result -[role="queryresult",options="header,footer",cols="10*+ | ++ -5+d|Rows: 1 - -|=== - -====== - - -.+CREATE ALIAS+ -====== - -Adding a local alias with the same name as an existing local or remote alias will do nothing with the `IF NOT EXISTS` clause but fail without it. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -CREATE ALIAS `northwind` IF NOT EXISTS FOR DATABASE `northwind-graph-2020` ----- - -[source, result, role="noheader"] ----- -Rows: 0 ----- - -====== - - -.+CREATE OR REPLACE ALIAS+ -====== - -It is possible to replace an alias. -The old alias may be either local or remote. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -CREATE OR REPLACE ALIAS `northwind` FOR DATABASE `northwind-graph-2020` ----- - -[source, result, role="noheader"] ----- -System updates: 2 -Rows: 0 ----- - -This is equivalent to running: - -.Query -[source, cypher, indent=0] ----- -DROP ALIAS `northwind` IF EXISTS FOR DATABASE -CREATE ALIAS `northwind` FOR DATABASE `northwind-graph-2020` ----- - -====== - - -[role=enterprise-edition] -[[database-management-create-remote-database-alias]] -=== Creating remote database aliases - -Database aliases can also point to remote databases by providing an url and the credentials of a user on the remote Neo4j DBMS. -See link:{neo4j-docs-base-uri}/operations-manual/{page-version}/manage-databases/remote-alias[Connecting remote databases] for the necessary configurations. - -Creating remote aliases also allows `IF NOT EXISTS` and `OR REPLACE` clauses. -Both check for any remote or local database aliases. - - -.+CREATE ALIAS+ -====== - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -CREATE ALIAS `remote-northwind` FOR DATABASE `northwind-graph-2020` -AT "neo4j+s://location:7687" -USER alice -PASSWORD 'example_secret' ----- - -[source, result, role="noheader"] ----- -System updates: 1 -Rows: 0 ----- - -====== - - -.+CREATE ALIAS+ -====== - -It is possible to override the default driver settings per alias, which are used for connecting to the remote database. -The full list of supported driver settings can be seen xref::aliases.adoc#remote-alias-driver-settings[here]. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -CREATE ALIAS `remote-with-driver-settings` FOR DATABASE `northwind-graph-2020` -AT "neo4j+s://location:7687" -USER alice -PASSWORD 'example_secret' -DRIVER { - connection_timeout: duration({minutes: 1}), - connection_pool_max_size: 10 -} ----- - -[source, result, role="noheader"] ----- -System updates: 1 -Rows: 0 ----- - -====== - - -.+SHOW ALIASES FOR DATABASE+ -====== - -When a database alias pointing to a remote database has been created, its details can be shown with the `SHOW ALIASES FOR DATABASE` command. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -SHOW ALIASES FOR DATABASE -WHERE name = 'remote-northwind' ----- - -.Result -[role="queryresult",options="header,footer",cols="5* 10, connection_timeout -> PT1M}+ -6+d|Rows: 1 - -|=== - -====== - - -[role=enterprise-edition] -[[alias-management-alter-database-alias]] -== Altering database aliases - -Aliases can be altered using `ALTER ALIAS` to change its database target, url, user credentials, or driver settings. -The required privileges are described xref::access-control/dbms-administration.adoc#access-control-dbms-administration-alias-management[here]. -Only the clauses used will be altered. - -[NOTE] -==== -Local aliases can not be altered to remote aliases or vice versa. -==== - - -.+ALTER ALIAS+ -====== - -Example of altering a local database alias target. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -ALTER ALIAS `northwind` -SET DATABASE TARGET `northwind-graph-2021` ----- - -[source, result, role="noheader"] ----- -System updates: 1 -Rows: 0 ----- - -====== - - -.+ALTER ALIAS+ -====== - -Example of altering a remote database alias target. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -ALTER ALIAS `remote-northwind` SET DATABASE -TARGET `northwind-graph-2020` AT "neo4j+s://other-location:7687" ----- - -[source, result, role="noheader"] ----- -System updates: 1 -Rows: 0 ----- - -====== - - -.+ALTER ALIAS+ -====== - -Example of altering a remote alias credentials and driver settings. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -ALTER ALIAS `remote-with-driver-settings` SET DATABASE -USER bob -PASSWORD 'new_example_secret' -DRIVER { - connection_timeout: duration({ minutes: 1}), - logging_level: 'debug' -} ----- - -[source, result, role="noheader"] ----- -System updates: 1 -Rows: 0 ----- - -[IMPORTANT] -==== -All driver settings are replaced by the new ones. -In this case, by not repeating the driver setting `connection_pool_max_size` the value will be deleted and fallback to the default value. -==== - -====== - - -.+ALTER ALIAS+ -====== - -Example of altering a remote alias to remove all custom driver settings. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -ALTER ALIAS `movie scripts` SET DATABASE -DRIVER {} ----- - -[source, result, role="noheader"] ----- -System updates: 1 -Rows: 0 ----- - -====== - - -.+SHOW DATABASE+ -====== - -When a local database alias has been altered, it will show up in the aliases column for the target database provided by the command `SHOW DATABASES`. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -SHOW DATABASE `northwind` ----- - -.Result -[role="queryresult",options="header,footer",cols="10*+ | ++ | ++ -| +"remote-northwind"+ | +"northwind-graph-2020"+ | +"remote"+ | +"neo4j+s://other-location:7687"+ | +"alice"+ | +{}+ -| +"remote-with-driver-settings"+ | +"northwind-graph-2020"+ | +"remote"+ | +"neo4j+s://location:7687"+ | +"bob"+ | +{logging_level -> "DEBUG", connection_timeout -> PT1M}+ -6+d|Rows: 4 - -|=== - -====== - - -.+ALTER ALIAS+ -====== - -This command is optionally idempotent, with the default behavior to fail with an error if the alias does not exist. -Appending `IF EXISTS` to the command ensures that no error is returned and nothing happens should the alias not exist. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -ALTER ALIAS `no-alias` IF EXISTS SET DATABASE TARGET `northwind-graph-2021` ----- - -[source, result, role="noheader"] ----- -Rows: 0 ----- - -====== - - -[role=enterprise-edition] -[[alias-management-drop-database-alias]] -== Deleting database aliases - -Both local and remote aliases can be deleted using the `DROP ALIAS` command. -The required privileges are described xref::access-control/dbms-administration.adoc#access-control-dbms-administration-alias-management[here]. - - -.+DROP ALIAS+ -====== - -Drop a local database alias. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -DROP ALIAS `northwind` FOR DATABASE ----- - -[source, result, role="noheader"] ----- -System updates: 1 -Rows: 0 ----- - -====== - - -.+DROP ALIAS+ -====== - -Drop a remote database alias. - -//// -CREATE DATABASE `example-database` -CREATE ALIAS `example-local-alias` FOR DATABASE `example-database` -CREATE ALIAS `example-remote-alias` FOR DATABASE `example-database` -AT "neo4j+s://location:7687" -USER alice -PASSWORD 'example_secret' -DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -DROP ALIAS `remote-northwind` FOR DATABASE ----- - -[source, result, role="noheader"] ----- -System updates: 1 -Rows: 0 ----- - -====== - - -.+SHOW DATABASE+ -====== - -When a database alias has been deleted, it will no longer show up in the aliases column provided by the command `SHOW DATABASES`. - - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -SHOW DATABASE `northwind-graph-2021` ----- - -.Result -[role="queryresult",options="header,footer",cols="10*+ | ++ -| +"motion pictures"+ | +"movies"+ | +"local"+ | ++ | ++ -| +"movie scripts"+ | +"scripts"+ | +"remote"+ | +"neo4j+s://location:7687"+ | +"alice"+ -| +"remote-with-driver-settings"+ | +"northwind-graph-2020"+ | +"remote"+ | +"neo4j+s://location:7687"+ | +"bob"+ -5+d|Rows: 4 - -|=== - -====== - - -.+DROP ALIAS+ -====== - -This command is optionally idempotent, with the default behavior to fail with an error if the alias does not exist. -Inserting `IF EXISTS` after the alias name ensures that no error is returned and nothing happens should the alias not exist. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -CREATE DATABASE `northwind-graph-2020` -CREATE DATABASE `northwind-graph-2021` -CREATE ALIAS `movie scripts` FOR DATABASE `scripts` AT "neo4j+s://location:7687" USER alice PASSWORD "password" DRIVER { - ssl_enforced: true, - connection_timeout: duration({seconds: 5}), - connection_max_lifetime: duration({hours: 1}), - connection_pool_acquisition_timeout: duration({minutes: 1}), - connection_pool_idle_test: duration({minutes: 2}), - connection_pool_max_size: 10, - logging_level: 'info' -} -//// - -.Query -[source, cypher, indent=0] ----- -DROP ALIAS `northwind` IF EXISTS FOR DATABASE ----- - -[source, result, role="noheader"] ----- -Rows: 0 ----- - -====== - diff --git a/modules/ROOT/pages/clauses/call-subquery.adoc b/modules/ROOT/pages/clauses/call-subquery.adoc index 1bf98f7bd..f17c38c43 100644 --- a/modules/ROOT/pages/clauses/call-subquery.adoc +++ b/modules/ROOT/pages/clauses/call-subquery.adoc @@ -1,51 +1,67 @@ -:description: The `CALL {}` clause evaluates a subquery that returns some values. - [[query-call-subquery]] = CALL {} (subquery) +:description: The `CALL {}` clause evaluates a subquery that returns some values. + +* xref:clauses/call-subquery.adoc#subquery-call-introduction[Introduction] +* xref:clauses/call-subquery.adoc#subquery-correlated-importing[Importing variables into subqueries] +* xref:clauses/call-subquery.adoc#subquery-post-union[Post-union processing] +* xref:clauses/call-subquery.adoc#subquery-aggregation[Aggregation and side-effects] +* xref:clauses/call-subquery.adoc#subquery-correlated-aggregation[Aggregation on imported variables] -[abstract] --- -The `CALL {}` clause evaluates a subquery that returns some values. --- +[[subquery-call-introduction]] +== Introduction -`CALL` allows to execute subqueries, i.e. queries inside of other queries. +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]. +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. +A subquery is evaluated for each incoming input row and may produce an arbitrary number of output rows. +Every output row is then combined with the input row to build the result of the subquery. +That means that a 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: +There are restrictions on what queries are allowed as subqueries and how they interact with the enclosing query: +* A subquery must end with a `RETURN` clause. * 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[] - -//// -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) -//// +.Graph +["dot", "CALL {} (subquery)-1.svg", "neoviz", ""] +---- + N0 [ + label = "{Person, Child|age = 20\lname = \'Alice\'\l}" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "CHILD_OF\n" + ] + N0 -> N1 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "FRIEND_OF\n" + ] + N1 [ + label = "{Person|age = 27\lname = \'Bob\'\l}" + ] + N2 [ + label = "{Person, Parent|age = 65\lname = \'Charlie\'\l}" + ] + N3 [ + label = "{Person|age = 30\lname = \'Dora\'\l}" + ] +---- + [[subquery-correlated-importing]] == Importing variables into subqueries @@ -53,8 +69,9 @@ CREATE Variables are imported into a subquery using an importing `WITH` clause. As the subquery is evaluated for each incoming input row, the imported variables get bound to the corresponding values from the input row in each evaluation. + .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [0, 1, 2] AS x CALL { @@ -74,20 +91,45 @@ RETURN x, y 2+d|Rows: 3 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b) + CREATE (a)-[:CHILD_OF]->(c) + +]]> +++++ +endif::nonhtmloutput[] + An importing `WITH` clause must: * Consist only of simple references to outside variables - e.g. `WITH x, y, z`. Aliasing or expressions are not supported in importing `WITH` clauses - e.g. `WITH a AS b` or `WITH a+1 AS b`. * Be the first clause of a subquery (or the second clause, if directly following a `USE` clause). - [[subquery-post-union]] == Post-union processing Subqueries can be used to process the results of a `UNION` query further. This example query finds the youngest and the oldest person in the database and orders them by name. + .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL { MATCH (p:Person) @@ -113,12 +155,45 @@ ORDER BY p.name 2+d|Rows: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b) + CREATE (a)-[:CHILD_OF]->(c) + +]]> +++++ +endif::nonhtmloutput[] + If different parts of a result should be matched differently, with some aggregation over the whole results, subqueries need to be used. This example query finds friends and/or parents for each person. Subsequently the number of friends and parents are counted together. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (p:Person) CALL { @@ -144,95 +219,95 @@ RETURN DISTINCT p.name, count(other) 2+d|Rows: 4 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b) + CREATE (a)-[:CHILD_OF]->(c) -[[subquery-aggregation]] -== Aggregations - -Returning subqueries change the number of results of the query: The result of the `CALL` clause is the combined result of evaluating the subquery for each input row. - -The following example finds the name of each person and the names of their friends: - -.Query -[source, cypher, indent=0] ----- +]]>(other:Person) + RETURN other +UNION + WITH p + OPTIONAL MATCH (p)-[:CHILD_OF]->(other:Parent) + RETURN other } -RETURN p.name, friend ----- +RETURN DISTINCT p.name, count(other) +]]> +++++ +endif::nonhtmloutput[] -.Result -[role="queryresult",options="header,footer",cols="2* +Try this query live +(b) + CREATE (a)-[:CHILD_OF]->(c) -[[subquery-unit]] -== Unit subqueries and side-effects - -Unit subqueries do not return any rows and are therefore used for their side effects. - -This example query creates five clones of each existing person. -As the subquery is a unit subquery, it does not change the number of rows of the enclosing query. - -.Query -[source, cypher, indent=0] ----- +]]> +++++ +endif::nonhtmloutput[] [[subquery-correlated-aggregation]] == Aggregation on imported variables @@ -240,8 +315,9 @@ Labels added: 20 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, indent=0] +[source, cypher] ---- MATCH (p:Person) CALL { @@ -264,191 +340,30 @@ RETURN p.name, youngerPersonsCount 2+d|Rows: 4 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b) + CREATE (a)-[:CHILD_OF]->(c) -[[subquery-call-in-transactions]] -== Subqueries in transactions - -Subqueries can be made to execute in separate, inner transactions, producing intermediate commits. -This can come in handy when doing large write operations, like batch updates, imports, and deletes. -To execute a subquery in separate transactions, you add the modifier `IN TRANSACTIONS` after the subquery. - -The following 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`: - -// neo4j-manual-modeling-antora/cypherManual/build/4.4/antora/modules/ROOT/examples/neo4j-cypher-docs/docs/dev/ql/csv-files/friends.csv - -.friends.csv -[source, csv, role="noheader"] ----- -1,Bill,26 -2,Max,27 -3,Anna,22 -4,Gladys,29 -5,Summer,24 ----- - -.Query -[source, cypher, indent=0] ----- -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* +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/clauses/call.adoc b/modules/ROOT/pages/clauses/call.adoc index c6d1ae164..c8106964e 100644 --- a/modules/ROOT/pages/clauses/call.adoc +++ b/modules/ROOT/pages/clauses/call.adoc @@ -1,12 +1,6 @@ -:description: The `CALL` clause is used to call a procedure deployed in the database. - [[query-call]] = CALL procedure - -[abstract] --- -The `CALL` clause is used to call a procedure deployed in the database. --- +:description: The `CALL` clause is used to call a procedure deployed in the database. [[query-call-introduction]] == Introduction @@ -16,7 +10,9 @@ Procedures are called using the `CALL` clause. [TIP] ==== The `CALL` clause is also used to evaluate a subquery. -For descriptions of the `CALL` clause in this context, refer to xref::clauses/call-subquery.adoc[`CALL {}` (subquery)]. +For descriptions of the `CALL` clause in this context, refer to xref:clauses/call-subquery.adoc[CALL {} (subquery)]. + + ==== Each procedure call needs to specify all required procedure arguments. @@ -31,7 +27,7 @@ All new variables bound by a procedure call are added to the set of variables al It is an error if a procedure call tries to rebind a previously bound variable (i.e., a procedure call cannot shadow a variable that was previously bound in the current scope). In a standalone procedure call, `+YIELD *+` can be used to select all columns. In this case, the name of the output parameters does not need to be known in advance. -For more information on how to determine the input parameters for the `CALL` procedure and the output parameters for the `YIELD` procedure, see xref::clauses/call.adoc#call-view-the-signature-for-a-procedure[View the signature for a procedure]. +For more information on how to determine the input parameters for the `CALL` procedure and the output parameters for the `YIELD` procedure, see xref:clauses/call.adoc#call-view-the-signature-for-a-procedure[View the signature for a procedure]. Inside a larger query, the records returned from a procedure call with an explicit `YIELD` may be further filtered using a `WHERE` sub-clause followed by a predicate (similar to `+WITH ... WHERE ...+`). @@ -42,7 +38,7 @@ In this case, all result fields are yielded as newly-bound variables from the pr Neo4j supports the notion of `VOID` procedures. A `VOID` procedure is a procedure that does not declare any result fields and returns no result records and that has explicitly been declared as `VOID`. Calling a `VOID` procedure may only have a side effect and thus does neither allow nor require the use of `YIELD`. -Calling a `VOID` procedure in the middle of a larger query will simply pass on each input record (i.e., it acts like `+WITH *+` in terms of the record stream). +Calling a `VOID` procedure in the middle of a larger query will simply pass on each input record (i.e., it acts like `WITH *` in terms of the record stream). [NOTE] ==== @@ -51,20 +47,18 @@ For a list of these, see link:{neo4j-docs-base-uri}/operations-manual/{page-vers Users can also develop custom procedures and deploy to the database. See link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/procedures#extending-neo4j-procedures[Java Reference -> User-defined procedures] for details. -==== +==== + [[call-call-a-procedure-using-call]] == Call a procedure using `CALL` This calls the built-in procedure `db.labels`, which lists all labels used in the database. -//// -CREATE (a:User:Administrator {name: 'Adrian'}) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL db.labels() ---- @@ -78,19 +72,32 @@ CALL db.labels() 1+d|Rows: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + Cypher allows the omission of parentheses on procedures of arity-0 (no arguments). [NOTE] ==== Best practice is to use parentheses for procedures. + + ==== -//// -CREATE (a:User:Administrator {name: 'Adrian'}) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL db.labels ---- @@ -104,6 +111,19 @@ CALL db.labels 1+d|Rows: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[call-view-the-signature-for-a-procedure]] == View the signature for a procedure @@ -112,12 +132,9 @@ To `CALL` a procedure, its input parameters need to be known, and to use `YIELD` The built-in procedure `dbms.procedures` returns the name, signature and description for all procedures. The following query can be used to return the signature for a particular procedure: -//// -CREATE (a:User:Administrator {name: 'Adrian'}) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL dbms.procedures() YIELD name, signature WHERE name='dbms.listConfig' @@ -134,32 +151,68 @@ We can see that the `dbms.listConfig` has one input parameter, `searchString`, a 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[call-call-a-procedure-using-a-quoted-namespace-and-name]] == Call a procedure using a quoted namespace and name This calls the built-in procedure `db.labels`, which lists all labels used in the database. -//// -CREATE (a:User:Administrator {name: 'Adrian'}) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL `db`.`labels()` ---- -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL `db`.`labels` ---- +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[call-call-a-procedure-with-literal-arguments]] == Call a procedure with literal arguments @@ -167,18 +220,28 @@ CALL `db`.`labels` This calls the example procedure `dbms.security.createUser` using literal arguments. The arguments are written out directly in the statement text. -//// -CREATE (a:User:Administrator {name: 'Adrian'}) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL dbms.security.createUser('example_username', 'example_password', false) ---- Since our example procedure does not return any result, the result is empty. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[call-call-a-procedure-with-parameter-arguments]] == Call a procedure with parameter arguments @@ -189,76 +252,125 @@ Each procedure argument is taken to be the value of a corresponding statement pa [NOTE] ==== Examples that use parameter arguments shows the given parameters in JSON format; the exact manner in which they are to be submitted depends upon the driver being used. -See xref::syntax/parameters.adoc[], for more about querying with parameters. +See xref:syntax/parameters.adoc[], for more about querying with parameters + + ==== + .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { - "username": "example_username", - "password": "example_password", - "requirePasswordChange": false + "username" : "example_username", + "password" : "example_password", + "requirePasswordChange" : false } ---- + .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL dbms.security.createUser($username, $password, $requirePasswordChange) ---- Since our example procedure does not return any result, the result is empty. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + Cypher allows the omission of parentheses for procedures with arity-n (n arguments), Cypher implicitly passes the parameter arguments. [NOTE] ==== Best practice is to use parentheses for procedures. Omission of parantheses is available only in a so-called standalone procedure call, when the whole query consists of a single `CALL` clause. + + ==== .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { - "username": "example_username", - "password": "example_password", - "requirePasswordChange": false + "username" : "example_username", + "password" : "example_password", + "requirePasswordChange" : false } ---- + .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL dbms.security.createUser ---- Since our example procedure does not return any result, the result is empty. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[call-call-a-procedure-with-mixed-literal-and-parameter-arguments]] == Call a procedure with mixed literal and parameter arguments This calls the example procedure `dbms.security.createUser` using both literal and parameter arguments. + .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { - "password": "example_password" + "password" : "example_password" } ---- + .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL dbms.security.createUser('example_username', $password, false) ---- Since our example procedure does not return any result, the result is empty. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[call-call-a-procedure-with-literal-and-default-arguments]] == Call a procedure with literal and default arguments @@ -266,44 +378,65 @@ Since our example procedure does not return any result, the result is empty. This calls the example procedure `dbms.security.createUser` using literal arguments. That is, arguments that are written out directly in the statement text, and a trailing default argument that is provided by the procedure itself. + .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL dbms.security.createUser('example_username', 'example_password') ---- Since our example procedure does not return any result, the result is empty. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[call-call-a-procedure-call-yield-star]] == Call a procedure using `+CALL YIELD *+` This calls the built-in procedure `db.labels` to count all labels used in the database. -//// -CREATE (a:User:Administrator {name: 'Adrian'}) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL db.labels() YIELD * ---- If the procedure has deprecated return columns, those columns are also returned. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[call-call-a-procedure-within-a-complex-query-using-call-yield]] == Call a procedure within a complex query using `CALL YIELD` This calls the built-in procedure `db.labels` to count all labels used in the database. -//// -CREATE (a:User:Administrator {name: 'Adrian'}) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL db.labels() YIELD label RETURN count(label) AS numLabels @@ -311,18 +444,29 @@ RETURN count(label) AS numLabels Since the procedure call is part of a larger query, all outputs must be named explicitly. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[call-call-a-procedure-and-filter-its-results]] == Call a procedure and filter its results -This calls the built-in procedure `db.labels` to count all in-use labels in the database that contain the string `'User'`. +This calls the built-in procedure `db.labels` to count all in-use labels in the database that contain the word 'User'. -//// -CREATE (a:User:Administrator {name: 'Adrian'}) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL db.labels() YIELD label WHERE label CONTAINS 'User' @@ -331,18 +475,30 @@ RETURN count(label) AS numLabels Since the procedure call is part of a larger query, all outputs must be named explicitly. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[call-call-a-procedure-within-a-complex-query-and-rename-its-outputs]] == Call a procedure within a complex query and rename its outputs This calls the built-in procedure `db.propertyKeys` as part of counting the number of nodes per property key that is currently used in the database. -//// -CREATE (a:User:Administrator {name: 'Adrian'}) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL db.propertyKeys() YIELD propertyKey AS prop MATCH (n) @@ -352,3 +508,20 @@ RETURN prop, count(n) AS numNodes Since the procedure call is part of a larger query, all outputs must be named explicitly. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/create.adoc b/modules/ROOT/pages/clauses/create.adoc index 89d350fa6..6c43422b8 100644 --- a/modules/ROOT/pages/clauses/create.adoc +++ b/modules/ROOT/pages/clauses/create.adoc @@ -1,32 +1,28 @@ -:description: The `CREATE` clause is used to create nodes and relationships. - [[query-create]] = CREATE - -[abstract] --- -The `CREATE` clause is used to create nodes and relationships. --- - -* xref::clauses/create.adoc#create-nodes[Create nodes] -** xref::clauses/create.adoc#create-create-single-node[Create single node] -** xref::clauses/create.adoc#create-create-multiple-nodes[Create multiple nodes] -** xref::clauses/create.adoc#create-create-a-node-with-a-label[Create a node with a label] -** xref::clauses/create.adoc#create-create-a-node-with-multiple-labels[Create a node with multiple labels] -** xref::clauses/create.adoc#create-create-node-and-add-labels-and-properties[Create node and add labels and properties] -** xref::clauses/create.adoc#create-return-created-node[Return created node] -* xref::clauses/create.adoc#create-relationships[Create relationships] -** xref::clauses/create.adoc#create-create-a-relationship-between-two-nodes[Create a relationship between two nodes] -** xref::clauses/create.adoc#create-create-a-relationship-and-set-properties[Create a relationship and set properties] -* xref::clauses/create.adoc#create-create-a-full-path[Create a full path] -* xref::clauses/create.adoc#use-parameters-with-create[Use parameters with `CREATE`] -** xref::clauses/create.adoc#create-create-node-with-a-parameter-for-the-properties[Create node with a parameter for the properties] -** xref::clauses/create.adoc#create-create-multiple-nodes-with-a-parameter-for-their-properties[Create multiple nodes with a parameter for their properties] +:description: The `CREATE` clause is used to create nodes and relationships. + +* xref:clauses/create.adoc#create-nodes[Create nodes] +** xref:clauses/create.adoc#create-create-single-node[Create single node] +** xref:clauses/create.adoc#create-create-multiple-nodes[Create multiple nodes] +** xref:clauses/create.adoc#create-create-a-node-with-a-label[Create a node with a label] +** xref:clauses/create.adoc#create-create-a-node-with-multiple-labels[Create a node with multiple labels] +** xref:clauses/create.adoc#create-create-node-and-add-labels-and-properties[Create node and add labels and properties] +** xref:clauses/create.adoc#create-return-created-node[Return created node] +* xref:clauses/create.adoc#create-relationships[Create relationships] +** xref:clauses/create.adoc#create-create-a-relationship-between-two-nodes[Create a relationship between two nodes] +** xref:clauses/create.adoc#create-create-a-relationship-and-set-properties[Create a relationship and set properties] +* xref:clauses/create.adoc#create-create-a-full-path[Create a full path] +* xref:clauses/create.adoc#use-parameters-with-create[Use parameters with `CREATE`] +** xref:clauses/create.adoc#create-create-node-with-a-parameter-for-the-properties[Create node with a parameter for the properties] +** xref:clauses/create.adoc#create-create-multiple-nodes-with-a-parameter-for-their-properties[Create multiple nodes with a parameter for their properties] [TIP] ==== In the `CREATE` clause, patterns are used extensively. -Read xref::syntax/patterns.adoc[Patterns] for an introduction. +Read xref:syntax/patterns.adoc[Patterns] for an introduction. + + ==== [[create-nodes]] @@ -37,8 +33,9 @@ Read xref::syntax/patterns.adoc[Patterns] for an introduction. Creating a single node is done by issuing the following query: + .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (n) ---- @@ -51,14 +48,30 @@ CREATE (n) Nodes created: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[create-create-multiple-nodes]] === Create multiple nodes Creating multiple nodes is done by separating them with a comma. + .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (n), (m) ---- @@ -71,14 +84,30 @@ CREATE (n), (m) Nodes created: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[create-create-a-node-with-a-label]] === Create a node with a label To add a label when creating a node, use the syntax below: + .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (n:Person) ---- @@ -92,6 +121,21 @@ Nodes created: 1 + Labels added: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[create-create-a-node-with-multiple-labels]] === Create a node with multiple labels @@ -99,8 +143,9 @@ Labels added: 1 To add labels when creating a node, use the syntax below. In this case, we add two labels. + .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (n:Person:Swedish) ---- @@ -114,14 +159,30 @@ Nodes created: 1 + Labels added: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[create-create-node-and-add-labels-and-properties]] === Create node and add labels and properties When creating a new node with labels, you can add properties at the same time. + .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (n:Person {name: 'Andy', title: 'Developer'}) ---- @@ -136,14 +197,30 @@ Properties set: 2 + Labels added: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[create-return-created-node]] === Return created node Creating a single node is done by issuing the following query: + .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (a {name: 'Andy'}) RETURN a.name @@ -161,6 +238,22 @@ Nodes created: 1 + Properties set: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[create-relationships]] == Create relationships @@ -171,14 +264,9 @@ Properties set: 1 To create a relationship between two nodes, we first get the two nodes. Once the nodes are loaded, we simply create a relationship between them. -//// -CREATE - (a:Person {name: 'A'}), - (b:Person {name: 'B'}) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:Person), @@ -199,6 +287,26 @@ The created relationship is returned by the query. Relationships created: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b) +RETURN type(r) +]]> +++++ +endif::nonhtmloutput[] [[create-create-a-relationship-and-set-properties]] === Create a relationship and set properties @@ -206,14 +314,9 @@ Relationships created: 1 Setting properties on relationships is done in a similar manner to how it's done when creating nodes. Note that the values can be any expression. -//// -CREATE - (a:Person {name: 'A'}), - (b:Person {name: 'B'}) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:Person), @@ -235,14 +338,35 @@ Relationships created: 1 + Properties set: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +' + b.name}]->(b) +RETURN type(r), r.name +]]> +++++ +endif::nonhtmloutput[] [[create-create-a-full-path]] == Create a full path When you use `CREATE` and a pattern, all parts of the pattern that are not already in scope at this time will be created. + .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE p = (andy {name:'Andy'})-[:WORKS_AT]->(neo)<-[:WORKS_AT]-(michael {name: 'Michael'}) RETURN p @@ -261,6 +385,22 @@ Relationships created: 2 + Properties set: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(neo)<-[:WORKS_AT]-(michael {name: 'Michael'}) +RETURN p +]]> +++++ +endif::nonhtmloutput[] [[use-parameters-with-create]] == Use parameters with `CREATE` @@ -272,19 +412,21 @@ You can also create a graph entity from a map. All the key/value pairs in the map will be set as properties on the created relationship or node. In this case we add a `Person` label to the node as well. + .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { - "props": { - "name": "Andy", - "position": "Developer" + "props" : { + "name" : "Andy", + "position" : "Developer" } } ---- + .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (n:Person $props) RETURN n @@ -301,28 +443,46 @@ Properties set: 2 + Labels added: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[create-create-multiple-nodes-with-a-parameter-for-their-properties]] === Create multiple nodes with a parameter for their properties By providing Cypher an array of maps, it will create a node for each map. + .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { - "props": [ { - "name": "Andy", - "position": "Developer" + "props" : [ { + "name" : "Andy", + "position" : "Developer" }, { - "name": "Michael", - "position": "Developer" + "name" : "Michael", + "position" : "Developer" } ] } ---- + .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND $props AS map CREATE (n) @@ -338,3 +498,21 @@ Nodes created: 2 + Properties set: 4 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/delete.adoc b/modules/ROOT/pages/clauses/delete.adoc index 352437a0c..75b2947a9 100644 --- a/modules/ROOT/pages/clauses/delete.adoc +++ b/modules/ROOT/pages/clauses/delete.adoc @@ -1,39 +1,59 @@ -:description: The `DELETE` clause is used to delete nodes, relationships or paths. - [[query-delete]] = DELETE +:description: The `DELETE` clause is used to delete nodes, relationships or paths. + +* xref:clauses/delete.adoc#query-delete-introduction[Introduction] +* xref:clauses/delete.adoc#delete-delete-single-node[Delete a single node] +* xref:clauses/delete.adoc#delete-delete-all-nodes-and-relationships[Delete all nodes and relationships] +* xref:clauses/delete.adoc#delete-delete-a-node-with-all-its-relationships[Delete a node with all its relationships] +* xref:clauses/delete.adoc#delete-delete-relationships-only[Delete relationships only] -[abstract] --- -The `DELETE` clause is used to delete nodes, relationships or paths. --- +[[query-delete-introduction]] +== Introduction -For removing properties and labels, see xref::clauses/remove.adoc[REMOVE]. +For removing properties and labels, see xref:clauses/remove.adoc[REMOVE]. Remember that you cannot delete a node without also deleting relationships that start or end on said node. Either explicitly delete the relationships, or use `DETACH DELETE`. The examples start out with the following database: -image:graph_delete_clause.svg[] - -//// -CREATE - (a:Person {name: 'Andy', age: 36}), - (p:Person {name: 'Timothy', age: 25}), - (t:Person {name: 'Peter', age: 34}), - (z:Person {name: 'UNKNOWN'}), - (a)-[:KNOWS]->(t), - (a)-[:KNOWS]->(p) -//// +.Graph +["dot", "DELETE-1.svg", "neoviz", ""] +---- + N0 [ + label = "{Person|age = 36\lname = \'Andy\'\l}" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 [ + label = "{Person|age = 25\lname = \'Timothy\'\l}" + ] + N2 [ + label = "{Person|age = 34\lname = \'Peter\'\l}" + ] + N3 [ + label = "{Person|name = \'UNKNOWN\'\l}" + ] +---- + [[delete-delete-single-node]] == Delete single node To delete a node, use the `DELETE` clause. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person {name: 'UNKNOWN'}) DELETE n @@ -47,14 +67,35 @@ DELETE n Nodes deleted: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(t), + (a)-[:KNOWS]->(p) + +]]> +++++ +endif::nonhtmloutput[] [[delete-delete-all-nodes-and-relationships]] == Delete all nodes and relationships This query is not for deleting large amounts of data, but is useful when experimenting with small example data sets. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) DETACH DELETE n @@ -69,14 +110,35 @@ Nodes deleted: 4 + Relationships deleted: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(t), + (a)-[:KNOWS]->(p) + +]]> +++++ +endif::nonhtmloutput[] [[delete-delete-a-node-with-all-its-relationships]] == Delete a node with all its relationships When you want to delete a node and any relationship going to or from it, use `DETACH DELETE`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'Andy'}) DETACH DELETE n @@ -91,19 +153,42 @@ Nodes deleted: 1 + Relationships deleted: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(t), + (a)-[:KNOWS]->(p) + +]]> +++++ +endif::nonhtmloutput[] + [NOTE] ==== For `DETACH DELETE` for users with restricted security privileges, see link:{neo4j-docs-base-uri}/operations-manual/{page-version}/authentication-authorization/access-control#detach-delete-restricted-user[Operations Manual -> Fine-grained access control]. -==== +==== + [[delete-delete-relationships-only]] == Delete relationships only It is also possible to delete relationships only, leaving the node(s) otherwise unaffected. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'Andy'})-[r:KNOWS]->() DELETE r @@ -119,3 +204,24 @@ This deletes all outgoing `KNOWS` relationships from the node with the name *'An Relationships deleted: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(t), + (a)-[:KNOWS]->(p) + +]]>() +DELETE r +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/foreach.adoc b/modules/ROOT/pages/clauses/foreach.adoc index f2f85b2e7..6e88ab571 100644 --- a/modules/ROOT/pages/clauses/foreach.adoc +++ b/modules/ROOT/pages/clauses/foreach.adoc @@ -1,12 +1,9 @@ -:description: The `FOREACH` clause is used to update data within a collection whether components of a path, or result of aggregation. - [[query-foreach]] = FOREACH +:description: The `FOREACH` clause is used to update data within a collection whether components of a path, or result of aggregation. -[abstract] --- -The `FOREACH` clause is used to update data within a collection whether components of a path, or result of aggregation. --- +[[query-foreach-introduction]] +== Introduction Lists and paths are key concepts in Cypher. The `FOREACH` clause can be used to update data, such as executing update commands on elements in a path, or on a list created by aggregation. @@ -16,32 +13,50 @@ This means that if you `CREATE` a node variable within a `FOREACH`, you will _no Within the `FOREACH` parentheses, you can do any of the updating commands -- `SET`, `REMOVE`, `CREATE`, `MERGE`, `DELETE`, and `FOREACH`. -[TIP] -==== -If you want to execute an additional `MATCH` for each element in a list then the xref::clauses/unwind.adoc[`UNWIND`] clause would be a more appropriate command. -==== - -image:graph_foreach_clause.svg[] +If you want to execute an additional `MATCH` for each element in a list then the xref:clauses/unwind.adoc[`UNWIND`] clause would be a more appropriate command. -//// -CREATE - (a:Person {name: 'A'}), - (b:Person {name: 'B'}), - (c:Person {name: 'C'}), - (d:Person {name: 'D'}), - (a)-[:KNOWS]->(b), - (b)-[:KNOWS]->(c), - (c)-[:KNOWS]->(d) -//// +.Graph +["dot", "FOREACH-1.svg", "neoviz", ""] +---- + N0 [ + label = "{Person|name = \'A\'\l}" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 [ + label = "{Person|name = \'B\'\l}" + ] + N1 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N2 [ + label = "{Person|name = \'C\'\l}" + ] + N2 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N3 [ + label = "{Person|name = \'D\'\l}" + ] +---- + [[foreach-mark-all-nodes-along-a-path]] == Mark all nodes along a path This query will set the property `marked` to `true` on all nodes along a path. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH p=(start)-[*]->(finish) WHERE start.name = 'A' AND finish.name = 'D' @@ -56,3 +71,26 @@ FOREACH (n IN nodes(p) | SET n.marked = true) Properties set: 4 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (b)-[:KNOWS]->(c), + (c)-[:KNOWS]->(d) + +]]>(finish) +WHERE start.name = 'A' AND finish.name = 'D' +FOREACH (n IN nodes(p) | SET n.marked = true) +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/index.adoc b/modules/ROOT/pages/clauses/index.adoc index 8fceee6bc..044c6dc9f 100644 --- a/modules/ROOT/pages/clauses/index.adoc +++ b/modules/ROOT/pages/clauses/index.adoc @@ -1,286 +1,303 @@ -:description: This section contains information on all the clauses in the Cypher query language. - [[query-clause]] = Clauses +:description: This section contains information on all the clauses in the Cypher query language. + +* xref:clauses/index.adoc#header-reading-clauses[Reading clauses] +* xref:clauses/index.adoc#header-projecting-clauses[Projecting clauses] +* xref:clauses/index.adoc#header-reading-sub-clauses[Reading sub-clauses] +* xref:clauses/index.adoc#header-reading-hints[Reading hints] +* xref:clauses/index.adoc#header-writing-clauses[Writing clauses] +* xref:clauses/index.adoc#header-reading-writing-clauses[Reading/Writing clauses] +* xref:clauses/index.adoc#header-set-operations-clauses[Set operations] +* xref:clauses/index.adoc#header-subquery-clauses[Subquery clauses] +* xref:clauses/index.adoc#header-multiple-graphs-clauses[Multiple graphs] +* xref:clauses/index.adoc#header-importing-clauses[Importing data] +* xref:clauses/index.adoc#header-listing-procs-functions[Listing functions and procedures] +* xref:clauses/index.adoc#header-administration-clauses[Administration clauses] -[abstract] --- -This section contains information on all the clauses in the Cypher query language. --- +[[header-reading-clauses]] +**Reading clauses** -[[administration-clauses]] -== Administration clauses +These comprise clauses that read data from the database. -These comprise clauses used to manage databases, schema and security; further details can found in xref::databases.adoc[Database management] and xref::access-control/index.adoc[Access control]. +The flow of data within a Cypher query is an unordered sequence of maps with key-value pairs -- a set of possible bindings between the variables in the query and values derived from the database. +This set is refined and augmented by subsequent parts of the query. [options="header"] |=== -| Clause | Description - -m| xref::databases.adoc[CREATE \| DROP \| START \| STOP DATABASE] -| Create, drop, start or stop a database. - -m| xref::indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE \| DROP INDEX] -| Create or drop an index on all nodes with a particular label and property. +|Clause |Description -m| xref::constraints/syntax.adoc[CREATE \| DROP CONSTRAINT] -| Create or drop a constraint pertaining to either a node label or relationship type, and a property. +m|xref:clauses/match.adoc[MATCH] +|Specify the patterns to search for in the database. -| xref::access-control/index.adoc[Access control] -| Manage users, roles, and privileges for database, graph and sub-graph access control. +m|xref:clauses/optional-match.adoc[OPTIONAL MATCH] +|Specify the patterns to search for in the database while using `nulls` for missing parts of the pattern. |=== -[[importing-clauses]] -== Importing data +[[header-projecting-clauses]] +**Projecting clauses** + +These comprise clauses that define which expressions to return in the result set. +The returned expressions may all be aliased using `AS`. [options="header"] |=== -| Clause | Description +|Clause |Description + +//RETURN ... [AS] +m|xref:clauses/return.adoc[RETURN +++...+++ [AS\]] +|Defines what to include in the query result set. -m| xref::clauses/load-csv.adoc[LOAD CSV] -| Use when importing data from CSV files. +//WITH ... [AS] +m|xref:clauses/with.adoc[WITH +++...+++ [AS\]] +|Allows query parts to be chained together, piping the results from one to be used as starting points or criteria in the next. -m| --- xref::query-tuning/using.adoc#query-using-periodic-commit-hint[USING PERIODIC COMMIT] -| This query hint may be used to prevent an out-of-memory error from occurring when importing large amounts of data using `LOAD CSV`. +//UNWIND ... [AS] +m|xref:clauses/unwind.adoc[UNWIND +++...+++ [AS\]] +|Expands a list into a sequence of rows. |=== -[[listing-functions-and-procedures]] -== Listing functions and procedures +[[header-reading-sub-clauses]] +**Reading sub-clauses** + +These comprise sub-clauses that must operate as part of reading clauses. [options="header"] |=== -| Clause | Description +|Sub-clause | Description -m| xref::clauses/listing-functions.adoc[SHOW FUNCTIONS] -| List the available functions. +m|xref:clauses/where.adoc[WHERE] +|Adds constraints to the patterns in a `MATCH` or `OPTIONAL MATCH` clause or filters the results of a `WITH` clause. -m| xref::clauses/listing-procedures.adoc[SHOW PROCEDURES] -| List the available procedures. +//WHERE EXISTS { ... } +m|xref:clauses/where.adoc#existential-subqueries[WHERE EXISTS +++{ ... }+++] +|An existential sub-query used to filter the results of a `MATCH`, `OPTIONAL MATCH` or `WITH` clause. -|=== +//ORDER BY [ASC[ENDING] | DESC[ENDING]] +m|xref:clauses/order-by.adoc[ORDER BY [ASC[ENDING] | DESC[ENDING]]] +|A sub-clause following `RETURN` or `WITH`, specifying that the output should be sorted in either ascending (the default) or descending order. +m|xref:clauses/skip.adoc[SKIP] +|Defines from which row to start including the rows in the output. -[[multiple-graphs-clauses]] -== Multiple graphs +m|xref:clauses/limit.adoc[LIMIT] +|Constrains the number of rows in the output. -[options="header"] |=== -| Clause | Description -m| xref::clauses/use.adoc[USE] -| Determines which graph a query, or query part, is executed against. label:fabric[] -|=== - - -[[projecting-clauses]] -== Projecting clauses +[[header-reading-hints]] +**Reading hints** -These comprise clauses that define which expressions to return in the result set. -The returned expressions may all be aliased using `AS`. +These comprise clauses used to specify planner hints when tuning a query. +More details regarding the usage of these -- and query tuning in general -- can be found in xref:query-tuning/using.adoc[Planner hints and the USING keyword]. [options="header"] |=== -| Clause | Description +|Hint |Description -m| xref::clauses/return.adoc[+RETURN ... [AS]+] -| Defines what to include in the query result set. +m|xref:query-tuning/using.adoc#query-using-index-hint[USING INDEX] +|Index hints are used to specify which index, if any, the planner should use as a starting point. -m| xref::clauses/with.adoc[+WITH ... [AS]+] -| Allows query parts to be chained together, piping the results from one to be used as starting points or criteria in the next. +m|xref:query-tuning/using.adoc#query-using-index-hint[USING INDEX SEEK] +|Index seek hint instructs the planner to use an index seek for this clause. -m| xref::clauses/unwind.adoc[+UNWIND ... [AS]+] -| Expands a list into a sequence of rows. +m|xref:query-tuning/using.adoc#query-using-scan-hint[USING SCAN] +|Scan hints are used to force the planner to do a label scan (followed by a filtering operation) instead of using an index. -|=== +m|xref:query-tuning/using.adoc#query-using-join-hint[USING JOIN] +|Join hints are used to enforce a join operation at specified points. +|=== -[[reading-clauses]] -== Reading clauses -These comprise clauses that read data from the database. +[[header-writing-clauses]] +**Writing clauses** -The flow of data within a Cypher query is an unordered sequence of maps with key-value pairs -- a set of possible bindings between the variables in the query and values derived from the database. -This set is refined and augmented by subsequent parts of the query. +These comprise clauses that write the data to the database. [options="header"] |=== -| Clause | Description +|Clause |Description + +m|xref:clauses/create.adoc[CREATE] +|Create nodes and relationships. + +m|xref:clauses/delete.adoc[DELETE] +a| +Delete nodes, relationships or paths. +Any node to be deleted must also have all associated relationships explicitly deleted. + +m|xref:clauses/delete.adoc[DETACH DELETE] +a| +Delete a node or set of nodes. +All associated relationships will automatically be deleted. -m| xref::clauses/match.adoc[MATCH] -| Specify the patterns to search for in the database. +m|xref:clauses/set.adoc[SET] +|Update labels on nodes and properties on nodes and relationships. -m| xref::clauses/optional-match.adoc[OPTIONAL MATCH] -| Specify the patterns to search for in the database while using `nulls` for missing parts of the pattern. +m|xref:clauses/remove.adoc[REMOVE] +|Remove properties and labels from nodes and relationships. + +m|xref:clauses/foreach.adoc[FOREACH] +|Update data within a list, whether components of a path, or the result of aggregation. |=== -[[reading-hints]] -== Reading hints +[[header-reading-writing-clauses]] +**Reading/Writing clauses** -These comprise clauses used to specify planner hints when tuning a query. -More details regarding the usage of these -- and query tuning in general -- can be found in xref::query-tuning/using.adoc[Planner hints and the USING keyword]. +These comprise clauses that both read data from and write data to the database. [options="header"] |=== -| Hint | Description +|Clause |Description -m| xref::query-tuning/using.adoc#query-using-index-hint[USING INDEX] -| Index hints are used to specify which index, if any, the planner should use as a starting point. +m|xref:clauses/merge.adoc[MERGE] +|Ensures that a pattern exists in the graph. Either the pattern already exists, or it needs to be created. -m| xref::query-tuning/using.adoc#query-using-index-hint[USING INDEX SEEK] -| Index seek hint instructs the planner to use an index seek for this clause. +m|--- xref:clauses/merge.adoc#query-merge-on-create-on-match[ON CREATE] +|Used in conjunction with `MERGE`, this write sub-clause specifies the actions to take if the pattern needs to be created. -m| xref::query-tuning/using.adoc#query-using-scan-hint[USING SCAN] -| Scan hints are used to force the planner to do a label scan (followed by a filtering operation) instead of using an index. +m|--- xref:clauses/merge.adoc#query-merge-on-create-on-match[ON MATCH] +|Used in conjunction with `MERGE`, this write sub-clause specifies the actions to take if the pattern already exists. -m| xref::query-tuning/using.adoc#query-using-join-hint[USING JOIN] -| Join hints are used to enforce a join operation at specified points. +//CALL ... [YIELD ... ] +m|xref:clauses/call.adoc[CALL +++...+++ [YIELD +++...+++ ]] +|Invokes a procedure deployed in the database and return any results. |=== -[[reading-sub-clauses]] -== Reading sub-clauses - -These comprise sub-clauses that must operate as part of reading clauses. +[[header-set-operations-clauses]] +**Set operations** [options="header"] |=== -| Sub-clause | Description - -m| xref::clauses/where.adoc[WHERE] -| Adds constraints to the patterns in a `MATCH` or `OPTIONAL MATCH` clause or filters the results of a `WITH` clause. +|Clause |Description -m| xref::clauses/where.adoc#existential-subqueries[+WHERE EXISTS { ... }+] -| An existential sub-query used to filter the results of a `MATCH`, `OPTIONAL MATCH` or `WITH` clause. +m|xref:clauses/union.adoc[UNION] +a| +Combines the result of multiple queries into a single result set. +Duplicates are removed. -m| xref::clauses/order-by.adoc[+ORDER BY [ASC[ENDING] \| DESC[ENDING]]+] -| A sub-clause following `RETURN` or `WITH`, specifying that the output should be sorted in either ascending (the default) or descending order. +m|xref:clauses/union.adoc[UNION ALL] +a| +Combines the result of multiple queries into a single result set. +Duplicates are retained. -m| xref::clauses/skip.adoc[SKIP] -| Defines from which row to start including the rows in the output. +|=== -m| xref::clauses/limit.adoc[LIMIT] -| Constrains the number of rows in the output. +[[header-subquery-clauses]] +**Subquery clauses** +[options="header"] |=== +|Clause |Description +//CALL { ... } +m|xref:clauses/call-subquery.adoc[CALL +++{ ... }+++] +|Evaluates a subquery, typically used for post-union processing or aggregations. -[[reading-writing-clauses]] -== Reading/Writing clauses +|=== -These comprise clauses that both read data from and write data to the database. +[[header-multiple-graphs-clauses]] +**Multiple graphs** [options="header"] |=== -| Clause | Description - -m| xref::clauses/merge.adoc[MERGE] -| Ensures that a pattern exists in the graph. Either the pattern already exists, or it needs to be created. - -m| --- xref::clauses/merge.adoc#query-merge-on-create-on-match[ON CREATE] -| Used in conjunction with `MERGE`, this write sub-clause specifies the actions to take if the pattern needs to be created. - -m| --- xref::clauses/merge.adoc#query-merge-on-create-on-match[ON MATCH] -| Used in conjunction with `MERGE`, this write sub-clause specifies the actions to take if the pattern already exists. +|Clause |Description -m| xref::clauses/call.adoc[+CALL ... [YIELD ... ]+] -| Invokes a procedure deployed in the database and return any results. +m|xref:clauses/use.adoc[USE] +|[fabric]#Determines which graph a query, or query part, is executed against.# |=== -[[set-operations-clauses]] -== Set operations +[[header-importing-clauses]] +**Importing data** [options="header"] |=== |Clause |Description -m| xref::clauses/union.adoc[UNION] -a| -Combines the result of multiple queries into a single result set. -Duplicates are removed. +m|xref:clauses/load-csv.adoc[LOAD CSV] +|Use when importing data from CSV files. -m| xref::clauses/union.adoc[UNION ALL] -a| -Combines the result of multiple queries into a single result set. -Duplicates are retained. +m|--- xref:query-tuning/using.adoc#query-using-periodic-commit-hint[USING PERIODIC COMMIT] +|This query hint may be used to prevent an out-of-memory error from occurring when importing large amounts of data using `LOAD CSV`. |=== -[[subquery-clauses]] -== Subquery clauses +[[header-listing-procs-functions]] +**Listing functions and procedures** [options="header"] |=== |Clause |Description -m| xref::clauses/call-subquery.adoc[+CALL { ... }+] -| Evaluates a subquery, typically used for post-union processing or aggregations. +m|xref:clauses/listing-functions.adoc[SHOW FUNCTIONS] +|Lists the available functions. -m| xref::clauses/call-subquery.adoc#subquery-call-in-transactions[+CALL { ... } IN TRANSACTIONS+] -a| -Evaluates a subquery in separate transactions. -Typically used when modifying or importing large amounts of data. +m|xref:clauses/listing-procedures.adoc[SHOW PROCEDURES] +|Lists the available procedures. |=== -[[transaction-commands]] -== Transaction Commands +[[header-administration-clauses]] +**Administration clauses** + +These comprise clauses used to manage databases, schema and security; further details can found in xref:databases.adoc[Database management] and xref:access-control/index.adoc[Access control]. [options="header"] |=== -| Clause | Description +|Clause |Description + +m|xref:databases.adoc[CREATE \| DROP \| START \| STOP DATABASE] +|Create, drop, start or stop a database. -m| xref:clauses/transaction-clauses.adoc#query-listing-transactions[SHOW TRANSACTIONS] -| List the available transactions. +m|xref:indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE \| DROP INDEX] +|Create or drop an index on all nodes with a particular label and property. -m| xref:clauses/transaction-clauses.adoc#query-terminate-transactions[TERMINATE TRANSACTIONS] -| Terminate transactions by their IDs. +m|xref:constraints/syntax.adoc[CREATE \| DROP CONSTRAINT] +|Create or drop a constraint pertaining to either a node label or relationship type, and a property. + +|xref:access-control/index.adoc[Access control] +|Manage users, roles, and privileges for database, graph and sub-graph access control. |=== -[[writing-clauses]] -== Writing clauses +//Reading -These comprise clauses that write the data to the database. -[options="header"] -|=== -| Clause | Description +//Projecting -m| xref::clauses/create.adoc[CREATE] -| Create nodes and relationships. -m| xref::clauses/delete.adoc[DELETE] -a| -Delete nodes, relationships or paths. -Any node to be deleted must also have all associated relationships explicitly deleted. +//Reading sub-clauses -m| xref::clauses/delete.adoc[DETACH DELETE] -a| -Delete a node or set of nodes. -All associated relationships will automatically be deleted. -m| xref::clauses/set.adoc[SET] -| Update labels on nodes and properties on nodes and relationships. +//Writing -m| xref::clauses/remove.adoc[REMOVE] -| Remove properties and labels from nodes and relationships. -m| xref::clauses/foreach.adoc[FOREACH] -| Update data within a list, whether components of a path, or the result of aggregation. +//Reading/Writing -|=== + +//Set + +//Multiple graphs +// NOTE that the following is static content that should be converted into the test framework: + + +//Importing + +//Listing diff --git a/modules/ROOT/pages/clauses/limit.adoc b/modules/ROOT/pages/clauses/limit.adoc index 3f8ae2f2a..498fafbdf 100644 --- a/modules/ROOT/pages/clauses/limit.adoc +++ b/modules/ROOT/pages/clauses/limit.adoc @@ -1,38 +1,67 @@ -:description: `LIMIT` constrains the number of returned rows. - [[query-limit]] = LIMIT +:description: `LIMIT` constrains the number of returned rows. -[abstract] --- -`LIMIT` constrains the number of returned rows. --- +* xref:clauses/limit.adoc#limit-introduction[Introduction] +* xref:clauses/limit.adoc#limit-subset-rows[Return a subset of the rows] +* xref:clauses/limit.adoc#limit-subset-rows-using-expression[Using an expression with `LIMIT` to return a subset of the rows] +* xref:clauses/limit.adoc#limit-will-not-stop-side-effects[`LIMIT` will not stop side effects] -`LIMIT` accepts any expression that evaluates to a positive integer -- however the expression cannot refer to nodes or relationships. +[[limit-introduction]] +== Introduction -image:graph_limit_clause.svg[] +`LIMIT` accepts any expression that evaluates to a positive integer -- however the expression cannot refer to nodes or relationships. -//// -CREATE - (a {name: 'A'}), - (b {name: 'B'}), - (c {name: 'C'}), - (d {name: 'D'}), - (e {name: 'E'}), - (a)-[:KNOWS]->(b), - (a)-[:KNOWS]->(c), - (a)-[:KNOWS]->(d), - (a)-[:KNOWS]->(e) -//// +.Graph +["dot", "LIMIT-1.svg", "neoviz", ""] +---- + N0 [ + label = "name = \'A\'\l" + ] + N0 -> N4 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 [ + label = "name = \'B\'\l" + ] + N2 [ + label = "name = \'C\'\l" + ] + N3 [ + label = "name = \'D\'\l" + ] + N4 [ + label = "name = \'E\'\l" + ] +---- + [[limit-subset-rows]] == Return a limited subset of the rows To return a limited subset of the rows, use this syntax: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n.name @@ -52,14 +81,40 @@ Limit to 3 rows by the example query. 1+d|Rows: 3 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(d), + (a)-[:KNOWS]->(e) + +]]> +++++ +endif::nonhtmloutput[] [[limit-subset-rows-using-expression]] == Using an expression with `LIMIT` to return a subset of the rows Limit accepts any expression that evaluates to a positive integer as long as it is not referring to any external variables: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n.name @@ -67,8 +122,7 @@ ORDER BY n.name LIMIT 1 + toInteger(3 * rand()) ---- -Limit 1 row plus randomly 0, 1, or 2. -So randomly limit to 1, 2, or 3 rows. +Limit 1 row plus randomly 0, 1, or 2. So randomly limit to 1, 2, or 3 rows. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(b), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(d), + (a)-[:KNOWS]->(e) + +]]> +++++ +endif::nonhtmloutput[] [[limit-will-not-stop-side-effects]] == `LIMIT` will not stop side effects -The use of `LIMIT` in a query will not stop side effects, like `CREATE`, `DELETE`, or `SET`, from happening if the limit is in the same query part as the side effect. -This behaviour was undefined in Neo4j versions before `4.3`. +The use of `LIMIT` in a query will not stop side effects, like `CREATE`, `DELETE` or `SET`, from happening if the limit is in the same query part as the side effect. +This behaviour was undefined in versions before `4.3`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (n) RETURN n @@ -105,8 +184,34 @@ This query returns nothing, but creates one node: Nodes created: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(d), + (a)-[:KNOWS]->(e) + +]]> +++++ +endif::nonhtmloutput[] + + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'A'}) SET n.age = 60 @@ -124,10 +229,37 @@ This query returns nothing, but writes one property: Properties set: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(d), + (a)-[:KNOWS]->(e) + +]]> +++++ +endif::nonhtmloutput[] + If we want to limit the number of updates we can split the query using the `WITH` clause: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WITH n LIMIT 1 @@ -146,3 +278,29 @@ Writes `locked` property on one node and return that node: Properties set: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(d), + (a)-[:KNOWS]->(e) + +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/listing-functions.adoc b/modules/ROOT/pages/clauses/listing-functions.adoc index dfbb4ce70..2ee114e98 100644 --- a/modules/ROOT/pages/clauses/listing-functions.adoc +++ b/modules/ROOT/pages/clauses/listing-functions.adoc @@ -1,20 +1,13 @@ -:description: This section explains the `SHOW FUNCTIONS` command. - [[query-listing-functions]] = SHOW FUNCTIONS - -[abstract] --- -This section explains the `SHOW FUNCTIONS` command. --- +:description: This section explains the `SHOW FUNCTIONS` command. Listing the available functions can be done with `SHOW FUNCTIONS`. [NOTE] ==== -The command `SHOW FUNCTIONS` returns only the default output. -For a full output use the optional `YIELD` command. +The command `SHOW FUNCTIONS` only outputs the default output; for a full output use the optional `YIELD` command. Full output: `SHOW FUNCTIONS YIELD *`. ==== @@ -24,7 +17,8 @@ This command will produce a table with the following columns: .List functions output [options="header", cols="4,6"] |=== -| Column | Description +| Column +| Description m| name a| The name of the function. label:default-output[] @@ -53,21 +47,20 @@ a| Whether the function is aggregating or not. m| rolesExecution a| List of roles permitted to execute this function. -Is `null` without the xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[`SHOW ROLE`] privilege. +Is `null` without the xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[`SHOW ROLE`] privilege. m| rolesBoostedExecution a| List of roles permitted to use boosted mode when executing this function. -Is `null` without the xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[`SHOW ROLE`] privilege. - +Is `null` without the xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[`SHOW ROLE`] privilege. |=== - == Syntax + List functions, either all or only built-in or user-defined:: -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- SHOW [ALL|BUILT IN|USER DEFINED] FUNCTION[S] [YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] @@ -77,12 +70,12 @@ SHOW [ALL|BUILT IN|USER DEFINED] FUNCTION[S] [NOTE] ==== -When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. +When using the `RETURN` clause, the `YIELD` clause is mandatory and may not be omitted. ==== List functions that the current user can execute:: -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- SHOW [ALL|BUILT IN|USER DEFINED] FUNCTION[S] EXECUTABLE [BY CURRENT USER] [YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] @@ -92,12 +85,12 @@ SHOW [ALL|BUILT IN|USER DEFINED] FUNCTION[S] EXECUTABLE [BY CURRENT USER] [NOTE] ==== -When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. +When using the `RETURN` clause, the `YIELD` clause is mandatory and may not be omitted. ==== List functions that the specified user can execute:: -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- SHOW [ALL|BUILT IN|USER DEFINED] FUNCTION[S] EXECUTABLE BY username [YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] @@ -105,12 +98,12 @@ SHOW [ALL|BUILT IN|USER DEFINED] FUNCTION[S] EXECUTABLE BY username [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ---- -Required privilege xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[`SHOW USER`]. +Required privilege xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[`SHOW USER`]. This command cannot be used for LDAP users. [NOTE] ==== -When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. +When using the `RETURN` clause, the `YIELD` clause is mandatory and may not be omitted. ==== == Listing all functions @@ -120,7 +113,7 @@ If all columns are required, use `SHOW FUNCTIONS YIELD *`. .Query -[source, cypher, indent=0] +[source, cypher] ---- SHOW FUNCTIONS ---- @@ -129,90 +122,41 @@ SHOW FUNCTIONS [role="queryresult",options="header,footer",cols="3* +Try this query live + +++++ +endif::nonhtmloutput[] == Listing functions with filtering on output columns @@ -221,9 +165,11 @@ One way is through the type keywords, `BUILT IN` and `USER DEFINED`. A more flexible way is to use the `WHERE` clause. For example, getting the name of all built-in functions starting with the letter 'a': + .Query -[source, cypher, indent=0] +[source, cypher] ---- + SHOW BUILT IN FUNCTIONS YIELD name, isBuiltIn WHERE name STARTS WITH 'a' ---- @@ -231,23 +177,35 @@ WHERE name STARTS WITH 'a' .Result [role="queryresult",options="header,footer",cols="2* +Try this query live + +++++ +endif::nonhtmloutput[] == Listing functions with other filtering @@ -258,8 +216,9 @@ This is due to using the user's privileges instead of filtering on the available There are two options, how to use the `EXECUTABLE` clause. The first option, is to filter for the current user: + .Query -[source, cypher, indent=0] +[source, cypher] ---- SHOW FUNCTIONS EXECUTABLE BY CURRENT USER YIELD * ---- @@ -268,86 +227,39 @@ SHOW FUNCTIONS EXECUTABLE BY CURRENT USER YIELD * [role="queryresult",options="header,footer",cols="6*+ -| ++ -| - -| +"abs"+ -| +"Numeric"+ -| +"Returns the absolute value of a floating point number."+ -| ++ -| ++ -| - -| +"acos"+ -| +"Trigonometric"+ -| +"Returns the arccosine of a number in radians."+ -| ++ -| ++ -| - -| +"all"+ -| +"Predicate"+ -| +"Returns true if the predicate holds for all elements in the given list."+ -| ++ -| ++ -| - -| +"any"+ -| +"Predicate"+ -| +"Returns true if the predicate holds for at least one element in the given list."+ -| ++ -| ++ -| - -| +"asin"+ -| +"Trigonometric"+ -| +"Returns the arcsine of a number in radians."+ -| ++ -| ++ -| - -| +"atan"+ -| +"Trigonometric"+ -| +"Returns the arctangent of a number in radians."+ -| ++ -| ++ -| - -| +"atan2"+ -| +"Trigonometric"+ -| +"Returns the arctangent2 of a set of coordinates in radians."+ -| ++ -| ++ -| - -| +"avg"+ -| +"Aggregating"+ -| +"Returns the average of a set of integer values."+ -| ++ -| ++ -| - -| +"avg"+ -| +"Aggregating"+ -| +"Returns the average of a set of floating point values."+ -| ++ -| ++ -| - +| +"abs"+ | +"Numeric"+ | +"Returns the absolute value of an integer."+ | ++ | ++ | +| +"abs"+ | +"Numeric"+ | +"Returns the absolute value of a floating point number."+ | ++ | ++ | +| +"acos"+ | +"Trigonometric"+ | +"Returns the arccosine of a number in radians."+ | ++ | ++ | +| +"all"+ | +"Predicate"+ | +"Returns true if the predicate holds for all elements in the given list."+ | ++ | ++ | +| +"any"+ | +"Predicate"+ | +"Returns true if the predicate holds for at least one element in the given list."+ | ++ | ++ | +| +"asin"+ | +"Trigonometric"+ | +"Returns the arcsine of a number in radians."+ | ++ | ++ | +| +"atan"+ | +"Trigonometric"+ | +"Returns the arctangent of a number in radians."+ | ++ | ++ | +| +"atan2"+ | +"Trigonometric"+ | +"Returns the arctangent2 of a set of coordinates in radians."+ | ++ | ++ | +| +"avg"+ | +"Aggregating"+ | +"Returns the average of a set of integer values."+ | ++ | ++ | +| +"avg"+ | +"Aggregating"+ | +"Returns the average of a set of floating point values."+ | ++ | ++ | 6+d|Rows: 10 |=== -Notice that the two `roles` columns are empty due to missing the xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[`SHOW ROLE`] privilege. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + +Notice that the two `roles` columns are empty due to missing the xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[`SHOW ROLE`] privilege. The second option, is to filter for a specific user: + .Query -[source, cypher, indent=0] +[source, cypher] ---- SHOW FUNCTIONS EXECUTABLE BY jake ---- @@ -356,47 +268,29 @@ SHOW FUNCTIONS EXECUTABLE BY jake [role="queryresult",options="header,footer",cols="3* +Try this query live + +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/listing-procedures.adoc b/modules/ROOT/pages/clauses/listing-procedures.adoc index 5f2db572f..c3bffac27 100644 --- a/modules/ROOT/pages/clauses/listing-procedures.adoc +++ b/modules/ROOT/pages/clauses/listing-procedures.adoc @@ -1,27 +1,24 @@ -:description: This section explains the `SHOW PROCEDURES` command. - [[query-listing-procedures]] = SHOW PROCEDURES - -[abstract] --- -This section explains the `SHOW PROCEDURES` command. --- +:description: This section explains the `SHOW PROCEDURES` command. Listing the available procedures can be done with `SHOW PROCEDURES`. + [NOTE] ==== -The command `SHOW PROCEDURES` returns only the default output. For a full output use the optional `YIELD` command. +The command `SHOW PROCEDURES` only outputs the default output; for a full output use the optional `YIELD` command. Full output: `SHOW PROCEDURES YIELD *`. ==== This command will produce a table with the following columns: + .List procedures output [options="header", cols="4,6"] |=== -| Column | Description +| Column +| Description m| name a| The name of the procedure. label:default-output[] @@ -50,39 +47,38 @@ a| `true` if this procedure is an admin procedure. m| rolesExecution a| List of roles permitted to execute this procedure. -Is `null` without the xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[`SHOW ROLE`] privilege. +Is `null` without the xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[`SHOW ROLE`] privilege. m| rolesBoostedExecution a| List of roles permitted to use boosted mode when executing this procedure. -Is `null` without the xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[`SHOW ROLE`] privilege. +Is `null` without the xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[`SHOW ROLE`] privilege. m| option a| Map of extra output, e.g. if the procedure is deprecated. - |=== == Syntax + List all procedures:: -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- SHOW PROCEDURE[S] [YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] [WHERE expression] [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ---- - [NOTE] ==== -When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. +When using the `RETURN` clause, the `YIELD` clause is mandatory and may not be omitted. ==== List procedures that the current user can execute:: -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- SHOW PROCEDURE[S] EXECUTABLE [BY CURRENT USER] [YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] @@ -92,12 +88,12 @@ SHOW PROCEDURE[S] EXECUTABLE [BY CURRENT USER] [NOTE] ==== -When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. +When using the `RETURN` clause, the `YIELD` clause is mandatory and may not be omitted. ==== List procedures that the specified user can execute:: -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- SHOW PROCEDURE[S] EXECUTABLE BY username [YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] @@ -105,12 +101,12 @@ SHOW PROCEDURE[S] EXECUTABLE BY username [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ---- -Requires the privilege xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[`SHOW USER`]. +Requires the privilege xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[`SHOW USER`]. This command cannot be used for LDAP users. [NOTE] ==== -When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. +When using the `RETURN` clause, the `YIELD` clause is mandatory and may not be omitted. ==== @@ -119,8 +115,9 @@ When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be To list all available procedures with the default output columns, the `SHOW PROCEDURES` command can be used. If all columns are required, use `SHOW PROCEDURES YIELD *`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- SHOW PROCEDURES ---- @@ -129,94 +126,47 @@ SHOW PROCEDURES [role="queryresult",options="header,footer",cols="4* +Try this query live + +++++ +endif::nonhtmloutput[] == Listing procedures with filtering on output columns The listed procedures can be filtered in multiple ways, one way is to use the `WHERE` clause. For example, returning the names of all admin procedures: + .Query -[source, cypher, indent=0] +[source, cypher] ---- + SHOW PROCEDURES YIELD name, admin WHERE admin ---- @@ -225,7 +175,6 @@ WHERE admin [role="queryresult",options="header,footer",cols="2* +Try this query live + +++++ +endif::nonhtmloutput[] == Listing procedures with other filtering @@ -247,8 +209,9 @@ This is due to using the user's privileges instead of filtering on the available There are two options, how to use the `EXECUTABLE` clause. The first option, is to filter for the current user: + .Query -[source, cypher, indent=0] +[source, cypher] ---- SHOW PROCEDURES EXECUTABLE BY CURRENT USER YIELD * ---- @@ -257,77 +220,39 @@ SHOW PROCEDURES EXECUTABLE BY CURRENT USER YIELD * [role="queryresult",options="header,footer",cols="5*+ -| ++ -| - -| +"db.awaitIndexes"+ -| +"Wait for all indexes to come online (for example: CALL db.awaitIndexes(300))."+ -| ++ -| ++ -| - -| +"db.checkpoint"+ -| +"Initiate and wait for a new check point, or wait any already on-going check point to complete. Note that this temporarily disables the `dbms.checkpoint.iops.limit` setting in order to make the check point complete faster. This might cause transaction throughput to degrade slightly, due to increased IO load."+ -| ++ -| ++ -| - -| +"db.constraints"+ -| +"List all constraints in the database."+ -| ++ -| ++ -| - -| +"db.createIndex"+ -| +"Create a named schema index with specified index provider and configuration (optional). Yield: name, labels, properties, providerName, status"+ -| ++ -| ++ -| - -| +"db.createLabel"+ -| +"Create a label"+ -| ++ -| ++ -| - -| +"db.createNodeKey"+ -| +"Create a named node key constraint. Backing index will use specified index provider and configuration (optional). Yield: name, labels, properties, providerName, status"+ -| ++ -| ++ -| - -| +"db.createProperty"+ -| +"Create a Property"+ -| ++ -| ++ -| - -| +"db.createRelationshipType"+ -| +"Create a RelationshipType"+ -| ++ -| ++ -| - -| +"db.createUniquePropertyConstraint"+ -| +"Create a named unique property constraint. Backing index will use specified index provider and configuration (optional). Yield: name, labels, properties, providerName, status"+ -| ++ -| ++ -| - +| +"db.awaitIndex"+ | +"Wait for an index to come online (for example: CALL db.awaitIndex("MyIndex", 300))."+ | ++ | ++ | +| +"db.awaitIndexes"+ | +"Wait for all indexes to come online (for example: CALL db.awaitIndexes(300))."+ | ++ | ++ | +| +"db.checkpoint"+ | +"Initiate and wait for a new check point, or wait any already on-going check point to complete. Note that this temporarily disables the `dbms.checkpoint.iops.limit` setting in order to make the check point complete faster. This might cause transaction throughput to degrade slightly, due to increased IO load."+ | ++ | ++ | +| +"db.constraints"+ | +"List all constraints in the database."+ | ++ | ++ | +| +"db.createIndex"+ | +"Create a named schema index with specified index provider and configuration (optional). Yield: name, labels, properties, providerName, status"+ | ++ | ++ | +| +"db.createLabel"+ | +"Create a label"+ | ++ | ++ | +| +"db.createNodeKey"+ | +"Create a named node key constraint. Backing index will use specified index provider and configuration (optional). Yield: name, labels, properties, providerName, status"+ | ++ | ++ | +| +"db.createProperty"+ | +"Create a Property"+ | ++ | ++ | +| +"db.createRelationshipType"+ | +"Create a RelationshipType"+ | ++ | ++ | +| +"db.createUniquePropertyConstraint"+ | +"Create a named unique property constraint. Backing index will use specified index provider and configuration (optional). Yield: name, labels, properties, providerName, status"+ | ++ | ++ | 5+d|Rows: 10 |=== -Note that the two `roles` columns are empty due to missing the xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[`SHOW ROLE`] privilege. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + +Note that the two `roles` columns are empty due to missing the xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[SHOW ROLE] privilege. The second option, filters the list to only contain procedures executable by a specific user: .Query -[source, cypher, indent=0] +[source, cypher] ---- SHOW PROCEDURES EXECUTABLE BY jake ---- @@ -336,57 +261,29 @@ SHOW PROCEDURES EXECUTABLE BY jake [role="queryresult",options="header,footer",cols="4* +Try this query live + +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/load-csv.adoc b/modules/ROOT/pages/clauses/load-csv.adoc index 92a9d0ad3..090de0edd 100644 --- a/modules/ROOT/pages/clauses/load-csv.adoc +++ b/modules/ROOT/pages/clauses/load-csv.adoc @@ -1,39 +1,30 @@ -:description: `LOAD CSV` is used to import data from CSV files. - [[query-load-csv]] = LOAD CSV +:description: `LOAD CSV` is used to import data from CSV files. -[abstract] --- -`LOAD CSV` is used to import data from CSV files. --- - -* xref::clauses/load-csv.adoc#query-load-csv-introduction[Introduction] -* xref::clauses/load-csv.adoc#csv-file-format[CSV file format] -* xref::clauses/load-csv.adoc#load-csv-import-data-from-a-csv-file[Import data from a CSV file] -* xref::clauses/load-csv.adoc#load-csv-import-data-from-a-remote-csv-file[Import data from a remote CSV file] -* xref::clauses/load-csv.adoc#load-csv-import-data-from-a-csv-file-containing-headers[Import data from a CSV file containing headers] -* xref::clauses/load-csv.adoc#load-csv-import-data-from-a-csv-file-with-a-custom-field-delimiter[Import data from a CSV file with a custom field delimiter] -* xref::clauses/load-csv.adoc#load-csv-importing-large-amounts-of-data[Importing large amounts of data] -* xref::clauses/load-csv.adoc#load-csv-setting-the-rate-of-periodic-commits[Setting the rate of periodic commits] -* xref::clauses/load-csv.adoc#load-csv-import-data-containing-escaped-characters[Import data containing escaped characters] -* xref::clauses/load-csv.adoc#load-csv-using-linenumber-with-load-csv[Using linenumber() with LOAD CSV] -* xref::clauses/load-csv.adoc#load-csv-using-file-with-load-csv[Using file() with LOAD CSV] +* xref:clauses/load-csv.adoc#query-load-csv-introduction[Introduction] +* xref:clauses/load-csv.adoc#csv-file-format[CSV file format] +* xref:clauses/load-csv.adoc#load-csv-import-data-from-a-csv-file[Import data from a CSV file] +* xref:clauses/load-csv.adoc#load-csv-import-data-from-a-remote-csv-file[Import data from a remote CSV file] +* xref:clauses/load-csv.adoc#load-csv-import-data-from-a-csv-file-containing-headers[Import data from a CSV file containing headers] +* xref:clauses/load-csv.adoc#load-csv-import-data-from-a-csv-file-with-a-custom-field-delimiter[Import data from a CSV file with a custom field delimiter] +* xref:clauses/load-csv.adoc#load-csv-importing-large-amounts-of-data[Importing large amounts of data] +* xref:clauses/load-csv.adoc#load-csv-setting-the-rate-of-periodic-commits[Setting the rate of periodic commits] +* xref:clauses/load-csv.adoc#load-csv-import-data-containing-escaped-characters[Import data containing escaped characters] +* xref:clauses/load-csv.adoc#load-csv-using-linenumber-with-load-csv[Using linenumber() with LOAD CSV] +* xref:clauses/load-csv.adoc#load-csv-using-file-with-load-csv[Using file() with LOAD CSV] [[query-load-csv-introduction]] == Introduction -:url_encoded_link: link:https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding[] -:url_encoded_foot_note: footnote:[See {url_encoded_link}] - * The URL of the CSV file is specified by using `FROM` followed by an arbitrary expression evaluating to the URL in question. * It is required to specify a variable for the CSV data using `AS`. * CSV files can be stored on the database server and are then accessible using a `+file:///+` URL. Alternatively, `LOAD CSV` also supports accessing CSV files via _HTTPS_, _HTTP_, and _FTP_. * `LOAD CSV` supports resources compressed with _gzip_ and _Deflate_. Additionally `LOAD CSV` supports locally stored CSV files compressed with _ZIP_. * `LOAD CSV` will follow _HTTP_ redirects but for security reasons it will not follow redirects that changes the protocol, for example if the redirect is going from _HTTPS_ to _HTTP_. -* `LOAD CSV` is often used in conjunction with the query hint `PERIODIC COMMIT`; more information on this may be found in xref::query-tuning/using.adoc#query-using-periodic-commit-hint[[deprecated\]#`PERIODIC COMMIT` query hint]. +* `LOAD CSV` is often used in conjunction with the query hint `PERIODIC COMMIT`; more information on this may be found in xref:query-tuning/using.adoc#query-using-periodic-commit-hint[`PERIODIC COMMIT` query hint]. .Configuration settings for file URLs link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_dbms.security.allow_csv_import_from_file_urls[dbms.security.allow_csv_import_from_file_urls]:: @@ -56,7 +47,7 @@ This is definitely not recommended. File URLs will be resolved relative to the `dbms.directories.import` directory. For example, a file URL will typically look like `+file:///myfile.csv+` or `+file:///myproject/myfile.csv+`. -* When using `+file:///+` URLs, spaces and other non-alphanumeric characters need to be URL encoded.{url_encoded_foot_note} +* When using `+file:///+` URLs, spaces and other non-alphanumeric characters need to be URL encoded. footnote:[See https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding]. * If `dbms.directories.import` is set to the default value _import_, using the above URLs in `LOAD CSV` would read from _/import/myfile.csv_ and _/import/myproject/myfile.csv_ respectively. * If it is set to _/data/csv_, using the above URLs in `LOAD CSV` would read from _/data/csv/myfile.csv_ and _/data/csv/myproject/myfile.csv_ respectively. @@ -83,12 +74,12 @@ The CSV file to use with `LOAD CSV` must have the following characteristics: * a double quote must be in a quoted string and escaped, either with the escape character or a second double quote. [[load-csv-import-data-from-a-csv-file]] -== Import data from a CSV file +== Import data from a CSV file == To import data from a CSV file into Neo4j, you can use `LOAD CSV` to get the data into your query. Then you write it to your database using the normal updating clauses of Cypher. .artists.csv -[source, csv, role="noheader"] +[source] ---- 1,ABBA,1992 2,Roxette,1986 @@ -97,12 +88,13 @@ Then you write it to your database using the normal updating clauses of Cypher. ---- .Query -[source, cypher, subs=attributes+, indent=0] +[source, cypher, subs=attributes+] ---- LOAD CSV FROM 'file:///artists.csv' AS line CREATE (:Artist {name: line[1], year: toInteger(line[2])}) ---- + A new node with the `Artist` label is created for each row in the CSV file. In addition, two columns from the CSV file are set as properties on the nodes. @@ -118,14 +110,24 @@ Labels added: 4 ---- +.Try this query live +[console] +---- +none + +LOAD CSV FROM 'file:///artists.csv' AS line +CREATE (:Artist {name: line[1], year: toInteger(line[2])}) +---- + + [[load-csv-import-data-from-a-remote-csv-file]] -== Import data from a remote CSV file +== Import data from a remote CSV file == Accordingly, you can import data from a CSV file in a remote location into Neo4j. Note that this applies to all variations of CSV files (see examples below for other variations). .data.neo4j.com/bands/artists.csv -[source, csv, role="noheader"] +[source] ---- 1,ABBA,1992 2,Roxette,1986 @@ -133,13 +135,15 @@ Note that this applies to all variations of CSV files (see examples below for ot 4,The Cardigans,1992 ---- + .Query -[source, cypher, subs=attributes+, indent=0] +[source, cypher, subs=attributes+] ---- -LOAD CSV FROM 'https://data.neo4j.com/bands/artists.csv' AS line -CREATE (:Artist {name: line[1], year: toInteger(line[2])}) +LOAD CSV FROM 'https://data.neo4j.com/bands/artists.csv' AS line CREATE (:Artist {name: line[1], + year: toInteger(line[2])}) ---- + .Result [queryresult] ---- @@ -152,8 +156,17 @@ Labels added: 4 ---- +.Try this query live +[console] +---- +none + +LOAD CSV FROM 'https://data.neo4j.com/bands/artists.csv' AS line CREATE (:Artist {name: line[1], year: toInteger(line[2])}) +---- + + [[load-csv-import-data-from-a-csv-file-containing-headers]] -== Import data from a CSV file containing headers +== Import data from a CSV file containing headers == When your CSV file has headers, you can view each row in the file as a map instead of as an array of strings. .artists-with-headers.csv @@ -167,12 +180,13 @@ Id,Name,Year ---- .Query -[source, cypher, subs=attributes+, indent=0] +[source, cypher, subs=attributes+] ---- LOAD CSV WITH HEADERS FROM 'file:///artists-with-headers.csv' AS line CREATE (:Artist {name: line.Name, year: toInteger(line.Year)}) ---- + This time, the file starts with a single row containing column names. Indicate this using `WITH HEADERS` and you can access specific fields by their corresponding column name. @@ -188,8 +202,18 @@ Labels added: 4 ---- +.Try this query live +[console] +---- +none + +LOAD CSV WITH HEADERS FROM 'file:///artists-with-headers.csv' AS line +CREATE (:Artist {name: line.Name, year: toInteger(line.Year)}) +---- + + [[load-csv-import-data-from-a-csv-file-with-a-custom-field-delimiter]] -== Import data from a CSV file with a custom field delimiter +== Import data from a CSV file with a custom field delimiter == Sometimes, your CSV file has other field delimiters than commas. You can specify which delimiter your file uses, using `FIELDTERMINATOR`. Hexadecimal representation of the unicode character encoding can be used if prepended by `{backslash}u`. @@ -205,13 +229,15 @@ For example, `{backslash}u003B` is equivalent to `;` (SEMICOLON). 4;The Cardigans;1992 ---- + .Query -[source, cypher, subs=attributes+, indent=0] +[source, cypher, subs=attributes+] ---- LOAD CSV FROM 'file:///artists-fieldterminator.csv' AS line FIELDTERMINATOR ';' CREATE (:Artist {name: line[1], year: toInteger(line[2])}) ---- + As values in this file are separated by a semicolon, a custom `FIELDTERMINATOR` is specified in the `LOAD CSV` clause. .Result @@ -226,31 +252,33 @@ Labels added: 4 ---- +.Try this query live +[console] +---- +none + +LOAD CSV FROM 'file:///artists-fieldterminator.csv' AS line FIELDTERMINATOR ';' +CREATE (:Artist {name: line[1], year: toInteger(line[2])}) +---- + + [[load-csv-importing-large-amounts-of-data]] -== Importing large amounts of data +== Importing large amounts of data == If the CSV file contains a significant number of rows (approaching hundreds of thousands or millions), `USING PERIODIC COMMIT` can be used to instruct Neo4j to perform a commit after a number of rows. This reduces the memory overhead of the transaction state. -By default, the commit happens every 1000 rows. -Note that `PERIODIC COMMIT` is only allowed in xref::introduction/transactions.adoc[implicit (auto-commit or `:auto`) transactions]. -For more information, see xref::query-tuning/using.adoc#query-using-periodic-commit-hint[[deprecated\]#`PERIODIC COMMIT` query hint]. - -[NOTE] -==== -The xref::clauses/use.adoc[`USE` clause] can not be used together with the `PERIODIC COMMIT` query hint. -==== +By default, the commit will happen every 1000 rows. +For more information, see xref:query-tuning/using.adoc#query-using-periodic-commit-hint[`PERIODIC COMMIT` query hint]. -[NOTE] -==== -Queries with the `PERIODIC COMMIT` query hint can not be routed by link:{neo4j-docs-base-uri}/operations-manual/{page-version}/clustering/internals#causal-clustering-routing[Server-side routing]. -==== +Note: The xref:clauses/use.adoc[`USE` clause] can not be used together with the `PERIODIC COMMIT` clause. .Query -[source, cypher, subs=attributes+, indent=0] +[source, cypher, subs=attributes+] ---- USING PERIODIC COMMIT LOAD CSV FROM 'file:///artists.csv' AS line CREATE (:Artist {name: line[1], year: toInteger(line[2])}) ---- + .Result [queryresult] ---- @@ -263,17 +291,28 @@ Labels added: 4 ---- +.Try this query live +[console] +---- +none + +USING PERIODIC COMMIT LOAD CSV FROM 'file:///artists.csv' AS line +CREATE (:Artist {name: line[1], year: toInteger(line[2])}) +---- + + [[load-csv-setting-the-rate-of-periodic-commits]] -== Setting the rate of periodic commits +== Setting the rate of periodic commits == You can set the number of rows as in the example, where it is set to 500 rows. .Query -[source, cypher, subs=attributes+, indent=0] +[source, cypher, subs=attributes+] ---- USING PERIODIC COMMIT 500 LOAD CSV FROM 'file:///artists.csv' AS line CREATE (:Artist {name: line[1], year: toInteger(line[2])}) ---- + .Result [queryresult] ---- @@ -286,8 +325,18 @@ Labels added: 4 ---- +.Try this query live +[console] +---- +none + +USING PERIODIC COMMIT 500 LOAD CSV FROM 'file:///artists.csv' AS line +CREATE (:Artist {name: line[1], year: toInteger(line[2])}) +---- + + [[load-csv-import-data-containing-escaped-characters]] -== Import data containing escaped characters +== Import data containing escaped characters == In this example, we both have additional quotes around the values, as well as escaped quotes inside one value. .artists-with-escaped-char.csv @@ -297,7 +346,7 @@ In this example, we both have additional quotes around the values, as well as es ---- .Query -[source, cypher, subs=attributes+, indent=0] +[source, cypher, subs=attributes+] ---- LOAD CSV FROM 'file:///artists-with-escaped-char.csv' AS line CREATE (a:Artist {name: line[1], year: toInteger(line[2])}) @@ -307,6 +356,7 @@ RETURN size(a.name) AS size ---- + Note that strings are wrapped in quotes in the output here. You can see that when comparing to the length of the string in this case! @@ -325,8 +375,22 @@ Labels added: 1 ---- +.Try this query live +[console] +---- +none + +LOAD CSV FROM 'file:///artists-with-escaped-char.csv' AS line +CREATE (a:Artist {name: line[1], year: toInteger(line[2])}) +RETURN + a.name AS name, + a.year AS year, + size(a.name) AS size +---- + + [[load-csv-using-linenumber-with-load-csv]] -== Using `linenumber()` with LOAD CSV +== Using linenumber() with LOAD CSV == For certain scenarios, like debugging a problem with a csv file, it may be useful to get the current line number that `LOAD CSV` is operating on. The `linenumber()` function provides exactly that or `null` if called without a `LOAD CSV` context. @@ -339,13 +403,15 @@ The `linenumber()` function provides exactly that or `null` if called without a 4,The Cardigans,1992 ---- + .Query -[source, cypher, subs=attributes+, indent=0] +[source, cypher, subs=attributes+] ---- LOAD CSV FROM 'file:///artists.csv' AS line RETURN linenumber() AS number, line ---- + .Result [queryresult] ---- @@ -361,8 +427,21 @@ RETURN linenumber() AS number, line ---- +.Try this query live +[console] +---- +none + +LOAD CSV FROM 'file:///artists.csv' AS line +RETURN linenumber() AS number, line +---- + + +//This example was outputting the team city path +//include::using-file-with-load-csv.adoc[] + [[load-csv-using-file-with-load-csv]] -== Using `file()` with LOAD CSV +== Using file() with LOAD CSV == For certain scenarios, like debugging a problem with a csv file, it may be useful to get the absolute path of the file that `LOAD CSV` is operating on. The `file()` function provides exactly that or `null` if called without a `LOAD CSV` context. @@ -376,7 +455,7 @@ The `file()` function provides exactly that or `null` if called without a `LOAD ---- .Query -[source, cypher, subs=attributes+, indent=0] +[source, cypher, subs=attributes+] ---- LOAD CSV FROM 'file:///artists.csv' AS line RETURN DISTINCT file() AS path @@ -395,4 +474,3 @@ If `LOAD CSV` is invoked with a `file:///` URL that points to your disk `file()` +------------------------------------------+ 1 row ---- - diff --git a/modules/ROOT/pages/clauses/match.adoc b/modules/ROOT/pages/clauses/match.adoc index 808b9c7d2..ab4a83ad7 100644 --- a/modules/ROOT/pages/clauses/match.adoc +++ b/modules/ROOT/pages/clauses/match.adoc @@ -1,51 +1,46 @@ -:description: The `MATCH` clause is used to search for the pattern described in it. - [[query-match]] = MATCH - -[abstract] --- -The `MATCH` clause is used to search for the pattern described in it. --- - -* xref::clauses/match.adoc#match-introduction[Introduction] -* xref::clauses/match.adoc#basic-node-finding[Basic node finding] - ** xref::clauses/match.adoc#get-all-nodes[Get all nodes] - ** xref::clauses/match.adoc#get-all-nodes-with-label[Get all nodes with a label] - ** xref::clauses/match.adoc#related-nodes[Related nodes] - ** xref::clauses/match.adoc#match-with-labels[Match with labels] -* xref::clauses/match.adoc#relationship-basics[Relationship basics] - ** xref::clauses/match.adoc#outgoing-relationships[Outgoing relationships] - ** xref::clauses/match.adoc#directed-rels-and-variable[Directed relationships and variable] - ** xref::clauses/match.adoc#match-on-rel-type[Match on relationship type] - ** xref::clauses/match.adoc#match-on-multiple-rel-types[Match on multiple relationship types] - ** xref::clauses/match.adoc#match-on-rel-type-use-variable[Match on relationship type and use a variable] -* xref::clauses/match.adoc#relationships-in-depth[Relationships in depth] - ** xref::clauses/match.adoc#rel-types-with-uncommon-chars[Relationship types with uncommon characters] - ** xref::clauses/match.adoc#multiple-rels[Multiple relationships] - ** xref::clauses/match.adoc#varlength-rels[Variable length relationships] - ** xref::clauses/match.adoc#varlength-rels-multiple-types[Variable length relationships with multiple relationship types] - ** xref::clauses/match.adoc#rel-variable-in-varlength-rels[Relationship variable in variable length relationships] - ** xref::clauses/match.adoc#match-props-on-varlength-path[Match with properties on a variable length path] - ** xref::clauses/match.adoc#zero-length-paths[Zero length paths] - ** xref::clauses/match.adoc#named-paths[Named paths] - ** xref::clauses/match.adoc#match-on-bound-rel[Matching on a bound relationship] -* xref::clauses/match.adoc#query-shortest-path[Shortest path] - ** xref::clauses/match.adoc#single-shortest-path[Single shortest path] - ** xref::clauses/match.adoc#single-shortest-path-with-predicates[Single shortest path with predicates] - ** xref::clauses/match.adoc#all-shortest-paths[All shortest paths] -* xref::clauses/match.adoc#get-node-rel-by-id[Get node or relationship by ID] - ** xref::clauses/match.adoc#match-node-by-id[Node by ID] - ** xref::clauses/match.adoc#match-rel-by-id[Relationship by ID] - ** xref::clauses/match.adoc#match-multiple-nodes-by-id[Multiple nodes by ID] - +:description: The `MATCH` clause is used to search for the pattern described in it. + + +* xref:clauses/match.adoc#match-introduction[Introduction] +* xref:clauses/match.adoc#basic-node-finding[Basic node finding] + ** xref:clauses/match.adoc#get-all-nodes[Get all nodes] + ** xref:clauses/match.adoc#get-all-nodes-with-label[Get all nodes with a label] + ** xref:clauses/match.adoc#related-nodes[Related nodes] + ** xref:clauses/match.adoc#match-with-labels[Match with labels] +* xref:clauses/match.adoc#relationship-basics[Relationship basics] + ** xref:clauses/match.adoc#outgoing-relationships[Outgoing relationships] + ** xref:clauses/match.adoc#directed-rels-and-variable[Directed relationships and variable] + ** xref:clauses/match.adoc#match-on-rel-type[Match on relationship type] + ** xref:clauses/match.adoc#match-on-multiple-rel-types[Match on multiple relationship types] + ** xref:clauses/match.adoc#match-on-rel-type-use-variable[Match on relationship type and use a variable] +* xref:clauses/match.adoc#relationships-in-depth[Relationships in depth] + ** xref:clauses/match.adoc#rel-types-with-uncommon-chars[Relationship types with uncommon characters] + ** xref:clauses/match.adoc#multiple-rels[Multiple relationships] + ** xref:clauses/match.adoc#varlength-rels[Variable length relationships] + ** xref:clauses/match.adoc#varlength-rels-multiple-types[Variable length relationships with multiple relationship types] + ** xref:clauses/match.adoc#rel-variable-in-varlength-rels[Relationship variable in variable length relationships] + ** xref:clauses/match.adoc#match-props-on-varlength-path[Match with properties on a variable length path] + ** xref:clauses/match.adoc#zero-length-paths[Zero length paths] + ** xref:clauses/match.adoc#named-paths[Named paths] + ** xref:clauses/match.adoc#match-on-bound-rel[Matching on a bound relationship] +* xref:clauses/match.adoc#query-shortest-path[Shortest path] + ** xref:clauses/match.adoc#single-shortest-path[Single shortest path] + ** xref:clauses/match.adoc#single-shortest-path-with-predicates[Single shortest path with predicates] + ** xref:clauses/match.adoc#all-shortest-paths[All shortest paths] +* xref:clauses/match.adoc#get-node-rel-by-id[Get node or relationship by id] + ** xref:clauses/match.adoc#match-node-by-id[Node by id] + ** xref:clauses/match.adoc#match-rel-by-id[Relationship by id] + ** xref:clauses/match.adoc#match-multiple-nodes-by-id[Multiple nodes by id] + [[match-introduction]] == Introduction The `MATCH` clause allows you to specify the patterns Neo4j will search for in the database. This is the primary way of getting data into the current set of bindings. -It is worth reading up more on the specification of the patterns themselves in xref::syntax/patterns.adoc[Patterns]. +It is worth reading up more on the specification of the patterns themselves in xref:syntax/patterns.adoc[Patterns]. `MATCH` is often coupled to a `WHERE` part which adds restrictions, or predicates, to the `MATCH` patterns, making them more specific. The predicates are part of the pattern description, and should not be considered a filter applied only after the matching is done. @@ -61,34 +56,79 @@ Cypher is declarative, and so usually the query itself does not specify the algo Neo4j will automatically work out the best approach to finding start nodes and matching patterns. Predicates in `WHERE` parts can be evaluated before pattern matching, during pattern matching, or after finding matches. However, there are cases where you can influence the decisions taken by the query compiler. -Read more about indexes in xref::indexes-for-search-performance.adoc[], and more about specifying hints to force Neo4j to solve a query in a specific way in xref::query-tuning/using.adoc[Planner hints and the USING keyword]. +Read more about indexes in xref:indexes-for-search-performance.adoc[], and more about specifying hints to force Neo4j to solve a query in a specific way in xref:query-tuning/using.adoc[Planner hints and the USING keyword]. [TIP] ==== -To understand more about the patterns used in the `MATCH` clause, read xref::syntax/patterns.adoc[Patterns] +To understand more about the patterns used in the `MATCH` clause, read xref:syntax/patterns.adoc[Patterns] + + ==== The following graph is used for the examples below: -image:graph_match_clause.svg[] +.Graph +["dot", "MATCH-3.svg", "neoviz", ""] +---- + N0 [ + label = "{Person|name = \'Charlie Sheen\'\l}" + ] + N0 -> N5 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\nrole = \'Bud Fox\'\l" + ] + N1 [ + label = "{Person|name = \'Martin Sheen\'\l}" + ] + N1 -> N6 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\nrole = \'A.J. MacInerney\'\l" + ] + N1 -> N5 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\nrole = \'Carl Fox\'\l" + ] + N2 [ + label = "{Person|name = \'Michael Douglas\'\l}" + ] + N2 -> N5 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\nrole = \'Gordon Gekko\'\l" + ] + N2 -> N6 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\nrole = \'President Andrew Shepherd\'\l" + ] + N3 [ + label = "{Person|name = \'Oliver Stone\'\l}" + ] + N3 -> N5 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "DIRECTED\n" + ] + N4 [ + label = "{Person|name = \'Rob Reiner\'\l}" + ] + N4 -> N6 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "DIRECTED\n" + ] + N5 [ + label = "{Movie|title = \'Wall Street\'\l}" + ] + N6 [ + label = "{Movie|title = \'The American President\'\l}" + ] -//// -CREATE - (charlie:Person {name: 'Charlie Sheen'}), - (martin:Person {name: 'Martin Sheen'}), - (michael:Person {name: 'Michael Douglas'}), - (oliver:Person {name: 'Oliver Stone'}), - (rob:Person {name: 'Rob Reiner'}), - (wallStreet:Movie {title: 'Wall Street'}), - (charlie)-[:ACTED_IN {role: 'Bud Fox'}]->(wallStreet), - (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), - (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), - (oliver)-[:DIRECTED]->(wallStreet), - (thePresident:Movie {title: 'The American President'}), - (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), - (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), - (rob)-[:DIRECTED]->(thePresident) -//// +---- + [[basic-node-finding]] == Basic node finding @@ -98,8 +138,9 @@ CREATE By just specifying a pattern with a single node and no labels, all nodes in the graph will be returned. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n @@ -121,14 +162,43 @@ Returns all the nodes in the database. 1+d|Rows: 7 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]> +++++ +endif::nonhtmloutput[] [[get-all-nodes-with-label]] === Get all nodes with a label Getting all nodes with a label on them is done with a single node pattern where the node has a label on it. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (movie:Movie) RETURN movie.title @@ -145,14 +215,43 @@ Returns all the movies in the database. 1+d|Rows: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]> +++++ +endif::nonhtmloutput[] [[related-nodes]] === Related nodes The symbol `--` means _related to,_ without regard to type or direction of the relationship. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (director {name: 'Oliver Stone'})--(movie) RETURN movie.title @@ -168,14 +267,43 @@ Returns all the movies directed by *'Oliver Stone'*. 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]> +++++ +endif::nonhtmloutput[] [[match-with-labels]] === Match with labels To constrain your pattern with labels on nodes, you add it to your pattern nodes, using the label syntax. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (:Person {name: 'Oliver Stone'})--(movie:Movie) RETURN movie.title @@ -191,6 +319,34 @@ Returns any nodes connected with the `Person` *'Oliver'* that are labeled `Movie 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]> +++++ +endif::nonhtmloutput[] [[relationship-basics]] == Relationship basics @@ -200,8 +356,9 @@ Returns any nodes connected with the `Person` *'Oliver'* that are labeled `Movie When the direction of a relationship is of interest, it is shown by using `+-->+` or `+<--+`, like this: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (:Person {name: 'Oliver Stone'})-->(movie) RETURN movie.title @@ -217,14 +374,43 @@ Returns any nodes connected with the `Person` *'Oliver'* by an outgoing relation 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]>(movie) +RETURN movie.title +]]> +++++ +endif::nonhtmloutput[] [[directed-rels-and-variable]] === Directed relationships and variable If a variable is required, either for filtering on properties of the relationship, or to return the relationship, this is how you introduce the variable. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (:Person {name: 'Oliver Stone'})-[r]->(movie) RETURN type(r) @@ -240,14 +426,43 @@ Returns the type of each outgoing relationship from *'Oliver'*. 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]>(movie) +RETURN type(r) +]]> +++++ +endif::nonhtmloutput[] [[match-on-rel-type]] === Match on relationship type When you know the relationship type you want to match on, you can specify it by using a colon together with the relationship type. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (wallstreet:Movie {title: 'Wall Street'})<-[:ACTED_IN]-(actor) RETURN actor.name @@ -265,14 +480,43 @@ Returns all actors that `ACTED_IN` *'Wall Street'*. 1+d|Rows: 3 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]> +++++ +endif::nonhtmloutput[] [[match-on-multiple-rel-types]] === Match on multiple relationship types To match on one of multiple types, you can specify this by chaining them together with the pipe symbol `|`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (wallstreet {title: 'Wall Street'})<-[:ACTED_IN|DIRECTED]-(person) RETURN person.name @@ -291,14 +535,43 @@ Returns nodes with an `ACTED_IN` or `DIRECTED` relationship to *'Wall Street'*. 1+d|Rows: 4 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]> +++++ +endif::nonhtmloutput[] [[match-on-rel-type-use-variable]] === Match on relationship type and use a variable If you both want to introduce an variable to hold the relationship, and specify the relationship type you want, just add them both, like this: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (wallstreet {title: 'Wall Street'})<-[r:ACTED_IN]-(actor) RETURN r.role @@ -316,24 +589,55 @@ Returns `ACTED_IN` roles for *'Wall Street'*. 1+d|Rows: 3 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]> +++++ +endif::nonhtmloutput[] [[relationships-in-depth]] == Relationships in depth [NOTE] ==== -Inside a single pattern, relationships will only be matched once. You can read more about this in xref::introduction/uniqueness.adoc[]. +Inside a single pattern, relationships will only be matched once. You can read more about this in xref:introduction/uniqueness.adoc[]. + + ==== [[rel-types-with-uncommon-chars]] === Relationship types with uncommon characters Sometimes your database will have types with non-letter characters, or with spaces in them. -Use ``` (backtick) to quote these. -To demonstrate this we can add an additional relationship between *'Charlie Sheen'* and *'Rob Reiner'*: + Use ``` (backtick) to quote these. + To demonstrate this we can add an additional relationship between *'Charlie Sheen'* and *'Rob Reiner'*: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (charlie:Person {name: 'Charlie Sheen'}), @@ -341,11 +645,82 @@ MATCH CREATE (rob)-[:`TYPE INCLUDING A SPACE`]->(charlie) ---- -Which leads to the following graph: +Which leads to the following graph: -image:graph_match_clause_backtick.svg[] +.Graph +["dot", "MATCH-1.svg", "neoviz", ""] +---- + N0 [ + label = "{Person|name = \'Charlie Sheen\'\l}" + ] + N0 -> N5 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\nrole = \'Bud Fox\'\l" + ] + N1 [ + label = "{Person|name = \'Martin Sheen\'\l}" + ] + N1 -> N6 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\nrole = \'A.J. MacInerney\'\l" + ] + N1 -> N5 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\nrole = \'Carl Fox\'\l" + ] + N2 [ + label = "{Person|name = \'Michael Douglas\'\l}" + ] + N2 -> N5 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\nrole = \'Gordon Gekko\'\l" + ] + N2 -> N6 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\nrole = \'President Andrew Shepherd\'\l" + ] + N3 [ + label = "{Person|name = \'Oliver Stone\'\l}" + ] + N3 -> N5 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "DIRECTED\n" + ] + N4 [ + label = "{Person|name = \'Rob Reiner\'\l}" + ] + N4 -> N0 [ + color = "#a40000" + fontcolor = "#a40000" + label = "TYPE INCLUDING A SPACE\n" + ] + N4 -> N6 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "DIRECTED\n" + ] + N5 [ + label = "{Movie|title = \'Wall Street\'\l}" + ] + N6 [ + label = "{Movie|title = \'The American President\'\l}" + ] -//// +---- + + +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(thePresident), (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) + +]]>(charlie) -//// +]]> +++++ +endif::nonhtmloutput[] + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'Rob Reiner'})-[r:`TYPE INCLUDING A SPACE`]->() RETURN type(r) @@ -384,13 +764,12 @@ Returns a relationship type with spaces in it. 1+d|Rows: 1 |=== - -[[multiple-rels]] -=== Multiple relationships - -Relationships can be expressed by using multiple statements in the form of `()--()`, or they can be strung together, like this: - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(thePresident), (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) -//// +MATCH + (charlie:Person {name: 'Charlie Sheen'}), + (rob:Person {name: 'Rob Reiner'}) +CREATE (rob)-[:`TYPE INCLUDING A SPACE`]->(charlie) +]]>() +RETURN type(r) +]]> +++++ +endif::nonhtmloutput[] + +[[multiple-rels]] +=== Multiple relationships + +Relationships can be expressed by using multiple statements in the form of `()--()`, or they can be strung together, like this: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (charlie {name: 'Charlie Sheen'})-[:ACTED_IN]->(movie)<-[:DIRECTED]-(director) RETURN movie.title, director.name @@ -425,17 +819,12 @@ Returns the movie *'Charlie Sheen'* acted in and its director. 2+d|Rows: 1 |=== - -[[varlength-rels]] -=== Variable length relationships - -Nodes that are a variable number of `+relationship->node+` hops away can be found using the following syntax: -`+-[:TYPE*minHops..maxHops]->+`. -`minHops` and `maxHops` are optional and default to 1 and infinity respectively. -When no bounds are given the dots may be omitted. -The dots may also be omitted when setting only one bound and this implies a fixed length pattern. - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(thePresident), (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) -//// + +]]>(movie)<-[:DIRECTED]-(director) +RETURN movie.title, director.name +]]> +++++ +endif::nonhtmloutput[] + +[[varlength-rels]] +=== Variable length relationships + +Nodes that are a variable number of `+relationship->node+` hops away can be found using the following syntax: +`+-[:TYPE*minHops..maxHops]->+`. +`minHops` and `maxHops` are optional and default to 1 and infinity respectively. +When no bounds are given the dots may be omitted. +The dots may also be omitted when setting only one bound and this implies a fixed length pattern. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (charlie {name: 'Charlie Sheen'})-[:ACTED_IN*1..3]-(movie:Movie) RETURN movie.title @@ -472,13 +877,12 @@ Returns all movies related to *'Charlie Sheen'* by 1 to 3 hops. 1+d|Rows: 3 |=== - -[[varlength-rels-multiple-types]] -=== Variable length relationships with multiple relationship types - -Variable length relationships can be combined with multiple relationship types. In this case the `*minHops..maxHops` applies to all relationship types as well as any combination of them. - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(thePresident), (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) -//// + +]]> +++++ +endif::nonhtmloutput[] + +[[varlength-rels-multiple-types]] +=== Variable length relationships with multiple relationship types + +Variable length relationships can be combined with multiple relationship types. In this case the `*minHops..maxHops` applies to all relationship types as well as any combination of them. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (charlie {name: 'Charlie Sheen'})-[:ACTED_IN|DIRECTED*2]-(person:Person) RETURN person.name @@ -515,13 +931,12 @@ Returns all people related to *'Charlie Sheen'* by 2 hops with any combination o 1+d|Rows: 3 |=== - -[[rel-variable-in-varlength-rels]] -=== Relationship variable in variable length relationships - -When the connection between two nodes is of variable length, the list of relationships comprising the connection can be returned using the following syntax: - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(thePresident), (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) -//// + +]]> +++++ +endif::nonhtmloutput[] + +[[rel-variable-in-varlength-rels]] +=== Relationship variable in variable length relationships + +When the connection between two nodes is of variable length, the list of relationships comprising the connection can be returned using the following syntax: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH p = (actor {name: 'Charlie Sheen'})-[:ACTED_IN*2]-(co_actor) RETURN relationships(p) @@ -557,16 +984,12 @@ Returns a list of relationships. 1+d|Rows: 2 |=== - -[[match-props-on-varlength-path]] -=== Match with properties on a variable length path - -A variable length relationship with properties defined on in it means that all relationships in the path must have the property set to the given value. -In this query, there are two paths between *'Charlie Sheen'* and his father *'Martin Sheen'*. -One of them includes a *'blocked'* relationship and the other does not. -In this case we first alter the original graph by using the following query to add `BLOCKED` and `UNBLOCKED` relationships: - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(thePresident), (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) -//// + +]]> +++++ +endif::nonhtmloutput[] + +[[match-props-on-varlength-path]] +=== Match with properties on a variable length path + +A variable length relationship with properties defined on in it means that all relationships in the path must have the property set to the given value. +In this query, there are two paths between *'Charlie Sheen'* and his father *'Martin Sheen'*. +One of them includes a *'blocked'* relationship and the other does not. +In this case we first alter the original graph by using the following query to add `BLOCKED` and `UNBLOCKED` relationships: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (charlie:Person {name: 'Charlie Sheen'}), @@ -594,11 +1032,103 @@ CREATE (charlie)-[:X {blocked: false}]->(:UNBLOCKED)<-[:X {blocked: false}]-(mar CREATE (charlie)-[:X {blocked: true}]->(:BLOCKED)<-[:X {blocked: false}]-(martin) ---- -This means that we are starting out with the following graph: +This means that we are starting out with the following graph: -image:graph_match_clause_variable_length.svg[] +.Graph +["dot", "MATCH-2.svg", "neoviz", ""] +---- + N0 [ + label = "{Person|name = \'Charlie Sheen\'\l}" + ] + N0 -> N7 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "X\nblocked = false\l" + ] + N0 -> N8 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "X\nblocked = true\l" + ] + N0 -> N5 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\nrole = \'Bud Fox\'\l" + ] + N1 [ + label = "{Person|name = \'Martin Sheen\'\l}" + ] + N1 -> N8 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "X\nblocked = false\l" + ] + N1 -> N7 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "X\nblocked = false\l" + ] + N1 -> N6 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\nrole = \'A.J. MacInerney\'\l" + ] + N1 -> N5 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\nrole = \'Carl Fox\'\l" + ] + N2 [ + label = "{Person|name = \'Michael Douglas\'\l}" + ] + N2 -> N5 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\nrole = \'Gordon Gekko\'\l" + ] + N2 -> N6 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\nrole = \'President Andrew Shepherd\'\l" + ] + N3 [ + label = "{Person|name = \'Oliver Stone\'\l}" + ] + N3 -> N5 [ + color = "#a40000" + fontcolor = "#a40000" + label = "DIRECTED\n" + ] + N4 [ + label = "{Person|name = \'Rob Reiner\'\l}" + ] + N4 -> N6 [ + color = "#a40000" + fontcolor = "#a40000" + label = "DIRECTED\n" + ] + N5 [ + label = "{Movie|title = \'Wall Street\'\l}" + ] + N6 [ + label = "{Movie|title = \'The American President\'\l}" + ] + N7 [ + label = "{UNBLOCKED|}" + ] + N8 [ + label = "{BLOCKED|}" + ] -//// +---- + + +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(thePresident), (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) + +]]>(:UNBLOCKED)<-[:X {blocked: false}]-(martin) CREATE (charlie)-[:X {blocked: true}]->(:BLOCKED)<-[:X {blocked: false}]-(martin) -//// +]]> +++++ +endif::nonhtmloutput[] + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH p = (charlie:Person)-[* {blocked:false}]-(martin:Person) WHERE charlie.name = 'Charlie Sheen' AND martin.name = 'Martin Sheen' @@ -639,15 +1174,12 @@ Returns the paths between *'Charlie Sheen'* and *'Martin Sheen'* where all relat 1+d|Rows: 1 |=== - -[[zero-length-paths]] -=== Zero length paths - -Using variable length paths that have the lower bound zero means that two variables can point to the same node. -If the path length between two nodes is zero, they are by definition the same node. -Note that when matching zero length paths the result may contain a match even when matching on a relationship type not in use. - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(thePresident), (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) -//// +MATCH + (charlie:Person {name: 'Charlie Sheen'}), + (martin:Person {name: 'Martin Sheen'}) +CREATE (charlie)-[:X {blocked: false}]->(:UNBLOCKED)<-[:X {blocked: false}]-(martin) +CREATE (charlie)-[:X {blocked: true}]->(:BLOCKED)<-[:X {blocked: false}]-(martin) +]]> +++++ +endif::nonhtmloutput[] + +[[zero-length-paths]] +=== Zero length paths + +Using variable length paths that have the lower bound zero means that two variables can point to the same node. +If the path length between two nodes is zero, they are by definition the same node. +Note that when matching zero length paths the result may contain a match even when matching on a relationship type not in use. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (wallstreet:Movie {title: 'Wall Street'})-[*0..1]-(x) RETURN x @@ -686,13 +1237,12 @@ Returns the movie itself as well as actors and directors one relationship away 1+d|Rows: 5 |=== - -[[named-paths]] -=== Named paths - -If you want to return or filter on a path in your pattern graph, you can a introduce a named path. - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(thePresident), (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) -//// + +]]> +++++ +endif::nonhtmloutput[] + +[[named-paths]] +=== Named paths + +If you want to return or filter on a path in your pattern graph, you can a introduce a named path. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH p = (michael {name: 'Michael Douglas'})-->() RETURN p @@ -728,14 +1290,43 @@ Returns the two paths starting from *'Michael Douglas'* 1+d|Rows: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]>() +RETURN p +]]> +++++ +endif::nonhtmloutput[] [[match-on-bound-rel]] === Matching on a bound relationship When your pattern contains a bound relationship, and that relationship pattern does not specify direction, Cypher will try to match the relationship in both directions. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a)-[r]-(b) WHERE id(r) = 0 @@ -753,6 +1344,35 @@ This returns the two connected nodes, once as the start node, and once as the en 2+d|Rows: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]> +++++ +endif::nonhtmloutput[] [[query-shortest-path]] == Shortest path @@ -762,8 +1382,9 @@ This returns the two connected nodes, once as the start node, and once as the en Finding a single shortest path between two nodes is as easy as using the `shortestPath` function. It is done like this: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (martin:Person {name: 'Martin Sheen'}), @@ -776,7 +1397,7 @@ This means: find a single shortest path between two nodes, as long as the path i Within the parentheses you define a single link of a path -- the starting node, the connecting relationship and the end node. Characteristics describing the relationship like relationship type, max hops and direction are all used when finding the shortest path. If there is a `WHERE` clause following the match of a `shortestPath`, relevant predicates will be included in the `shortestPath`. -If the predicate is a `none()` or `all()` on the relationship elements of the path, it will be used during the search to improve performance (see xref::execution-plans/shortestpath-planning.adoc[Shortest path planning]). +If the predicate is a `none()` or `all()` on the relationship elements of the path, it will be used during the search to improve performance (see xref:execution-plans/shortestpath-planning.adoc[Shortest path planning]). .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]> +++++ +endif::nonhtmloutput[] [[single-shortest-path-with-predicates]] === Single shortest path with predicates Predicates used in the `WHERE` clause that apply to the shortest path pattern are evaluated before deciding what the shortest matching path is. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (charlie:Person {name: 'Charlie Sheen'}), @@ -813,14 +1466,47 @@ This query will find the shortest path between *'Charlie Sheen'* and *'Martin Sh 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]> +++++ +endif::nonhtmloutput[] [[all-shortest-paths]] === All shortest paths Finds all the shortest paths between two nodes. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (martin:Person {name: 'Martin Sheen'} ), @@ -840,24 +1526,58 @@ Finds the two shortest paths between *'Martin Sheen'* and *'Michael Douglas'*. 1+d|Rows: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]> +++++ +endif::nonhtmloutput[] [[get-node-rel-by-id]] -== Get node or relationship by ID +== Get node or relationship by id [[match-node-by-id]] -=== Node by ID +=== Node by id -Searching for nodes by ID can be done with the `id()` function in a predicate. +Searching for nodes by id can be done with the `id()` function in a predicate. [NOTE] ==== -Neo4j reuses its internal IDs when nodes and relationships are deleted. -This means that applications using, and relying on internal Neo4j IDs, are brittle or at risk of making mistakes. -It is therefore recommended to rather use application-generated IDs. +Neo4j reuses its internal ids when nodes and relationships are deleted. +This means that applications using, and relying on internal Neo4j ids, are brittle or at risk of making mistakes. +It is therefore recommended to rather use application-generated ids. + + ==== + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE id(n) = 0 @@ -874,24 +1594,54 @@ The corresponding node is returned. 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]> +++++ +endif::nonhtmloutput[] [[match-rel-by-id]] -=== Relationship by ID +=== Relationship by id -Search for relationships by ID can be done with the `id()` function in a predicate. +Search for relationships by id can be done with the `id()` function in a predicate. This is not the recommended practice. -See xref::clauses/match.adoc#match-node-by-id[Node by ID] for more information on the use of Neo4j IDs. +See xref:clauses/match.adoc#match-node-by-id[Node by id] for more information on the use of Neo4j ids. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH ()-[r]->() WHERE id(r) = 0 RETURN r ---- -The relationship with ID `0` is returned. +The relationship with id `0` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]>() +WHERE id(r) = 0 +RETURN r +]]> +++++ +endif::nonhtmloutput[] [[match-multiple-nodes-by-id]] -=== Multiple nodes by ID +=== Multiple nodes by id + +Multiple nodes are selected by specifying them in an IN clause. -Multiple nodes are selected by specifying them in an `IN`-clause. .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE id(n) IN [0, 3, 5] RETURN n ---- -This returns the nodes listed in the `IN`-expression. +This returns the nodes listed in the `IN` expression. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(wallStreet), + (martin)-[:ACTED_IN {role: 'Carl Fox'}]->(wallStreet), + (michael)-[:ACTED_IN {role: 'Gordon Gekko'}]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN {role: 'A.J. MacInerney'}]->(thePresident), + (michael)-[:ACTED_IN {role: 'President Andrew Shepherd'}]->(thePresident), + (rob)-[:DIRECTED]->(thePresident) + +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/merge.adoc b/modules/ROOT/pages/clauses/merge.adoc index f85978153..ad517c008 100644 --- a/modules/ROOT/pages/clauses/merge.adoc +++ b/modules/ROOT/pages/clauses/merge.adoc @@ -1,37 +1,30 @@ -:description: The `MERGE` clause ensures that a pattern exists in the graph. - [[query-merge]] = MERGE - -[abstract] --- -The `MERGE` clause ensures that a pattern exists in the graph. -Either the pattern already exists, or it needs to be created. --- - -* xref::clauses/merge.adoc#query-merge-introduction[Introduction] -* xref::clauses/merge.adoc#query-merge-node-derived[Merge nodes] -** xref::clauses/merge.adoc#merge-merge-single-node-with-a-label[Merge single node with a label] -** xref::clauses/merge.adoc#merge-merge-single-node-with-properties[Merge single node with properties] -** xref::clauses/merge.adoc#merge-merge-single-node-specifying-both-label-and-property[Merge single node specifying both label and property] -** xref::clauses/merge.adoc#merge-merge-single-node-derived-from-an-existing-node-property[Merge single node derived from an existing node property] -* xref::clauses/merge.adoc#query-merge-on-create-on-match[Use `ON CREATE` and `ON MATCH`] -** xref::clauses/merge.adoc#merge-merge-with-on-create[Merge with `ON CREATE`] -** xref::clauses/merge.adoc#merge-merge-with-on-match[Merge with `ON MATCH`] -** xref::clauses/merge.adoc#merge-merge-with-on-create-and-on-match[Merge with `ON CREATE` and `ON MATCH`] -** xref::clauses/merge.adoc#merge-merge-with-on-match-setting-multiple-properties[Merge with `ON MATCH` setting multiple properties] -* xref::clauses/merge.adoc#query-merge-relationships[Merge relationships] -** xref::clauses/merge.adoc#merge-merge-on-a-relationship[Merge on a relationship] -** xref::clauses/merge.adoc#merge-merge-on-multiple-relationships[Merge on multiple relationships] -** xref::clauses/merge.adoc#merge-merge-on-an-undirected-relationship[Merge on an undirected relationship] -** xref::clauses/merge.adoc#merge-merge-on-a-relationship-between-two-existing-nodes[Merge on a relationship between two existing nodes] -** xref::clauses/merge.adoc#merge-merge-on-a-relationship-between-an-existing-node-and-a-merged-node-derived-from-a-node-property[Merge on a relationship between an existing node and a merged node derived from a node property] -* xref::clauses/merge.adoc#query-merge-using-unique-constraints[Using unique constraints with `MERGE`] -** xref::clauses/merge.adoc#merge-merge-using-unique-constraints-creates-a-new-node-if-no-node-is-found[Merge using unique constraints creates a new node if no node is found] -** xref::clauses/merge.adoc#merge-merge-using-unique-constraints-matches-an-existing-node[Merge using unique constraints matches an existing node] -** xref::clauses/merge.adoc#merge-merge-with-unique-constraints-and-partial-matches[Merge with unique constraints and partial matches] -** xref::clauses/merge.adoc#merge-merge-with-unique-constraints-and-conflicting-matches[Merge with unique constraints and conflicting matches] -* xref::clauses/merge.adoc#merge-using-map-parameters-with-merge[Using map parameters with `MERGE`] +:description: The `MERGE` clause ensures that a pattern exists in the graph. Either the pattern already exists, or it needs to be created. + +* xref:clauses/merge.adoc#query-merge-introduction[Introduction] +* xref:clauses/merge.adoc#query-merge-node-derived[Merge nodes] +** xref:clauses/merge.adoc#merge-merge-single-node-with-a-label[Merge single node with a label] +** xref:clauses/merge.adoc#merge-merge-single-node-with-properties[Merge single node with properties] +** xref:clauses/merge.adoc#merge-merge-single-node-specifying-both-label-and-property[Merge single node specifying both label and property] +** xref:clauses/merge.adoc#merge-merge-single-node-derived-from-an-existing-node-property[Merge single node derived from an existing node property] +* xref:clauses/merge.adoc#query-merge-on-create-on-match[Use `ON CREATE` and `ON MATCH`] +** xref:clauses/merge.adoc#merge-merge-with-on-create[Merge with `ON CREATE`] +** xref:clauses/merge.adoc#merge-merge-with-on-match[Merge with `ON MATCH`] +** xref:clauses/merge.adoc#merge-merge-with-on-create-and-on-match[Merge with `ON CREATE` and `ON MATCH`] +** xref:clauses/merge.adoc#merge-merge-with-on-match-setting-multiple-properties[Merge with `ON MATCH` setting multiple properties] +* xref:clauses/merge.adoc#query-merge-relationships[Merge relationships] +** xref:clauses/merge.adoc#merge-merge-on-a-relationship[Merge on a relationship] +** xref:clauses/merge.adoc#merge-merge-on-multiple-relationships[Merge on multiple relationships] +** xref:clauses/merge.adoc#merge-merge-on-an-undirected-relationship[Merge on an undirected relationship] +** xref:clauses/merge.adoc#merge-merge-on-a-relationship-between-two-existing-nodes[Merge on a relationship between two existing nodes] +** xref:clauses/merge.adoc#merge-merge-on-a-relationship-between-an-existing-node-and-a-merged-node-derived-from-a-node-property[Merge on a relationship between an existing node and a merged node derived from a node property] +* xref:clauses/merge.adoc#query-merge-using-unique-constraints[Using unique constraints with `MERGE`] +** xref:clauses/merge.adoc#merge-merge-using-unique-constraints-creates-a-new-node-if-no-node-is-found[Merge using unique constraints creates a new node if no node is found] +** xref:clauses/merge.adoc#merge-merge-using-unique-constraints-matches-an-existing-node[Merge using unique constraints matches an existing node] +** xref:clauses/merge.adoc#merge-merge-with-unique-constraints-and-partial-matches[Merge with unique constraints and partial matches] +** xref:clauses/merge.adoc#merge-merge-with-unique-constraints-and-conflicting-matches[Merge with unique constraints and conflicting matches] +* xref:clauses/merge.adoc#merge-using-map-parameters-with-merge[Using map parameters with `MERGE`] [[query-merge-introduction]] == Introduction @@ -45,7 +38,9 @@ If there isn't a node with the correct name, a new node will be created and its [NOTE] ==== For performance reasons, creating a schema index on the label or property is highly recommended when using `MERGE`. -See xref::indexes-for-search-performance.adoc[] for more information. +See xref:indexes-for-search-performance.adoc[] for more information. + + ==== When using `MERGE` on full patterns, the behavior is that either the whole pattern matches, or the whole pattern is created. @@ -55,8 +50,10 @@ If partial matches are needed, this can be accomplished by splitting a pattern u [IMPORTANT] ==== Under concurrent updates, `MERGE` only guarantees existence of the `MERGE` pattern, but not uniqueness. -To guarantee uniqueness of nodes with certain properties, a xref::constraints/index.adoc[unique constraint] should be used. -See xref::clauses/merge.adoc#query-merge-using-unique-constraints[Using unique constraints with `MERGE`] to see how `MERGE` can be used in combination with a unique constraint. +To guarantee uniqueness of nodes with certain properties, a xref:constraints/index.adoc[unique constraint] should be used. +See xref:clauses/merge.adoc#query-merge-using-unique-constraints[Using unique constraints with `MERGE`] to see how `MERGE` can be used in combination with a unique constraint. + + ==== As with `MATCH`, `MERGE` can match multiple occurrences of a pattern. @@ -67,29 +64,73 @@ These allow a query to express additional changes to the properties of a node or The following graph is used for the examples below: -image:graph_merge_clause.svg[] - -//// -CREATE CONSTRAINT FOR (person:Person) REQUIRE person.name IS UNIQUE -CREATE CONSTRAINT FOR (movie:Movie) REQUIRE movie.title IS UNIQUE -CREATE - (charlie:Person {name: 'Charlie Sheen', bornIn: 'New York', chauffeurName: 'John Brown'}), - (martin:Person {name: 'Martin Sheen', bornIn: 'Ohio', chauffeurName: 'Bob Brown'}), - (michael:Person {name: 'Michael Douglas', bornIn: 'New Jersey', chauffeurName: 'John Brown'}), - (oliver:Person {name: 'Oliver Stone', bornIn: 'New York', chauffeurName: 'Bill White'}), - (rob:Person {name: 'Rob Reiner', bornIn: 'New York', chauffeurName: 'Ted Green'}), - (wallStreet:Movie {title: 'Wall Street'}), - (theAmericanPresident:Movie {title: 'The American President'}), - (charlie)-[:ACTED_IN]->(wallStreet), - (martin)-[:ACTED_IN]->(wallStreet), - (michael)-[:ACTED_IN]->(wallStreet), - (martin)-[:ACTED_IN]->(theAmericanPresident), - (michael)-[:ACTED_IN]->(theAmericanPresident), - (oliver)-[:ACTED_IN]->(wallStreet), - (rob)-[:ACTED_IN]->(theAmericanPresident), - (charlie)-[:FATHER]->(martin) -//// +.Graph +["dot", "MERGE-1.svg", "neoviz", ""] +---- + N0 [ + label = "{Person|bornIn = \'New York\'\lchauffeurName = \'John Brown\'\lname = \'Charlie Sheen\'\l}" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "FATHER\n" + ] + N0 -> N5 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\n" + ] + N1 [ + label = "{Person|bornIn = \'Ohio\'\lchauffeurName = \'Bob Brown\'\lname = \'Martin Sheen\'\l}" + ] + N1 -> N6 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\n" + ] + N1 -> N5 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\n" + ] + N2 [ + label = "{Person|name = \'Michael Douglas\'\lchauffeurName = \'John Brown\'\lbornIn = \'New Jersey\'\l}" + ] + N2 -> N5 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\n" + ] + N2 -> N6 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\n" + ] + N3 [ + label = "{Person|bornIn = \'New York\'\lchauffeurName = \'Bill White\'\lname = \'Oliver Stone\'\l}" + ] + N3 -> N5 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\n" + ] + N4 [ + label = "{Person|bornIn = \'New York\'\lchauffeurName = \'Ted Green\'\lname = \'Rob Reiner\'\l}" + ] + N4 -> N6 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\n" + ] + N5 [ + label = "{Movie|title = \'Wall Street\'\l}" + ] + N6 [ + label = "{Movie|title = \'The American President\'\l}" + ] +---- + [[query-merge-node-derived]] == Merge nodes @@ -99,8 +140,9 @@ CREATE Merging a single node with the given label. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MERGE (robert:Critic) RETURN robert, labels(robert) @@ -118,20 +160,52 @@ Nodes created: 1 + Labels added: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]> +++++ +endif::nonhtmloutput[] [[merge-merge-single-node-with-properties]] === Merge single node with properties Merging a single node with properties where not all properties match any existing node. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MERGE (charlie {name: 'Charlie Sheen', age: 10}) RETURN charlie ---- -A new node with the name `'Charlie Sheen'` will be created since not all properties matched the existing `'Charlie Sheen'` node. +A new node with the name *'Charlie Sheen'* will be created since not all properties matched the existing *'Charlie Sheen'* node. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]> +++++ +endif::nonhtmloutput[] [[merge-merge-single-node-specifying-both-label-and-property]] === Merge single node specifying both label and property Merging a single node with both label and property matching an existing node. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MERGE (michael:Person {name: 'Michael Douglas'}) RETURN michael.name, michael.bornIn ---- -`'Michael Douglas'` will be matched and the `name` and `bornIn` properties returned. +*'Michael Douglas'* will be matched and the `name` and `bornIn` properties returned. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) +]]> +++++ +endif::nonhtmloutput[] [[merge-merge-single-node-derived-from-an-existing-node-property]] === Merge single node derived from an existing node property -For some property `p` in each bound node in a set of nodes, a single new node is created for each unique value for `p`. +For some property 'p' in each bound node in a set of nodes, a single new node is created for each unique value for 'p'. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (person:Person) MERGE (city:City {name: person.bornIn}) RETURN person.name, person.bornIn, city ---- -Three nodes labeled `City` are created, each of which contains a `name` property with the value of `'New York'`, `'Ohio'`, and `'New Jersey'`, respectively. -Note that even though the `MATCH` clause results in three bound nodes having the value `'New York'` for the `bornIn` property, only a single `'New York'` node (i.e. a `City` node with a name of `'New York'`) is created. -As the `'New York'` node is not matched for the first bound node, it is created. -However, the newly-created `'New York'` node is matched and bound for the second and third bound nodes. +Three nodes labeled `City` are created, each of which contains a `name` property with the value of *'New York'*, *'Ohio'*, and *'New Jersey'*, respectively. +Note that even though the `MATCH` clause results in three bound nodes having the value *'New York'* for the `bornIn` property, only a single *'New York'* node (i.e. a `City` node with a name of *'New York'*) is created. +As the *'New York'* node is not matched for the first bound node, it is created. +However, the newly-created *'New York'* node is matched and bound for the second and third bound nodes. .Result [role="queryresult",options="header,footer",cols="3* +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]> +++++ +endif::nonhtmloutput[] [[query-merge-on-create-on-match]] == Use `ON CREATE` and `ON MATCH` @@ -218,8 +384,9 @@ Labels added: 3 Merge a node and set properties if the node needs to be created. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MERGE (keanu:Person {name: 'Keanu Reeves'}) ON CREATE @@ -227,27 +394,61 @@ ON CREATE RETURN keanu.name, keanu.created ---- -The query creates the `'keanu'` node and sets a timestamp on creation time. +The query creates the *'keanu'* node and sets a timestamp on creation time. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]> +++++ +endif::nonhtmloutput[] [[merge-merge-with-on-match]] === Merge with `ON MATCH` Merging nodes and setting properties on found nodes. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MERGE (person:Person) ON MATCH @@ -270,12 +471,46 @@ The query finds all the `Person` nodes, sets a property on them, and returns the Properties set: 5 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]> +++++ +endif::nonhtmloutput[] [[merge-merge-with-on-create-and-on-match]] === Merge with `ON CREATE` and `ON MATCH` + .Query -[source, cypher, indent=0] +[source, cypher] ---- MERGE (keanu:Person {name: 'Keanu Reeves'}) ON CREATE @@ -285,28 +520,64 @@ ON MATCH RETURN keanu.name, keanu.created, keanu.lastSeen ---- -The query creates the `'keanu'` node, and sets a timestamp on creation time. -If `'keanu'` had already existed, a different property would have been set. +The query creates the *'keanu'* node, and sets a timestamp on creation time. +If *'keanu'* had already existed, a different property would have been set. .Result [role="queryresult",options="header,footer",cols="3*+ +| +"Keanu Reeves"+ | +1668159309958+ | ++ 3+d|Rows: 1 + Nodes created: 1 + Properties set: 2 + Labels added: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]> +++++ +endif::nonhtmloutput[] [[merge-merge-with-on-match-setting-multiple-properties]] === Merge with `ON MATCH` setting multiple properties If multiple properties should be set, simply separate them with commas. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MERGE (person:Person) ON MATCH @@ -320,15 +591,50 @@ RETURN person.name, person.found, person.lastAccessed [role="queryresult",options="header,footer",cols="3* +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]> +++++ +endif::nonhtmloutput[] [[query-merge-relationships]] == Merge relationships @@ -338,8 +644,9 @@ Properties set: 10 `MERGE` can be used to match or create a relationship. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (charlie:Person {name: 'Charlie Sheen'}), @@ -348,7 +655,7 @@ MERGE (charlie)-[r:ACTED_IN]->(wallStreet) RETURN charlie.name, type(r), wallStreet.title ---- -`'Charlie Sheen'` had already been marked as acting in `'Wall Street'`, so the existing relationship is found and returned. +*'Charlie Sheen'* had already been marked as acting in *'Wall Street'*, so the existing relationship is found and returned. Note that in order to match or create a relationship when using `MERGE`, at least one bound node must be specified, which is done via the `MATCH` clause in the above example. .Result @@ -359,12 +666,47 @@ Note that in order to match or create a relationship when using `MERGE`, at leas 3+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]>(wallStreet) +RETURN charlie.name, type(r), wallStreet.title +]]> +++++ +endif::nonhtmloutput[] [[merge-merge-on-multiple-relationships]] === Merge on multiple relationships + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (oliver:Person {name: 'Oliver Stone'}), @@ -373,9 +715,9 @@ MERGE (oliver)-[:DIRECTED]->(movie:Movie)<-[:ACTED_IN]-(reiner) RETURN movie ---- -In our example graph, `'Oliver Stone'` and `'Rob Reiner'` have never worked together. +In our example graph, *'Oliver Stone'* and *'Rob Reiner'* have never worked together. When we try to `MERGE` a "movie between them, Neo4j will not use any of the existing movies already connected to either person. -Instead, a new `'movie'` node is created. +Instead, a new *'movie'* node is created. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]>(movie:Movie)<-[:ACTED_IN]-(reiner) +RETURN movie +]]> +++++ +endif::nonhtmloutput[] [[merge-merge-on-an-undirected-relationship]] === Merge on an undirected relationship @@ -395,8 +771,9 @@ Labels added: 1 `MERGE` can also be used with an undirected relationship. When it needs to create a new one, it will pick a direction. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (charlie:Person {name: 'Charlie Sheen'}), @@ -405,7 +782,7 @@ MERGE (charlie)-[r:KNOWS]-(oliver) RETURN r ---- -As `'Charlie Sheen'` and `'Oliver Stone'` do not know each other this `MERGE` query will create a `KNOWS` relationship between them. +As *'Charlie Sheen'* and *'Oliver Stone'* do not know each other this `MERGE` query will create a `KNOWS` relationship between them. The direction of the created relationship is arbitrary. .Result @@ -417,14 +794,49 @@ The direction of the created relationship is arbitrary. Relationships created: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]> +++++ +endif::nonhtmloutput[] [[merge-merge-on-a-relationship-between-two-existing-nodes]] === Merge on a relationship between two existing nodes -`MERGE` can be used in conjunction with preceding `MATCH` and `MERGE` clauses to create a relationship between two bound nodes `m` and `n`, where `m` is returned by `MATCH` and `n` is created or matched by the earlier `MERGE`. +`MERGE` can be used in conjunction with preceding `MATCH` and `MERGE` clauses to create a relationship between two bound nodes 'm' and 'n', where 'm' is returned by `MATCH` and 'n' is created or matched by the earlier `MERGE`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (person:Person) MERGE (city:City {name: person.bornIn}) @@ -432,9 +844,8 @@ MERGE (person)-[r:BORN_IN]->(city) RETURN person.name, person.bornIn, city ---- -This builds on the example from xref::clauses/merge.adoc#merge-merge-single-node-derived-from-an-existing-node-property[Merge single node derived from an existing node property]. -The second `MERGE` creates a `BORN_IN` relationship between each person and a city corresponding to the value of the person’s `bornIn` property. -`'Charlie Sheen'`, `'Rob Reiner'` and `'Oliver Stone'` all have a `BORN_IN` relationship to the _same_ `City` node (`'New York'`). +This builds on the example from xref:clauses/merge.adoc#merge-merge-single-node-derived-from-an-existing-node-property[Merge single node derived from an existing node property]. +The second `MERGE` creates a `BORN_IN` relationship between each person and a city corresponding to the value of the person’s `bornIn` property. *'Charlie Sheen'*, *'Rob Reiner'* and *'Oliver Stone'* all have a `BORN_IN` relationship to the 'same' `City` node (*'New York'*). .Result [role="queryresult",options="header,footer",cols="3* +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]>(city) +RETURN person.name, person.bornIn, city +]]> +++++ +endif::nonhtmloutput[] [[merge-merge-on-a-relationship-between-an-existing-node-and-a-merged-node-derived-from-a-node-property]] === Merge on a relationship between an existing node and a merged node derived from a node property -`MERGE` can be used to simultaneously create both a new node `n` and a relationship between a bound node `m` and `n`. +`MERGE` can be used to simultaneously create both a new node 'n' and a relationship between a bound node 'm' and 'n'. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (person:Person) MERGE (person)-[r:HAS_CHAUFFEUR]->(chauffeur:Chauffeur {name: person.chauffeurName}) @@ -468,8 +913,8 @@ RETURN person.name, person.chauffeurName, chauffeur As `MERGE` found no matches -- in our example graph, there are no nodes labeled with `Chauffeur` and no `HAS_CHAUFFEUR` relationships -- `MERGE` creates five nodes labeled with `Chauffeur`, each of which contains a `name` property whose value corresponds to each matched `Person` node's `chauffeurName` property value. `MERGE` also creates a `HAS_CHAUFFEUR` relationship between each `Person` node and the newly-created corresponding `Chauffeur` node. -As `'Charlie Sheen'` and `'Michael Douglas'` both have a chauffeur with the same name -- `'John Brown'` -- a new node is created in each case, resulting in _two_ `Chauffeur` nodes having a `name` of `'John Brown'`, correctly denoting the fact that even though the `name` property may be identical, these are two separate people. -This is in contrast to the example shown above in xref::clauses/merge.adoc#merge-merge-on-a-relationship-between-two-existing-nodes[Merge on a relationship between two existing nodes], where we used the first `MERGE` to bind the `City` nodes to prevent them from being recreated (and thus duplicated) in the second `MERGE`. +As *'Charlie Sheen'* and *'Michael Douglas'* both have a chauffeur with the same name -- *'John Brown'* -- a new node is created in each case, resulting in 'two' `Chauffeur` nodes having a `name` of *'John Brown'*, correctly denoting the fact that even though the `name` property may be identical, these are two separate people. +This is in contrast to the example shown above in xref:clauses/merge.adoc#merge-merge-on-a-relationship-between-two-existing-nodes[Merge on a relationship between two existing nodes], where we used the first `MERGE` to bind the `City` nodes to prevent them from being recreated (and thus duplicated) in the second `MERGE`. .Result [role="queryresult",options="header,footer",cols="3* +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]>(chauffeur:Chauffeur {name: person.chauffeurName}) +RETURN person.name, person.chauffeurName, chauffeur +]]> +++++ +endif::nonhtmloutput[] [[query-merge-using-unique-constraints]] == Using unique constraints with `MERGE` @@ -499,27 +976,27 @@ In other words, there must be exactly one node that matches the pattern, or no m Note that the following examples assume the existence of unique constraints that have been created using: -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT FOR (n:Person) REQUIRE n.name IS UNIQUE; -CREATE CONSTRAINT FOR (n:Person) REQUIRE n.role IS UNIQUE; +CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS UNIQUE; +CREATE CONSTRAINT ON (n:Person) ASSERT n.role IS UNIQUE; ---- - [[merge-merge-using-unique-constraints-creates-a-new-node-if-no-node-is-found]] === Merge using unique constraints creates a new node if no node is found Merge using unique constraints creates a new node if no node is found. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MERGE (laurence:Person {name: 'Laurence Fishburne'}) RETURN laurence.name ---- -The query creates the `'laurence'` node. -If `'laurence'` had already existed, `MERGE` would just match the existing node. +The query creates the *'laurence'* node. +If *'laurence'* had already existed, `MERGE` would just match the existing node. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]> +++++ +endif::nonhtmloutput[] [[merge-merge-using-unique-constraints-matches-an-existing-node]] === Merge using unique constraints matches an existing node Merge using unique constraints matches an existing node. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MERGE (oliver:Person {name: 'Oliver Stone'}) RETURN oliver.name, oliver.bornIn ---- -The `'oliver'` node already exists, so `MERGE` just matches it. +The *'oliver'* node already exists, so `MERGE` just matches it. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]> +++++ +endif::nonhtmloutput[] [[merge-merge-with-unique-constraints-and-partial-matches]] === Merge with unique constraints and partial matches Merge using unique constraints fails when finding partial matches. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MERGE (michael:Person {name: 'Michael Douglas', role: 'Gordon Gekko'}) -#RETURN michael + #RETURN michael ---- -While there is a matching unique `'michael'` node with the name `'Michael Douglas'`, there is no unique node with the role of `'Gordon Gekko'` and `MERGE` fails to match. +While there is a matching unique *'michael'* node with the name *'Michael Douglas'*, there is no unique node with the role of *'Gordon Gekko'* and `MERGE` fails to match. .Error message [source] @@ -577,29 +1118,94 @@ Merge did not find a matching node michael and can not create a new node due to conflicts with existing unique nodes ---- +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]> +++++ +endif::nonhtmloutput[] + If we want to give Michael Douglas the role of Gordon Gekko, we can use the `SET` clause instead: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MERGE (michael:Person {name: 'Michael Douglas'}) SET michael.role = 'Gordon Gekko' ---- +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]> +++++ +endif::nonhtmloutput[] [[merge-merge-with-unique-constraints-and-conflicting-matches]] === Merge with unique constraints and conflicting matches Merge using unique constraints fails when finding conflicting matches. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MERGE (oliver:Person {name: 'Oliver Stone', role: 'Gordon Gekko'}) RETURN oliver ---- -While there is a matching unique `'oliver'` node with the name `'Oliver Stone'`, there is also another unique node with the role of `'Gordon Gekko'` and `MERGE` fails to match. +While there is a matching unique *'oliver'* node with the name *'Oliver Stone'*, there is also another unique node with the role of *'Gordon Gekko'* and `MERGE` fails to match. .Error message [source] @@ -608,27 +1214,60 @@ Merge did not find a matching node oliver and can not create a new node due to conflicts with existing unique nodes ---- +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]> +++++ +endif::nonhtmloutput[] [[merge-using-map-parameters-with-merge]] === Using map parameters with `MERGE` `MERGE` does not support map parameters the same way `CREATE` does. To use map parameters with `MERGE`, it is necessary to explicitly use the expected properties, such as in the following example. -For more information on parameters, see xref::syntax/parameters.adoc[]. +For more information on parameters, see xref:syntax/parameters.adoc[]. + .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { - "param": { - "name": "Keanu Reeves", - "role": "Neo" + "param" : { + "name" : "Keanu Reeves", + "role" : "Neo" } } ---- + .Query -[source, cypher, indent=0] +[source, cypher] ---- MERGE (person:Person {name: $param.name, role: $param.role}) RETURN person.name, person.role @@ -645,3 +1284,35 @@ Properties set: 2 + Labels added: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (martin)-[:ACTED_IN]->(theAmericanPresident), + (michael)-[:ACTED_IN]->(theAmericanPresident), + (oliver)-[:ACTED_IN]->(wallStreet), + (rob)-[:ACTED_IN]->(theAmericanPresident), + (charlie)-[:FATHER]->(martin) + +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/optional-match.adoc b/modules/ROOT/pages/clauses/optional-match.adoc index 906a75d50..f38c52fca 100644 --- a/modules/ROOT/pages/clauses/optional-match.adoc +++ b/modules/ROOT/pages/clauses/optional-match.adoc @@ -1,12 +1,14 @@ -:description: The `OPTIONAL MATCH` clause is used to search for the pattern described in it, while using nulls for missing parts of the pattern. - [[query-optional-match]] = OPTIONAL MATCH +:description: The `OPTIONAL MATCH` clause is used to search for the pattern described in it, while using nulls for missing parts of the pattern. + +* xref:clauses/optional-match.adoc#optional-match-introduction[Introduction] +* xref:clauses/optional-match.adoc#optional-relationships[Optional relationships] +* xref:clauses/optional-match.adoc#properties-on-optional-elements[Properties on optional elements] +* xref:clauses/optional-match.adoc#optional-typed-named-relationship[Optional typed and named relationship] -[abstract] --- -The `OPTIONAL MATCH` clause is used to search for the pattern described in it, while using nulls for missing parts of the pattern. --- +[[optional-match-introduction]] +== Introduction `OPTIONAL MATCH` matches patterns against your graph database, just like `MATCH` does. The difference is that if no matches are found, `OPTIONAL MATCH` will use a `null` for missing parts of the pattern. @@ -18,31 +20,80 @@ This matters especially in the case of multiple (`OPTIONAL`) `MATCH` clauses, wh [TIP] ==== -To understand the patterns used in the `OPTIONAL MATCH` clause, read xref::syntax/patterns.adoc[Patterns]. +To understand the patterns used in the `OPTIONAL MATCH` clause, read xref:syntax/patterns.adoc[Patterns]. + + ==== The following graph is used for the examples below: -image:graph_optional_match_clause.svg[] +.Graph +["dot", "OPTIONAL MATCH-1.svg", "neoviz", ""] +---- + N0 [ + label = "{Person|name = \'Charlie Sheen\'\l}" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "FATHER\n" + ] + N0 -> N5 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\n" + ] + N1 [ + label = "{Person|name = \'Martin Sheen\'\l}" + ] + N1 -> N6 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\n" + ] + N1 -> N5 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\n" + ] + N2 [ + label = "{Person|name = \'Michael Douglas\'\l}" + ] + N2 -> N5 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\n" + ] + N2 -> N6 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "ACTED_IN\n" + ] + N3 [ + label = "{Person|name = \'Oliver Stone\'\l}" + ] + N3 -> N5 [ + color = "#a40000" + fontcolor = "#a40000" + label = "DIRECTED\n" + ] + N4 [ + label = "{Person|name = \'Rob Reiner\'\l}" + ] + N4 -> N6 [ + color = "#a40000" + fontcolor = "#a40000" + label = "DIRECTED\n" + ] + N5 [ + label = "{Movie|title = \'Wall Street\'\l}" + ] + N6 [ + label = "{Movie|title = \'The American President\'\l}" + ] -//// -CREATE - (charlie:Person {name: 'Charlie Sheen'}), - (martin:Person {name: 'Martin Sheen'}), - (michael:Person {name: 'Michael Douglas'}), - (oliver:Person {name: 'Oliver Stone'}), - (rob:Person {name: 'Rob Reiner'}), - (wallStreet:Movie {title: 'Wall Street'}), - (charlie)-[:ACTED_IN]->(wallStreet), - (martin)-[:ACTED_IN]->(wallStreet), - (michael)-[:ACTED_IN]->(wallStreet), - (oliver)-[:DIRECTED]->(wallStreet), - (thePresident:Movie {title: 'The American President'}), - (martin)-[:ACTED_IN]->(thePresident), - (michael)-[:ACTED_IN]->(thePresident), - (rob)-[:DIRECTED]->(thePresident), - (charlie)-[:FATHER]->(martin) -//// +---- + [[optional-relationships]] == Optional relationships @@ -50,10 +101,11 @@ CREATE If a relationship is optional, use the `OPTIONAL MATCH` clause. This is similar to how a SQL outer join works. If the relationship is there, it is returned. -If it is not, `null` is returned in its place. +If it's not, `null` is returned in its place. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:Movie {title: 'Wall Street'}) OPTIONAL MATCH (a)-->(x) @@ -70,14 +122,45 @@ Returns `null`, since the node has no outgoing relationships. 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN]->(thePresident), + (michael)-[:ACTED_IN]->(thePresident), + (rob)-[:DIRECTED]->(thePresident), + (charlie)-[:FATHER]->(martin) + +]]>(x) +RETURN x +]]> +++++ +endif::nonhtmloutput[] [[properties-on-optional-elements]] == Properties on optional elements Returning a property from an optional element that is `null` will also return `null`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:Movie {title: 'Wall Street'}) OPTIONAL MATCH (a)-->(x) @@ -94,14 +177,45 @@ Returns the element x (`null` in this query), and `null` as its name. 2+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN]->(thePresident), + (michael)-[:ACTED_IN]->(thePresident), + (rob)-[:DIRECTED]->(thePresident), + (charlie)-[:FATHER]->(martin) + +]]>(x) +RETURN x, x.name +]]> +++++ +endif::nonhtmloutput[] [[optional-typed-named-relationship]] == Optional typed and named relationship Just as with a normal relationship, you can decide which variable it goes into, and what relationship type you need. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:Movie {title: 'Wall Street'}) OPTIONAL MATCH (a)-[r:ACTS_IN]->() @@ -118,3 +232,34 @@ This returns the title of the node, *'Wall Street'*, and, since the node has no 2+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallStreet), + (martin)-[:ACTED_IN]->(wallStreet), + (michael)-[:ACTED_IN]->(wallStreet), + (oliver)-[:DIRECTED]->(wallStreet), + (thePresident:Movie {title: 'The American President'}), + (martin)-[:ACTED_IN]->(thePresident), + (michael)-[:ACTED_IN]->(thePresident), + (rob)-[:DIRECTED]->(thePresident), + (charlie)-[:FATHER]->(martin) + +]]>() +RETURN a.title, r +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/order-by.adoc b/modules/ROOT/pages/clauses/order-by.adoc index 75d97b523..c428e522e 100644 --- a/modules/ROOT/pages/clauses/order-by.adoc +++ b/modules/ROOT/pages/clauses/order-by.adoc @@ -1,14 +1,20 @@ -:description: `ORDER BY` is a sub-clause following `RETURN` or `WITH`, and it specifies that the output should be sorted and how. - [[query-order]] = ORDER BY +:description: `ORDER BY` is a sub-clause following `RETURN` or `WITH`, and it specifies that the output should be sorted and how. + +* xref:clauses/order-by.adoc#order-introduction[Introduction] +* xref:clauses/order-by.adoc#order-nodes-by-property[Order nodes by property] +* xref:clauses/order-by.adoc#order-nodes-by-multiple-properties[Order nodes by multiple properties] +* xref:clauses/order-by.adoc#order-nodes-by-id[Order nodes by id] +* xref:clauses/order-by.adoc#order-nodes-by-expression[Order nodes by expression] +* xref:clauses/order-by.adoc#order-nodes-in-descending-order[Order nodes in descending order] +* xref:clauses/order-by.adoc#order-null[Ordering `null`] +* xref:clauses/order-by.adoc#order-with[Ordering in a `WITH` clause] -[abstract] --- -`ORDER BY` is a sub-clause following `RETURN` or `WITH`, and it specifies that the output should be sorted and how. --- +[[order-introduction]] +== Introduction -`ORDER BY` relies on comparisons to sort the output, see xref::syntax/operators.adoc#cypher-ordering[Ordering and comparison of values]. +`ORDER BY` relies on comparisons to sort the output, see xref:syntax/operators.adoc#cypher-ordering[Ordering and comparison of values]. You can sort on many different values, e.g. node/relationship properties, the node/relationship ids, or on most expressions. If you do not specify what to sort on, there is a risk that the results are arbitrarily sorted and therefore it is best practice to be specific when using `ORDER BY`. @@ -22,25 +28,42 @@ This last rule is to make sure that `ORDER BY` does not change the results, only The performance of Cypher queries using `ORDER BY` on node properties can be influenced by the existence and use of an index for finding the nodes. If the index can provide the nodes in the order requested in the query, Cypher can avoid the use of an expensive `Sort` operation. -Read more about this capability in xref::query-tuning/indexes.adoc[The use of indexes]. +Read more about this capability in xref:query-tuning/indexes.adoc[The use of indexes]. The following graph is used for the examples below: -image:graph_order_by_clause.svg[] +.Graph +["dot", "ORDER BY-1.svg", "neoviz", ""] +---- + N0 [ + label = "name = \'A\'\lage = 34\llength = 170\l" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 [ + label = "name = \'B\'\lage = 36\l" + ] + N1 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N2 [ + label = "name = \'C\'\lage = 32\llength = 185\l" + ] -//// -CREATE - (a {name: 'A', age: 34, length: 170}), - (b {name: 'B', age: 36}), - (c {name: 'C', age: 32, length: 185}), - (a)-[:KNOWS]->(b), - (b)-[:KNOWS]->(c) -//// +---- + [NOTE] ==== Strings that contain special characters can have inconsistent or non-deterministic ordering in Neo4j. -For details, see xref::syntax/values.adoc#property-types-sip-note[Sorting of special characters]. +For details, see xref:syntax/values.adoc#property-types-sip-note[Sorting of special characters]. + + ==== [[order-nodes-by-property]] @@ -48,8 +71,9 @@ For details, see xref::syntax/values.adoc#property-types-sip-note[Sorting of spe `ORDER BY` is used to sort the output. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n.name, n.age @@ -68,6 +92,26 @@ The nodes are returned, sorted by their name. 2+d|Rows: 3 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (b)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[order-nodes-by-multiple-properties]] == Order nodes by multiple properties @@ -75,8 +119,9 @@ The nodes are returned, sorted by their name. You can order by multiple properties by stating each variable in the `ORDER BY` clause. Cypher will sort the result by the first variable listed, and for equals values, go to the next property in the `ORDER BY` clause, and so on. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n.name, n.age @@ -95,21 +140,42 @@ This returns the nodes, sorted first by their age, and then by their name. 2+d|Rows: 3 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (b)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[order-nodes-by-id]] -== Order nodes by ID +== Order nodes by id `ORDER BY` is used to sort the output. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n.name, n.age ORDER BY id(n) ---- -The nodes are returned, sorted by their internal ID. +The nodes are returned, sorted by their internal id. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(b), + (b)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] + [NOTE] ==== -Keep in mind that Neo4j reuses its internal IDs when nodes and relationships are deleted. -This means that applications using, and relying on, internal Neo4j IDs, are brittle or at risk of making mistakes. -It is therefore recommended to use application-generated IDs instead. -==== +Keep in mind that Neo4j reuses its internal ids when nodes and relationships are deleted. +This means that applications using, and relying on, internal Neo4j ids, are brittle or at risk of making mistakes. +It is therefore recommended to use application-generated ids instead. +==== + [[order-nodes-by-expression]] == Order nodes by expression `ORDER BY` is used to sort the output. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n.name, n.age, n.length @@ -154,14 +243,35 @@ The nodes are returned, sorted by their properties. 3+d|Rows: 3 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (b)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[order-nodes-in-descending-order]] == Order nodes in descending order By adding `DESC[ENDING]` after the variable to sort on, the sort will be done in reverse order. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n.name, n.age @@ -180,14 +290,35 @@ The example returns the nodes, sorted by their name in reverse order. 2+d|Rows: 3 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (b)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[order-null]] == Ordering `null` When sorting the result set, `null` will always come at the end of the result set for ascending sorting, and first when doing descending sort. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n.length, n.name, n.age @@ -206,6 +337,26 @@ The nodes are returned sorted by the length property, with a node without that p 3+d|Rows: 3 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (b)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[order-with]] == Ordering in a `WITH` clause @@ -216,8 +367,9 @@ The ordering guarantee can be useful to exploit by operations which depend on th For example, this can be used to control the order of items in the list produced by the `collect()` aggregating function. The `MERGE` and `SET` clauses also have ordering dependencies which can be controlled this way. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WITH n ORDER BY n.age @@ -234,3 +386,24 @@ The list of names built from the `collect` aggregating function contains the nam 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (b)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/remove.adoc b/modules/ROOT/pages/clauses/remove.adoc index 51f4fda9f..7cf9de344 100644 --- a/modules/ROOT/pages/clauses/remove.adoc +++ b/modules/ROOT/pages/clauses/remove.adoc @@ -1,37 +1,53 @@ -:description: The `REMOVE` clause is used to remove properties from nodes and relationships, and to remove labels from nodes. - [[query-remove]] = REMOVE +:description: The `REMOVE` clause is used to remove properties from nodes and relationships, and to remove labels from nodes. -[abstract] --- -The `REMOVE` clause is used to remove properties from nodes and relationships, and to remove labels from nodes. --- +* xref:clauses/remove.adoc#query-remove-introduction[Introduction] +* xref:clauses/remove.adoc#remove-remove-a-property[Remove a property] +* xref:clauses/remove.adoc#remove-remove-all-properties[Remove all properties] +* xref:clauses/remove.adoc#remove-remove-a-label-from-a-node[Remove a label from a node] +* xref:clauses/remove.adoc#remove-remove-multiple-labels[Remove multiple labels from a node] -[TIP] -==== -For deleting nodes and relationships, see xref::clauses/delete.adoc[`DELETE`]. -==== +[[query-remove-introduction]] +== Introduction + +For deleting nodes and relationships, see xref:clauses/delete.adoc[DELETE]. [NOTE] ==== Removing labels from a node is an idempotent operation: if you try to remove a label from a node that does not have that label on it, nothing happens. The query statistics will tell you if something needed to be done or not. + + ==== The examples use the following database: -image:graph_remove_clause.svg[] - -//// -CREATE - (a:Swedish {name: 'Andy', age: 36}), - (t:Swedish {name: 'Timothy', age: 25}), - (p:German:Swedish {name: 'Peter', age: 34}), - (a)-[:KNOWS]->(t), - (a)-[:KNOWS]->(p) -//// +.Graph +["dot", "REMOVE-1.svg", "neoviz", ""] +---- + N0 [ + label = "{Swedish|age = 36\lname = \'Andy\'\l}" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 [ + label = "{Swedish|age = 25\lname = \'Timothy\'\l}" + ] + N2 [ + label = "{Swedish, German|age = 34\lname = \'Peter\'\l}" + ] +---- + [[remove-remove-a-property]] == Remove a property @@ -40,8 +56,9 @@ Neo4j doesn't allow storing `null` in properties. Instead, if no value exists, the property is just not there. So, `REMOVE` is used to remove a property value from a node or a relationship. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a {name: 'Andy'}) REMOVE a.age @@ -59,21 +76,41 @@ The node is returned, and no property `age` exists on it. Properties set: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(t), + (a)-[:KNOWS]->(p) + +]]> +++++ +endif::nonhtmloutput[] [[remove-remove-all-properties]] == Remove all properties `REMOVE` cannot be used to remove all existing properties from a node or relationship. -Instead, using xref::clauses/set.adoc#set-remove-properties-using-empty-map[`SET` with `=` and an empty map as the right operand] will clear all properties from the node or relationship. - +Instead, using xref:clauses/set.adoc#set-remove-properties-using-empty-map[`SET` with `=` and an empty map as the right operand] will clear all properties from the node or relationship. [[remove-remove-a-label-from-a-node]] == Remove a label from a node To remove labels, you use `REMOVE`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'Peter'}) REMOVE n:German @@ -89,14 +126,35 @@ RETURN n.name, labels(n) Labels removed: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(t), + (a)-[:KNOWS]->(p) + +]]> +++++ +endif::nonhtmloutput[] [[remove-remove-multiple-labels]] == Remove multiple labels from a node To remove multiple labels, you use `REMOVE`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'Peter'}) REMOVE n:German:Swedish @@ -112,3 +170,24 @@ RETURN n.name, labels(n) Labels removed: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(t), + (a)-[:KNOWS]->(p) + +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/return.adoc b/modules/ROOT/pages/clauses/return.adoc index e2c2e9825..680cd82dc 100644 --- a/modules/ROOT/pages/clauses/return.adoc +++ b/modules/ROOT/pages/clauses/return.adoc @@ -1,12 +1,20 @@ -:description: The `RETURN` clause defines what to include in the query result set. - [[query-return]] = RETURN - -[abstract] --- -The `RETURN` clause defines what to include in the query result set. --- +:description: The `RETURN` clause defines what to include in the query result set. + +* xref:clauses/return.adoc#return-introduction[Introduction] +* xref:clauses/return.adoc#return-nodes[Return nodes] +* xref:clauses/return.adoc#return-relationships[Return relationships] +* xref:clauses/return.adoc#return-property[Return property] +* xref:clauses/return.adoc#return-all-elements[Return all elements] +* xref:clauses/return.adoc#return-variable-with-uncommon-characters[Variable with uncommon characters] +* xref:clauses/return.adoc#return-column-alias[Column alias] +* xref:clauses/return.adoc#return-optional-properties[Optional properties] +* xref:clauses/return.adoc#return-other-expressions[Other expressions] +* xref:clauses/return.adoc#return-unique-results[Unique results] + +[[return-introduction]] +== Introduction In the `RETURN` part of your query, you define which parts of the pattern you are interested in. It can be nodes, relationships, or properties on these. @@ -15,26 +23,41 @@ It can be nodes, relationships, or properties on these. ==== If what you actually want is the value of a property, make sure to not return the full node/relationship. This will improve performance. -==== -image:graph_return_clause.svg[] -//// -CREATE - (a {name: 'A', happy: 'Yes!', age: 55}), - (b {name: 'B'}), - (a)-[:KNOWS]->(b), - (a)-[:BLOCKS]->(b) -//// +==== +.Graph +["dot", "RETURN-1.svg", "neoviz", ""] +---- + N0 [ + label = "name = \'A\'\lage = 55\lhappy = \'Yes!\'\l" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "BLOCKS\n" + ] + N0 -> N1 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "KNOWS\n" + ] + N1 [ + label = "name = \'B\'\l" + ] + +---- + [[return-nodes]] == Return nodes To return a node, list it in the `RETURN` statement. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'B'}) RETURN n @@ -50,14 +73,33 @@ The example will return the node. 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:BLOCKS]->(b) + +]]> +++++ +endif::nonhtmloutput[] [[return-relationships]] == Return relationships To return a relationship, just include it in the `RETURN` list. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'A'})-[r:KNOWS]->(c) RETURN r @@ -73,14 +115,33 @@ The relationship is returned by the example. 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:BLOCKS]->(b) + +]]>(c) +RETURN r +]]> +++++ +endif::nonhtmloutput[] [[return-property]] == Return property To return a property, use the dot separator, like this: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'A'}) RETURN n.name @@ -96,14 +157,33 @@ The value of the property `name` gets returned. 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:BLOCKS]->(b) + +]]> +++++ +endif::nonhtmloutput[] [[return-all-elements]] == Return all elements When you want to return all nodes, relationships and paths found in a query, you can use the `*` symbol. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH p = (a {name: 'A'})-[r]->(b) RETURN * @@ -120,14 +200,33 @@ This returns the two nodes, the relationship and the path used in the query. 4+d|Rows: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:BLOCKS]->(b) + +]]>(b) +RETURN * +]]> +++++ +endif::nonhtmloutput[] [[return-variable-with-uncommon-characters]] == Variable with uncommon characters To introduce a placeholder that is made up of characters that are not contained in the English alphabet, you can use the ``` to enclose the variable, like this: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (`This isn\'t a common variable`) WHERE `This isn\'t a common variable`.name = 'A' @@ -144,14 +243,34 @@ The node with name "A" is returned. 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:BLOCKS]->(b) + +]]> +++++ +endif::nonhtmloutput[] [[return-column-alias]] == Column alias If the name of the column should be different from the expression used, you can rename it by using `AS` . + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a {name: 'A'}) RETURN a.age AS SomethingTotallyDifferent @@ -167,6 +286,24 @@ Returns the age property of a node, but renames the column. 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:BLOCKS]->(b) + +]]> +++++ +endif::nonhtmloutput[] [[return-optional-properties]] == Optional properties @@ -174,8 +311,9 @@ Returns the age property of a node, but renames the column. If a property might or might not be there, you can still select it as usual. It will be treated as `null` if it is missing. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n.age @@ -192,17 +330,36 @@ This example returns the age when the node has that property, or `null` if the p 1+d|Rows: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:BLOCKS]->(b) + +]]> +++++ +endif::nonhtmloutput[] [[return-other-expressions]] == Other expressions Any expression can be used as a return item -- literals, predicates, properties, functions, and everything else. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a {name: 'A'}) -RETURN a.age > 30, "I'm a literal", [p=(a)-->() | p] AS `(a)-->()` AS `(a)-->()` +RETURN a.age > 30, "I'm a literal", (a)-->() ---- Returns a predicate, a literal and function call with a pattern expression parameter. @@ -215,14 +372,33 @@ Returns a predicate, a literal and function call with a pattern expression param 3+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:BLOCKS]->(b) + +]]> 30, "I'm a literal", (a)-->() +]]> +++++ +endif::nonhtmloutput[] [[return-unique-results]] == Unique results `DISTINCT` retrieves only unique rows depending on the columns that have been selected to output. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a {name: 'A'})-->(b) RETURN DISTINCT b @@ -238,3 +414,22 @@ The node named "B" is returned by the query, but only once. 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:BLOCKS]->(b) + +]]>(b) +RETURN DISTINCT b +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/set.adoc b/modules/ROOT/pages/clauses/set.adoc index ccd663d2c..1cc5a3a3b 100644 --- a/modules/ROOT/pages/clauses/set.adoc +++ b/modules/ROOT/pages/clauses/set.adoc @@ -1,12 +1,23 @@ -:description: The `SET` clause is used to update labels on nodes and properties on nodes and relationships. - [[query-set]] = SET - -[abstract] --- -The `SET` clause is used to update labels on nodes and properties on nodes and relationships. --- +:description: The `SET` clause is used to update labels on nodes and properties on nodes and relationships. + +* xref:clauses/set.adoc#query-set-introduction[Introduction] +* xref:clauses/set.adoc#set-set-a-property[Set a property] +* xref:clauses/set.adoc#set-update-a-property[Update a property] +* xref:clauses/set.adoc#set-remove-a-property[Remove a property] +* xref:clauses/set.adoc#set-copying-properties-between-nodes-and-relationships[Copy properties between nodes and relationships] +* xref:clauses/set.adoc#set-replace-properties-using-map[Replace all properties using a map and `=`] +* xref:clauses/set.adoc#set-remove-properties-using-empty-map[Remove all properties using an empty map and `=`] +* xref:clauses/set.adoc#set-setting-properties-using-map[Mutate specific properties using a map and `+=`] +* xref:clauses/set.adoc#set-set-multiple-properties-using-one-set-clause[Set multiple properties using one `SET` clause] +* xref:clauses/set.adoc#set-set-a-property-using-a-parameter[Set a property using a parameter] +* xref:clauses/set.adoc#set-set-all-properties-using-a-parameter[Set all properties using a parameter] +* xref:clauses/set.adoc#set-set-a-label-on-a-node[Set a label on a node] +* xref:clauses/set.adoc#set-set-multiple-labels-on-a-node[Set multiple labels on a node] + +[[query-set-introduction]] +== Introduction `SET` can be used with a map -- provided as a literal, a parameter, or a node or relationship -- to set properties. @@ -14,31 +25,54 @@ The `SET` clause is used to update labels on nodes and properties on nodes and r ==== Setting labels on a node is an idempotent operation -- nothing will occur if an attempt is made to set a label on a node that already has that label. The query statistics will state whether any updates actually took place. -==== -The examples use this graph as a starting point: -image:graph_set_clause.svg[] +==== -//// -CREATE - (a:Swedish {name: 'Andy', age: 36, hungry: true}), - (b {name: 'Stefan'}), - (c {name: 'Peter', age: 34}), - (d {name: 'George'}), - (a)-[:KNOWS]->(c), - (b)-[:KNOWS]->(a), - (d)-[:KNOWS]->(c) -//// +The examples use this graph as a starting point: +.Graph +["dot", "SET-1.svg", "neoviz", ""] +---- + N0 [ + label = "{Swedish|name = \'Andy\'\lage = 36\lhungry = true\l}" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 [ + label = "name = \'Stefan\'\l" + ] + N1 -> N0 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N2 [ + label = "name = \'Peter\'\lage = 34\l" + ] + N3 [ + label = "name = \'George\'\l" + ] + N3 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + +---- + [[set-set-a-property]] == Set a property Use `SET` to set a property on a node or relationship: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'Andy'}) SET n.surname = 'Taylor' @@ -56,11 +90,35 @@ The newly-changed node is returned by the query. Properties set: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(c), + (b)-[:KNOWS]->(a), + (d)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] + It is possible to set a property on a node or relationship using more complex expressions. -For instance, in contrast to specifying the node directly, the following query shows how to set a property for a node selected by an expression: +For instance, in contrast to specifying the node directly, the following query shows how to set a property for a node selected by an expression: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'Andy'}) SET (CASE WHEN n.age = 36 THEN n END).worksIn = 'Malmo' @@ -76,10 +134,34 @@ RETURN n.name, n.worksIn Properties set: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(c), + (b)-[:KNOWS]->(a), + (d)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] + No action will be taken if the node expression evaluates to `null`, as shown in this example: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'Andy'}) SET (CASE WHEN n.age = 55 THEN n END).worksIn = 'Malmo' @@ -97,6 +179,28 @@ As a consequence, no updates occur, and therefore no `worksIn` property is set. 2+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(c), + (b)-[:KNOWS]->(a), + (d)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[set-update-a-property]] == Update a property @@ -104,8 +208,9 @@ As a consequence, no updates occur, and therefore no `worksIn` property is set. `SET` can be used to update a property on a node or relationship. This query forces a change of type in the `age` property: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'Andy'}) SET n.age = toString(n.age) @@ -123,15 +228,38 @@ The `age` property has been converted to the string `'36'`. Properties set: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(c), + (b)-[:KNOWS]->(a), + (d)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[set-remove-a-property]] == Remove a property -Although xref::clauses/remove.adoc[`REMOVE`] is normally used to remove a property, it is sometimes convenient to do it using the `SET` command. +Although `xref:clauses/remove.adoc[REMOVE]` is normally used to remove a property, it's sometimes convenient to do it using the `SET` command. A case in point is if the property is provided by a parameter. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'Andy'}) SET n.name = null @@ -149,6 +277,28 @@ The `name` property is now missing. Properties set: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(c), + (b)-[:KNOWS]->(a), + (d)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[set-copying-properties-between-nodes-and-relationships]] == Copy properties between nodes and relationships @@ -156,8 +306,9 @@ Properties set: 1 `SET` can be used to copy all properties from one node or relationship to another. This will remove _all_ other properties on the node or relationship being copied to. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (at {name: 'Andy'}), @@ -166,7 +317,7 @@ SET at = pn RETURN at.name, at.age, at.hungry, pn.name, pn.age ---- -The `'Andy'` node has had all its properties replaced by the properties of the `'Peter'` node. +The *'Andy'* node has had all its properties replaced by the properties of the *'Peter'* node. .Result [role="queryresult",options="header,footer",cols="5* +Try this query live +(c), + (b)-[:KNOWS]->(a), + (d)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[set-replace-properties-using-map]] == Replace all properties using a map and `=` The property replacement operator `=` can be used with `SET` to replace all existing properties on a node or relationship with those provided by a map: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (p {name: 'Peter'}) SET p = {name: 'Peter Smith', position: 'Entrepreneur'} RETURN p.name, p.age, p.position ---- -This query updated the `name` property from `Peter` to `Peter Smith`, deleted the `age` property, and added the `position` property to the `'Peter'` node. +This query updated the `name` property from `Peter` to `Peter Smith`, deleted the `age` property, and added the `position` property to the *'Peter'* node. .Result [role="queryresult",options="header,footer",cols="3* +Try this query live +(c), + (b)-[:KNOWS]->(a), + (d)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[set-remove-properties-using-empty-map]] == Remove all properties using an empty map and `=` All existing properties can be removed from a node or relationship by using `SET` with `=` and an empty map as the right operand: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (p {name: 'Peter'}) SET p = {} RETURN p.name, p.age ---- -This query removed all the existing properties -- namely, `name` and `age` -- from the `'Peter'` node. +This query removed all the existing properties -- namely, `name` and `age` -- from the *'Peter'* node. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(c), + (b)-[:KNOWS]->(a), + (d)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[set-setting-properties-using-map]] == Mutate specific properties using a map and `+=` @@ -238,15 +459,16 @@ The property mutation operator `+=` can be used with `SET` to mutate properties * Any properties that are in both the map and the node or relationship will be _replaced_ in the node or relationship. However, if any property in the map is `null`, it will be _removed_ from the node or relationship. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (p {name: 'Peter'}) SET p += {age: 38, hungry: true, position: 'Entrepreneur'} RETURN p.name, p.age, p.hungry, p.position ---- -This query left the `name` property unchanged, updated the `age` property from `34` to `38`, and added the `hungry` and `position` properties to the `'Peter'` node. +This query left the `name` property unchanged, updated the `age` property from `34` to `38`, and added the `hungry` and `position` properties to the *'Peter'* node. .Result [role="queryresult",options="header,footer",cols="4* +Try this query live +(c), + (b)-[:KNOWS]->(a), + (d)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] + +xref:clauses/set.adoc#set-remove-properties-using-empty-map[In contrast to the property replacement operator `=`], providing an empty map as the right operand to `+=` will not remove any existing properties from a node or relationship. In line with the semantics detailed above, passing in an empty map with `+=` will have no effect: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (p {name: 'Peter'}) SET p += {} @@ -276,14 +522,37 @@ RETURN p.name, p.age 2+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(c), + (b)-[:KNOWS]->(a), + (d)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[set-set-multiple-properties-using-one-set-clause]] == Set multiple properties using one `SET` clause Set multiple properties at once by separating them with a comma: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'Andy'}) SET n.position = 'Developer', n.surname = 'Taylor' @@ -297,29 +566,52 @@ SET n.position = 'Developer', n.surname = 'Taylor' Properties set: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(c), + (b)-[:KNOWS]->(a), + (d)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[set-set-a-property-using-a-parameter]] == Set a property using a parameter Use a parameter to set the value of a property: + .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { - "surname": "Taylor" + "surname" : "Taylor" } ---- + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'Andy'}) SET n.surname = $surname RETURN n.name, n.surname ---- -A `surname` property has been added to the `'Andy'` node. +A `surname` property has been added to the *'Andy'* node. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(c), + (b)-[:KNOWS]->(a), + (d)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[set-set-all-properties-using-a-parameter]] == Set all properties using a parameter This will replace all existing properties on the node with the new set provided by the parameter. + .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { "props" : { - "name": "Andy", - "position": "Developer" + "name" : "Andy", + "position" : "Developer" } } ---- .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'Andy'}) SET n = $props RETURN n.name, n.position, n.age, n.hungry ---- -The `'Andy'` node has had all its properties replaced by the properties in the `props` parameter. +The *'Andy'* node has had all its properties replaced by the properties in the `props` parameter. .Result [role="queryresult",options="header,footer",cols="4* +Try this query live +(c), + (b)-[:KNOWS]->(a), + (d)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[set-set-a-label-on-a-node]] == Set a label on a node Use `SET` to set a label on a node: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'Stefan'}) SET n:German @@ -392,14 +730,37 @@ The newly-labeled node is returned by the query. Labels added: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(c), + (b)-[:KNOWS]->(a), + (d)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] [[set-set-multiple-labels-on-a-node]] == Set multiple labels on a node Set multiple labels on a node with `SET` and use `:` to separate the different labels: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'George'}) SET n:Swedish:Bossman @@ -417,3 +778,26 @@ The newly-labeled node is returned by the query. Labels added: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(c), + (b)-[:KNOWS]->(a), + (d)-[:KNOWS]->(c) + +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/skip.adoc b/modules/ROOT/pages/clauses/skip.adoc index 9e2d3ba13..03503052e 100644 --- a/modules/ROOT/pages/clauses/skip.adoc +++ b/modules/ROOT/pages/clauses/skip.adoc @@ -1,40 +1,69 @@ -:description: `SKIP` defines from which row to start including the rows in the output. - [[query-skip]] = SKIP +:description: `SKIP` defines from which row to start including the rows in the output. + +* xref:clauses/skip.adoc#skip-introduction[Introduction] +* xref:clauses/skip.adoc#skip-first-three-rows[Skip first three rows] +* xref:clauses/skip.adoc#skip-return-middle-rows[Return middle two rows] +* xref:clauses/skip.adoc#skip-using-expression[Using an expression with `SKIP` to return a subset of the rows] -[abstract] --- -`SKIP` defines from which row to start including the rows in the output. --- +[[skip-introduction]] +== Introduction By using `SKIP`, the result set will get trimmed from the top. Please note that no guarantees are made on the order of the result unless the query specifies the `ORDER BY` clause. `SKIP` accepts any expression that evaluates to a positive integer -- however the expression cannot refer to nodes or relationships. -image:graph_skip_clause.svg[] - -//// -CREATE - (a {name: 'A'}), - (b {name: 'B'}), - (c {name: 'C'}), - (d {name: 'D'}), - (e {name: 'E'}), - (a)-[:KNOWS]->(b), - (a)-[:KNOWS]->(c), - (a)-[:KNOWS]->(d), - (a)-[:KNOWS]->(e) -//// +.Graph +["dot", "SKIP-1.svg", "neoviz", ""] +---- + N0 [ + label = "name = \'A\'\l" + ] + N0 -> N4 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 [ + label = "name = \'B\'\l" + ] + N2 [ + label = "name = \'C\'\l" + ] + N3 [ + label = "name = \'D\'\l" + ] + N4 [ + label = "name = \'E\'\l" + ] +---- + [[skip-first-three-rows]] == Skip first three rows To return a subset of the result, starting from the fourth result, use the following syntax: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n.name @@ -53,14 +82,40 @@ The first three nodes are skipped, and only the last two are returned in the res 1+d|Rows: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(d), + (a)-[:KNOWS]->(e) + +]]> +++++ +endif::nonhtmloutput[] [[skip-return-middle-rows]] == Return middle two rows To return a subset of the result, starting from somewhere in the middle, use this syntax: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n.name @@ -80,14 +135,41 @@ Two nodes from the middle are returned. 1+d|Rows: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(d), + (a)-[:KNOWS]->(e) + +]]> +++++ +endif::nonhtmloutput[] [[skip-using-expression]] == Using an expression with `SKIP` to return a subset of the rows Skip accepts any expression that evaluates to a positive integer as long as it is not referring to any external variables: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n.name @@ -95,17 +177,40 @@ ORDER BY n.name SKIP 1 + toInteger(3*rand()) ---- -Skip the firs row plus randomly 0, 1, or 2. -So randomly skip 1, 2, or 3 rows. +Skip the firs row plus randomly 0, 1, or 2. So randomly skip 1, 2, or 3 rows. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(b), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(d), + (a)-[:KNOWS]->(e) + +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/transaction-clauses.adoc b/modules/ROOT/pages/clauses/transaction-clauses.adoc deleted file mode 100644 index 312256649..000000000 --- a/modules/ROOT/pages/clauses/transaction-clauses.adoc +++ /dev/null @@ -1,333 +0,0 @@ -:description: This section explains the `SHOW TRANSACTIONS` and `TERMINATION TRANSACTIONS` commands. - -[[query-transaction-clauses]] -= Transaction commands - -[abstract] --- -This section explains the `SHOW TRANSACTIONS` and `TERMINATION TRANSACTIONS` commands. --- - -[[query-listing-transactions]] -== SHOW TRANSACTIONS - -The `SHOW TRANSACTIONS` command is used to display running transactions within the instance. -This also includes fabric transactions. -For remote database aliases, transactions can be tracked by running `SHOW TRANSACTIONS` when connected to the remote database alias. - -[NOTE] -==== -The command `SHOW TRANSACTIONS` returns only the default output. For a full output use the optional `YIELD` command. -Full output: `SHOW TRANSACTIONS YIELD *`. -==== - -This command will produce a table with the following columns: - -.List transactions output -[options="header", cols="4,6,2"] -|=== -| Column | Description | Type - -m|database -a|The name of the database the transaction is executing against. label:default-output[] -m|STRING - -m|transactionId -a|The transaction ID. label:default-output[] -m|STRING - -m|currentQueryId -a|The ID of the query currently executing in this transaction, or an empty string if no query is currently executing. label:default-output[] -m|STRING - -m|connectionId -a|The ID of the database connection attached to the transaction or an empty string for embedded connections. label:default-output[] -m|STRING - -m|clientAddress -a|The client address of the connection issuing the transaction or an empty string if unavailable. label:default-output[] -m|STRING - -m|username -a|The username of the user executing the transaction. label:default-output[] -m|STRING - -m|currentQuery -a|The query text of the query currently executing in this transaction, or an empty string if no query is currently executing. label:default-output[] -m|STRING - -m|startTime -a|The time at which the transaction was started. label:default-output[] -m|STRING - -m|status -a|The current status of the transaction (`Terminated`, `Blocked`, `Closing` or `Running`). label:default-output[] -m|STRING - -m|elapsedTime -a|The time that has elapsed since the transaction was started. label:default-output[] -m|DURATION - -m|allocatedBytes -a|The number of bytes allocated on the heap so far by the transaction, or `0` if unavailable. label:default-output[] -m|LONG - -m|outerTransactionId -a|The ID of this transaction's outer transaction, if such exists, otherwise an empty string. For details, see xref::clauses/call-subquery.adoc#subquery-call-in-transactions[`CALL { ... } IN TRANSACTIONS`]. -m|STRING - -m|metaData -a|Any metadata associated with the transaction, or an empty map if there is none. -m|MAP - -m|parameters -a|A map containing all the parameters used by the query currently executing in this transaction, or an empty map if no query is currently executing. -m|MAP - -m|planner -a|The name of the Cypher planner used to plan the query currently executing in this transaction, or an empty string if no query is currently executing. For details, see xref::query-tuning/index.adoc#cypher-planner[Cypher planner]. -m|STRING - -m|runtime -a|The name of the Cypher runtime used by the query currently executing in this transaction, or an empty string if no query is currently executing. For details, see xref::query-tuning/index.adoc#cypher-runtime[Cypher runtime]. -m|STRING - -m|indexes -a|The indexes utilised by the query currently executing in this transaction, or an empty list if no query is currently executing. -m|LIST OF MAP - -m|protocol -a|The protocol used by the connection issuing the transaction. -This is not necessarily an internet protocol, such as _http_, et.c., although it could be. It might also be "embedded", for example, if this connection represents an embedded session. -m|STRING - -m|requestUri -a|The request URI used by the client connection issuing the transaction, or `null` if the URI is not available. -m|STRING - -m|statusDetails -a|Provide additional status details from the underlying transaction or an empty string if none is available. -m|STRING - -m|resourceInformation -a|Information about any blocked transactions, or an empty map if there is none. -m|MAP - -m|activeLockCount -a|Count of active locks held by the transaction. -m|LONG - -m|cpuTime -a|CPU time that has been actively spent executing the transaction, or `0` if unavailable. -m|DURATION - -m|waitTime -a|Wait time that has been spent waiting to acquire locks. -m|DURATION - -m|idleTime -a|Idle time for this transaction, or `0` if unavailable. -m|DURATION - -m|allocatedDirectBytes -a|Amount of off-heap (native) memory allocated by the transaction in bytes, or `0` if unavailable. -m|LONG - -m|estimatedUsedHeapMemory -a|The estimated amount of used heap memory allocated by the transaction in bytes, or `0` if unavailable. -m|LONG - -m|pageHits -a|The total number of page cache hits that the transaction performed. -m|LONG - -m|pageFaults -a|The total number of page cache faults that the transaction performed. -m|LONG - -m|initializationStackTrace -a|The initialization stacktrace for this transaction, or an empty string if unavailable. -m|STRING -|=== - - -=== Syntax - -List transactions on the current server:: - -[source, cypher, role="noheader", indent=0] ----- -SHOW TRANSACTION[S] [transaction-id[,...]] -[YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] -[WHERE expression] -[RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ----- - -The format of `transaction-id` is `-transaction-`. Transaction IDs must be supplied as a comma-separated list of one or more quoted strings, a string parameter, or a list parameter. - -[NOTE] -==== -When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. -==== - -A user with the xref::access-control/database-administration.adoc#access-control-database-administration-transaction[`SHOW TRANSACTION`] privilege can view the currently executing transactions in accordance with the privilege grants. -All users may view all of their own currently executing transactions. - - -=== Listing all transactions - -To list all available transactions with the default output columns, use the `SHOW TRANSACTIONS` command. -If all columns are required, use `SHOW TRANSACTIONS YIELD *`. - -.Query -[source, cypher, indent=0] ----- -SHOW TRANSACTIONS ----- - -.Result -[role="queryresult",options="header,footer",cols="11*-transaction-`. Transaction IDs must be supplied as a comma-separated list of one or more quoted strings, a string parameter, or a list parameter. - -A user with the xref::access-control/database-administration.adoc#access-control-database-administration-transaction[`TERMINATE TRANSACTION`] privilege can terminate transactions in accordance with the privilege grants. -All users may terminate their own currently executing transactions. - - -=== Terminate transactions - -To end running transactions without waiting for them to complete on their own, use the `TERMINATE TRANSACTIONS` command. - -.Query -[source, cypher, indent=0] ----- -TERMINATE TRANSACTIONS "neo4j-transaction-1","neo4j-transaction-2" ----- - -.Result -[role="queryresult",options="header,footer",cols="3*(hm), - (ah)-[:ACTS_IN]->(hitchcockMovie), - (hm)-[:ACTS_IN]->(hitchcockMovie) -//// +.Graph +["dot", "UNION-1.svg", "neoviz", ""] +---- + N0 [ + label = "{Actor|name = \'Anthony Hopkins\'\l}" + ] + N0 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTS_IN\n" + ] + N0 -> N1 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "KNOWS\n" + ] + N1 [ + label = "{Actor|name = \'Helen Mirren\'\l}" + ] + N1 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTS_IN\n" + ] + N2 [ + label = "{Actor|name = \'Hitchcock\'\l}" + ] + N3 [ + label = "{Movie|title = \'Hitchcock\'\l}" + ] +---- + [[union-combine-queries-retain-duplicates]] == Combine two queries and retain duplicates Combining the results from two queries is done using `UNION ALL`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Actor) RETURN n.name AS name @@ -57,14 +79,39 @@ The combined result is returned, including duplicates. 1+d|Rows: 4 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(hm), + (ah)-[:ACTS_IN]->(hitchcockMovie), + (hm)-[:ACTS_IN]->(hitchcockMovie) + +]]> +++++ +endif::nonhtmloutput[] [[union-combine-queries-remove-duplicates]] == Combine two queries and remove duplicates -By not including `ALL` in the `UNION`, duplicates are removed from the combined result set. +By not including `ALL` in the `UNION`, duplicates are removed from the combined result set + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Actor) RETURN n.name AS name @@ -85,3 +132,28 @@ The combined result is returned, without duplicates. 1+d|Rows: 3 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(hm), + (ah)-[:ACTS_IN]->(hitchcockMovie), + (hm)-[:ACTS_IN]->(hitchcockMovie) + +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/unwind.adoc b/modules/ROOT/pages/clauses/unwind.adoc index 5ce93777b..13345433e 100644 --- a/modules/ROOT/pages/clauses/unwind.adoc +++ b/modules/ROOT/pages/clauses/unwind.adoc @@ -1,34 +1,35 @@ -:description: `UNWIND` expands a list into a sequence of rows. - [[query-unwind]] = UNWIND +:description: `UNWIND` expands a list into a sequence of rows. -[abstract] --- -`UNWIND` expands a list into a sequence of rows. --- - -The `UNWIND` clause makes it possible to transform any list back into individual rows. -These lists can be parameters that were passed in, previously `collect`-ed result, or other list expressions. +* xref:clauses/unwind.adoc#query-unwind-introduction[Introduction] +* xref:clauses/unwind.adoc#unwind-unwinding-a-list[Unwinding a list] +* xref:clauses/unwind.adoc#unwind-creating-a-distinct-list[Creating a distinct list] +* xref:clauses/unwind.adoc#unwind-using-unwind-with-any-expression-returning-a-list[Using `UNWIND` with any expression returning a list] +* xref:clauses/unwind.adoc#unwind-using-unwind-with-a-list-of-lists[Using `UNWIND` with a list of lists] +* xref:clauses/unwind.adoc#unwind-using-unwind-with-an-empty-list[Using `UNWIND` with an empty list] +* xref:clauses/unwind.adoc#unwind-using-unwind-with-an-expression-that-is-not-a-list[Using `UNWIND` with an expression that is not a list] +* xref:clauses/unwind.adoc#unwind-creating-nodes-from-a-list-parameter[Creating nodes from a list parameter] -Common usage of the `UNWIND` clause: +[[query-unwind-introduction]] +== Introduction -* Create distinct lists. -* Create data from parameter lists that are provided to the query. +With `UNWIND`, you can transform any list back into individual rows. +These lists can be parameters that were passed in, previously `collect` -ed result or other list expressions. -[NOTE] -==== -The `UNWIND` clause requires you to specify a new name for the inner values. -==== +One common usage of unwind is to create distinct lists. +Another is to create data from parameter lists that are provided to the query. +`UNWIND` requires you to specify a new name for the inner values. [[unwind-unwinding-a-list]] == Unwinding a list We want to transform the literal list into rows named `x` and return them. + .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [1, 2, 3, null] AS x RETURN x, 'val' AS y @@ -47,14 +48,28 @@ Each value of the original list -- including `null` -- is returned as an individ 2+d|Rows: 4 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[unwind-creating-a-distinct-list]] == Creating a distinct list We want to transform a list of duplicates into a set using `DISTINCT`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH [1, 1, 2, 2] AS coll UNWIND coll AS x @@ -72,14 +87,30 @@ Each value of the original list is unwound and passed through `DISTINCT` to crea 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[unwind-using-unwind-with-any-expression-returning-a-list]] == Using `UNWIND` with any expression returning a list Any expression that returns a list may be used with `UNWIND`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH [1, 2] AS a, @@ -101,14 +132,31 @@ The two lists -- _a_ and _b_ -- are concatenated to form a new list, which is th 1+d|Rows: 4 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[unwind-using-unwind-with-a-list-of-lists]] == Using `UNWIND` with a list of lists Multiple `UNWIND` clauses can be chained to unwind nested list elements. + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH [[1, 2], [3, 4], 5] AS nested UNWIND nested AS x @@ -116,7 +164,7 @@ UNWIND x AS y RETURN y ---- -The first `UNWIND` results in three rows for `x`, each of which contains an element of the original list (two of which are also lists); namely, `[1, 2]`, `[3, 4]`, and `5`. +The first `UNWIND` results in three rows for `x`, each of which contains an element of the original list (two of which are also lists); namely, `[1, 2]`, `[3, 4]` and `5`. The second `UNWIND` then operates on each of these rows in turn, resulting in five rows for `y`. .Result @@ -131,6 +179,21 @@ The second `UNWIND` then operates on each of these rows in turn, resulting in fi 1+d|Rows: 5 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[unwind-using-unwind-with-an-empty-list]] == Using `UNWIND` with an empty list @@ -142,7 +205,7 @@ This has value in cases such as `UNWIND v`, where `v` is a variable from an earl .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [] AS empty RETURN empty, 'literal_that_is_not_returned' @@ -155,9 +218,23 @@ RETURN empty, 'literal_that_is_not_returned' 2+d|Rows: 0 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + To avoid inadvertently using `UNWIND` on an empty list, `CASE` may be used to replace an empty list with a `null`: -[source, cypher, indent=0] +[source, cypher] ---- WITH [] AS list UNWIND @@ -168,7 +245,6 @@ UNWIND RETURN emptylist ---- - [[unwind-using-unwind-with-an-expression-that-is-not-a-list]] == Using `UNWIND` with an expression that is not a list @@ -176,8 +252,9 @@ Using `UNWIND` on an expression that does not return a list, will return the sam As an example, `UNWIND 5` is effectively equivalent to `UNWIND[5]`. The exception to this is when the expression returns `null` -- this will reduce the number of rows to zero, causing it to cease its execution and return no results. + .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND null AS x RETURN x, 'some_literal' @@ -190,14 +267,28 @@ RETURN x, 'some_literal' 2+d|Rows: 0 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[unwind-creating-nodes-from-a-list-parameter]] == Creating nodes from a list parameter Create a number of nodes and relationships from a parameter-list without using `FOREACH`. + .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { "events" : [ { @@ -210,8 +301,9 @@ Create a number of nodes and relationships from a parameter-list without using ` } ---- + .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND $events AS event MERGE (y:Year {year: event.year}) @@ -234,3 +326,19 @@ Properties set: 3 + Labels added: 3 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/clauses/use.adoc b/modules/ROOT/pages/clauses/use.adoc index 3f0628a3d..aafc20d4a 100644 --- a/modules/ROOT/pages/clauses/use.adoc +++ b/modules/ROOT/pages/clauses/use.adoc @@ -1,20 +1,21 @@ -:description: The `USE` clause determines which graph a query, or query part, is executed against. - [[query-use]] = USE +:description: The `USE` clause determines which graph a query, or query part, is executed against. -[abstract] --- -The `USE` clause determines which graph a query, or query part, is executed against. --- +* xref:clauses/use.adoc#query-use-introduction[Introduction] +* xref:clauses/use.adoc#query-use-syntax[Syntax] +* xref:clauses/use.adoc#query-use-examples[Examples] +** xref:clauses/use.adoc#query-use-examples-query-graph-by-name[Query remote graph by name] +** xref:clauses/use.adoc#query-use-examples-query-graph-by-graph-id[Query remote graph by graph ID] + +[[query-use-introduction]] +== Introduction The `USE` clause determines which graph a query, or query part, is executed against. It is supported for queries and schema commands. [NOTE] -==== -The `USE` clause can not be used together with the xref::clauses/load-csv.adoc#load-csv-importing-large-amounts-of-data[`PERIODIC COMMIT` clause]. -==== +The `USE` clause can not be used together with the xref:clauses/load-csv.adoc#load-csv-importing-large-amounts-of-data[`PERIODIC COMMIT` clause]. [[query-use-syntax]] @@ -22,13 +23,13 @@ The `USE` clause can not be used together with the xref::clauses/load-csv.adoc#l The `USE` clause can only appear as the prefix of schema commands, or as the first clause of queries: -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- USE ---- -Where `` refers to the name or alias of a database in the DBMS. +Where `` refers to the name of a database in the DBMS. [role=fabric] @@ -39,7 +40,7 @@ When running queries against a Fabric database, the `USE` clause can also appear * Union parts: + -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- USE @@ -50,7 +51,7 @@ USE * Subqueries: + -[source, syntax, role="noheader"] +[source, cypher, role=noplay] ---- CALL { USE @@ -58,7 +59,7 @@ CALL { } ---- + -In subqueries, a `USE` clause may appear as the second clause, if directly following an xref::clauses/call-subquery.adoc#subquery-correlated-importing[importing `WITH` clause] +In subqueries, a `USE` clause may appear as the second clause, if directly following an xref:clauses/call-subquery.adoc#subquery-correlated-importing[importing `WITH` clause] When executing queries against a Fabric database, in addition to referring to databases in the DBMS, the `` may also refer to a graph mounted through the Fabric configuration. For more information, see link:{neo4j-docs-base-uri}/operations-manual/{page-version}/fabric[Operations Manual -> Fabric]. @@ -74,7 +75,7 @@ For more information, see link:{neo4j-docs-base-uri}/operations-manual/{page-ver In this example we assume that your DBMS contains a database named `myDatabase`: .Query. -[source, cypher, indent=0] +[source, cypher] ---- USE myDatabase MATCH (n) RETURN n @@ -89,7 +90,7 @@ In this example we assume that we have configured a Fabric database called `exam The graph that we wish to query is named `exampleDatabaseName`: .Query. -[source, cypher, indent=0] +[source, cypher] ---- USE exampleFabricSetup.exampleDatabaseName MATCH (n) RETURN n @@ -105,9 +106,8 @@ This examples continues with a Fabric database called `exampleFabricSetup`. The graph we wish to query is configured with the graph id `0`, which is why we can refer to it using the built-in function `graph()` with the argument `0`: .Query. -[source, cypher, indent=0] +[source, cypher] ---- USE exampleFabricSetup.graph(0) MATCH (n) RETURN n ---- - diff --git a/modules/ROOT/pages/clauses/where.adoc b/modules/ROOT/pages/clauses/where.adoc index f0420a70b..2347e2a28 100644 --- a/modules/ROOT/pages/clauses/where.adoc +++ b/modules/ROOT/pages/clauses/where.adoc @@ -1,56 +1,49 @@ -:description: `WHERE` adds constraints to the patterns in a `MATCH` or `OPTIONAL MATCH` clause or filters the results of a `WITH` clause. - [[query-where]] = WHERE - -[abstract] --- -`WHERE` adds constraints to the patterns in a `MATCH` or `OPTIONAL MATCH` clause or filters the results of a `WITH` clause. --- - -* xref::clauses/where.adoc#where-introduction[Introduction] -* xref::clauses/where.adoc#query-where-basic[Basic usage] - ** xref::clauses/where.adoc#boolean-operations[Boolean operations] - ** xref::clauses/where.adoc#filter-on-node-label[Filter on node label] - ** xref::clauses/where.adoc#filter-on-node-property[Filter on node property] - ** xref::clauses/where.adoc#filter-on-relationship-property[Filter on relationship property] - ** xref::clauses/where.adoc#filter-on-dynamic-property[Filter on dynamically-computed property] - ** xref::clauses/where.adoc#property-existence-checking[Property existence checking] -* xref::clauses/where.adoc#query-where-string[String matching] - ** xref::clauses/where.adoc#match-string-start[Prefix string search using `STARTS WITH`] - ** xref::clauses/where.adoc#match-string-end[Suffix string search using `ENDS WITH`] - ** xref::clauses/where.adoc#match-string-contains[Substring search using `CONTAINS`] - ** xref::clauses/where.adoc#match-string-negation[String matching negation] -* xref::clauses/where.adoc#query-where-regex[Regular expressions] - ** xref::clauses/where.adoc#matching-using-regular-expressions[Matching using regular expressions] - ** xref::clauses/where.adoc#escaping-in-regular-expressions[Escaping in regular expressions] - ** xref::clauses/where.adoc#case-insensitive-regular-expressions[Case-insensitive regular expressions] -* xref::clauses/where.adoc#query-where-patterns[Using path patterns in `WHERE`] - ** xref::clauses/where.adoc#filter-on-patterns[Filter on patterns] - ** xref::clauses/where.adoc#filter-on-patterns-using-not[Filter on patterns using `NOT`] - ** xref::clauses/where.adoc#filter-on-patterns-with-properties[Filter on patterns with properties] - ** xref::clauses/where.adoc#filter-on-relationship-type[Filter on relationship type] - * xref::clauses/where.adoc#existential-subqueries[Using existential subqueries in `WHERE`] - ** xref::clauses/where.adoc#existential-subquery-simple-case[Simple existential subquery] - ** xref::clauses/where.adoc#existential-subquery-with-where[Existential subquery with `WHERE` clause] - ** xref::clauses/where.adoc#existential-subquery-nesting[Nesting existential subqueries] -* xref::clauses/where.adoc#query-where-lists[Lists] - ** xref::clauses/where.adoc#where-in-operator[`IN` operator] -* xref::clauses/where.adoc#missing-properties-and-values[Missing properties and values] - ** xref::clauses/where.adoc#default-to-false-missing-property[Default to `false` if property is missing] - ** xref::clauses/where.adoc#default-to-true-missing-property[Default to `true` if property is missing] - ** xref::clauses/where.adoc#filter-on-null[Filter on `null`] -* xref::clauses/where.adoc#query-where-ranges[Using ranges] - ** xref::clauses/where.adoc#simple-range[Simple range] - ** xref::clauses/where.adoc#composite-range[Composite range] -* xref::clauses/where.adoc#pattern-element-predicates[Pattern element predicates] - ** xref::clauses/where.adoc#node-pattern-predicates[Node pattern predicates] - +:description: `WHERE` adds constraints to the patterns in a `MATCH` or `OPTIONAL MATCH` clause or filters the results of a `WITH` clause. + + +* xref:clauses/where.adoc#where-introduction[Introduction] +* xref:clauses/where.adoc#query-where-basic[Basic usage] + ** xref:clauses/where.adoc#boolean-operations[Boolean operations] + ** xref:clauses/where.adoc#filter-on-node-label[Filter on node label] + ** xref:clauses/where.adoc#filter-on-node-property[Filter on node property] + ** xref:clauses/where.adoc#filter-on-relationship-property[Filter on relationship property] + ** xref:clauses/where.adoc#filter-on-dynamic-property[Filter on dynamically-computed property] + ** xref:clauses/where.adoc#property-existence-checking[Property existence checking] +* xref:clauses/where.adoc#query-where-string[String matching] + ** xref:clauses/where.adoc#match-string-start[Prefix string search using `STARTS WITH`] + ** xref:clauses/where.adoc#match-string-end[Suffix string search using `ENDS WITH`] + ** xref:clauses/where.adoc#match-string-contains[Substring search using `CONTAINS`] + ** xref:clauses/where.adoc#match-string-negation[String matching negation] +* xref:clauses/where.adoc#query-where-regex[Regular expressions] + ** xref:clauses/where.adoc#matching-using-regular-expressions[Matching using regular expressions] + ** xref:clauses/where.adoc#escaping-in-regular-expressions[Escaping in regular expressions] + ** xref:clauses/where.adoc#case-insensitive-regular-expressions[Case-insensitive regular expressions] +* xref:clauses/where.adoc#query-where-patterns[Using path patterns in `WHERE`] + ** xref:clauses/where.adoc#filter-on-patterns[Filter on patterns] + ** xref:clauses/where.adoc#filter-on-patterns-using-not[Filter on patterns using `NOT`] + ** xref:clauses/where.adoc#filter-on-patterns-with-properties[Filter on patterns with properties] + ** xref:clauses/where.adoc#filter-on-relationship-type[Filter on relationship type] + * xref:clauses/where.adoc#existential-subqueries[Using existential subqueries in `WHERE`] + ** xref:clauses/where.adoc#existential-subquery-simple-case[Simple existential subquery] + ** xref:clauses/where.adoc#existential-subquery-with-where[Existential subquery with `WHERE` clause] + ** xref:clauses/where.adoc#existential-subquery-nesting[Nesting existential subqueries] +* xref:clauses/where.adoc#query-where-lists[Lists] + ** xref:clauses/where.adoc#where-in-operator[`IN` operator] +* xref:clauses/where.adoc#missing-properties-and-values[Missing properties and values] + ** xref:clauses/where.adoc#default-to-false-missing-property[Default to `false` if property is missing] + ** xref:clauses/where.adoc#default-to-true-missing-property[Default to `true` if property is missing] + ** xref:clauses/where.adoc#filter-on-null[Filter on `null`] +* xref:clauses/where.adoc#query-where-ranges[Using ranges] + ** xref:clauses/where.adoc#simple-range[Simple range] + ** xref:clauses/where.adoc#composite-range[Composite range] + [[where-introduction]] == Introduction -`WHERE` is not a clause in its own right -- rather, it is part of `MATCH`, `OPTIONAL MATCH`, and `WITH`. +`WHERE` is not a clause in its own right -- rather, it's part of `MATCH`, `OPTIONAL MATCH` and `WITH`. In the case of `WITH`, `WHERE` simply filters the results. @@ -61,28 +54,76 @@ _It should not be seen as a filter after the matching is finished._ ==== In the case of multiple `MATCH` / `OPTIONAL MATCH` clauses, the predicate in `WHERE` is always a part of the patterns in the directly preceding `MATCH` / `OPTIONAL MATCH`. Both results and performance may be impacted if the `WHERE` is put inside the wrong `MATCH` clause. + + ==== [NOTE] ==== -xref::indexes-for-search-performance.adoc[Indexes] may be used to optimize queries using `WHERE` in a variety of cases. +xref:indexes-for-search-performance.adoc[Indexes] may be used to optimize queries using `WHERE` in a variety of cases. + + ==== The following graph is used for the examples below: -image:graph_where_clause.svg[] - -//// -CREATE (andy:Swedish:Person {name: 'Andy', age: 36, belt: 'white'}), -(timothy:Person {name: 'Timothy', age: 25, address: 'Sweden/Malmo'}), -(peter:Person {name: 'Peter', age: 35, email: 'peter_n@example.com'}), -(andy)-[:KNOWS {since: 2012}]->(timothy), -(andy)-[:KNOWS {since: 1999}]->(peter), -(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), -(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), -(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) -//// +.Graph +["dot", "WHERE-1.svg", "neoviz", ""] +---- + N0 [ + label = "{Swedish, Person|name = \'Andy\'\lage = 36\lbelt = \'white\'\l}" + ] + N0 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "HAS_DOG\nsince = 2016\l" + ] + N0 -> N2 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "KNOWS\nsince = 1999\l" + ] + N0 -> N1 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "KNOWS\nsince = 2012\l" + ] + N1 [ + label = "{Person|age = 25\laddress = \'Sweden/Malmo\'\lname = \'Timothy\'\l}" + ] + N2 [ + label = "{Person|name = \'Peter\'\lemail = \'peter_n@example.com\'\lage = 35\l}" + ] + N2 -> N4 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "HAS_DOG\nsince = 2010\l" + ] + N2 -> N5 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "HAS_DOG\nsince = 2018\l" + ] + N3 [ + label = "{Dog|name = \'Andy\'\l}" + ] + N4 [ + label = "{Dog|name = \'Fido\'\l}" + ] + N4 -> N6 [ + color = "#a40000" + fontcolor = "#a40000" + label = "HAS_TOY\n" + ] + N5 [ + label = "{Dog|name = \'Ozzy\'\l}" + ] + N6 [ + label = "{Toy|name = \'Banana\'\l}" + ] +---- + [[query-where-basic]] == Basic usage @@ -91,37 +132,58 @@ CREATE (andy:Swedish:Person {name: 'Andy', age: 36, belt: 'white'}), === Boolean operations You can use the boolean operators `AND`, `OR`, `XOR` and `NOT`. -See xref::syntax/working-with-null.adoc[] for more information on how this works with `null`. +See xref:syntax/working-with-null.adoc[] for more information on how this works with `null`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) WHERE n.name = 'Peter' XOR (n.age < 30 AND n.name = 'Timothy') OR NOT (n.name = 'Timothy' OR n.name = 'Peter') -RETURN - n.name AS name, - n.age AS age -ORDER BY name +RETURN n.name, n.age ---- .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[filter-on-node-label]] === Filter on node label To filter nodes by label, write a label predicate after the `WHERE` keyword using `WHERE n:foo`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE n:Swedish @@ -138,14 +200,37 @@ The name and age for the *'Andy'* node will be returned. 2+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[filter-on-node-property]] === Filter on node property To filter on a node property, write your clause after the `WHERE` keyword. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) WHERE n.age < 30 @@ -162,14 +247,37 @@ The name and age values for the *'Timothy'* node are returned because he is less 2+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[filter-on-relationship-property]] === Filter on relationship property To filter on a relationship property, write your clause after the `WHERE` keyword. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person)-[k:KNOWS]->(f) WHERE k.since < 2000 @@ -186,14 +294,37 @@ The name, age and email values for the *'Peter'* node are returned because Andy 3+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]>(f) +WHERE k.since < 2000 +RETURN f.name, f.age, f.email +]]> +++++ +endif::nonhtmloutput[] [[filter-on-dynamic-property]] === Filter on dynamically-computed node property To filter on a property using a dynamically computed name, use square bracket syntax. + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH 'AGE' AS propname MATCH (n:Person) @@ -211,14 +342,38 @@ The name and age values for the *'Timothy'* node are returned because he is less 2+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[property-existence-checking]] === Property existence checking Use the `IS NOT NULL` predicate to only include nodes or relationships in which a property exists. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) WHERE n.belt IS NOT NULL @@ -230,6 +385,8 @@ The name and belt for the *'Andy'* node are returned because he is the only one [IMPORTANT] ==== The `exists()` function has been deprecated for property existence checking and has been superseded by `IS NOT NULL`. + + ==== .Result @@ -240,6 +397,79 @@ The `exists()` function has been deprecated for property existence checking and 2+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] + +[[usage-with-with-clause]] +=== Usage with `WITH` + +As `WHERE` is not considered a clause in its own right, its scope is not limited by a `WITH` directly before it. + + +.Query +[source, cypher] +---- +MATCH (n:Person) +WITH n.name as name +WHERE n.age = 25 +RETURN name +---- + +The name for the *'Timothy'* node is returned because the `WHERE` clause still acts as a filter on the `MATCH`. +The `WITH` reduces the scope for the rest of the query moving forward. +In this case 'name' is now the only variable in scope for the `RETURN` clause. + +.Result +[role="queryresult",options="header,footer",cols="1* +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[query-where-string]] == String matching @@ -254,8 +484,9 @@ Attempting to use these operators on values which are not strings will return `n The `STARTS WITH` operator is used to perform case-sensitive matching on the beginning of a string. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) WHERE n.name STARTS WITH 'Pet' @@ -272,14 +503,37 @@ The name and age for the *'Peter'* node are returned because his name starts wit 2+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[match-string-end]] === Suffix string search using `ENDS WITH` The `ENDS WITH` operator is used to perform case-sensitive matching on the ending of a string. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) WHERE n.name ENDS WITH 'ter' @@ -296,14 +550,37 @@ The name and age for the *'Peter'* node are returned because his name ends with 2+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[match-string-contains]] === Substring search using `CONTAINS` The `CONTAINS` operator is used to perform case-sensitive matching regardless of location within a string. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) WHERE n.name CONTAINS 'ete' @@ -320,14 +597,37 @@ The name and age for the *'Peter'* node are returned because his name contains w 2+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[match-string-negation]] === String matching negation Use the `NOT` keyword to exclude all matches on given string from your result: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) WHERE NOT n.name ENDS WITH 'y' @@ -344,23 +644,36 @@ The name and age for the *'Peter'* node are returned because his name does not e 2+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[query-where-regex]] == Regular expressions Cypher supports filtering using regular expressions. -The regular expression syntax is inherited from the link:https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/regex/Pattern.html[Java regular expressions]. +The regular expression syntax is inherited from link:https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/regex/Pattern.html[the Java regular expressions]. This includes support for flags that change how strings are matched, including case-insensitive `(?i)`, multiline `(?m)` and dotall `(?s)`. -Flags are given at the beginning of the regular expression, for example: - -.Query -[source, cypher, role="noheader"] ----- -MATCH (n) WHERE n.name =~ '(?i)Lon.*' -RETURN n ----- - -will return nodes with name `'London'` or with name `'LonDoN'`. +Flags are given at the beginning of the regular expression, for example `MATCH (n) WHERE n.name =~ '(?i)Lon.*' RETURN n` will return nodes with name 'London' or with name 'LonDoN'. [[matching-using-regular-expressions]] === Matching using regular expressions @@ -369,14 +682,14 @@ You can match on regular expressions by using `=~ 'regexp'`, like this: .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) WHERE n.name =~ 'Tim.*' RETURN n.name, n.age ---- -The name and age for the `'Timothy'` node are returned because his name starts with `'Tim'`. +The name and age for the *'Timothy'* node are returned because his name starts with *'Tim'*. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[escaping-in-regular-expressions]] === Escaping in regular expressions @@ -393,8 +728,9 @@ The name and age for the `'Timothy'` node are returned because his name starts w Characters like `.` or `*` have special meaning in a regular expression. To use these as ordinary characters, without special meaning, escape them. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) WHERE n.email =~ '.*\\.com' @@ -411,21 +747,44 @@ The name, age and email for the 'Peter' node are returned because his email ends 3+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[case-insensitive-regular-expressions]] === Case-insensitive regular expressions By pre-pending a regular expression with `(?i)`, the whole expression becomes case-insensitive. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) WHERE n.name =~ '(?i)AND.*' RETURN n.name, n.age ---- -The name and age for the `'Andy'` node are returned because his name starts with `'AND'` irrespective of casing. +The name and age for the 'Andy' node are returned because his name starts with 'AND' irrespective of casing. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[query-where-patterns]] == Using path patterns in `WHERE` @@ -457,7 +838,7 @@ The first will produce a path for every path it can find between `a` and `b`, wh .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (timothy:Person {name: 'Timothy'}), @@ -466,7 +847,7 @@ WHERE other.name IN ['Andy', 'Peter'] AND (other)-->(timothy) RETURN other.name, other.age ---- -The name and age for nodes that have an outgoing relationship to the `'Timothy'` node are returned. +The name and age for nodes that have an outgoing relationship to the *'Timothy'* node are returned. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]>(timothy) +RETURN other.name, other.age +]]> +++++ +endif::nonhtmloutput[] [[filter-on-patterns-using-not]] === Filter on patterns using `NOT` The `NOT` operator can be used to exclude a pattern. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (person:Person), @@ -492,7 +898,7 @@ WHERE NOT (person)-->(peter) RETURN person.name, person.age ---- -Name and age values for nodes that do not have an outgoing relationship to the `'Peter'` node are returned. +Name and age values for nodes that do not have an outgoing relationship to the *'Peter'* node are returned. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]>(peter) +RETURN person.name, person.age +]]> +++++ +endif::nonhtmloutput[] [[filter-on-patterns-with-properties]] === Filter on patterns with properties You can also add properties to your patterns: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) WHERE (n)-[:KNOWS]-({name: 'Timothy'}) RETURN n.name, n.age ---- -Finds all name and age values for nodes that have a relationship with the `KNOWS`-type, to a node with the property-key `name` and value `'Timothy'`. +Finds all name and age values for nodes that have a `KNOWS` relationship to a node with the name *'Timothy'*. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[filter-on-relationship-type]] === Filter on relationship type @@ -535,15 +988,16 @@ You can put the exact relationship type in the `MATCH` pattern, but sometimes yo You can use the special property `type` to compare the type with something else. In this example, the query does a regular expression comparison with the name of the relationship type. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person)-[r]->() WHERE n.name='Andy' AND type(r) =~ 'K.*' RETURN type(r), r.since ---- -This returns all relationships having a type whose name starts with `'K'`. +This returns all relationships having a type whose name starts with *'K'*. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]>() +WHERE n.name='Andy' AND type(r) =~ 'K.*' +RETURN type(r), r.since +]]> +++++ +endif::nonhtmloutput[] + An existential subquery can be used to find out if a specified pattern exists at least once in the data. It can be used in the same way as a path pattern but it allows you to use `MATCH` and `WHERE` clauses internally. A subquery has a scope, as indicated by the opening and closing braces, `{` and `}`. @@ -562,15 +1039,16 @@ Variables introduced inside the subquery are not part of the outside scope and t If the subquery evaluates even once to anything that is not null, the whole expression will become true. This also means that the system only needs to calculate the first occurrence where the subquery evaluates to something that is not null and can skip the rest of the work. -.Syntax -[source, cypher, role="noheader"] + +*Syntax:* +[source, cypher, role=noplay] EXISTS { MATCH [Pattern] WHERE [Expression] } -It is worth noting that the `MATCH` keyword can be omitted in subqueries and that the `WHERE` clause is optional. +It is worth noting that the `MATCH` keyword can be omitted in subqueries and that the `WHERE` clause is optional. [[existential-subqueries]] == Using existential subqueries in `WHERE` @@ -580,8 +1058,9 @@ It is worth noting that the `MATCH` keyword can be omitted in subqueries and tha Variables introduced by the outside scope can be used in the inner `MATCH` clause. The following example shows this: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (person:Person) WHERE EXISTS { @@ -599,6 +1078,30 @@ RETURN person.name AS name 1+d|Rows: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]>(:Dog) +} +RETURN person.name AS name +]]> +++++ +endif::nonhtmloutput[] [[existential-subquery-with-where]] === Existential subquery with `WHERE` clause @@ -606,8 +1109,9 @@ RETURN person.name AS name A `WHERE` clause can be used in conjunction to the `MATCH`. Variables introduced by the `MATCH` clause and the outside scope can be used in this scope. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (person:Person) WHERE EXISTS { @@ -625,6 +1129,31 @@ RETURN person.name AS name 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]>(dog:Dog) + WHERE person.name = dog.name +} +RETURN person.name AS name +]]> +++++ +endif::nonhtmloutput[] [[existential-subquery-nesting]] === Nesting existential subqueries @@ -633,8 +1162,9 @@ Existential subqueries can be nested like the following example shows. The nesting also affects the scopes. That means that it is possible to access all variables from inside the subquery which are either on the outside scope or defined in the very same subquery. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (person:Person) WHERE EXISTS { @@ -655,6 +1185,34 @@ RETURN person.name AS name 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]>(dog:Dog) + WHERE EXISTS { + MATCH (dog)-[:HAS_TOY]->(toy:Toy) + WHERE toy.name = 'Banana' + } +} +RETURN person.name AS name +]]> +++++ +endif::nonhtmloutput[] [[query-where-lists]] == Lists @@ -664,8 +1222,9 @@ RETURN person.name AS name To check if an element exists in a list, you can use the `IN` operator. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:Person) WHERE a.name IN ['Peter', 'Timothy'] @@ -683,6 +1242,28 @@ This query shows how to check if a property exists in a literal list. 2+d|Rows: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[missing-properties-and-values]] == Missing properties and values @@ -692,15 +1273,16 @@ This query shows how to check if a property exists in a literal list. As missing properties evaluate to `null`, the comparison in the example will evaluate to `false` for nodes without the `belt` property. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) WHERE n.belt = 'white' RETURN n.name, n.age, n.belt ---- -Only the name, age, and belt values of nodes with white belts are returned. +Only the name, age and belt values of nodes with white belts are returned. .Result [role="queryresult",options="header,footer",cols="3* +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[default-to-true-missing-property]] === Default to `true` if property is missing If you want to compare a property on a node or relationship, but only if it exists, you can compare the property against both the value you are looking for and `null`, like: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) WHERE n.belt = 'white' OR n.belt IS NULL @@ -737,6 +1342,29 @@ This returns all values for all nodes, even those without the belt property. 3+d|Rows: 3 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[filter-on-null]] === Filter on `null` @@ -745,15 +1373,16 @@ Sometimes you might want to test if a value or a variable is `null`. This is done just like SQL does it, using `IS NULL`. Also like SQL, the negative is `IS NOT NULL`, although `NOT(IS NULL x)` also works. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (person:Person) WHERE person.name = 'Peter' AND person.belt IS NULL RETURN person.name, person.age, person.belt ---- -The name and age values for nodes that have name `'Peter'` but no belt property are returned. +The name and age values for nodes that have name *'Peter'* but no belt property are returned. .Result [role="queryresult",options="header,footer",cols="3* +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]> +++++ +endif::nonhtmloutput[] [[query-where-ranges]] == Using ranges @@ -770,17 +1421,18 @@ The name and age values for nodes that have name `'Peter'` but no belt property [[simple-range]] === Simple range -To check for an element being inside a specific range, use the inequality operators `+<+`, `+<=+`, `+>=+`, `+>+`. +To check for an element being inside a specific range, use the inequality operators `<`, `\<=`, `>=`, `>`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:Person) WHERE a.name >= 'Peter' RETURN a.name, a.age ---- -The name and age values of nodes having a name property lexicographically greater than or equal to `'Peter'` are returned. +The name and age values of nodes having a name property lexicographically greater than or equal to *'Peter'* are returned. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) + +]]>= 'Peter' +RETURN a.name, a.age +]]> +++++ +endif::nonhtmloutput[] [[composite-range]] === Composite range Several inequalities can be used to construct a range. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:Person) WHERE a.name > 'Andy' AND a.name < 'Timothy' RETURN a.name, a.age ---- -The name and age values of nodes having a name property lexicographically between `'Andy'` and `'Timothy'` are returned. +The name and age values of nodes having a name property lexicographically between *'Andy'* and *'Timothy'* are returned. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(timothy), +(andy)-[:KNOWS {since: 1999}]->(peter), +(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) -[[pattern-element-predicates]] -=== Pattern element predicates - -[[node-pattern-predicates]] -==== Node pattern predicates - -`WHERE` can appear inside a node pattern in a `MATCH` clause or a pattern comprehension: - -.Query -[source, cypher, indent=0] ----- -WITH 30 AS minAge -MATCH (a:Person WHERE a.name = 'Andy')-[:KNOWS]->(b:Person WHERE b.age > minAge) -RETURN b.name ----- - -.Result -[role="queryresult",options="header,footer",cols="1*(b WHERE b:Person) | b.name] AS friends ----- - -.Result -[role="queryresult",options="header,footer",cols="1* 'Andy' AND a.name < 'Timothy' +RETURN a.name, a.age +]]> +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/clauses/with.adoc b/modules/ROOT/pages/clauses/with.adoc index 94517a395..ff026d6d9 100644 --- a/modules/ROOT/pages/clauses/with.adoc +++ b/modules/ROOT/pages/clauses/with.adoc @@ -1,116 +1,98 @@ -:description: The `WITH` clause allows query parts to be chained together, piping the results from one to be used as starting points or criteria in the next. - [[query-with]] = WITH - -[abstract] --- -The `WITH` clause allows query parts to be chained together, piping the results from one to be used as starting points or criteria in the next. --- +:description: The `WITH` clause allows query parts to be chained together, piping the results from one to be used as starting points or criteria in the next. [NOTE] ==== -It is important to note that `WITH` affects variables in scope. -Any variables not included in the `WITH` clause are not carried over to the rest of the query. -The wildcard `*` can be used to include all variables that are currently in scope. +It is important to note that `WITH` affects variables in scope. Any variables not included in the `WITH` clause are not carried over to the rest of the query. + + ==== -Using `WITH`, you can manipulate the output before it is passed on to the following query parts. -Manipulations can be done to the shape and/or number of entries in the result set. +* xref:clauses/with.adoc#with-introduction[Introduction] +* xref:clauses/with.adoc#with-filter-on-aggregate-function-results[Filter on aggregate function results] +* xref:clauses/with.adoc#with-sort-results-before-using-collect-on-them[Sort results before using collect on them] +* xref:clauses/with.adoc#with-limit-branching-of-path-search[Limit branching of a path search] + + +[[with-introduction]] +== Introduction -One common usage of `WITH` is to limit the number of entries passed on to other `MATCH` clauses. -By combining `ORDER BY` and `LIMIT`, it is possible to get the top X entries by some criteria and then bring in additional data from the graph. +Using `WITH`, you can manipulate the output before it is passed on to the following query parts. +The manipulations can be of the shape and/or number of entries in the result set. -`WITH` can also be used to introduce new variables containing the results of expressions for use in the following query parts (see xref::clauses/with.adoc#with-introduce-variables[Introducing variables for expressions]). -For convenience, the wildcard `*` expands to all variables that are currently in scope and carries them over to the next query part (see xref::clauses/with.adoc#with-wildcard[Using the wildcard to carry over variables]). +One common usage of `WITH` is to limit the number of entries that are then passed on to other `MATCH` clauses. +By combining `ORDER BY` and `LIMIT`, it's possible to get the top X entries by some criteria, and then bring in additional data from the graph. Another use is to filter on aggregated values. `WITH` is used to introduce aggregates which can then be used in predicates in `WHERE`. These aggregate expressions create new bindings in the results. +`WITH` can also, like `RETURN`, alias expressions that are introduced into the results using the aliases as the binding name. `WITH` is also used to separate reading from updating of the graph. Every part of a query must be either read-only or write-only. When going from a writing part to a reading part, the switch must be done with a `WITH` clause. -image:graph_with_clause.svg[] - -//// -CREATE - (a {name: 'Anders'}), - (b {name: 'Bossman'}), - (c {name: 'Caesar'}), - (d {name: 'David'}), - (e {name: 'George'}), - (a)-[:KNOWS]->(b), - (a)-[:BLOCKS]->(c), - (d)-[:KNOWS]->(a), - (b)-[:KNOWS]->(e), - (c)-[:KNOWS]->(e), - (b)-[:BLOCKS]->(d) -//// - - -[[with-introduce-variables]] -== Introducing variables for expressions - -You can introduce new variables for the result of evaluating expressions. - -.Query -[source, cypher, indent=0] ----- -MATCH (george {name: 'George'})<--(otherPerson) -WITH otherPerson, toUpper(otherPerson.name) AS upperCaseName -WHERE upperCaseName STARTS WITH 'C' -RETURN otherPerson.name +.Graph +["dot", "WITH-1.svg", "neoviz", ""] ---- + N0 [ + label = "name = \'Anders\'\l" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "BLOCKS\n" + ] + N0 -> N1 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "KNOWS\n" + ] + N1 [ + label = "name = \'Bossman\'\l" + ] + N1 -> N4 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "KNOWS\n" + ] + N1 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "BLOCKS\n" + ] + N2 [ + label = "name = \'Caesar\'\l" + ] + N2 -> N4 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "KNOWS\n" + ] + N3 [ + label = "name = \'David\'\l" + ] + N3 -> N0 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "KNOWS\n" + ] + N4 [ + label = "name = \'George\'\l" + ] -This query returns the name of persons connected to *'George'* whose name starts with a `C`, regardless of capitalization. - -.Result -[role="queryresult",options="header,footer",cols="1*(otherPerson) -WITH *, type(r) AS connectionType -RETURN person.name, otherPerson.name, connectionType ----- - -This query returns the names of all related persons and the type of relationship between them. - -.Result -[role="queryresult",options="header,footer",cols="3*() WITH otherPerson, count(*) AS foaf @@ -128,14 +110,42 @@ The name of the person connected to *'David'* with the at least more than one ou 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:BLOCKS]->(c), + (d)-[:KNOWS]->(a), + (b)-[:KNOWS]->(e), + (c)-[:KNOWS]->(e), + (b)-[:BLOCKS]->(d) + +]]>() +WITH otherPerson, count(*) AS foaf +WHERE foaf > 1 +RETURN otherPerson.name +]]> +++++ +endif::nonhtmloutput[] [[with-sort-results-before-using-collect-on-them]] == Sort results before using collect on them You can sort your results before passing them to collect, thus sorting the resulting list. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WITH n @@ -154,14 +164,43 @@ A list of the names of people in reverse order, limited to 3, is returned in a l 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:BLOCKS]->(c), + (d)-[:KNOWS]->(a), + (b)-[:KNOWS]->(e), + (c)-[:KNOWS]->(e), + (b)-[:BLOCKS]->(d) + +]]> +++++ +endif::nonhtmloutput[] [[with-limit-branching-of-path-search]] == Limit branching of a path search You can match paths, limit to a certain number, and then match again using those paths as a base, as well as any number of similar limited searches. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'Anders'})--(m) WITH m @@ -182,3 +221,33 @@ Starting at *'Anders'*, find all matching nodes, order by name descending and ge 1+d|Rows: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(b), + (a)-[:BLOCKS]->(c), + (d)-[:KNOWS]->(a), + (b)-[:KNOWS]->(e), + (c)-[:KNOWS]->(e), + (b)-[:BLOCKS]->(d) + +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/constraints/examples.adoc b/modules/ROOT/pages/constraints/examples.adoc index 7d776a497..f35066e04 100644 --- a/modules/ROOT/pages/constraints/examples.adoc +++ b/modules/ROOT/pages/constraints/examples.adoc @@ -1,45 +1,22 @@ -:description: Examples of how to manage constraints used for ensuring data integrity. - [[administration-constraints-examples]] = Examples - -[abstract] --- -Examples of how to manage constraints used for ensuring data integrity. --- - +:description: Examples of how to manage constraints used for ensuring data integrity. [[administration-constraints-unique-nodes]] == Unique node property constraints -* xref::constraints/examples.adoc#administration-constraints-create-a-unique-constraint[] -* xref::constraints/examples.adoc#administration-constraints-create-a-unique-constraint-only-if-it-does-not-already-exist[] -* xref::constraints/examples.adoc#administration-constraints-create-a-unique-constraint-with-specified-index-provider-and-configuration[] -* xref::constraints/examples.adoc#administration-constraints-failure-to-create-an-already-existing-unique-property-constraint[] -* xref::constraints/examples.adoc#administration-constraints-failure-to-create-a-unique-property-constraint-on-same-schema-as-existing-index[] -* xref::constraints/examples.adoc#administration-constraints-create-a-node-that-complies-with-unique-property-constraints[] -* xref::constraints/examples.adoc#administration-constraints-create-a-node-that-violates-a-unique-property-constraint[] -* xref::constraints/examples.adoc#administration-constraints-failure-to-create-a-unique-property-constraint-due-to-conflicting-nodes[] - - [discrete] [[administration-constraints-create-a-unique-constraint]] -=== Create a unique constraint - -When creating a unique constraint, a name can be provided. -The constraint ensures that your database will never contain more than one node with a specific label and one property value. - - -.+CREATE CONSTRAINT+ -====== +== Create a unique constraint == +When creating a unique constraint, a name can be provided. The constraint ensures that your database will never contain more than one node with a specific label and one property value. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT constraint_name -FOR (book:Book) REQUIRE book.isbn IS UNIQUE +CREATE CONSTRAINT constraint_name ON (book:Book) ASSERT book.isbn IS UNIQUE ---- + .Result [queryresult] ---- @@ -49,32 +26,30 @@ FOR (book:Book) REQUIRE book.isbn IS UNIQUE Unique constraints added: 1 ---- -====== - - -[discrete] -[[administration-constraints-create-a-unique-constraint-only-if-it-does-not-already-exist]] -=== Create a unique constraint only if it does not already exist -If it is not known whether a constraint exists or not, add `IF NOT EXISTS` to ensure it does. -The uniqueness constraint ensures that your database will never contain more than one node with a specific label and one property value. +.Try this query live +[console] +---- +none -[NOTE] -==== -No constraint will be created if any other constraint with that name or another uniqueness constraint on the same schema already exists. -==== +CREATE CONSTRAINT constraint_name ON (book:Book) ASSERT book.isbn IS UNIQUE +---- -.+CREATE CONSTRAINT+ -====== +[discrete] +[[administration-constraints-create-a-unique-constraint-only-if-it-does-not-already-exist]] +== Create a unique constraint only if it does not already exist == +If it is unknown if a constraint exists or not but we want to make sure it does, we add the `IF NOT EXISTS`. The uniqueness constraint ensures that your database will never contain more than one node with a specific label and one property value. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT constraint_name IF NOT EXISTS -FOR (book:Book) REQUIRE book.isbn IS UNIQUE +CREATE CONSTRAINT constraint_name IF NOT EXISTS ON (book:Book) ASSERT book.isbn IS UNIQUE ---- + +Note no constraint will be created if any other constraint with that name or another uniqueness constraint on the same schema already exists. Assuming no such constraints existed: + .Result [queryresult] ---- @@ -84,51 +59,36 @@ FOR (book:Book) REQUIRE book.isbn IS UNIQUE Unique constraints added: 1 ---- -====== + +.Try this query live +[console] +---- +none + +CREATE CONSTRAINT constraint_name IF NOT EXISTS ON (book:Book) ASSERT book.isbn IS UNIQUE +---- [discrete] [[administration-constraints-create-a-unique-constraint-with-specified-index-provider-and-configuration]] -=== Create a unique constraint with specified index provider and configuration - +== Create a unique constraint with specified index provider and configuration == To create a unique constraint with a specific index provider and configuration for the backing index, the `OPTIONS` clause is used. -Valid values for the index provider are `native-btree-1.0` (deprecated), `lucene+native-3.0` (deprecated), and `range-1.0` (future index), default is `native-btree-1.0`. -The index type of the backing index is set depending on the provider, the `range-1.0` generates a xref::indexes-for-search-performance.adoc#indexes-future-indexes[future range index] while the other providers generates a B-tree index. -The range index have no configuration settings. - -The valid B-tree configuration settings are: - -* `spatial.cartesian.min` -* `spatial.cartesian.max` -* `spatial.cartesian-3d.min` -* `spatial.cartesian-3d.max` -* `spatial.wgs-84.min` -* `spatial.wgs-84.max` -* `spatial.wgs-84-3d.min` -* `spatial.wgs-84-3d.max` - -Non-specified settings have their respective default values. - -In Neo4j 4.4, B-tree index-backed constraints are still the correct alternative to use. - - -.+CREATE CONSTRAINT+ -====== +Valid values for the index provider is `native-btree-1.0` and `lucene+native-3.0`, default if nothing is specified is `native-btree-1.0`. +Valid configuration settings are `spatial.cartesian.min`, `spatial.cartesian.max`, `spatial.cartesian-3d.min`, `spatial.cartesian-3d.max`, +`spatial.wgs-84.min`, `spatial.wgs-84.max`, `spatial.wgs-84-3d.min`, and `spatial.wgs-84-3d.max`. +Non-specified settings get their respective default values. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT constraint_with_options -FOR (n:Label) REQUIRE (n.prop1, n.prop2) IS UNIQUE +CREATE CONSTRAINT constraint_with_options ON (n:Label) ASSERT n.prop IS UNIQUE OPTIONS { - indexProvider: 'lucene+native-3.0', - indexConfig: { - `spatial.wgs-84.min`: [-100.0, -80.0], - `spatial.wgs-84.max`: [100.0, 80.0] - } + indexProvider: 'lucene+native-3.0', + indexConfig: {`spatial.wgs-84.min`: [-100.0, -80.0], `spatial.wgs-84.max`: [100.0, 80.0]} } ---- + Specifying index provider and configuration can be done individually. .Result @@ -140,81 +100,32 @@ Specifying index provider and configuration can be done individually. Unique constraints added: 1 ---- -====== - -[discrete] -[[administration-constraints-failure-to-create-an-already-existing-unique-property-constraint]] -=== Failure to create an already existing unique property constraint - - -.+CREATE CONSTRAINT+ -====== - -Create a unique property constraint on the property `title` on nodes with the `Book` label, when that constraint already exists. - -.Query -[source, cypher, indent=0] ----- -CREATE CONSTRAINT FOR (book:Book) REQUIRE book.title IS UNIQUE +.Try this query live +[console] ---- +none -In this case the constraint can not be created because it already exists. - -.Error message -[source, "error message", role="noheader"] ----- -Constraint already exists: -Constraint( id=4, name='preExistingUnique', type='UNIQUENESS', schema=(:Book {title}), ownedIndex=3 ) ----- - -====== - - -[discrete] -[[administration-constraints-failure-to-create-a-unique-property-constraint-on-same-schema-as-existing-index]] -=== Failure to create a unique property constraint on same schema as existing index - - -.+CREATE CONSTRAINT+ -====== - -Create a unique property constraint on the property `wordCount` on nodes with the `Book` label, when an index already exists on that label and property combination. - -.Query -[source, cypher, indent=0] ----- -CREATE CONSTRAINT FOR (book:Book) REQUIRE book.wordCount IS UNIQUE ----- - -In this case the constraint can not be created because there already exists an index covering that schema. - -.Error message -[source, "error message", role="noheader"] ----- -There already exists an index (:Book {wordCount}). -A constraint cannot be created until the index has been dropped. +CREATE CONSTRAINT constraint_with_options ON (n:Label) ASSERT n.prop IS UNIQUE +OPTIONS { + indexProvider: 'lucene+native-3.0', + indexConfig: {`spatial.wgs-84.min`: [-100.0, -80.0], `spatial.wgs-84.max`: [100.0, 80.0]} +} ---- -====== - [discrete] [[administration-constraints-create-a-node-that-complies-with-unique-property-constraints]] -=== Create a node that complies with unique property constraints - - -.+CREATE CONSTRAINT+ -====== - -Create a `Book` node with an `isbn` that is not already in the database. +== Create a node that complies with unique property constraints == +Create a `Book` node with an `isbn` that isn't already in the database. .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE (book:Book {isbn: '1449356265', title: 'Graph Databases'}) ---- + .Result [queryresult] ---- @@ -226,98 +137,71 @@ Properties set: 2 Labels added: 1 ---- -====== [discrete] [[administration-constraints-create-a-node-that-violates-a-unique-property-constraint]] -=== Create a node that violates a unique property constraint - - -.+CREATE CONSTRAINT+ -====== - +== Create a node that violates a unique property constraint == Create a `Book` node with an `isbn` that is already used in the database. .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE (book:Book {isbn: '1449356265', title: 'Graph Databases'}) ---- -In this case the node is not created in the graph. + +In this case the node isn't created in the graph. .Error message -[source, "error message", role="noheader"] +[source] ---- Node(0) already exists with label `Book` and property `isbn` = '1449356265' ---- -====== [discrete] [[administration-constraints-failure-to-create-a-unique-property-constraint-due-to-conflicting-nodes]] -=== Failure to create a unique property constraint due to conflicting nodes - - -.+CREATE CONSTRAINT+ -====== - +== Failure to create a unique property constraint due to conflicting nodes == Create a unique property constraint on the property `isbn` on nodes with the `Book` label when there are two nodes with the same `isbn`. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT FOR (book:Book) REQUIRE book.isbn IS UNIQUE +CREATE CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE ---- -In this case the constraint can not be created because it is violated by existing data. -You may choose to use xref::indexes-for-search-performance.adoc[] instead or remove the offending nodes and then re-apply the constraint. + +In this case the constraint can't be created because it is violated by existing data. We may choose to use xref:indexes-for-search-performance.adoc[] instead or remove the offending nodes and then re-apply the constraint. .Error message -[source, "error message", role="noheader"] +[source] ---- -Unable to create Constraint( name='constraint_62365a16', type='UNIQUENESS', +Unable to create Constraint( name='constraint_ca412c3d', type='UNIQUENESS', schema=(:Book {isbn}) ): Both Node(0) and Node(1) have the label `Book` and property `isbn` = '1449356265' ---- -====== + [role=enterprise-edition] [[administration-constraints-prop-exist-nodes]] == Node property existence constraints -* xref::constraints/examples.adoc#administration-constraints-create-a-node-property-existence-constraint[] -* xref::constraints/examples.adoc#administration-constraints-create-a-node-property-existence-constraint-only-if-it-does-not-already-exist[] -* xref::constraints/examples.adoc#administration-constraints-failure-to-create-an-already-existing-node-property-existence-constraint[] -* xref::constraints/examples.adoc#administration-constraints-create-a-node-that-complies-with-property-existence-constraints[] -* xref::constraints/examples.adoc#administration-constraints-create-a-node-that-violates-a-property-existence-constraint[] -* xref::constraints/examples.adoc#administration-constraints-removing-an-existence-constrained-node-property[] -* xref::constraints/examples.adoc#administration-constraints-failure-to-create-a-node-property-existence-constraint-due-to-existing-node[] -//* xref::constraints/examples.adoc# - - [discrete] [[administration-constraints-create-a-node-property-existence-constraint]] -=== Create a node property existence constraint - -When creating a node property existence constraint, a name can be provided. -The constraint ensures that all nodes with a certain label have a certain property. - - -.+CREATE CONSTRAINT+ -====== +== Create a node property existence constraint == +When creating a node property existence constraint, a name can be provided. The constraint ensures that all nodes with a certain label have a certain property. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT constraint_name -FOR (book:Book) REQUIRE book.isbn IS NOT NULL +CREATE CONSTRAINT constraint_name ON (book:Book) ASSERT book.isbn IS NOT NULL ---- + .Result [queryresult] ---- @@ -327,28 +211,29 @@ FOR (book:Book) REQUIRE book.isbn IS NOT NULL Property existence constraints added: 1 ---- -====== -[discrete] -[[administration-constraints-create-a-node-property-existence-constraint-only-if-it-does-not-already-exist]] -=== Create a node property existence constraint only if it does not already exist +.Try this query live +[console] +---- +none -If it is not known whether a constraint exists or not, add `IF NOT EXISTS` to ensure it does. -The node property existence constraint ensures that all nodes with a certain label have a certain property. -No constraint will be created if any other constraint with that name or another node property existence constraint on the same schema already exists. +CREATE CONSTRAINT constraint_name ON (book:Book) ASSERT book.isbn IS NOT NULL +---- -.+CREATE CONSTRAINT+ -====== +[discrete] +[[administration-constraints-create-a-node-property-existence-constraint-only-if-it-does-not-already-exist]] +== Create a node property existence constraint only if it does not already exist == +If it is unknown if a constraint exists or not but we want to make sure it does, we add the `IF NOT EXISTS`. The node property existence constraint ensures that all nodes with a certain label have a certain property. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT constraint_name IF NOT EXISTS -FOR (book:Book) REQUIRE book.isbn IS NOT NULL +CREATE CONSTRAINT constraint_name IF NOT EXISTS ON (book:Book) ASSERT book.isbn IS NOT NULL ---- -Assuming a constraint with the name `constraint_name` already existed: + +Note no constraint will be created if any other constraint with that name or another node property existence constraint on the same schema already exists. Assuming a constraint with the name `constraint_name` already existed: .Result [queryresult] @@ -358,54 +243,28 @@ Assuming a constraint with the name `constraint_name` already existed: +--------------------------------------------+ ---- -====== - -[discrete] -[[administration-constraints-failure-to-create-an-already-existing-node-property-existence-constraint]] -=== Failure to create an already existing node property existence constraint - - -.+CREATE CONSTRAINT+ -====== - -Create a node property existence constraint on the property `title` on nodes with the `Book` label, when that constraint already exists. - -.Query -[source, cypher, indent=0] ----- -CREATE CONSTRAINT booksShouldHaveTitles -FOR (book:Book) REQUIRE book.title IS NOT NULL +.Try this query live +[console] ---- +none -In this case the constraint can not be created because it already exists. - -.Error message -[source, "error message", role="noheader"] ----- -Constraint already exists: -Constraint( id=3, name='preExistingNodePropExist', type='NODE PROPERTY EXISTENCE', schema=(:Book {title}) ) +CREATE CONSTRAINT constraint_name IF NOT EXISTS ON (book:Book) ASSERT book.isbn IS NOT NULL ---- -====== - [discrete] [[administration-constraints-create-a-node-that-complies-with-property-existence-constraints]] -=== Create a node that complies with property existence constraints - - -.+CREATE CONSTRAINT+ -====== - +== Create a node that complies with property existence constraints == Create a `Book` node with an `isbn` property. .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE (book:Book {isbn: '1449356265', title: 'Graph Databases'}) ---- + .Result [queryresult] ---- @@ -417,124 +276,93 @@ Properties set: 2 Labels added: 1 ---- -====== [discrete] [[administration-constraints-create-a-node-that-violates-a-property-existence-constraint]] -=== Create a node that violates a property existence constraint - - -.+CREATE CONSTRAINT+ -====== - +== Create a node that violates a property existence constraint == Trying to create a `Book` node without an `isbn` property, given a property existence constraint on `:Book(isbn)`. .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE (book:Book {title: 'Graph Databases'}) ---- -In this case the node is not created in the graph. + +In this case the node isn't created in the graph. .Error message -[source, "error message", role="noheader"] +[source] ---- Node(0) with label `Book` must have the property `isbn` ---- -====== [discrete] [[administration-constraints-removing-an-existence-constrained-node-property]] -=== Removing an existence constrained node property - - -.+CREATE CONSTRAINT+ -====== - +== Removing an existence constrained node property == Trying to remove the `isbn` property from an existing node `book`, given a property existence constraint on `:Book(isbn)`. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (book:Book {title: 'Graph Databases'}) -REMOVE book.isbn +MATCH (book:Book {title: 'Graph Databases'}) REMOVE book.isbn ---- + In this case the property is not removed. .Error message -[source, "error message", role="noheader"] +[source] ---- Node(0) with label `Book` must have the property `isbn` ---- -====== [discrete] [[administration-constraints-failure-to-create-a-node-property-existence-constraint-due-to-existing-node]] -=== Failure to create a node property existence constraint due to existing node - - -.+CREATE CONSTRAINT+ -====== - +== Failure to create a node property existence constraint due to existing node == Create a constraint on the property `isbn` on nodes with the `Book` label when there already exists a node without an `isbn`. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT FOR (book:Book) REQUIRE book.isbn IS NOT NULL +CREATE CONSTRAINT ON (book:Book) ASSERT book.isbn IS NOT NULL ---- + In this case the constraint can't be created because it is violated by existing data. We may choose to remove the offending nodes and then re-apply the constraint. .Error message -[source, "error message", role="noheader"] +[source] ---- Unable to create Constraint( type='NODE PROPERTY EXISTENCE', schema=(:Book {isbn}) ): Node(0) with label `Book` must have the property `isbn` ---- -====== + [role=enterprise-edition] [[administration-constraints-prop-exist-rels]] == Relationship property existence constraints -* xref::constraints/examples.adoc#administration-constraints-create-a-relationship-property-existence-constraint[] -* xref::constraints/examples.adoc#administration-constraints-create-a-relationship-property-existence-constraint-only-if-it-does-not-already-exist[] -* xref::constraints/examples.adoc#administration-constraints-failure-to-create-an-already-existing-relationship-property-existence-constraint[] -* xref::constraints/examples.adoc#administration-constraints-create-a-relationship-that-complies-with-property-existence-constraints[] -* xref::constraints/examples.adoc#administration-constraints-create-a-relationship-that-violates-a-property-existence-constraint[] -* xref::constraints/examples.adoc#administration-constraints-removing-an-existence-constrained-relationship-property[] -* xref::constraints/examples.adoc#administration-constraints-failure-to-create-a-relationship-property-existence-constraint-due-to-existing-relationship[] - - [discrete] [[administration-constraints-create-a-relationship-property-existence-constraint]] -=== Create a relationship property existence constraint - -When creating a relationship property existence constraint, a name can be provided. -The constraint ensures all relationships with a certain type have a certain property. - - -.+CREATE CONSTRAINT+ -====== +== Create a relationship property existence constraint == +When creating a relationship property existence constraint, a name can be provided. The constraint ensures all relationships with a certain type have a certain property. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT constraint_name -FOR ()-[like:LIKED]-() REQUIRE like.day IS NOT NULL +CREATE CONSTRAINT constraint_name ON ()-[like:LIKED]-() ASSERT like.day IS NOT NULL ---- + .Result [queryresult] ---- @@ -544,29 +372,29 @@ FOR ()-[like:LIKED]-() REQUIRE like.day IS NOT NULL Property existence constraints added: 1 ---- -====== - -[discrete] -[[administration-constraints-create-a-relationship-property-existence-constraint-only-if-it-does-not-already-exist]] -=== Create a relationship property existence constraint only if it does not already exist +.Try this query live +[console] +---- +none -If it is not known whether a constraint exists or not, add `IF NOT EXISTS` to ensure it does. -The relationship property existence constraint ensures all relationships with a certain type have a certain property. -No constraint will be created if any other constraint with that name or another relationship property existence constraint on the same schema already exists. +CREATE CONSTRAINT constraint_name ON ()-[like:LIKED]-() ASSERT like.day IS NOT NULL +---- -.+CREATE CONSTRAINT+ -====== +[discrete] +[[administration-constraints-create-a-relationship-property-existence-constraint-only-if-it-does-not-already-exist]] +== Create a relationship property existence constraint only if it does not already exist == +If it is unknown if a constraint exists or not but we want to make sure it does, we add the `IF NOT EXISTS`. The relationship property existence constraint ensures all relationships with a certain type have a certain property. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT constraint_name -IF NOT EXISTS FOR ()-[like:LIKED]-() REQUIRE like.day IS NOT NULL +CREATE CONSTRAINT constraint_name IF NOT EXISTS ON ()-[like:LIKED]-() ASSERT like.day IS NOT NULL ---- -Assuming a constraint with the name `constraint_name` already existed: + +Note no constraint will be created if any other constraint with that name or another relationship property existence constraint on the same schema already exists. Assuming a constraint with the name `constraint_name` already existed: .Result [queryresult] @@ -576,53 +404,28 @@ Assuming a constraint with the name `constraint_name` already existed: +--------------------------------------------+ ---- -====== - - -[discrete] -[[administration-constraints-failure-to-create-an-already-existing-relationship-property-existence-constraint]] -=== Failure to create an already existing relationship property existence constraint - -.+CREATE CONSTRAINT+ -====== - -Create a named relationship property existence constraint on the property `week` on relationships with the `LIKED` type, when a constraint with that name already exists. - -.Query -[source, cypher, indent=0] +.Try this query live +[console] ---- -CREATE CONSTRAINT relPropExist -FOR ()-[like:LIKED]-() REQUIRE like.week IS NOT NULL ----- - -In this case the constraint can not be created because there already exists a constraint with that name. +none -.Error message -[source, "error message", role="noheader"] +CREATE CONSTRAINT constraint_name IF NOT EXISTS ON ()-[like:LIKED]-() ASSERT like.day IS NOT NULL ---- -There already exists a constraint called 'relPropExist'. ----- - -====== [discrete] [[administration-constraints-create-a-relationship-that-complies-with-property-existence-constraints]] -=== Create a relationship that complies with property existence constraints - - -.+CREATE CONSTRAINT+ -====== - +== Create a relationship that complies with property existence constraints == Create a `LIKED` relationship with a `day` property. .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE (user:User)-[like:LIKED {day: 'yesterday'}]->(book:Book) ---- + .Result [queryresult] ---- @@ -635,126 +438,93 @@ Properties set: 1 Labels added: 2 ---- -====== [discrete] [[administration-constraints-create-a-relationship-that-violates-a-property-existence-constraint]] -=== Create a relationship that violates a property existence constraint - - -.+CREATE CONSTRAINT+ -====== - +== Create a relationship that violates a property existence constraint == Trying to create a `LIKED` relationship without a `day` property, given a property existence constraint `:LIKED(day)`. .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE (user:User)-[like:LIKED]->(book:Book) ---- -In this case the relationship is not created in the graph. + +In this case the relationship isn't created in the graph. .Error message -[source, "error message", role="noheader"] +[source] ---- Relationship(0) with type `LIKED` must have the property `day` ---- -====== [discrete] [[administration-constraints-removing-an-existence-constrained-relationship-property]] -=== Removing an existence constrained relationship property - - -.+CREATE CONSTRAINT+ -====== - +== Removing an existence constrained relationship property == Trying to remove the `day` property from an existing relationship `like` of type `LIKED`, given a property existence constraint `:LIKED(day)`. .Query -[source, cypher, indent=0] +[source,cypher] ---- MATCH (user:User)-[like:LIKED]->(book:Book) REMOVE like.day ---- + In this case the property is not removed. .Error message -[source, "error message", role="noheader"] +[source] ---- Relationship(0) with type `LIKED` must have the property `day` ---- -====== [discrete] [[administration-constraints-failure-to-create-a-relationship-property-existence-constraint-due-to-existing-relationship]] -=== Failure to create a relationship property existence constraint due to existing relationship - - -.+CREATE CONSTRAINT+ -====== - +== Failure to create a relationship property existence constraint due to existing relationship == Create a constraint on the property `day` on relationships with the `LIKED` type when there already exists a relationship without a property named `day`. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT FOR ()-[like:LIKED]-() REQUIRE like.day IS NOT NULL +CREATE CONSTRAINT ON ()-[like:LIKED]-() ASSERT like.day IS NOT NULL ---- -In this case the constraint can not be created because it is violated by existing data. We may choose to remove the offending relationships and then re-apply the constraint. + +In this case the constraint can't be created because it is violated by existing data. We may choose to remove the offending relationships and then re-apply the constraint. .Error message -[source, "error message", role="noheader"] +[source] ---- Unable to create Constraint( type='RELATIONSHIP PROPERTY EXISTENCE', schema=-[:LIKED {day}]- ): Relationship(0) with type `LIKED` must have the property `day` ---- -====== + [role=enterprise-edition] [[administration-constraints-node-key]] == Node key constraints -* xref::constraints/examples.adoc#administration-constraints-create-a-node-key-constraint[] -* xref::constraints/examples.adoc#administration-constraints-create-a-node-key-constraint-only-if-it-does-not-already-exist[] -* xref::constraints/examples.adoc#administration-constraints-create-a-node-key-constraint-with-specified-index-provider[] -* xref::constraints/examples.adoc#administration-constraints-create-a-node-key-constraint-with-specified-index-configuration[] -* xref::constraints/examples.adoc#administration-constraints-failure-to-create-a-node-key-constraint-when-a-unique-property-constraint-exists-on-the-same-schema[] -* xref::constraints/examples.adoc#administration-constraints-failure-to-create-a-node-key-constraint-with-the-same-name-as-existing-index[] -* xref::constraints/examples.adoc#administration-constraints-create-a-node-that-complies-with-node-key-constraints[] -* xref::constraints/examples.adoc#administration-constraints-create-a-node-that-violates-a-node-key-constraint[] -* xref::constraints/examples.adoc#administration-constraints-removing-a-node-key-constrained-property[] -* xref::constraints/examples.adoc#administration-constraints-failure-to-create-a-node-key-constraint-due-to-existing-node[] - - [discrete] [[administration-constraints-create-a-node-key-constraint]] -=== Create a node key constraint - -When creating a node key constraint, a name can be provided. -The constraint ensures that all nodes with a particular label have a set of defined properties whose combined value is unique and all properties in the set are present. - - -.+CREATE CONSTRAINT+ -====== +== Create a node key constraint == +When creating a node key constraint, a name can be provided. The constraint ensures that all nodes with a particular label have a set of defined properties whose combined value is unique and all properties in the set are present. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT constraint_name -FOR (n:Person) REQUIRE (n.firstname, n.surname) IS NODE KEY +CREATE CONSTRAINT constraint_name ON (n:Person) ASSERT (n.firstname, n.surname) IS NODE KEY ---- + .Result [queryresult] ---- @@ -764,29 +534,30 @@ FOR (n:Person) REQUIRE (n.firstname, n.surname) IS NODE KEY Node key constraints added: 1 ---- -====== +.Try this query live +[console] +---- +none -[discrete] -[[administration-constraints-create-a-node-key-constraint-only-if-it-does-not-already-exist]] -=== Create a node key constraint only if it does not already exist - -If it is not known whether a constraint exists or not, add `IF NOT EXISTS` to ensure it does. -The node key constraint ensures that all nodes with a particular label have a set of defined properties whose combined value is unique and all properties in the set are present. -No constraint will be created if any other constraint with that name or another node key constraint on the same schema already exists. +CREATE CONSTRAINT constraint_name ON (n:Person) ASSERT (n.firstname, n.surname) IS NODE KEY +---- -.+CREATE CONSTRAINT+ -====== +[discrete] +[[administration-constraints-create-a-node-key-constraint-only-if-it-does-not-already-exist]] +== Create a node key constraint only if it does not already exist == +If it is unknown if a constraint exists or not but we want to make sure it does, we add the `IF NOT EXISTS`. The node key constraint ensures that all nodes with a particular label have a set of defined properties whose combined value is unique and all properties in the set are present. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT constraint_name IF NOT EXISTS -FOR (n:Person) REQUIRE (n.firstname, n.surname) IS NODE KEY +CREATE CONSTRAINT constraint_name IF NOT EXISTS ON (n:Person) ASSERT (n.firstname, + n.surname) IS NODE KEY ---- -Assuming a node key constraint on `(:Person {firstname, surname})` already existed: + +Note no constraint will be created if any other constraint with that name or another node key constraint on the same schema already exists. Assuming a node key constraint on `(:Person {firstname, surname})` already existed: .Result [queryresult] @@ -796,32 +567,31 @@ Assuming a node key constraint on `(:Person {firstname, surname})` already exist +--------------------------------------------+ ---- -====== + +.Try this query live +[console] +---- +none + +CREATE CONSTRAINT constraint_name IF NOT EXISTS ON (n:Person) ASSERT (n.firstname, n.surname) IS NODE KEY +---- [discrete] [[administration-constraints-create-a-node-key-constraint-with-specified-index-provider]] -=== Create a node key constraint with specified index provider - +== Create a node key constraint with specified index provider == To create a node key constraint with a specific index provider for the backing index, the `OPTIONS` clause is used. -Valid values for the index provider are `native-btree-1.0` (deprecated), `lucene+native-3.0` (deprecated), and `range-1.0` (future index), default is `native-btree-1.0`. -The index type of the backing index is set depending on the provider, the `range-1.0` generates a xref::indexes-for-search-performance.adoc#indexes-future-indexes[future range index] while the other providers generates a B-tree index. - -In Neo4j 4.4, B-tree index-backed constraints are still the correct alternative to use. - - -.+CREATE CONSTRAINT+ -====== +Valid values for the index provider is `native-btree-1.0` and `lucene+native-3.0`, default if nothing is specified is `native-btree-1.0`. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT constraint_with_provider -FOR (n:Label) REQUIRE (n.prop1) IS NODE KEY -OPTIONS {indexProvider: 'native-btree-1.0'} +CREATE CONSTRAINT constraint_with_provider ON (n:Label) ASSERT (n.prop1) IS NODE KEY OPTIONS + {indexProvider: 'native-btree-1.0'} ---- -B-tree providers can be combined with specifying index configuration. + +Can be combined with specifying index configuration. .Result [queryresult] @@ -832,47 +602,34 @@ B-tree providers can be combined with specifying index configuration. Node key constraints added: 1 ---- -====== + +.Try this query live +[console] +---- +none + +CREATE CONSTRAINT constraint_with_provider ON (n:Label) ASSERT (n.prop1) IS NODE KEY OPTIONS {indexProvider: 'native-btree-1.0'} +---- [discrete] [[administration-constraints-create-a-node-key-constraint-with-specified-index-configuration]] -=== Create a node key constraint with specified index configuration - +== Create a node key constraint with specified index configuration == To create a node key constraint with a specific index configuration for the backing index, the `OPTIONS` clause is used. -The index type of the backing index is set depending on the provider and xref::indexes-for-search-performance.adoc#indexes-future-indexes[future range indexes] have no configuration settings. - -The valid B-tree configuration settings are: - -* `spatial.cartesian.min` -* `spatial.cartesian.max` -* `spatial.cartesian-3d.min` -* `spatial.cartesian-3d.max` -* `spatial.wgs-84.min` -* `spatial.wgs-84.max` -* `spatial.wgs-84-3d.min` -* `spatial.wgs-84-3d.max` - -Non-specified settings have their respective default values. - - -.+CREATE CONSTRAINT+ -====== +Valid configuration settings are `spatial.cartesian.min`, `spatial.cartesian.max`, `spatial.cartesian-3d.min`, `spatial.cartesian-3d.max`, +`spatial.wgs-84.min`, `spatial.wgs-84.max`, `spatial.wgs-84-3d.min`, and `spatial.wgs-84-3d.max`. +Non-specified settings get their respective default values. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT constraint_with_config -FOR (n:Label) REQUIRE (n.prop2) IS NODE KEY -OPTIONS { - indexConfig: { - `spatial.cartesian.min`: [-100.0, -100.0], - `spatial.cartesian.max`: [100.0, 100.0] - } -} +CREATE CONSTRAINT constraint_with_config ON (n:Label) ASSERT (n.prop2) IS NODE KEY +OPTIONS {indexConfig: {`spatial.cartesian.min`: [-100.0, -100.0], `spatial.cartesian.max`: [100.0, + 100.0]}} ---- -Can be combined with specifying a B-tree index provider. + +Can be combined with specifying index provider. .Result [queryresult] @@ -883,81 +640,29 @@ Can be combined with specifying a B-tree index provider. Node key constraints added: 1 ---- -====== - -[discrete] -[[administration-constraints-failure-to-create-a-node-key-constraint-when-a-unique-property-constraint-exists-on-the-same-schema]] -=== Failure to create a node key constraint when a unique property constraint exists on the same schema - - -.+CREATE CONSTRAINT+ -====== - -Create a node key constraint on the properties `firstname` and `age` on nodes with the `Person` label, when a unique property constraint already exists on the same label and property combination. - -.Query -[source, cypher, indent=0] +.Try this query live +[console] ---- -CREATE CONSTRAINT FOR (p:Person) REQUIRE (p.firstname, p.age) IS NODE KEY ----- - -In this case the constraint can not be created because there already exist a conflicting constraint on that label and property combination. - -.Error message -[source, "error message", role="noheader"] ----- -Constraint already exists: -Constraint( id=4, name='preExistingUnique', type='UNIQUENESS', schema=(:Person {firstname, age}), ownedIndex=3 ) ----- - -====== - - -[discrete] -[[administration-constraints-failure-to-create-a-node-key-constraint-with-the-same-name-as-existing-index]] -=== Failure to create a node key constraint with the same name as existing index - - -.+CREATE CONSTRAINT+ -====== - -Create a named node key constraint on the property `title` on nodes with the `Book` label, when an index already exists with that name. +none -.Query -[source, cypher, indent=0] ----- -CREATE CONSTRAINT bookTitle -FOR (book:Book) REQUIRE book.title IS NODE KEY ----- - -In this case the constraint can't be created because there already exists an index with that name. - -.Error message -[source, "error message", role="noheader"] ----- -There already exists an index called 'bookTitle'. +CREATE CONSTRAINT constraint_with_config ON (n:Label) ASSERT (n.prop2) IS NODE KEY +OPTIONS {indexConfig: {`spatial.cartesian.min`: [-100.0, -100.0], `spatial.cartesian.max`: [100.0, 100.0]}} ---- -====== - [discrete] [[administration-constraints-create-a-node-that-complies-with-node-key-constraints]] -=== Create a node that complies with node key constraints - - -.+CREATE CONSTRAINT+ -====== - +== Create a node that complies with node key constraints == Create a `Person` node with both a `firstname` and `surname` property. .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE (p:Person {firstname: 'John', surname: 'Wood', age: 55}) ---- + .Result [queryresult] ---- @@ -969,118 +674,97 @@ Properties set: 3 Labels added: 1 ---- -====== [discrete] [[administration-constraints-create-a-node-that-violates-a-node-key-constraint]] -=== Create a node that violates a node key constraint - - -.+CREATE CONSTRAINT+ -====== - +== Create a node that violates a node key constraint == Trying to create a `Person` node without a `surname` property, given a node key constraint on `:Person(firstname, surname)`, will fail. .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE (p:Person {firstname: 'Jane', age: 34}) ---- -In this case the node is not created in the graph. + +In this case the node isn't created in the graph. .Error message -[source, "error message", role="noheader"] +[source] ---- Node(0) with label `Person` must have the properties (firstname, surname) ---- -====== [discrete] [[administration-constraints-removing-a-node-key-constrained-property]] -=== Removing a +NODE KEY+-constrained property - - -.+CREATE CONSTRAINT+ -====== - +== Removing a `NODE KEY`-constrained property == Trying to remove the `surname` property from an existing node `Person`, given a `NODE KEY` constraint on `:Person(firstname, surname)`. .Query -[source, cypher, indent=0] +[source,cypher] ---- MATCH (p:Person {firstname: 'John', surname: 'Wood'}) REMOVE p.surname ---- + In this case the property is not removed. .Error message -[source, "error message", role="noheader"] +[source] ---- Node(0) with label `Person` must have the properties (firstname, surname) ---- -====== [discrete] [[administration-constraints-failure-to-create-a-node-key-constraint-due-to-existing-node]] -=== Failure to create a node key constraint due to existing node - - -.+CREATE CONSTRAINT+ -====== - -Trying to create a node key constraint on the property `surname` on nodes with the `Person` label will fail when a node without a `surname` already exists in the database. +== Failure to create a node key constraint due to existing node == +Trying to create a node key constraint on the property `surname` on nodes with the `Person` label will fail when a node without a `surname` already exists in the database. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE CONSTRAINT FOR (n:Person) REQUIRE (n.firstname, n.surname) IS NODE KEY +CREATE CONSTRAINT ON (n:Person) ASSERT (n.firstname, n.surname) IS NODE KEY ---- -In this case the node key constraint can not be created because it is violated by existing data. -We may choose to remove the offending nodes and then re-apply the constraint. + +In this case the node key constraint can't be created because it is violated by existing data. We may choose to remove the offending nodes and then re-apply the constraint. .Error message -[source, "error message", role="noheader"] +[source] ---- -Unable to create Constraint( type='NODE PROPERTY EXISTENCE', schema=(:Person -{firstname, surname}) ): -Node(0) with label `Person` must have the properties (firstname, surname) +Unable to create Constraint( name='constraint_c57fc9b0', type='NODE KEY', +schema=(:Person {firstname, surname}) ): +Failed during property existence validation: Unable to create constraint +org.neo4j.internal.schema.constraints.ConstraintDescriptorImplementation@12000642: +Node(0) does not satisfy Constraint( type='NODE PROPERTY EXISTENCE', +schema=(:Person {firstname, surname}) ). ---- -====== + [[administration-constraints-drop-constraint]] == Drop a constraint by name -* xref::constraints/examples.adoc#administration-constraints-drop-a-constraint[] -* xref::constraints/examples.adoc#administration-constraints-drop-a-non-existing-constraint[] - - [discrete] [[administration-constraints-drop-a-constraint]] -=== Drop a constraint - +== Drop a constraint == A constraint can be dropped using the name with the `DROP CONSTRAINT constraint_name` command. It is the same command for unique property, property existence and node key constraints. -The name of the constraint can be found using the xref::constraints/syntax.adoc#administration-constraints-syntax-list[`SHOW CONSTRAINTS` command], given in the output column `name`. - - -.+DROP CONSTRAINT+ -====== +The name of the constraint can be found using the xref:constraints/syntax.adoc#administration-constraints-syntax-list[`SHOW CONSTRAINTS` command], given in the output column `name`. .Query -[source, cypher, indent=0] +[source,cypher] ---- DROP CONSTRAINT constraint_name ---- + .Result [queryresult] ---- @@ -1090,25 +774,20 @@ DROP CONSTRAINT constraint_name Named constraints removed: 1 ---- -====== [discrete] [[administration-constraints-drop-a-non-existing-constraint]] -=== Drop a non-existing constraint - -If it is uncertain if any constraint with a given name exists and you want to drop it if it does but not get an error should it not, use `IF EXISTS`. -It is the same command for unique property, property existence and node key constraints. - -.+DROP CONSTRAINT+ -====== +== Drop a non-existing constraint == +If it is uncertain if any constraint with a given name exists and you want to drop it if it does but not get an error should it not, use `IF EXISTS`. It is the same command for unique property, property existence and node key constraints. .Query -[source, cypher, indent=0] +[source,cypher] ---- DROP CONSTRAINT missing_constraint_name IF EXISTS ---- + .Result [queryresult] ---- @@ -1117,76 +796,61 @@ DROP CONSTRAINT missing_constraint_name IF EXISTS +--------------------------------------------+ ---- -====== + [[administration-constraints-list-constraint]] == Listing constraints -* xref::constraints/examples.adoc#administration-constraints-listing-all-constraints[] -* xref::constraints/examples.adoc#administration-constraints-listing-constraints-with-filtering[] - - [discrete] [[administration-constraints-listing-all-constraints]] -=== Listing all constraints +== Listing all constraints == To list all constraints with the default output columns, the `SHOW CONSTRAINTS` command can be used. If all columns are required, use `SHOW CONSTRAINTS YIELD *`. -[NOTE] -==== -One of the output columns from `SHOW CONSTRAINTS` is the name of the constraint. -This can be used to drop the constraint with the xref::constraints/syntax.adoc#administration-constraints-syntax-drop[`DROP CONSTRAINT` command]. -==== - - -.+SHOW CONSTRAINTS+ -====== - .Query -[source, cypher, indent=0] +[source,cypher] ---- SHOW CONSTRAINTS ---- + +One of the output columns from `SHOW CONSTRAINTS` is the name of the constraint. +This can be used to drop the constraint with the xref:constraints/syntax.adoc#administration-constraints-syntax-drop[`DROP CONSTRAINT` command]. + .Result [queryresult] ---- +----------------------------------------------------------------------------------------------------+ | id | name | type | entityType | labelsOrTypes | properties | ownedIndexId | +----------------------------------------------------------------------------------------------------+ -| 4 | "constraint_62365a16" | "UNIQUENESS" | "NODE" | ["Book"] | ["isbn"] | 3 | +| 4 | "constraint_ca412c3d" | "UNIQUENESS" | "NODE" | ["Book"] | ["isbn"] | 3 | +----------------------------------------------------------------------------------------------------+ 1 row ---- -====== [discrete] [[administration-constraints-listing-constraints-with-filtering]] -=== Listing constraints with filtering +== Listing constraints with filtering == One way of filtering the output from `SHOW CONSTRAINTS` by constraint type is the use of type keywords, -listed in xref::constraints/syntax.adoc#administration-constraints-syntax-list[Syntax for listing constraints]. +listed in xref:constraints/syntax.adoc#administration-constraints-syntax-list[Syntax for listing constraints]. For example, to show only unique node property constraints, use `SHOW UNIQUE CONSTRAINTS`. Another more flexible way of filtering the output is to use the `WHERE` clause. An example is to only show constraints on relationships. - -.+SHOW CONSTRAINTS+ -====== - .Query -[source, cypher, indent=0] +[source,cypher] ---- -SHOW EXISTENCE CONSTRAINTS -WHERE entityType = 'RELATIONSHIP' +SHOW EXISTENCE CONSTRAINTS WHERE entityType = 'RELATIONSHIP' ---- + This will only return the default output columns. -To get all columns, use `+SHOW INDEXES YIELD * WHERE ...+`. +To get all columns, use `SHOW INDEXES YIELD * WHERE ...`. .Result [queryresult] @@ -1199,124 +863,24 @@ To get all columns, use `+SHOW INDEXES YIELD * WHERE ...+`. 1 row ---- -====== + [role=deprecated] [[administration-constraints-deprecated-syntax]] == Deprecated syntax -* xref::constraints/examples.adoc#administration-constraints-create-a-unique-constraint-using-deprecated-syntax[] -* xref::constraints/examples.adoc#administration-constraints-drop-a-unique-constraint[] -* xref::constraints/examples.adoc#administration-constraints-create-a-node-property-existence-constraint-using-deprecated-syntax-1[] -* xref::constraints/examples.adoc#administration-constraints-create-a-node-property-existence-constraint-using-deprecated-syntax-2[] -* xref::constraints/examples.adoc#administration-constraints-drop-a-node-property-existence-constraint[] -* xref::constraints/examples.adoc#administration-constraints-create-a-relationship-property-existence-constraint-using-deprecated-syntax-1[] -* xref::constraints/examples.adoc#administration-constraints-create-a-relationship-property-existence-constraint-using-deprecated-syntax-2[] -* xref::constraints/examples.adoc#administration-constraints-drop-a-relationship-property-existence-constraint[] -* xref::constraints/examples.adoc#administration-constraints-create-a-node-key-constraint-using-deprecated-syntax[] -* xref::constraints/examples.adoc#administration-constraints-drop-a-node-key-constraint[] - - -[discrete] -[[administration-constraints-create-a-unique-constraint-using-deprecated-syntax]] -=== Create a unique constraint using deprecated syntax - -The unique constraint ensures that your database will never contain more than one node with a specific label and one property value. - - -.+CREATE CONSTRAINT+ -====== - -.Query -[source, cypher, indent=0] ----- -CREATE CONSTRAINT ON (book:Book) ASSERT book.title IS UNIQUE ----- - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Unique constraints added: 1 ----- - -====== - - [discrete] [[administration-constraints-drop-a-unique-constraint]] -=== Drop a unique constraint - -By using `DROP CONSTRAINT`, a B-tree index-backed unique constraint is removed from the database. - - -.+DROP CONSTRAINT+ -====== +== Drop a unique constraint == +By using `DROP CONSTRAINT`, you remove a constraint from the database. .Query -[source, cypher, indent=0] +[source,cypher] ---- DROP CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE ---- -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Unique constraints removed: 1 ----- - -====== - - -[discrete] -[[administration-constraints-create-a-node-property-existence-constraint-using-deprecated-syntax-1]] -=== Create a node property existence constraint using deprecated syntax 1 - -The node property existence constraint ensures that all nodes with a certain label have a certain property. - - -.+CREATE CONSTRAINT+ -====== - -.Query -[source, cypher, indent=0] ----- -CREATE CONSTRAINT ON (book:Book) ASSERT book.title IS NOT NULL ----- - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Property existence constraints added: 1 ----- - -====== - - -[discrete] -[[administration-constraints-create-a-node-property-existence-constraint-using-deprecated-syntax-2]] -=== Create a node property existence constraint using deprecated syntax 2 - -The node property existence constraint ensures that all nodes with a certain label have a certain property. - - -.+CREATE CONSTRAINT+ -====== - -.Query -[source, cypher, indent=0] ----- -CREATE CONSTRAINT ON (book:Book) ASSERT exists(book.title) ----- .Result [queryresult] @@ -1324,83 +888,22 @@ CREATE CONSTRAINT ON (book:Book) ASSERT exists(book.title) +-------------------+ | No data returned. | +-------------------+ -Property existence constraints added: 1 +Unique constraints removed: 1 ---- -====== [discrete] [[administration-constraints-drop-a-node-property-existence-constraint]] -=== Drop a node property existence constraint - -By using `DROP CONSTRAINT`, a node property existence constraint is removed from the database. - - -.+DROP CONSTRAINT+ -====== +== Drop a node property existence constraint == +By using `DROP CONSTRAINT`, you remove a constraint from the database. .Query -[source, cypher, indent=0] +[source,cypher] ---- DROP CONSTRAINT ON (book:Book) ASSERT exists(book.isbn) ---- -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Property existence constraints removed: 1 ----- - -====== - - -[discrete] -[[administration-constraints-create-a-relationship-property-existence-constraint-using-deprecated-syntax-1]] -=== Create a relationship property existence constraint using deprecated syntax 1 - -The relationship property existence constraint ensures all relationships with a certain type have a certain property. - - -.+CREATE CONSTRAINT+ -====== - -.Query -[source, cypher, indent=0] ----- -CREATE CONSTRAINT ON ()-[like:LIKED]-() ASSERT like.week IS NOT NULL ----- - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Property existence constraints added: 1 ----- - -====== - - -[discrete] -[[administration-constraints-create-a-relationship-property-existence-constraint-using-deprecated-syntax-2]] -=== Create a relationship property existence constraint using deprecated syntax 2 - -The relationship property existence constraint ensures all relationships with a certain type have a certain property. - - -.+CREATE CONSTRAINT+ -====== - -.Query -[source, cypher, indent=0] ----- -CREATE CONSTRAINT ON ()-[like:LIKED]-() ASSERT exists(like.week) ----- .Result [queryresult] @@ -1408,55 +911,22 @@ CREATE CONSTRAINT ON ()-[like:LIKED]-() ASSERT exists(like.week) +-------------------+ | No data returned. | +-------------------+ -Property existence constraints added: 1 +Property existence constraints removed: 1 ---- -====== [discrete] [[administration-constraints-drop-a-relationship-property-existence-constraint]] -=== Drop a relationship property existence constraint - -To remove a relationship property existence constraint from the database, use `DROP CONSTRAINT`. - - -.+DROP CONSTRAINT+ -====== +== Drop a relationship property existence constraint == +To remove a constraint from the database, use `DROP CONSTRAINT`. .Query -[source, cypher, indent=0] +[source,cypher] ---- DROP CONSTRAINT ON ()-[like:LIKED]-() ASSERT exists(like.day) ---- -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Property existence constraints removed: 1 ----- - -====== - - -[discrete] -[[administration-constraints-create-a-node-key-constraint-using-deprecated-syntax]] -=== Create a node key constraint using deprecated syntax - -The node key constraint ensures that all nodes with a particular label have a set of defined properties whose combined value is unique and all properties in the set are present. - - -.+CREATE CONSTRAINT+ -====== - -.Query -[source, cypher, indent=0] ----- -CREATE CONSTRAINT ON (n:Person) ASSERT (n.firstname) IS NODE KEY ----- .Result [queryresult] @@ -1464,28 +934,23 @@ CREATE CONSTRAINT ON (n:Person) ASSERT (n.firstname) IS NODE KEY +-------------------+ | No data returned. | +-------------------+ -Node key constraints added: 1 +Property existence constraints removed: 1 ---- -====== [discrete] [[administration-constraints-drop-a-node-key-constraint]] -=== Drop a node key constraint - -Use `DROP CONSTRAINT` to remove a B-tree index-backed node key constraint from the database. - - -.+DROP CONSTRAINT+ -====== +== Drop a node key constraint == +Use `DROP CONSTRAINT` to remove a node key constraint from the database. .Query -[source, cypher, indent=0] +[source,cypher] ---- DROP CONSTRAINT ON (n:Person) ASSERT (n.firstname, n.surname) IS NODE KEY ---- + .Result [queryresult] ---- @@ -1495,5 +960,4 @@ DROP CONSTRAINT ON (n:Person) ASSERT (n.firstname, n.surname) IS NODE KEY Node key constraints removed: 1 ---- -====== diff --git a/modules/ROOT/pages/constraints/index.adoc b/modules/ROOT/pages/constraints/index.adoc index 48a149bdf..f5b0bd59b 100644 --- a/modules/ROOT/pages/constraints/index.adoc +++ b/modules/ROOT/pages/constraints/index.adoc @@ -1,13 +1,6 @@ -:description: This section explains how to manage constraints used for ensuring data integrity. - [[administration-constraints]] = Constraints - -[abstract] --- -This section explains how to manage constraints used for ensuring data integrity. --- - +:description: This section explains how to manage constraints used for ensuring data integrity. == Types of constraint @@ -15,20 +8,19 @@ The following constraint types are available: *Unique node property constraints*:: Unique property constraints ensure that property values are unique for all nodes with a specific label. -For unique property constraints on multiple properties, the combination of the property values is unique. -Unique constraints do not require all nodes to have a unique value for the properties listed -- nodes without all properties are not subject to this rule. +Unique constraints do not mean that all nodes have to have a unique value for the properties -- nodes without the property are not subject to this rule. -*Node property existence constraints* label:enterprise-edition[]:: +*[enterprise-edition]#Node property existence constraints#*:: Node property existence constraints ensure that a property exists for all nodes with a specific label. Queries that try to create new nodes of the specified label, but without this property, will fail. The same is true for queries that try to remove the mandatory property. -*Relationship property existence constraints* label:enterprise-edition[]:: +*[enterprise-edition]#Relationship property existence constraints#*:: Property existence constraints ensure that a property exists for all relationships with a specific type. All queries that try to create relationships of the specified type, but without this property, will fail. The same is true for queries that try to remove the mandatory property. -*Node key constraints* label:enterprise-edition[]:: +*[enterprise-edition]#Node key constraints#*:: Node key constraints ensure that, for a given label and set of properties: + [lowerroman] @@ -44,29 +36,26 @@ Queries attempting to do any of the following will fail: [NOTE] -==== Node key constraints, node property existence constraints and relationship property existence constraints are only available in Neo4j Enterprise Edition. Databases containing one of these constraint types cannot be opened using Neo4j Community Edition. -==== - == Implications on indexes Creating a constraint has the following implications on indexes: -* Adding a node key or unique property constraint on a single property also adds an index on that property and therefore, an index of the same index type, label, and property combination cannot be added separately. -* Adding a node key or unique property constraint for a set of properties also adds an index on those properties and therefore, an index of the same index type, label, and properties combination cannot be added separately. +* Adding a unique property constraint on a property will also add a xref:indexes-for-search-performance.adoc#administration-indexes-create-a-single-property-index-for-nodes[single-property index] on that property, so such an index cannot be added separately. +* Adding a node key constraint for a set of properties will also add a xref:indexes-for-search-performance.adoc#administration-indexes-create-a-composite-index-for-nodes[composite index] on those properties, so such an index cannot be added separately. * Cypher will use these indexes for lookups just like other indexes. - Refer to xref::indexes-for-search-performance.adoc[] for more details on indexes. -* If a node key or unique property constraint is dropped and the backing index is still required, the index need to be created explicitly. + Refer to xref:indexes-for-search-performance.adoc[] for more details on indexes. +* If a unique property constraint is dropped and the single-property index on the property is still required, the index will need to be created explicitly. +* If a node key constraint is dropped and the composite-property index on the properties is still required, the index will need to be created explicitly. Additionally, the following is true for constraints: * A given label can have multiple constraints, and unique and property existence constraints can be combined on the same property. -* Adding constraints is an atomic operation that can take a while -- all existing data has to be scanned before Neo4j DBMS can turn the constraint 'on'. +* Adding constraints is an atomic operation that can take a while -- all existing data has to be scanned before Neo4j can turn the constraint 'on'. * Best practice is to give the constraint a name when it is created. If the constraint is not explicitly named, it will get an auto-generated name. * The constraint name must be unique among both indexes and constraints. * Constraint creation is by default not idempotent, and an error will be thrown if you attempt to create the same constraint twice. Using the keyword `IF NOT EXISTS` makes the command idempotent, and no error will be thrown if you attempt to create the same constraint twice. - diff --git a/modules/ROOT/pages/constraints/syntax.adoc b/modules/ROOT/pages/constraints/syntax.adoc index 239a7d80d..0263b5c76 100644 --- a/modules/ROOT/pages/constraints/syntax.adoc +++ b/modules/ROOT/pages/constraints/syntax.adoc @@ -1,7 +1,6 @@ -:description: Syntax for how to manage constraints used for ensuring data integrity. - [[administration-constraints-syntax]] = Syntax +:description: Syntax for how to manage constraints used for ensuring data integrity. :check-mark: icon:check[] @@ -18,205 +17,131 @@ It may still throw an error if conflicting data, indexes, or constraints exist. Examples of this are nodes with missing properties, indexes with the same name, or constraints with same schema but a different constraint type. For constraints that are backed by an index, the index provider and configuration for the backing index can be specified using the `OPTIONS` clause. -Valid values for the index provider are `native-btree-1.0` (deprecated), `lucene+native-3.0` (deprecated), and `range-1.0` (future index), default is `native-btree-1.0`. -The index type of the backing index is set depending on the provider, the `range-1.0` generates a xref::indexes-for-search-performance.adoc#indexes-future-indexes[future range index] while the other providers generates a B-tree index. -The range index has no configuration settings. - -The valid B-tree configuration settings are: -* `spatial.cartesian.min` -* `spatial.cartesian.max` -* `spatial.cartesian-3d.min` -* `spatial.cartesian-3d.max` -* `spatial.wgs-84.min` -* `spatial.wgs-84.max` -* `spatial.wgs-84-3d.min` -* `spatial.wgs-84-3d.max` +Creating a constraint requires xref:access-control/database-administration.adoc#access-control-database-administration-constraints[the `CREATE CONSTRAINT` privilege]. -Non-specified settings have their respective default values. - -[NOTE] -==== -Creating a constraint requires the xref::access-control/database-administration.adoc#access-control-database-administration-constraints[`CREATE CONSTRAINT` privilege]. -==== - - -[[administration-constraints-syntax-create-unique]] [discrete] === Create a unique node property constraint -This command creates a uniqueness constraint on nodes with the specified label and properties. - -[source, syntax, role="noheader", indent=0] ----- -CREATE CONSTRAINT [constraint_name] [IF NOT EXISTS] -FOR (n:LabelName) -REQUIRE n.propertyName IS UNIQUE -[OPTIONS "{" option: value[, ...] "}"] ----- +This command creates a uniqueness constraint on nodes with the specified label and property. -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- CREATE CONSTRAINT [constraint_name] [IF NOT EXISTS] -FOR (n:LabelName) -REQUIRE (n.propertyName_1, ..., n.propertyName_n) IS UNIQUE +ON (n:LabelName) +ASSERT n.propertyName IS UNIQUE [OPTIONS "{" option: value[, ...] "}"] ---- -Index provider and configuration can be specified using the `OPTIONS` clause. - - -[[administration-constraints-syntax-create-node-exists]] [discrete] -=== Create a node property existence constraint label:enterprise-edition[] +=== [enterprise-edition]#Create a node property existence constraint# This command creates a property existence constraint on nodes with the specified label and property. -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- CREATE CONSTRAINT [constraint_name] [IF NOT EXISTS] -FOR (n:LabelName) -REQUIRE n.propertyName IS NOT NULL -[OPTIONS "{" "}"] +ON (n:LabelName) +ASSERT n.propertyName IS NOT NULL ---- -[NOTE] -==== -There are no supported `OPTIONS` values for existence constraints, but an empty options map is allowed for consistency. -==== - - -[[administration-constraints-syntax-create-rel-exists]] [discrete] -=== Create a relationship property existence constraint label:enterprise-edition[] +=== [enterprise-edition]#Create a relationship property existence constraint# This command creates a property existence constraint on relationships with the specified relationship type and property. -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- CREATE CONSTRAINT [constraint_name] [IF NOT EXISTS] -FOR ()-"["r:RELATIONSHIP_TYPE"]"-() -REQUIRE r.propertyName IS NOT NULL -[OPTIONS "{" "}"] +ON ()-"["R:RELATIONSHIP_TYPE"]"-() +ASSERT R.propertyName IS NOT NULL ---- -[NOTE] -==== -There are no supported `OPTIONS` values for existence constraints, but an empty options map is allowed for consistency. -==== - - -[[administration-constraints-syntax-create-node-key]] [discrete] -=== Create a node key constraint label:enterprise-edition[] +=== [enterprise-edition]#Create a node key constraint# This command creates a node key constraint on nodes with the specified label and properties. -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- CREATE CONSTRAINT [constraint_name] [IF NOT EXISTS] -FOR (n:LabelName) -REQUIRE n.propertyName IS NODE KEY -[OPTIONS "{" option: value[, ...] "}"] ----- - -[source, syntax, role="noheader", indent=0] ----- -CREATE CONSTRAINT [constraint_name] [IF NOT EXISTS] -FOR (n:LabelName) -REQUIRE (n.propertyName_1, ..., n.propertyName_n) IS NODE KEY +ON (n:LabelName) +ASSERT (n.propertyName_1, +n.propertyName_2, +… +n.propertyName_n) +IS NODE KEY [OPTIONS "{" option: value[, ...] "}"] ---- -Index provider and configuration can be specified using the `OPTIONS` clause. - - [[administration-constraints-syntax-drop]] == Syntax for dropping constraints - [discrete] === Drop a constraint The preferred way of dropping a constraint is by the name of the constraint. -[source, syntax, role="noheader", indent=0] ----- -DROP CONSTRAINT constraint_name [IF EXISTS] ----- - This drop command is optionally idempotent, with the default behavior to throw an error if you attempt to drop the same constraint twice. With the `IF EXISTS` flag, no error is thrown and nothing happens should the constraint not exist. -[NOTE] -==== -Dropping a constraint requires the xref::access-control/database-administration.adoc#access-control-database-administration-constraints[`DROP CONSTRAINT` privilege]. -==== +Dropping a constraint requires xref:access-control/database-administration.adoc#access-control-database-administration-constraints[the `DROP CONSTRAINT` privilege]. +[source, cypher, role=noplay] +---- +DROP CONSTRAINT constraint_name [IF EXISTS] +---- [discrete] -=== Drop a unique constraint without specifying a name label:deprecated[] +=== [deprecated]#Drop a unique constraint without specifying a name# An old way of dropping a uniqueness constraint was to drop the constraint by specifying the schema of the constraint. -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- DROP CONSTRAINT ON (n:LabelName) ASSERT n.propertyName IS UNIQUE ---- -[source, syntax, role="noheader", indent=0] ----- -DROP CONSTRAINT -ON (n:LabelName) -ASSERT (n.propertyName_1, ..., n.propertyName_n) IS UNIQUE ----- - - [discrete] -=== Drop a node property existence constraint without specifying a name label:deprecated[] +=== [deprecated]#Drop a node property existence constraint without specifying a name# An old way of dropping a node property existence constraint was to drop the constraint by specifying the schema of the constraint. -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- DROP CONSTRAINT ON (n:LabelName) ASSERT EXISTS (n.propertyName) ---- - [discrete] -=== Drop a relationship property existence constraint without specifying a name label:deprecated[] +=== [deprecated]#Drop a relationship property existence constraint without specifying a name# An old way of dropping a relationship property existence constraint was to drop the constraint by specifying the schema of the constraint. -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- DROP CONSTRAINT -ON ()-"["r:RELATIONSHIP_TYPE"]"-() -ASSERT EXISTS (r.propertyName) +ON ()-"["R:RELATIONSHIP_TYPE"]"-() +ASSERT EXISTS (R.propertyName) ---- - [discrete] -=== Drop a node key constraint without specifying a name label:deprecated[] +=== [deprecated]#Drop a node key constraint without specifying a name# An old way of dropping a node key constraint was to drop the constraint by specifying the schema of the constraint. -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- DROP CONSTRAINT ON (n:LabelName) -ASSERT n.propertyName IS NODE KEY ----- - -[source, syntax, role="noheader", indent=0] ----- -DROP CONSTRAINT -ON (n:LabelName) -ASSERT (n.propertyName_1, ..., n.propertyName_n) IS NODE KEY +ASSERT (n.propertyName_1, +n.propertyName_2, +… +n.propertyName_n) +IS NODE KEY ---- @@ -224,75 +149,81 @@ ASSERT (n.propertyName_1, ..., n.propertyName_n) IS NODE KEY == Syntax for listing constraints List constraints in the database, either all or filtered on constraint type. -This requires thexref::access-control/database-administration.adoc#access-control-database-administration-constraints[`SHOW CONSTRAINT`] privilege. +This requires xref:access-control/database-administration.adoc#access-control-database-administration-constraints[the `SHOW CONSTRAINT` privilege]. The simple version of the command allows for a `WHERE` clause and will give back the default set of output columns: -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- -SHOW [ALL - |UNIQUE - |NODE [PROPERTY] EXIST[ENCE] - |REL[ATIONSHIP] [PROPERTY] EXIST[ENCE] - |[PROPERTY] EXIST[ENCE] - |NODE KEY] CONSTRAINT[S] - [WHERE expression] +SHOW [ALL|UNIQUE|NODE [PROPERTY] EXIST[ENCE]|REL[ATIONSHIP] [PROPERTY] EXIST[ENCE]|[PROPERTY] EXIST[ENCE]|NODE KEY] CONSTRAINT[S] + [WHERE expression] ---- To get the full set of output columns, a yield clause is needed: -[source, syntax, role="noheader", indent=0] +[source, cypher, role=noplay] ---- -SHOW [ALL - |UNIQUE - |NODE [PROPERTY] EXIST[ENCE] - |REL[ATIONSHIP] [PROPERTY] EXIST[ENCE] - |[PROPERTY] EXIST[ENCE] - |NODE KEY] CONSTRAINT[S] -YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n] - [WHERE expression] - [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] +SHOW [ALL|UNIQUE|NODE [PROPERTY] EXIST[ENCE]|REL[ATIONSHIP] [PROPERTY] EXIST[ENCE]|[PROPERTY] EXIST[ENCE]|NODE KEY] CONSTRAINT[S] + YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n] + [WHERE expression] + [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ---- - The returned columns from the show command is: -.Listing constraints output -[options="header", width="100%", cols="4m,6a"] +.List constraints output +[options="header", width="100%", cols="2m,4a,^1,^1"] |=== -| Column | Description +| Column +| Description +| Default output +| Full output | id -| The id of the constraint. label:default-output[] +| The id of the constraint. +| {check-mark} +| {check-mark} | name -| Name of the constraint (explicitly set by the user or automatically assigned). label:default-output[] +| Name of the constraint (explicitly set by the user or automatically assigned). +| {check-mark} +| {check-mark} | type -| The ConstraintType of this constraint (`UNIQUENESS`, `NODE_PROPERTY_EXISTENCE`, `NODE_KEY`, or `RELATIONSHIP_PROPERTY_EXISTENCE`). label:default-output[] +| The ConstraintType of this constraint (`UNIQUENESS`, `NODE_PROPERTY_EXISTENCE`, `NODE_KEY`, or `RELATIONSHIP_PROPERTY_EXISTENCE`). +| {check-mark} +| {check-mark} | entityType -| Type of entities this constraint represents (nodes or relationship). label:default-output[] +| Type of entities this constraint represents (nodes or relationship). +| {check-mark} +| {check-mark} | labelsOrTypes -| The labels or relationship types of this constraint. label:default-output[] +| The labels or relationship types of this constraint. +| {check-mark} +| {check-mark} | properties -| The properties of this constraint. label:default-output[] +| The properties of this constraint. +| {check-mark} +| {check-mark} | ownedIndexId -| The id of the index associated to the constraint, or `null` if no index is associated with the constraint. label:default-output[] +| The id of the index associated to the constraint, or `null` if no index is associated with the constraint. +| {check-mark} +| {check-mark} | options | The options passed to `CREATE` command, for the index associated to the constraint, or `null` if no index is associated with the constraint. +| +| {check-mark} | createStatement | Statement used to create the constraint. - +| +| {check-mark} |=== [NOTE] -==== -The deprecated built-in procedures for listing constraints, such as `db.constraints`, work as before and are not affected by the xref::access-control/database-administration.adoc#access-control-database-administration-constraints[`SHOW CONSTRAINTS` privilege]. -==== - +The deprecated built-in procedures for listing constraints, such as `db.constraints`, work as before and are not affected by the xref:access-control/database-administration.adoc#access-control-database-administration-constraints[`SHOW CONSTRAINTS` privilege]. diff --git a/modules/ROOT/pages/databases.adoc b/modules/ROOT/pages/databases.adoc index 9bc2d79c0..97ce37c8d 100644 --- a/modules/ROOT/pages/databases.adoc +++ b/modules/ROOT/pages/databases.adoc @@ -1,12 +1,6 @@ -:description: How to use Cypher to manage databases in Neo4j DBMS: creating, modifying, deleting, starting, and stopping individual databases within a single server. - [[administration-databases]] = Database management - -[abstract] --- -This section explains how to use Cypher to manage databases in Neo4j DBMS: creating, modifying, deleting, starting, and stopping individual databases within a single server. --- +:description: This chapter explains how to use Cypher to manage Neo4j databases: creating, deleting, starting and stopping individual databases within a single server. Neo4j supports the management of multiple databases within the same DBMS. The metadata for these databases, including the associated security model, is maintained in a special database called the `system` database. @@ -16,336 +10,231 @@ These administrative commands are automatically routed to the `system` database The syntax of the database management commands is as follows: .Database management command syntax -[options="header", width="100%", cols="1m,5a"] +[options="header", width="100%", cols="1,5a"] |=== | Command | Syntax - -| SHOW DATABASE -| -[source, syntax, role="noheader"] ----- +| Show Database +| [source] SHOW { DATABASE name \| DATABASES \| DEFAULT DATABASE \| HOME DATABASE } [WHERE expression] ----- -[source, syntax, role="noheader"] ----- +[source] SHOW { DATABASE name \| DATABASES \| DEFAULT DATABASE \| HOME DATABASE } YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n] [WHERE expression] [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ----- - -| CREATE DATABASE -| -[source, syntax, role="noheader"] ----- +| Create Database +| [source] CREATE DATABASE name [IF NOT EXISTS] [OPTIONS "{" option: value[, ...] "}"] [WAIT [n [SEC[OND[S]]]]\|NOWAIT] ----- -[source, syntax, role="noheader"] ----- +[source] CREATE OR REPLACE DATABASE name [WAIT [n [SEC[OND[S]]]]\|NOWAIT] ----- - -| ALTER DATABASE -| -[source, syntax, role="noheader"] ----- -ALTER DATABASE name [IF EXISTS] SET ACCESS {READ ONLY \| READ WRITE} ----- - -| STOP DATABASE -| -[source, syntax, role="noheader"] ----- +|Stop Database +| [source] STOP DATABASE name [WAIT [n [SEC[OND[S]]]]\|NOWAIT] ----- - -| START DATABASE -| -[source, syntax, role="noheader"] ----- +|Start Database +| [source] START DATABASE name [WAIT [n [SEC[OND[S]]]]\|NOWAIT] ----- - -| DROP DATABASE -| -[source, syntax, role="noheader"] ----- +|Drop Database +| [source] DROP DATABASE name [IF EXISTS] [{DUMP\|DESTROY} [DATA]] [WAIT [n [SEC[OND[S]]]]\|NOWAIT] ----- - |=== [[administration-databases-show-databases]] == Listing databases -There are four different commands for listing databases: - -* Listing all databases. -* Listing a particular database. -* Listing the default database. -* Listing the home database. - -These commands return the following columns: - -.Listing databases output -[options="header", width="100%", cols="4m,6a"] -|=== -| Column | Description - -| name -| The name of the database. label:default-output[] - -| aliases -| The names of any aliases the database may have. label:default-output[] +There are three different commands for listing databases. Listing all databases, listing a particular database or listing the default database. -| access -| The database access mode, either `read-write` or `read-only`. label:default-output[] +All available databases can be seen using the command `SHOW DATABASES`. -| databaseID -| The database unique ID. - -| serverID -| The server instance ID. - -| address -| -Instance address in a clustered DBMS. -The default for a standalone database is `neo4j://localhost:7687`. label:default-output[] - -| role -| The current role of the database (`standalone`, `leader`, `follower`, `read_replica`, `unknown`). label:default-output[] - -| requestedStatus -| The expected status of the database. label:default-output[] - -| currentStatus -| The actual status of the database. label:default-output[] - -| error -| An error message explaining why the database is not in the correct state. label:default-output[] - -| default -| -Show if this is the default database for the DBMS. label:default-output[] - -[NOTE] -==== -Not returned by `SHOW HOME DATABASE` or `SHOW DEFAULT DATABASE`. -==== - -| home -| -Shown if this is the home database for the current user. label:default-output[] - -[NOTE] -==== -Not returned by `SHOW HOME DATABASE` or `SHOW DEFAULT DATABASE`. -==== - -| lastCommittedTxn -| The ID of the last transaction received. - -| replicationLag -| -Number of transactions the current database is behind compared to the database on the primary instance. -The lag is expressed in negative integers. In standalone environments, the value is always `0`. - -|=== - - -.+SHOW DATABASES+ -====== - -A summary of all available databases can be displayed using the command `SHOW DATABASES`. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- SHOW DATABASES ---- .Result -[role="queryresult",options="header,footer",cols="10* +Try this query live + +++++ +endif::nonhtmloutput[] [NOTE] ==== -The results of this command are filtered according to the `ACCESS` privileges of the user. -However, users with `CREATE/DROP/ALTER DATABASE`, `SET DATABASE ACCESS`, or `DATABASE MANAGEMENT` privileges can see all databases regardless of their `ACCESS` privileges. +Note that the results of this command are filtered according to the `ACCESS` privileges the user has. +However, a user with `CREATE/DROP DATABASE` or `DATABASE MANAGEMENT` privileges can see all databases regardless of their `ACCESS` privileges. If a user has not been granted `ACCESS` privilege to any databases, the command can still be executed but will only return the `system` database, which is always visible. -==== - -====== -.+SHOW DATABASES+ -====== +==== -In this example, the detailed information for a particular database can be displayed using the command `SHOW DATABASE name YIELD *`. -When a `YIELD` clause is provided, the full set of columns is returned. +The number of databases can be seen using a `count()` aggregation with `YIELD` and `RETURN`. -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- -SHOW DATABASE movies YIELD * +SHOW DATABASES YIELD * RETURN count(*) as count ---- .Result -[role="queryresult",options="header,footer",cols="14* +Try this query live + +++++ +endif::nonhtmloutput[] -The number of databases can be seen using a `count()` aggregation with `YIELD` and `RETURN`. +A particular database can be seen using the command `SHOW DATABASE name`. -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- -SHOW DATABASES YIELD * -RETURN count(*) AS count +SHOW DATABASE system ---- .Result -[role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] The default database can be seen using the command `SHOW DEFAULT DATABASE`. -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- SHOW DEFAULT DATABASE ---- .Result -[role="queryresult",options="header,footer",cols="8* +Try this query live + +++++ +endif::nonhtmloutput[] The home database for the current user can be seen using the command `SHOW HOME DATABASE`. -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- SHOW HOME DATABASE ---- .Result -[role="queryresult",options="header,footer",cols="8* +Try this query live + +++++ +endif::nonhtmloutput[] -.+SHOW DATABASES+ -====== +It is also possible to filter and sort the results by using `YIELD`, `ORDER BY` and `WHERE`. -It is also possible to filter and sort the results by using `YIELD`, `ORDER BY`, and `WHERE`. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- -SHOW DATABASES YIELD name, currentStatus, requestedStatus -ORDER BY currentStatus -WHERE name CONTAINS 'e' +SHOW DATABASES YIELD name, currentStatus, requestedStatus ORDER BY currentStatus WHERE name CONTAINS 'e' ---- In this example: * The number of columns returned has been reduced with the `YIELD` clause. * The order of the returned columns has been changed. -* The results have been filtered to only show database names containing `'e'`. -* The results are ordered by the `currentStatus` column using `ORDER BY`. +* The results have been filtered to only show database names containing 'e'. +* The results are ordered by the 'currentStatus' column using `ORDER BY`. It is also possible to use `SKIP` and `LIMIT` to paginate the results. @@ -353,15 +242,27 @@ It is also possible to use `SKIP` and `LIMIT` to paginate the results. .Result [role="queryresult",options="header,footer",cols="3* +Try this query live + +++++ +endif::nonhtmloutput[] [NOTE] ==== @@ -370,10 +271,9 @@ This often implies an error, but **does not always**. For example, a database may take a while to transition from `offline` to `online` due to performing recovery. Or, during normal operation a database's `currentStatus` may be transiently different from its `requestedStatus` due to a necessary automatic process, such as one Neo4j instance copying store files from another. The possible statuses are `initial`, `online`, `offline`, `store copying` and `unknown`. -==== -====== +==== [role=enterprise-edition] [[administration-databases-create-database]] @@ -382,236 +282,164 @@ The possible statuses are `initial`, `online`, `offline`, `store copying` and `u Databases can be created using `CREATE DATABASE`. -.+CREATE DATABASE+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE DATABASE customers ---- -.Result -[source, result, role="noheader"] ----- -System updates: 1 -Rows: 0 ----- +[role="statsonlyqueryresult"] +0 rows, System updates: 1 + +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [NOTE] ==== -Database names are subject to the xref::syntax/naming.adoc[standard Cypher restrictions on valid identifiers]. - +Database names are subject to the xref:syntax/naming.adoc[standard Cypher restrictions on valid identifiers]. The following naming rules apply: + * Database name length must be between 3 and 63 characters. * The first character must be an ASCII alphabetic character. * Subsequent characters can be ASCII alphabetic (`mydatabase`), numeric characters (`mydatabase2`), dots (`main.db`), and dashes (enclosed within backticks, e.g., `CREATE DATABASE ++`main-db`++`). * Names cannot end with dots or dashes. * Names that begin with an underscore or with the prefix `system` are reserved for internal use. -==== - -====== + -.+SHOW DATABASES+ -====== +==== When a database has been created, it will show up in the listing provided by the command `SHOW DATABASES`. -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- SHOW DATABASES ---- .Result -[role="queryresult",options="header,footer",cols="10* +Try this query live + +++++ +endif::nonhtmloutput[] [role=enterprise-edition] [[administration-databases-create-database-existing]] === Handling Existing Databases -This command is optionally idempotent, with the default behavior to fail with an error if the database already exists. -Appending `IF NOT EXISTS` to the command ensures that no error is returned and nothing happens should the database already exist. -Adding `OR REPLACE` to the command will result in any existing database being deleted and a new one created. +This command is optionally idempotent, with the default behavior to throw an exception if the database already exists. Appending `IF NOT EXISTS` to the command will ensure that no exception is thrown and nothing happens should the database already exist. Adding `OR REPLACE` to the command will result in any existing database being deleted and a new one created. -.+CREATE DATABASE+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE DATABASE customers IF NOT EXISTS ---- -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] -.+CREATE OR REPLACE DATABASE+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE OR REPLACE DATABASE customers ---- This is equivalent to running `DROP DATABASE customers IF EXISTS` followed by `CREATE DATABASE customers`. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + [NOTE] ==== The `IF NOT EXISTS` and `OR REPLACE` parts of this command cannot be used together. -==== -====== +==== [role=enterprise-edition] [[administration-databases-create-database-options]] === Options -The create database command can have a map of options, e.g. `OPTIONS {key: 'value'}`. +The create database command can have a map of options, e.g. `OPTIONS { key : 'value'}` [options="header"] |=== - | Key | Value | Description - -| `existingData` -| `use` -| -Controls how the system handles existing data on disk when creating the database. +| `existingData` | `use` | Controls how the system handles existing data on disk when creating the database. Currently this is only supported with `existingDataSeedInstance` and must be set to `use` which indicates the existing data files should be used for the new database. - -| `existingDataSeedInstance` -| instance ID of the cluster node -| -Defines which instance is used for seeding the data of the created database. +| `existingDataSeedInstance` | instance ID of the cluster node | Defines which instance is used for seeding the data of the created database. The instance id can be taken from the id column of the `dbms.cluster.overview()` procedure. Can only be used in clusters. - |=== [NOTE] ==== The `existingData` and `existingDataSeedInstance` options cannot be combined with the `OR REPLACE` part of this command. -==== - - -[role=enterprise-edition] -[[administration-databases-alter-database]] -== Altering databases - -Databases can be modified using the command `ALTER DATABASE`. -For example, a database always has read-write access mode on creation, unless the configuration parameter `dbms.databases.default_to_read_only` is set to `true`. -To change it to read-only, you can use the `ALTER DATABASE` command with the sub-clause `SET ACCESS READ ONLY`. -Subsequently, the database access mode can be switched back to read-write using the sub-clause `SET ACCESS READ WRITE`. -Altering the database access mode is allowed at all times, whether a database is online or offline. - -Database access modes can also be managed using the configuration parameters `dbms.databases.default_to_read_only`, `dbms.databases.read_only`, and `dbms.database.writable`. -For details, see link:{neo4j-docs-base-uri}/operations-manual/{page-version}/manage-databases/configuration#manage-databases-parameters[Configuration parameters]. -If conflicting modes are set by the `ALTER DATABASE` command and the configuration parameters, i.e. one says read-write and the other read-only, the database will be read-only and prevent write queries. - - -.+ALTER DATABASE+ -====== - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -//// - -.Query -[source, cypher, indent=0] ----- -ALTER DATABASE customers SET ACCESS READ ONLY ----- - -.Result -[source, result, role="noheader"] ----- -System updates: 1 -Rows: 0 ----- - -====== - -.+SHOW DATABASES+ -====== - -The database access mode can be seen in the `access` output column of the command `SHOW DATABASES`. - -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -//// - -.Query -[source, cypher, indent=0] ----- -SHOW DATABASES yield name, access ----- - -.Result -[role="queryresult",options="header,footer",cols="2* +Try this query live + +++++ +endif::nonhtmloutput[] The status of the stopped database can be seen using the command `SHOW DATABASE name`. -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- SHOW DATABASE customers ---- .Result -[role="queryresult",options="header,footer",cols="10* +Try this query live + +++++ +endif::nonhtmloutput[] [role=enterprise-edition] [[administration-databases-start-database]] @@ -680,46 +511,61 @@ SHOW DATABASE customers Databases can be started using the command `START DATABASE`. -.+START DATABASE+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- START DATABASE customers ---- -.Result -[source, result, role="noheader"] ----- -System updates: 1 -Rows: 0 ----- - -====== +[role="statsonlyqueryresult"] +0 rows, System updates: 1 +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] The status of the started database can be seen using the command `SHOW DATABASE name`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- SHOW DATABASE customers ---- .Result -[role="queryresult",options="header,footer",cols="10* +Try this query live + +++++ +endif::nonhtmloutput[] [role=enterprise-edition] [[administration-databases-drop-database]] @@ -728,127 +574,158 @@ SHOW DATABASE customers Databases can be deleted using the command `DROP DATABASE`. -.+DROP DATABASE+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- DROP DATABASE customers ---- -.Result -[source, result, role="noheader"] ----- -System updates: 1 -Rows: 0 ----- - -====== +[role="statsonlyqueryresult"] +0 rows, System updates: 1 +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] When a database has been deleted, it will no longer show up in the listing provided by the command `SHOW DATABASES`. -//// -CREATE DATABASE `movies` -CREATE ALIAS `films` FOR DATABASE `movies` -CREATE ALIAS `motion pictures` FOR DATABASE `movies` -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- SHOW DATABASES ---- .Result -[role="queryresult",options="header,footer",cols="10* +Try this query live + +++++ +endif::nonhtmloutput[] -.+DROP DATABASE+ -====== +This command is optionally idempotent, with the default behavior to throw an exception if the database does not exists. Appending `IF EXISTS` to the command will ensure that no exception is thrown and nothing happens should the database not exist. -This command is optionally idempotent, with the default behavior to fail with an error if the database does not exist. -Appending `IF EXISTS` to the command ensures that no error is returned and nothing happens should the database not exist. -It will always return an error, if there is an existing alias that targets the database. In that case, the alias needs to be dropped before dropping the database. .Query -[source, cypher, indent=0] +[source, cypher] ---- DROP DATABASE customers IF EXISTS ---- -The `DROP DATABASE` command will remove a database entirely. - -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] -.+DROP DATABASE+ -====== +The `DROP DATABASE` command will remove a database entirely. However, you can request that a dump of the store files is produced first, and stored in the path configured using the `dbms.directories.dumps.root` setting (by default `/data/dumps`). This can be achieved by appending `DUMP DATA` to the command (or `DESTROY DATA` to explicitly request the default behavior). These dumps are equivalent to those produced by `neo4j-admin dump` and can be similarly restored using `neo4j-admin load`. -You can request that a dump of the store files is produced first, and stored in the path configured using the `dbms.directories.dumps.root` setting (by default `/data/dumps`). -This can be achieved by appending `DUMP DATA` to the command (or `DESTROY DATA` to explicitly request the default behavior). -These dumps are equivalent to those produced by `neo4j-admin dump` and can be similarly restored using `neo4j-admin load`. .Query -[source, cypher, indent=0] +[source, cypher] ---- DROP DATABASE customers DUMP DATA ---- -The options `IF EXISTS` and `DUMP DATA`/ `DESTROY DATA` can also be combined. -An example could look like this: +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + +The options `IF EXISTS` and `DUMP DATA`/ `DESTROY DATA` can also be combined. An example could look like this: + .Query -[source, cypher, indent=0] +[source, cypher] ---- DROP DATABASE customers IF EXISTS DUMP DATA ---- -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [role=enterprise-edition] [[administration-wait-nowait]] == Wait options -Aside from `SHOW DATABASES` and `ALTER DATABASE`, all database management commands accept an optional `WAIT`/`NOWAIT` clause. The `WAIT`/`NOWAIT` clause allows you to specify a time limit in which the command must complete and return. - -The options are: +Aside from `SHOW DATABASES`, all database management commands accept an optional +`WAIT`/`NOWAIT` clause. The `WAIT`/`NOWAIT` clause allows you to specify a time limit in +which the command must complete and return. The options are: * `WAIT n SECONDS` - Return once completed or when the specified time limit of `n` seconds is up. * `WAIT` - Return once completed or when the default time limit of 300 seconds is up. * `NOWAIT` - Return immediately. -A command using a `WAIT` clause will automatically commit the current transaction when it executes successfully, as the command needs to run immediately for it to be possible to `WAIT` for it to complete. -Any subsequent commands executed will therefore be performed in a new transaction. -This is different to the usual transactional behavior, and for this reason it is recommended that these commands be run in their own transaction. -The default behavior is `NOWAIT`, so if no clause is specified the transaction will behave normally and the action is performed in the background post-commit. - -[NOTE] -==== -A command with a `WAIT` clause may be interrupted whilst it is waiting to complete. -In this event the command will continue to execute in the background and will not be aborted. -==== - +A command using a `WAIT` clause will automatically commit the current transaction when it executes successfully, as the +command needs to run immediately for it to be possible to `WAIT` for it to complete. Any subsequent commands executed will +therefore be performed in a new transaction. This is different to the usual transactional behavior, and for this reason +it is recommended that these commands be run in their own transaction. The default behavior is `NOWAIT`, so if no clause +is specified the transaction will behave normally and the action is performed in the background post-commit. -.+CREATE DATABASE+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE DATABASE slow WAIT 5 SECONDS ---- @@ -861,8 +738,31 @@ CREATE DATABASE slow WAIT 5 SECONDS 4+d|Rows: 1 |=== -The `success` column provides an aggregate status of whether or not the command is considered successful and thus every row will have the same value. -The intention of this column is to make it easy to determine, for example in a script, whether or not the command completed successfully without timing out. +The `success` column provides an aggregate status of whether or not the command is considered +successful and thus every row will have the same value. The intention of this column is to make it +easy to determine, for example in a script, whether or not the command completed successfully without +timing out. + +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + +[NOTE] +==== +A command with a `WAIT` clause may be interrupted whilst it is waiting to complete. In this event +the command will continue to execute in the background and will not be aborted. + + +==== diff --git a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc index aa45cfe56..6b3921223 100644 --- a/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc +++ b/modules/ROOT/pages/deprecations-additions-removals-compatibility.adoc @@ -1,631 +1,19 @@ -:description: Cypher is a graph query language that is constantly evolving. - -[[cypher-deprecations-additions-removals-compatibility]] -= Deprecations, additions and compatibility - -[abstract] --- -Cypher is a graph query language that is constantly evolving. -New features are added to the language continuously, and occasionally, some features become deprecated and are subsequently removed. --- - -This section list all of the features that have been removed, deprecated, added, or extended in different Cypher versions. -Replacement syntax for deprecated and removed features are also indicated. - -[[cypher-deprecations-additions-removals-4.4]] -== Version 4.4 - -=== Deprecated features - -[cols="2", options="header"] -|=== -| Feature | Details - -a| -label:functionality[] -label:deprecated[] -[source, cypher, role="noheader"] ----- -MATCH (n) RETURN n.propertyName_1, n.propertyName_2 + count(*) ----- -a| -Implied grouping keys are deprecated. -Only expressions that do _not_ contain aggregations are still considered grouping keys. - -In expressions that contain aggregations, the leaves must be either: - -- An aggregation. -- A literal. -- A parameter. -- A variable, *ONLY IF* it is either: + -1) A projection expression on its own (e.g. the `n` in `RETURN n AS myNode, n.value + count(*)`). + -2) A local variable in the expression (e.g the `x` in `RETURN n, n.prop + size([ x IN range(1, 10) \| x ]`). -- Property access, *ONLY IF* it is also a projection expression on its own (e.g. the `n.prop` in `RETURN n.prop, n.prop + count(*)`). -- Map access, *ONLY IF* it is also a projection expression on its own (e.g. the `map.prop` in `WITH {prop: 2} AS map RETURN map.prop, map.prop + count(*)`). - - -a| -label:syntax[] -label:deprecated[] -[source, cypher, role="noheader"] ----- -USING PERIODIC COMMIT ... ----- -a| -Replaced by: -[source, cypher, role="noheader"] ----- -CALL { - ... -} IN TRANSACTIONS ----- - - -a| -label:syntax[] -label:deprecated[] -[source, cypher, role="noheader"] ----- -CREATE (a {prop:7})-[r:R]->(b {prop: a.prop}) ----- -a| -`CREATE` clauses in which a variable introduced in the pattern is also referenced from the same pattern are deprecated. - - -a| -label:syntax[] -label:deprecated[] -[source, cypher, role="noheader"] ----- -CREATE CONSTRAINT ON ... ASSERT ... ----- -a| -Replaced by: -[source, cypher, role="noheader"] ----- -CREATE CONSTRAINT FOR ... REQUIRE ... ----- - -a| -label:functionality[] -label:deprecated[] -[source, cypher, role="noheader"] ----- -CREATE BTREE INDEX ... ----- -.2+.^a| -B-tree indexes are deprecated, partially replaced for now, and will be fully replaced in 5.0 by xref::indexes-for-search-performance.adoc#indexes-future-indexes[future indexes]. -In 4.4, b-tree indexes are still the correct alternative to use. - -B-tree indexes used for string queries are replaced by: -[source, cypher, role="noheader"] ----- -CREATE TEXT INDEX ... ----- - -B-tree indexes used for spatial queries will be replaced by: -[source, cypher, role="noheader"] ----- -CREATE POINT INDEX ... ----- - -B-tree indexes used for general queries or property value types will be replaced by: -[source, cypher, role="noheader"] ----- -CREATE RANGE INDEX ... ----- - -These new indexes may be combined for multiple use cases. - -a| -label:functionality[] -label:deprecated[] -[source, cypher, role="noheader"] ----- -CREATE INDEX -... -OPTIONS "{" btree-option: btree-value[, ...] "}" ----- - - -a| -label:functionality[] -label:deprecated[] -[source, cypher, role="noheader"] ----- -SHOW BTREE INDEXES ----- -a| -B-tree indexes are deprecated, partially replaced for now, and will be fully replaced in 5.0 by xref::indexes-for-search-performance.adoc#indexes-future-indexes[future indexes]. -In 4.4, b-tree indexes are still the correct alternative to use. - -Replaced by the new and future index types: -[source, cypher, role="noheader"] ----- -SHOW {POINT \| RANGE \| TEXT} INDEXES ----- - - -a| -label:functionality[] -label:deprecated[] -[source, cypher, role="noheader"] ----- -USING BTREE INDEX ----- -a| -B-tree indexes are deprecated. - -Replaced by: -[source, cypher, role="noheader"] ----- -USING {POINT \| RANGE \| TEXT} INDEX ----- - - -a| -label:functionality[] -label:deprecated[] -[source, cypher, role="noheader"] ----- -CREATE CONSTRAINT -... -OPTIONS "{" btree-option: btree-value[, ...] "}" ----- -a| -Node key and uniqueness constraints with b-tree options are deprecated and will be replaced in 5.0 by range options, see xref::indexes-for-search-performance.adoc#indexes-future-indexes[range indexes]. -In 4.4, the b-tree index-backed constraints are still the correct alternative to use. - -Will be replaced by: -[source, cypher, role="noheader"] ----- -CREATE CONSTRAINT -... -OPTIONS "{" range-option: range-value[, ...] "}" ----- -Constraints used for string properties will also require an additional text index to cover the string queries properly. -Constraints used for point properties will also require an additional point index to cover the spatial queries properly, see xref::indexes-for-search-performance.adoc#indexes-future-indexes[point indexes]. - - -a| -label:functionality[] -label:deprecated[] -[source, cypher, role="noheader"] ----- -distance(n.prop, point({x:0, y:0}) ----- -a| -Replaced by: -[source, cypher, role="noheader"] ----- -point.distance(n.prop, point({x:0, y:0}) ----- - -a| -label:functionality[] -label:deprecated[] -[source, cypher, role="noheader"] ----- -point({x:0, y:0}) <= point({x:1, y:1}) <= point({x:2, y:2}) ----- -a| -The ability to use the `+<+`, `+<=+`, `+>+`, and `+>=+` on spatial points is deprecated. - -Instead use: -[source, cypher, role="noheader"] ----- -point.withinBBox(point({x:1, y:1}), point({x:0, y:0}), point({x:2, y:2})) ----- - - -a| -label:procedure[] -label:deprecated[] - -[source, cypher, role="noheader"] ----- -dbms.listTransactions ----- -a| -Replaced by: -[source, cypher, role="noheader"] ----- -SHOW TRANSACTION[S] [transaction-id[,...]] -[YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] -[WHERE expression] -[RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ----- - - -a| -label:procedure[] -label:deprecated[] - -[source, cypher, role="noheader"] ----- -dbms.killTransaction ----- - -[source, cypher, role="noheader"] ----- -dbms.killTransactions ----- -a| -Replaced by: -[source, cypher, role="noheader"] ----- -TERMINATE TRANSACTION[S] transaction-id[,...] ----- - - -a| -label:procedure[] -label:deprecated[] - -[source, cypher, role="noheader"] ----- -dbms.listQueries ----- -a| -Replaced by: -[source, cypher, role="noheader"] ----- -SHOW TRANSACTION[S] [transaction-id[,...]] -[YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] -[WHERE expression] -[RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ----- - - -a| -label:procedure[] -label:deprecated[] - -[source, cypher, role="noheader"] ----- -dbms.killQuery ----- - -[source, cypher, role="noheader"] ----- -dbms.killQueries ----- -a| -Replaced by: -[source, cypher, role="noheader"] ----- -TERMINATE TRANSACTION[S] transaction-id[,...] ----- - -|=== - - -=== New features - -[cols="2", options="header"] -|=== -| Feature | Details - -a| -label:functionality[] -label:new[] -[source, cypher, role="noheader"] ----- -CALL { - ... -} IN TRANSACTIONS ----- -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+`]. - -a| -label:syntax[] -label:new[] -[source, cypher, role="noheader"] ----- -CREATE CONSTRAINT FOR ... REQUIRE ... ----- -a| -New syntax for creating constraints, applicable to all constraint types. - -a| -label:functionality[] -label:new[] -[source, cypher, role="noheader"] ----- -CREATE CONSTRAINT [constraint_name] [IF NOT EXISTS] -FOR (n:LabelName) -REQUIRE (n.propertyName_1, …, n.propertyName_n) IS UNIQUE -[OPTIONS "{" option: value[, ...] "}"] ----- -a| -Unique property constraints now allow multiple properties, ensuring that the combination of property values are unique. - -a| -label:functionality[] -label:new[] -label:deprecated[] -[source, cypher, role="noheader"] ----- -DROP CONSTRAINT -ON (n:LabelName) -ASSERT (n.propertyName_1, ..., n.propertyName_n) IS UNIQUE ----- -a| -Unique property constraints now allow multiple properties. - -Replaced by: -[source, cypher, role="noheader"] ----- -DROP CONSTRAINT name [IF EXISTS] ----- - -a| -label:syntax[] -label:new[] -[source, cypher, role="noheader"] ----- -CREATE CONSTRAINT [constraint_name] [IF NOT EXISTS] -FOR ... -REQUIRE ... IS NOT NULL -OPTIONS "{" "}" ----- -a| -Existence constraints now allow an `OPTIONS` map, however, at this point there are no available values for the map. - -a| -label:functionality[] -label:new[] -[source, cypher, role="noheader"] ----- -CREATE LOOKUP INDEX [index_name] [IF NOT EXISTS] -FOR ... ON ... -OPTIONS "{" option: value[, ...] "}" ----- -a| -Token lookup indexes now allow an `OPTIONS` map to specify the index provider. - - -a| -label:functionality[] -label:new[] -[source, cypher, role="noheader"] ----- -CREATE TEXT INDEX ... ----- -a| -Allows creating text indexes on nodes or relationships with a particular label or relationship type, and property combination. -They can be dropped by using their name. - - -a| -label:functionality[] -label:new[] -[source, cypher, role="noheader"] ----- -CREATE RANGE INDEX ... ----- -a| -xref::indexes-for-search-performance.adoc#indexes-future-indexes[FUTURE INDEX]: Allows creating range indexes on nodes or relationships with a particular label or relationship type, and properties combination. -They can be dropped by using their name. - -a| -label:functionality[] -label:new[] -[source, cypher, role="noheader"] ----- -CREATE CONSTRAINT -... -OPTIONS "{" indexProvider: 'range-1.0' "}" ----- -a| -xref::indexes-for-search-performance.adoc#indexes-future-indexes[FUTURE CONSTRAINT]: Allows creating node key and uniqueness constraints backed by range indexes by providing the range index provider in the `OPTIONS` map. - - -a| -label:functionality[] -label:new[] -[source, cypher, role="noheader"] ----- -CREATE POINT INDEX ... ----- -a| -xref::indexes-for-search-performance.adoc#indexes-future-indexes[FUTURE INDEX]: Allows creating point indexes on nodes or relationships with a particular label or relationship type, and property combination. -They can be dropped by using their name. - -a| -label:syntax[] -label:new[] + -New privilege: -[source, cypher, role="noheader"] ----- -IMPERSONATE ----- -a| -New privilege that allows a user to assume privileges of another one. - -a| -label:functionality[] -label:new[] -[source, cypher, role="noheader"] ----- -SHOW TRANSACTION[S] [transaction-id[,...]] -[YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] -[WHERE expression] -[RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ----- -a| -List transactions on the current server. - -The `transaction-id` is a comma-separated list of one or more quoted strings, a string parameter, or a list parameter. - -a| -label:functionality[] -label:new[] -[source, cypher, role="noheader"] ----- -TERMINATE TRANSACTION[S] transaction-id[,...] ----- -a| -Terminate transactions on the current server. - -The `transaction-id` is a comma-separated list of one or more quoted strings, a string parameter, or a list parameter. - - -a| -label:functionality[] -label:new[] -[source, cypher, role="noheader"] ----- -ALTER DATABASE ... [IF EXISTS] -SET ACCESS {READ ONLY \| READ WRITE} ----- -a| -New Cypher command for modifying a database by changing its access mode. - -a| -label:functionality[] -label:new[] -New privilege: -[source, cypher, role="noheader"] ----- -ALTER DATABASE ----- -a| -New privilege that allows a user to modify databases. - -a| -label:functionality[] -label:new[] -New privilege: -[source, cypher, role="noheader"] ----- -SET DATABASE ACCESS ----- -a| -New privilege that allows a user to modify database access mode. -a| -label:functionality[] -label:new[] -[source, cypher, role="noheader"] ----- -CREATE ALIAS ... [IF NOT EXISTS] -FOR DATABASE ... ----- -a| -New Cypher command for creating an alias for a database name. Remote aliases are only supported from version 4.4.8. - -a| -label:functionality[] -label:new[] -[source, cypher, role="noheader"] ----- -CREATE OR REPLACE ALIAS ... -FOR DATABASE ... ----- -a| -New Cypher command for creating or replacing an alias for a database name. Remote aliases are only supported from version 4.4.8. -a| -label:functionality[] -label:new[] -[source, cypher, role="noheader"] ----- -ALTER ALIAS ... [IF EXISTS] -SET DATABASE ... ----- -a| -New Cypher command for altering an alias. Remote aliases are only supported from version 4.4.8. - -a| -label:functionality[] -label:new[] -[source, cypher, role="noheader"] ----- -DROP ALIAS ... [IF EXISTS] FOR DATABASE ----- -a| -New Cypher command for dropping a database alias. - -a| -label:functionality[] -label:new[] -[source, cypher, role="noheader"] ----- -SHOW ALIASES FOR DATABASE ----- -a| -New Cypher command for listing database aliases. Only supported since version 4.4.8. - -a| -label:functionality[] -label:new[] -New privilege: -[source, cypher, role="noheader"] ----- -ALIAS MANAGEMENT ----- -a| -New privilege that allows a user to create, modify, delete and list aliases. Only supported since version 4.4.8. - -a| -label:functionality[] -label:new[] -New privilege: -[source, cypher, role="noheader"] ----- -CREATE ALIAS ----- -a| -New privilege that allows a user to create aliases. Only supported since version 4.4.8. - -a| -label:functionality[] -label:new[] -New privilege: -[source, cypher, role="noheader"] ----- -ALTER ALIAS ----- -a| -New privilege that allows a user to modify aliases. Only supported since version 4.4.8. - -a| -label:functionality[] -label:new[] -New privilege: -[source, cypher, role="noheader"] ----- -DROP ALIAS ----- -a| -New privilege that allows a user to delete aliases. Only supported since version 4.4.8. - -a| -label:functionality[] -label:new[] -New privilege: -[source, cypher, role="noheader"] ----- -SHOW ALIAS ----- -a| -New privilege that allows a user to show aliases. Only supported since version 4.4.8. - - -|=== +[[cypher-deprecations-additions-removals-compatibility]] += Deprecations, additions and compatibility +:description: Cypher is a language that is constantly evolving. New features are added to the language continuously, and occasionally, some features become deprecated and are subsequently removed. +This section list all of the features that have been removed, deprecated, added, or extended in different Cypher versions. +Replacement syntax for deprecated and removed features are also indicated. [[cypher-deprecations-additions-removals-4.3]] == Version 4.3 - === Deprecated features [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:syntax[] @@ -695,7 +83,7 @@ prop IS NULL a| label:syntax[] -label:deprecated[] +label:deprecated[] + `BRIEF [OUTPUT]` for `SHOW INDEXES` and `SHOW CONSTRAINTS`. a| Replaced by default output columns. @@ -703,7 +91,7 @@ Replaced by default output columns. a| label:syntax[] -label:deprecated[] +label:deprecated[] + `VERBOSE [OUTPUT]` for `SHOW INDEXES` and `SHOW CONSTRAINTS`. a| Replaced by: @@ -761,7 +149,7 @@ Still allows `BRIEF` and `VERBOSE` but not `YIELD` or `WHERE`. a| label:syntax[] -label:deprecated[] +label:deprecated[] + For privilege commands: [source, cypher, role="noheader"] ---- @@ -777,7 +165,7 @@ ON HOME DATABASE a| label:syntax[] -label:deprecated[] +label:deprecated[] + For privilege commands: [source, cypher, role="noheader"] ---- @@ -790,7 +178,6 @@ Replaced by: ON HOME GRAPH ---- - a| label:syntax[] label:deprecated[] @@ -800,61 +187,19 @@ MATCH (a) RETURN (a)--() ---- a| Pattern expressions producing lists of paths are deprecated, but they can still be used as existence predicates, for example in `WHERE` clauses. - Instead, use a pattern comprehension: [source, cypher, role="noheader"] ---- MATCH (a) RETURN [p=(a)--() \| p] ---- - - -a| -label:procedure[] -label:deprecated[] - -[source, cypher, role="noheader"] ----- -dbms.procedures ----- -a| -Replaced by: -[source, cypher, role="noheader"] ----- -SHOW PROCEDURE[S] -[EXECUTABLE [BY {CURRENT USER \| username}]] -[YIELD ...] -[WHERE ...] -[RETURN ...] ----- - - -a| -label:procedure[] -label:deprecated[] - -[source, cypher, role="noheader"] ----- -dbms.functions ----- -a| -Replaced by: -[source, cypher, role="noheader"] ----- -SHOW [ALL \| BUILT IN \| USER DEFINED] FUNCTION[S] -[EXECUTABLE [BY {CURRENT USER \| username}]] -[YIELD ...] -[WHERE ...] -[RETURN ...] ----- - |=== - === Updated features [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:functionality[] @@ -920,7 +265,7 @@ label:updated[] SHOW [PROPERTY] EXIST[ENCE] CONSTRAINTS ---- a| -New syntax for filtering `SHOW CONSTRAINTS` on property existence constraints. +New syntax for filtering `SHOW CONSTRAINTS` on property existence constraints. + Allows `YIELD` and `WHERE` but not `BRIEF` or `VERBOSE`. @@ -932,7 +277,7 @@ label:updated[] SHOW NODE [PROPERTY] EXIST[ENCE] CONSTRAINTS ---- a| -New syntax for filtering `SHOW CONSTRAINTS` on node property existence constraints. +New syntax for filtering `SHOW CONSTRAINTS` on node property existence constraints. + Allows `YIELD` and `WHERE` but not `BRIEF` or `VERBOSE`. @@ -944,7 +289,7 @@ label:updated[] SHOW REL[ATIONSHIP] [PROPERTY] EXIST[ENCE] CONSTRAINTS ---- a| -New syntax for filtering `SHOW CONSTRAINTS` on relationship property existence constraints. +New syntax for filtering `SHOW CONSTRAINTS` on relationship property existence constraints. + Allows `YIELD` and `WHERE` but not `BRIEF` or `VERBOSE`. @@ -956,7 +301,7 @@ label:updated[] SHOW FULLTEXT INDEXES ---- a| -Now allows easy filtering for `SHOW INDEXES` on fulltext indexes. +Now allows easy filtering for `SHOW INDEXES` on fulltext indexes. + Allows `YIELD` and `WHERE` but not `BRIEF` or `VERBOSE`. @@ -968,17 +313,16 @@ label:updated[] SHOW LOOKUP INDEXES ---- a| -Now allows easy filtering for `SHOW INDEXES` on token lookup indexes. +Now allows easy filtering for `SHOW INDEXES` on token lookup indexes. + Allows `YIELD` and `WHERE` but not `BRIEF` or `VERBOSE`. - |=== - === New features [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:syntax[] @@ -1080,7 +424,7 @@ New syntax for showing the home database of the current user. a| label:syntax[] -label:new[] +label:new[] + New privilege: [source, cypher, role="noheader"] ---- @@ -1092,7 +436,7 @@ New Cypher command for administering privilege for changing users home database. a| label:syntax[] -label:new[] +label:new[] + For privilege commands: [source, cypher, role="noheader"] ---- @@ -1104,7 +448,7 @@ New syntax for privileges affecting home database. a| label:syntax[] -label:new[] +label:new[] + For privilege commands: [source, cypher, role="noheader"] ---- @@ -1200,16 +544,15 @@ New Cypher commands for listing functions. |=== - [[cypher-deprecations-additions-removals-4.2]] == Version 4.2 - === Deprecated features [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:syntax[] @@ -1232,110 +575,6 @@ label:deprecated[] a| Only `+0x...+` (lowercase x) is supported. -a| -label:procedure[] -label:deprecated[] -[source, role="noheader"] ----- -db.createIndex ----- -a| -Replaced by `CREATE INDEX` command. - - -a| -label:procedure[] -label:deprecated[] -[source, role="noheader"] ----- -db.createNodeKey ----- -a| -Replaced by: -[source, cypher, role="noheader"] ----- -CREATE CONSTRAINT ... IS NODE KEY ----- - - -a| -label:procedure[] -label:deprecated[] -[source, role="noheader"] ----- -db.createUniquePropertyConstraint ----- -a| -Replaced by: -[source, cypher, role="noheader"] ----- -CREATE CONSTRAINT ... IS UNIQUE ----- - -a| -label:procedure[] -label:deprecated[] -[source, role="noheader"] ----- -db.indexes ----- -a| -Replaced by: -[source, cypher, role="noheader"] ----- -SHOW INDEXES ----- - - -a| -label:procedure[] -label:deprecated[] -[source, role="noheader"] ----- -db.indexDetails ----- -a| -Replaced by: -[source, cypher, role="noheader"] ----- -SHOW INDEXES YIELD * ----- - - -a| -label:procedure[] -label:deprecated[] -[source, role="noheader"] ----- -db.constraints ----- -a| -Replaced by: -[source, cypher, role="noheader"] ----- -SHOW CONSTRAINTS ----- - - -a| -label:procedure[] -label:deprecated[] -[source, role="noheader"] ----- -db.schemaStatements ----- -a| -Replaced by: -[source, cypher, role="noheader"] ----- -SHOW INDEXES YIELD * ----- -[source, cypher, role="noheader"] ----- -SHOW CONSTRAINTS YIELD * ----- - - a| label:syntax[] label:deprecated[] @@ -1344,22 +583,19 @@ label:deprecated[] CALL { RETURN 1 } ---- a| -Replaced by: +Unaliased expressions are deprecated in subquery `RETURN` clauses. Replaced by: [source, cypher, role="noheader"] ---- CALL { RETURN 1 AS one } ---- - -Unaliased expressions are deprecated in subquery `RETURN` clauses. - |=== - === Updated features [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:functionality[] @@ -1411,15 +647,14 @@ round(expression, precision, mode) ---- a| The `round()` function can now take two additional arguments to specify rounding precision and rounding mode. - |=== - === New features [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:functionality[] @@ -1439,7 +674,7 @@ label:new[] DEFAULT GRAPH ---- a| -New optional part of the Cypher commands for xref::access-control/database-administration.adoc[database privileges]. +New optional part of the Cypher commands for xref:access-control/database-administration.adoc[database privileges]. a| @@ -1465,16 +700,15 @@ For `CREATE USER` and `ALTER USER`, it is now possible to set (or update) a pass a| label:functionality[] -label:new[] +label:new[] + New privilege: [source, cypher, role="noheader"] ---- EXECUTE ---- a| -New Cypher commands for administering privileges for executing procedures and user-defined functions. - -See xref::access-control/dbms-administration.adoc#access-control-dbms-administration-execute[The DBMS `EXECUTE` privileges]. +New Cypher commands for administering privileges for executing procedures and user defined functions. +See xref:access-control/dbms-administration.adoc#access-control-dbms-administration-execute[The DBMS `EXECUTE` privileges]. a| @@ -1530,6 +764,7 @@ SHOW [ALL \| BTREE] INDEX[ES] [BRIEF \| VERBOSE [OUTPUT]] a| New Cypher commands for listing indexes. +Replaces the procedures `db.indexes`, `db.indexDetails` (verbose), and partially `db.schemaStatements` (verbose). a| label:functionality[] @@ -1541,9 +776,11 @@ SHOW [ALL \| UNIQUE \| NODE EXIST[S] \| RELATIONSHIP EXIST[S] \| EXIST[S] \| NOD a| New Cypher commands for listing constraints. +Replaces the procedures `db.constraints` and partially `db.schemaStatements` (verbose). + a| label:functionality[] -label:new[] +label:new[] + New privilege: [source, cypher, role="noheader"] ---- @@ -1555,7 +792,7 @@ New Cypher command for administering privilege for listing indexes. a| label:functionality[] -label:new[] +label:new[] + New privilege: [source, cypher, role="noheader"] ---- @@ -1563,10 +800,8 @@ SHOW CONSTRAINT ---- a| New Cypher command for administering privilege for listing constraints. - |=== - [[cypher-deprecations-additions-removals-4.1.3]] == Version 4.1.3 @@ -1575,7 +810,8 @@ New Cypher command for administering privilege for listing constraints. [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:syntax[] @@ -1587,7 +823,6 @@ CREATE INDEX [name] IF NOT EXISTS FOR ... a| Makes index creation idempotent. If an index with the name or schema already exists no error will be thrown. - a| label:syntax[] label:new[] @@ -1598,7 +833,6 @@ DROP INDEX name IF EXISTS a| Makes index deletion idempotent. If no index with the name exists no error will be thrown. - a| label:syntax[] label:new[] @@ -1609,7 +843,6 @@ CREATE CONSTRAINT [name] IF NOT EXISTS ON ... a| Makes constraint creation idempotent. If a constraint with the name or type and schema already exists no error will be thrown. - a| label:syntax[] label:new[] @@ -1622,16 +855,15 @@ Makes constraint deletion idempotent. If no constraint with the name exists no e |=== - [[cypher-deprecations-additions-removals-4.1]] == Version 4.1 - === Restricted features [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:functionality[] @@ -1643,7 +875,6 @@ REVOKE ... a| No longer revokes sub-privileges when revoking a compound privilege, e.g. when revoking `INDEX MANAGEMENT`, any `CREATE INDEX` and `DROP INDEX` privileges will no longer be revoked. - a| label:functionality[] label:restricted[] @@ -1653,15 +884,14 @@ ALL DATABASE PRIVILEGES ---- a| No longer includes the privileges `START DATABASE` and `STOP DATABASE`. - |=== - === Updated features [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:procedure[] @@ -1671,10 +901,7 @@ label:updated[] queryId ---- a| -The `queryId` procedure format has changed, and no longer includes the database name. -For example, `mydb-query-123` is now `query-123`. - -This change affects the procedures: `dbms.listQueries()`, `dbms.listActiveLocks(queryId)`, `dbms.killQueries(queryIds)`, and `dbms.killQuery(queryId)`. +The `queryId` procedure format has changed, and no longer includes the database name. For example, `mydb-query-123` is now `query-123`. This change affects built-in procedures `dbms.listQueries()`, `dbms.listActiveLocks(queryId)`, `dbms.killQueries(queryIds)` `and dbms.killQuery(queryId)`. a| label:functionality[] @@ -1684,21 +911,19 @@ label:updated[] SHOW PRIVILEGES ---- a| -The returned privileges are a closer match to the original grants and denies, e.g. if granted `MATCH` the command will show that specific privilege and not the `TRAVERSE` and `READ` privileges. -Added support for `YIELD` and `WHERE` clauses to allow filtering results. - +The returned privileges are a closer match to the original grants and denies, e.g. if granted `MATCH` the command will show that specific privilege and not the `TRAVERSE` and `READ` privileges. Added support for `YIELD` and `WHERE` clauses to allow filtering results. |=== - === New features [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:functionality[] -label:new[] +label:new[] + New role: [source, cypher, role="noheader"] ---- @@ -1707,10 +932,9 @@ PUBLIC a| The `PUBLIC` role is automatically assigned to all users, giving them a set of base privileges. - a| label:syntax[] -label:new[] +label:new[] + For privileges: [source, cypher, role="noheader"] ---- @@ -1719,7 +943,6 @@ REVOKE MATCH a| The `MATCH` privilege can now be revoked. - a| label:functionality[] label:new[] @@ -1730,7 +953,6 @@ SHOW USERS a| New support for `YIELD` and `WHERE` clauses to allow filtering results. - a| label:functionality[] label:new[] @@ -1741,7 +963,6 @@ SHOW ROLES a| New support for `YIELD` and `WHERE` clauses to allow filtering results. - a| label:functionality[] label:new[] @@ -1752,39 +973,35 @@ SHOW DATABASES a| New support for `YIELD` and `WHERE` clauses to allow filtering results. - a| label:functionality[] -label:new[] -xref::access-control/database-administration.adoc#access-control-database-administration-transaction[TRANSACTION MANAGEMENT] privileges +label:new[] + +xref:access-control/database-administration.adoc#access-control-database-administration-transaction[TRANSACTION MANAGEMENT] privileges a| New Cypher commands for administering transaction management. - a| label:functionality[] -label:new[] -DBMS xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[USER MANAGEMENT] privileges +label:new[] + +DBMS xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[USER MANAGEMENT] privileges a| New Cypher commands for administering user management. - a| label:functionality[] -label:new[] -DBMS xref::access-control/dbms-administration.adoc#access-control-dbms-administration-database-management[DATABASE MANAGEMENT] privileges +label:new[] + +DBMS xref:access-control/dbms-administration.adoc#access-control-dbms-administration-database-management[DATABASE MANAGEMENT] privileges a| New Cypher commands for administering database management. a| label:functionality[] -label:new[] -DBMS xref::access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[PRIVILEGE MANAGEMENT] privileges +label:new[] + +DBMS xref:access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[PRIVILEGE MANAGEMENT] privileges a| New Cypher commands for administering privilege management. - a| label:functionality[] label:new[] @@ -1806,15 +1023,13 @@ ALL GRAPH PRIVILEGES a| New Cypher command for administering read and write privileges. - a| label:functionality[] -label:new[] +label:new[] + Write privileges a| New Cypher commands for administering write privileges. - a| label:functionality[] label:new[] @@ -1823,20 +1038,18 @@ label:new[] ON DEFAULT DATABASE ---- a| -New optional part of the Cypher commands for xref::access-control/database-administration.adoc[database privileges]. - +New optional part of the Cypher commands for xref:access-control/database-administration.adoc[database privileges]. |=== - [[cypher-deprecations-additions-removals-4.0]] == Version 4.0 - === Removed features [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:function[] @@ -1846,14 +1059,7 @@ label:removed[] rels() ---- a| -Replaced by: -[source, cypher, role="noheader"] ----- -relationships() ----- - -See xref::functions/list.adoc#functions-relationships[`relationships()`]. - +Replaced by xref:functions/list.adoc#functions-relationships[relationships()]. a| label:function[] @@ -1863,14 +1069,7 @@ label:removed[] toInt() ---- a| -Replaced by: -[source, cypher, role="noheader"] ----- -toInteger() ----- - -See xref::functions/scalar.adoc#functions-tointeger[`toInteger()`]. - +Replaced by xref:functions/scalar.adoc#functions-tointeger[toInteger()]. a| label:function[] @@ -1880,13 +1079,7 @@ label:removed[] lower() ---- a| -Replaced by: -[source, cypher, role="noheader"] ----- -toLower() ----- - -See xref::functions/string.adoc#functions-tolower[`toLower()`]. +Replaced by xref:functions/string.adoc#functions-tolower[toLower()]. a| label:function[] @@ -1896,13 +1089,7 @@ label:removed[] upper() ---- a| -Replaced by: -[source, cypher, role="noheader"] ----- -toUpper() ----- - -See xref::functions/string.adoc#functions-toupper[`toUpper()`]. +Replaced by xref:functions/string.adoc#functions-toupper[toUpper()]. a| label:function[] @@ -1912,8 +1099,7 @@ label:removed[] extract() ---- a| -Replaced by xref::syntax/lists.adoc#cypher-list-comprehension[list comprehension]. - +Replaced by xref:syntax/lists.adoc#cypher-list-comprehension[list comprehension]. a| label:function[] @@ -1923,34 +1109,31 @@ label:removed[] filter() ---- a| -Replaced by xref::syntax/lists.adoc#cypher-list-comprehension[list comprehension]. - +Replaced by xref:syntax/lists.adoc#cypher-list-comprehension[list comprehension]. a| label:functionality[] -label:removed[] +label:removed[] + For Rule planner: [source, cypher, role="noheader"] ---- CYPHER planner=rule ---- a| -The `RULE` planner was removed in 3.2, but still possible to trigger using `START` or `CREATE UNIQUE` clauses. -Now it is completely removed. +The `RULE` planner was removed in 3.2, but still possible to trigger using `START` or `CREATE UNIQUE` clauses. Now it is completely removed. a| label:functionality[] -label:removed[] +label:removed[] + Explicit indexes a| -The removal of the `RULE` planner in 3.2 was the beginning of the end for explicit indexes. -Now they are completely removed, including the removal of the link:https://neo4j.com/docs/cypher-manual/3.5/schema/index/#explicit-indexes-procedures[built-in procedures for Neo4j 3.3 to 3.5]. +The removal of the `RULE` planner in 3.2 was the beginning of the end for explicit indexes. Now they are completely removed, including the removal of the link:https://neo4j.com/docs/cypher-manual/3.5/schema/index/#explicit-indexes-procedures[built-in procedures for Neo4j 3.3 to 3.5]. a| label:functionality[] -label:removed[] +label:removed[] + For compiled runtime: [source, cypher, role="noheader"] ---- @@ -1970,7 +1153,6 @@ CREATE UNIQUE a| Running queries with this clause will cause a syntax error. Running with `CYPHER 3.5` will cause a runtime error due to the removal of the rule planner. - a| label:clause[] label:removed[] @@ -1981,7 +1163,6 @@ START a| Running queries with this clause will cause a syntax error. Running with `CYPHER 3.5` will cause a runtime error due to the removal of the rule planner. - a| label:syntax[] label:removed[] @@ -1990,11 +1171,7 @@ label:removed[] MATCH (n)-[:A\|:B\|:C {foo: 'bar'}]-() RETURN n ---- a| -Replaced by: -[source, cypher, role="noheader"] ----- -MATCH (n)-[:A\|B\|C {foo: 'bar'}]-() RETURN n ----- +Replaced by `MATCH (n)-[:A\|B\|C {foo: 'bar'}]-() RETURN n`. a| label:syntax[] @@ -2004,11 +1181,8 @@ label:removed[] MATCH (n)-[x:A\|:B\|:C]-() RETURN n ---- a| -Replaced by: -[source, cypher, role="noheader"] ----- -MATCH (n)-[x:A\|B\|C]-() RETURN n ----- +Replaced by `MATCH (n)-[x:A\|B\|C]-() RETURN n`. + a| label:syntax[] @@ -2018,11 +1192,8 @@ label:removed[] MATCH (n)-[x:A\|:B\|:C*]-() RETURN n ---- a| -Replaced by: -[source, cypher, role="noheader"] ----- -MATCH (n)-[x:A\|B\|C*]-() RETURN n ----- +Replaced by `MATCH (n)-[x:A\|B\|C*]-() RETURN n`. + a| label:syntax[] @@ -2032,22 +1203,15 @@ label:removed[] {parameter} ---- a| -Replaced by: -[source, cypher, role="noheader"] ----- -$parameter ----- - -See xref::syntax/parameters.adoc[]. - +Replaced by xref:syntax/parameters.adoc[$parameter]. |=== - === Deprecated features [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:syntax[] @@ -2063,7 +1227,6 @@ As in Cypher 3.2, this is replaced by: MATCH p=(n)-[*]-() RETURN relationships(p) AS rs ---- - a| label:syntax[] label:deprecated[] @@ -2072,12 +1235,7 @@ label:deprecated[] CREATE INDEX ON :Label(prop) ---- a| -Replaced by: -[source, cypher, role="noheader"] ----- -CREATE INDEX FOR (n:Label) ON (n.prop) ----- - +Replaced by `CREATE INDEX FOR (n:Label) ON (n.prop)`. a| label:syntax[] @@ -2087,12 +1245,7 @@ label:deprecated[] DROP INDEX ON :Label(prop) ---- a| -Replaced by: -[source, cypher, role="noheader"] ----- -DROP INDEX name ----- - +Replaced by `DROP INDEX name`. a| label:syntax[] @@ -2102,12 +1255,7 @@ label:deprecated[] DROP CONSTRAINT ON (n:Label) ASSERT (n.prop) IS NODE KEY ---- a| -Replaced by: -[source, cypher, role="noheader"] ----- -DROP CONSTRAINT name ----- - +Replaced by `DROP CONSTRAINT name`. a| label:syntax[] @@ -2117,11 +1265,7 @@ label:deprecated[] DROP CONSTRAINT ON (n:Label) ASSERT (n.prop) IS UNIQUE ---- a| -Replaced by: -[source, cypher, role="noheader"] ----- -DROP CONSTRAINT name ----- +Replaced by `DROP CONSTRAINT name`. a| label:syntax[] @@ -2131,11 +1275,7 @@ label:deprecated[] DROP CONSTRAINT ON (n:Label) ASSERT exists(n.prop) ---- a| -Replaced by: -[source, cypher, role="noheader"] ----- -DROP CONSTRAINT name ----- +Replaced by `DROP CONSTRAINT name`. a| label:syntax[] @@ -2145,20 +1285,16 @@ label:deprecated[] DROP CONSTRAINT ON ()-[r:Type]-() ASSERT exists(r.prop) ---- a| -Replaced by: -[source, cypher, role="noheader"] ----- -DROP CONSTRAINT name ----- +Replaced by `DROP CONSTRAINT name`. |=== - === Restricted features [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:function[] @@ -2168,10 +1304,7 @@ label:restricted[] length() ---- a| -Restricted to only work on paths. - -See xref::functions/scalar.adoc#functions-length[`length()`]. - +Restricted to only work on paths. See xref:functions/scalar.adoc#functions-length[length()] for more details. a| label:function[] @@ -2181,19 +1314,15 @@ label:restricted[] size() ---- a| -No longer works for paths. -Only works for strings, lists and pattern expressions. - -See xref::functions/scalar.adoc[`size()`]. - +No longer works for paths. Only works for strings, lists and pattern expressions. See xref:functions/scalar.adoc[size()] for more details. |=== - === Updated features [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:syntax[] @@ -2205,18 +1334,19 @@ CREATE CONSTRAINT [name] ON ... a| The create constraint syntax can now include a name. -|=== - +The `IS NODE KEY` and `IS UNIQUE` versions of this command replace the procedures `db.createNodeKey` and `db.createUniquePropertyConstraint`, respectively. +|=== === New features [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:functionality[] -label:new[] +label:new[] + Pipelined runtime: [source, cypher, role="noheader"] ---- @@ -2225,31 +1355,27 @@ CYPHER runtime=pipelined a| This Neo4j Enterprise Edition only feature involves a new runtime that has many performance enhancements. - a| label:functionality[] -label:new[] -xref::databases.adoc[Multi-database administration] +label:new[] + +xref:databases.adoc[Multi-database administration] a| New Cypher commands for administering multiple databases. - a| label:functionality[] -label:new[] -xref::access-control/index.adoc[Access control] +label:new[] + +xref:access-control/index.adoc[Access control] a| New Cypher commands for administering role-based access control. - a| label:functionality[] -label:new[] -xref::access-control/manage-privileges.adoc[Fine-grained security] +label:new[] + +xref:access-control/manage-privileges.adoc[Fine-grained security] a| New Cypher commands for administering dbms, database, graph and sub-graph access control. - a| label:syntax[] label:new[] @@ -2260,6 +1386,7 @@ CREATE INDEX [name] FOR (n:Label) ON (n.prop) a| New syntax for creating indexes, which can include a name. +Replaces the `db.createIndex` procedure. a| label:syntax[] @@ -2269,7 +1396,7 @@ label:new[] DROP INDEX name ---- a| -xref::indexes-for-search-performance.adoc#administration-indexes-drop-an-index[New command] for dropping an index by name. +xref:indexes-for-search-performance.adoc#administration-indexes-drop-an-index[New command] for dropping an index by name. a| @@ -2280,7 +1407,7 @@ label:new[] DROP CONSTRAINT name ---- a| -xref::constraints/syntax.adoc#administration-constraints-syntax-drop[New command] for dropping a constraint by name, no matter the type. +xref:constraints/syntax.adoc#administration-constraints-syntax-drop[New command] for dropping a constraint by name, no matter the type. a| @@ -2293,7 +1420,6 @@ WHERE EXISTS {...} a| Existential sub-queries are sub-clauses used to filter the results of a `MATCH`, `OPTIONAL MATCH`, or `WITH` clause. - a| label:clause[] label:new[] @@ -2310,16 +1436,16 @@ New clause to specify which graph a query, or query part, is executed against. [[cypher-deprecations-additions-removals-3.5]] == Version 3.5 - === Deprecated features [cols="2", options="header"] |=== -| Feature | Details +| Feature +| Details a| label:functionality[] -label:deprecated[] +label:deprecated[] + Compiled runtime: [source, cypher, role="noheader"] ---- @@ -2328,7 +1454,6 @@ CYPHER runtime=compiled a| The compiled runtime will be discontinued in the next major release. It might still be used for default queries in order to not cause regressions, but explicitly requesting it will not be possible. - a| label:function[] label:deprecated[] @@ -2337,8 +1462,7 @@ label:deprecated[] extract() ---- a| -Replaced by xref::syntax/lists.adoc#cypher-list-comprehension[list comprehension]. - +Replaced by xref:syntax/lists.adoc#cypher-list-comprehension[list comprehension]. a| label:function[] @@ -2348,406 +1472,92 @@ label:deprecated[] filter() ---- a| -Replaced by xref::syntax/lists.adoc#cypher-list-comprehension[list comprehension]. - +Replaced by xref:syntax/lists.adoc#cypher-list-comprehension[list comprehension]. |=== [[cypher-deprecations-additions-removals-3.4]] == Version 3.4 - -[cols="4", options="header"] +[options="header"] |=== -| Feature | Type | Change | Details - -| xref::syntax/spatial.adoc[Spatial point types] -| Functionality -| Amendment -| -A point -- irrespective of which Coordinate Reference System is used -- can be stored as a property and is able to be backed by an index. -Prior to this, a point was a virtual property only. - -| xref::functions/spatial.adoc#functions-point-cartesian-3d[`point()` - Cartesian 3D] -| Function -| Added -| - -| xref::functions/spatial.adoc#functions-point-wgs84-3d[`point()` - WGS 84 3D] -| Function -| Added -| - -| xref::functions/scalar.adoc#functions-randomuuid[`randomUUID()`] -| Function -| Added -| - -| xref::syntax/temporal.adoc[Temporal types] -| Functionality -| Added -| Supports storing, indexing and working with the following temporal types: `Date`, `Time`, `LocalTime`, `DateTime`, `LocalDateTime`, and `Duration`. - -| xref::functions/temporal/index.adoc[Temporal functions] -| Functionality -| Added -| Functions allowing for the creation and manipulation of values for each temporal type: `Date`, `Time`, `LocalTime`, `DateTime`, `LocalDateTime`, and `Duration`. - -| xref::syntax/operators.adoc#query-operators-temporal[Temporal operators] -| Functionality -| Added -| Operators allowing for the manipulation of values for each temporal type: `Date`, `Time`, `LocalTime`, `DateTime`, `LocalDateTime`, and `Duration`. - -| xref::functions/string.adoc#functions-tostring[`toString()`] -| Function -| Extended -| Now also allows temporal values as input (i.e. values of type `Date`, `Time`, `LocalTime`, `DateTime`, `LocalDateTime`, or `Duration`). - +| Feature | Type | Change | Details +| xref:syntax/spatial.adoc[Spatial point types] | Functionality | Amendment | A point -- irrespective of which Coordinate Reference System is used -- can be stored as a property and is able to be backed by an index. Prior to this, a point was a virtual property only. +| xref:functions/spatial.adoc#functions-point-cartesian-3d[point() - Cartesian 3D] | Function | Added | +| xref:functions/spatial.adoc#functions-point-wgs84-3d[point() - WGS 84 3D] | Function | Added | +| xref:functions/scalar.adoc#functions-randomuuid[randomUUID()] | Function | Added | +| xref:syntax/temporal.adoc[Temporal types] | Functionality | Added | Supports storing, indexing and working with the following temporal types: Date, Time, LocalTime, DateTime, LocalDateTime and Duration. +| xref:functions/temporal/index.adoc[Temporal functions] | Functionality | Added | Functions allowing for the creation and manipulation of values for each temporal type -- _Date_, _Time_, _LocalTime_, _DateTime_, _LocalDateTime_ and _Duration_. +| xref:syntax/operators.adoc#query-operators-temporal[Temporal operators] | Functionality | Added | Operators allowing for the manipulation of values for each temporal type -- _Date_, _Time_, _LocalTime_, _DateTime_, _LocalDateTime_ and _Duration_. +| xref:functions/string.adoc#functions-tostring[toString()] | Function | Extended | Now also allows temporal values as input (i.e. values of type _Date_, _Time_, _LocalTime_, _DateTime_, _LocalDateTime_ or _Duration_). |=== [[cypher-deprecations-additions-removals-3.3]] == Version 3.3 - -[cols="4", options="header"] +[options="header"] |=== -| Feature | Type | Change | Details - -| `START` -| Clause -| Removed -| -As in Cypher 3.2, any queries using the `START` clause will revert back to Cypher 3.1 `planner=rule`. +| Feature | Type | Change | Details +| `START` | Clause | Removed | As in Cypher 3.2, any queries using the `START` clause will revert back to Cypher 3.1 `planner=rule`. However, there are link:https://neo4j.com/docs/cypher-manual/3.5/schema/index/#explicit-indexes-procedures[built-in procedures for Neo4j versions 3.3 to 3.5] for accessing explicit indexes. The procedures will enable users to use the current version of Cypher and the cost planner together with these indexes. -An example of this is `+CALL db.index.explicit.searchNodes('my_index', 'email:me*')+`. - -| `CYPHER runtime=slotted` (Faster interpreted runtime) -| Functionality -| Added -| Neo4j Enterprise Edition only. - -| xref::functions/aggregating.adoc#functions-max[`max()`], xref::functions/aggregating.adoc#functions-min[`min()`] -| Function -| Extended -| Now also supports aggregation over sets containing lists of strings and/or numbers, as well as over sets containing strings, numbers, and lists of strings and/or numbers. - +An example of this is `CALL db.index.explicit.searchNodes('my_index','email:me*')`. +| `CYPHER runtime=slotted` (Faster interpreted runtime) | Functionality | Added | Neo4j Enterprise Edition only +| xref:functions/aggregating.adoc#functions-max[max()], xref:functions/aggregating.adoc#functions-min[min()] | Function | Extended | Now also supports aggregation over sets containing lists of strings and/or numbers, as well as over sets containing strings, numbers, and lists of strings and/or numbers |=== [[cypher-deprecations-additions-removals-3.2]] == Version 3.2 - -[cols="4", options="header"] +[options="header"] |=== -| Feature | Type | Change | Details - -| `CYPHER planner=rule` (Rule planner) -| Functionality -| Removed -| All queries now use the cost planner. Any query prepended thus will fall back to using Cypher 3.1. - -| `CREATE UNIQUE` -| Clause -| Removed -| Running such queries will fall back to using Cypher 3.1 (and use the rule planner). - -| `START` -| Clause -| Removed -| Running such queries will fall back to using Cypher 3.1 (and use the rule planner). - -a| -[source, cypher, role="noheader"] ----- -MATCH (n)-[rs*]-() RETURN rs ----- -| Syntax -| Deprecated -a| -Replaced by: -[source, cypher, role="noheader"] ----- -MATCH p=(n)-[*]-() RETURN relationships(p) AS rs` ----- - -a| -[source, cypher, role="noheader"] ----- -MATCH (n)-[:A\|:B\|:C {foo: 'bar'}]-() RETURN n ----- -| Syntax -| Deprecated -a| -Replaced by: -[source, cypher, role="noheader"] ----- -MATCH (n)-[:A\|B\| C {foo: 'bar'}]-() RETURN n ----- - -a| -[source, cypher, role="noheader"] ----- -MATCH (n)-[x:A\|:B\|:C]-() RETURN n ----- -| Syntax -| Deprecated -a| -Replaced by: -[source, cypher, role="noheader"] ----- -MATCH (n)-[x:A\|B\|C]-() RETURN n ----- - -a| -[source, cypher, role="noheader"] ----- -MATCH (n)-[x:A\|:B\|:C*]-() RETURN n ----- -| Syntax -| Deprecated -a| -Replaced by: -[source, cypher, role="noheader"] ----- -MATCH (n)-[x:A\|B\|C*]-() RETURN n ----- - -| link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/aggregation-functions#extending-neo4j-aggregation-functions[User-defined aggregation functions] -| Functionality -| Added -| - -| xref::indexes-for-search-performance.adoc[Composite indexes] -| Index -| Added -| - -| xref::constraints/examples.adoc#administration-constraints-node-key[Node Key] -| Index -| Added -| Neo4j Enterprise Edition only. - -| `CYPHER runtime=compiled` (Compiled runtime) -| Functionality -| Added -| Neo4j Enterprise Edition only. - -| xref::functions/list.adoc#functions-reverse-list[`reverse()`] -| Function -| Extended -| Now also allows a list as input. - -| xref::functions/aggregating.adoc#functions-max[`max()`], xref::functions/aggregating.adoc#functions-min[`min()`] -| Function -| Extended -| Now also supports aggregation over a set containing both strings and numbers. - +| Feature | Type | Change | Details +| `CYPHER planner=rule` (Rule planner) | Functionality | Removed | All queries now use the cost planner. Any query prepended thus will fall back to using Cypher 3.1. +| `CREATE UNIQUE` | Clause | Removed | Running such queries will fall back to using Cypher 3.1 (and use the rule planner) +| `START` | Clause | Removed | Running such queries will fall back to using Cypher 3.1 (and use the rule planner) +| `MATCH (n)-[rs*]-() RETURN rs` | Syntax | Deprecated | Replaced by `MATCH p=(n)-[*]-() RETURN relationships(p) AS rs` +| `MATCH (n)-[:A\|:B\|:C {foo: 'bar'}]-() RETURN n` | Syntax | Deprecated | Replaced by `MATCH (n)-[:A\|B\|C {foo: 'bar'}]-() RETURN n` +| `MATCH (n)-[x:A\|:B\|:C]-() RETURN n` | Syntax | Deprecated | Replaced by `MATCH (n)-[x:A\|B\|C]-() RETURN n` +| `MATCH (n)-[x:A\|:B\|:C*]-() RETURN n` | Syntax | Deprecated | Replaced by `MATCH (n)-[x:A\|B\|C*]-() RETURN n` +| link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/aggregation-functions#extending-neo4j-aggregation-functions[User-defined aggregation functions] | Functionality | Added | +| xref:indexes-for-search-performance.adoc[Composite indexes] | Index | Added | +| xref:constraints/examples.adoc#administration-constraints-node-key[Node Key] | Index | Added | Neo4j Enterprise Edition only +| `CYPHER runtime=compiled` (Compiled runtime) | Functionality | Added | Neo4j Enterprise Edition only +| xref:functions/list.adoc#functions-reverse-list[reverse()] | Function | Extended | Now also allows a list as input +| xref:functions/aggregating.adoc#functions-max[max()], xref:functions/aggregating.adoc#functions-min[min()] | Function | Extended | Now also supports aggregation over a set containing both strings and numbers |=== [[cypher-deprecations-additions-removals-3.1]] == Version 3.1 - -[cols="4", options="header"] +[options="header"] |=== -| Feature | Type | Change | Details - -a| -[source, cypher, role="noheader"] ----- -rels() ----- -| Function -| Deprecated -a| -Replaced by: -[source, cypher, role="noheader"] ----- -relationships() ----- - -See xref::functions/list.adoc#functions-relationships[`relationships()`]. - -a| -[source, cypher, role="noheader"] ----- -toInt() ----- -| Function -| Deprecated -a| -Replaced by: -[source, cypher, role="noheader"] ----- -toInteger() ----- - -See xref::functions/scalar.adoc#functions-tointeger[`toInteger()`]. - -a| -[source, cypher, role="noheader"] ----- -lower() ----- -| Function -| Deprecated -a| -Replaced by: -[source, cypher, role="noheader"] ----- -toLower() ----- - -Replaced by xref::functions/string.adoc#functions-tolower[`toLower()`]. - -a| -[source, cypher, role="noheader"] ----- -upper() ----- -| Function -| Deprecated -a| -Replaced by: -[source, cypher, role="noheader"] ----- -toUpper() ----- - -See xref::functions/string.adoc#functions-toupper[`toUpper()`]. - -a| -[source, cypher, role="noheader"] ----- -toBoolean() ----- -| Function -| Added -a| See xref::functions/scalar.adoc#functions-toboolean[`toBoolean()`]. - -| xref::syntax/maps.adoc#cypher-map-projection[Map projection] -| Syntax -| Added -| - -| xref::syntax/lists.adoc#cypher-pattern-comprehension[Pattern comprehension] -| Syntax -| Added -| - -| link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/functions#extending-neo4j-functions[User-defined functions] -| Functionality -| Added -| - -| xref::clauses/call.adoc[`+CALL ... YIELD ... WHERE+`] -| Clause -| Extended -| Records returned by `YIELD` may be filtered further using `WHERE`. - +| Feature | Type | Change | Details +| `rels()` | Function | Deprecated | Replaced by xref:functions/list.adoc#functions-relationships[relationships()] +| `toInt()` | Function | Deprecated | Replaced by xref:functions/scalar.adoc#functions-tointeger[toInteger()] +| `lower()` | Function | Deprecated | Replaced by xref:functions/string.adoc#functions-tolower[toLower()] +| `upper()` | Function | Deprecated | Replaced by xref:functions/string.adoc#functions-toupper[toUpper()] +| xref:functions/scalar.adoc#functions-toboolean[toBoolean()] | Function | Added | +| xref:syntax/maps.adoc#cypher-map-projection[Map projection] | Syntax | Added | +| xref:syntax/lists.adoc#cypher-pattern-comprehension[Pattern comprehension] | Syntax | Added | +| link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/functions#extending-neo4j-functions[User-defined functions] | Functionality | Added | +| xref:clauses/call.adoc[CALL\...YIELD\...WHERE] | Clause | Extended | Records returned by `YIELD` may be filtered further using `WHERE` |=== [[cypher-deprecations-additions-removals-3.0]] == Version 3.0 - -[cols="4", options="header"] +[options="header"] |=== -| Feature | Type | Change | Details - -a| -[source, cypher, role="noheader"] ----- -has() ----- -| Function -| Removed -a| -Replaced by: -[source, cypher, role="noheader"] ----- -exists() ----- - -See xref::functions/predicate.adoc#functions-exists[`exists()`]. - -a| -[source, cypher, role="noheader"] ----- -str() ----- -| Function -| Removed -a| Replaced by: -[source, cypher, role="noheader"] ----- -toString() ----- - -See xref::functions/string.adoc#functions-tostring[`toString()`]. - -a| -[source, cypher, role="noheader"] ----- -{parameter} ----- -| Syntax -| Deprecated -a| -Replaced by: -[source, cypher, role="noheader"] ----- -$parameter ----- - -See xref::syntax/parameters.adoc[]. - -a| -[source, cypher, role="noheader"] ----- -properties() ----- -| Function -| Added -a| See xref::functions/scalar.adoc#functions-properties[`properties()`]. - -a| -[source, cypher, role="noheader"] ----- -CALL ... [YIELD] ----- -| Clause -| Added -a| See xref::clauses/call.adoc[]. - -| xref::functions/spatial.adoc#functions-point-cartesian-2d[`point()` - Cartesian 2D] -| Function -| Added -| - -| xref::functions/spatial.adoc#functions-point-wgs84-2d[`point()` - WGS 84 2D] -| Function -| Added -| - -a| -[source, cypher, role="noheader"] ----- -distance() ----- -| Function -| Added -a| See xref::functions/spatial.adoc#functions-distance[`distance()`]. - -| link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/procedures#extending-neo4j-procedures[User-defined procedures] -| Functionality -| Added -| - -| xref::functions/string.adoc#functions-tostring[`toString()`] -| Function -| Extended -| Now also allows Boolean values as input. - +| Feature | Type | Change | Details +| `has()` | Function | Removed | Replaced by xref:functions/predicate.adoc#functions-exists[exists()] +| `str()` | Function | Removed | Replaced by xref:functions/string.adoc#functions-tostring[toString()] +| `+{parameter}+` | Syntax | Deprecated | Replaced by xref:syntax/parameters.adoc[$parameter] +| xref:functions/scalar.adoc#functions-properties[properties()] | Function | Added | +| xref:clauses/call.adoc[CALL [\...YIELD\]] | Clause | Added | +| xref:functions/spatial.adoc#functions-point-cartesian-2d[point() - Cartesian 2D] | Function | Added | +| xref:functions/spatial.adoc#functions-point-wgs84-2d[point() - WGS 84 2D] | Function | Added | +| xref:functions/spatial.adoc#functions-distance[distance()] | Function | Added | +| link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/procedures#extending-neo4j-procedures[User-defined procedures] | Functionality | Added | +| xref:functions/string.adoc#functions-tostring[toString()] | Function | Extended | Now also allows Boolean values as input |=== @@ -2762,7 +1572,7 @@ All supported versions of Cypher ran on the same Neo4j kernel. However, this changed in Neo4j 3.4 when the runtime was excluded from the compatibility layer. When you run, e.g. a `CYPHER 3.1` query in Neo4j 3.5, the query is planned with the 3.1 planner, but run with 3.5 runtime and kernel. The compatibility layer changed again in Neo4j 4.0 and it now includes only the Cypher language parser. -When you run a `CYPHER 3.5` query, e.g., in Neo4j 4.4, Neo4j parses the older language features, but uses the 4.4 planner, runtime, and kernel to plan and run the query. +When you run a `CYPHER 3.5` query, e.g., in Neo4j 4.3, Neo4j parses the older language features, but uses the 4.3 planner, runtime, and kernel to plan and run the query. The primary reason for these changes is the optimizations in the Cypher runtime to allow Cypher queries to perform better. ==== @@ -2770,7 +1580,7 @@ Older versions of the language can still be accessed if required. There are two ways to select which version to use in queries. . Setting a version for all queries: -You can configure your database with the configuration parameter `cypher.default_language_version`, and enter which version you'd like to use (see xref::deprecations-additions-removals-compatibility.adoc#cypher-versions[]). +You can configure your database with the configuration parameter `cypher.default_language_version`, and enter which version you'd like to use (see xref:deprecations-additions-removals-compatibility.adoc#cypher-versions[]). Every Cypher query will use this version, provided the query hasn't explicitly been configured as described in the next item below. . Setting a version on a query by query basis: @@ -2793,7 +1603,7 @@ Without the `CYPHER 3.5` prefix this query would fail with a syntax error. With ==== In Neo4j {neo4j-version} the Cypher parser understands some older language features, even if they are no longer supported by the Neo4j kernel. These features result in runtime errors. -See the table at xref::deprecations-additions-removals-compatibility.adoc#cypher-deprecations-additions-removals-4.0[Cypher Version 4.0] for the list of affected features. +See the table at xref:deprecations-additions-removals-compatibility.adoc#cypher-deprecations-additions-removals-4.0[Cypher Version 4.0] for the list of affected features. ==== @@ -2803,8 +1613,8 @@ See the table at xref::deprecations-additions-removals-compatibility.adoc#cypher Neo4j {neo4j-version} supports the following versions of the Cypher language: * Neo4j Cypher 3.5 +* Neo4j Cypher 4.2 * Neo4j Cypher 4.3 -* Neo4j Cypher 4.4 [TIP] ==== diff --git a/modules/ROOT/pages/execution-plans/db-hits.adoc b/modules/ROOT/pages/execution-plans/db-hits.adoc index c0928e432..f23b579a6 100644 --- a/modules/ROOT/pages/execution-plans/db-hits.adoc +++ b/modules/ROOT/pages/execution-plans/db-hits.adoc @@ -1,64 +1,64 @@ -:description: Actions that triggers database hits (DBHits). - [[execution-plans-dbhits]] -= Database hits += Database hits (DbHits) +:description: This section contains an overview of actions that triggers database hits. Each operator will send a request to the storage engine to do work such as retrieving or updating data. -A _database hit_ (DBHits) is an abstract unit of this storage engine work. +A _database hit_ is an abstract unit of this storage engine work. + +We list below all the actions that trigger one or more database hits: -These are all the actions that trigger one or more database hits: +* Create actions +** Create a node +** Create a relationship +** Create a new node label +** Create a new relationship type +** Create a new ID for property keys with the same name -* **Create actions** -** Create a node. -** Create a relationship. -** Create a new node label. -** Create a new relationship type. -** Create a new ID for property keys with the same name. +* Delete actions +** Delete a node +** Delete a relationship -* **Delete actions** -** Delete a node. -** Delete a relationship. +* Update actions +** Set one or more labels on a node +** Remove one or more labels from a node -* **Update actions** -** Set one or more labels on a node. -** Remove one or more labels from a node. +* Node-specific actions +** Get a node by its ID +** Get the degree of a node +** Determine whether a node is dense +** Determine whether a label is set on a node +** Get the labels of a node +** Get a property of a node +** Get an existing node label +** Get the name of a label by its ID, or its ID by its name -* **Node-specific actions** -** Get a node by its ID. -** Get the degree of a node. -** Determine whether a node is dense. -** Determine whether a label is set on a node. -** Get the labels of a node. -** Get a property of a node. -** Get an existing node label. -** Get the name of a label by its ID, or its ID by its name. +* Relationship-specific actions +** Get a relationship by its ID +** Get a property of a relationship +** Get an existing relationship type +** Get a relationship type name by its ID, or its ID by its name -* **Relationship-specific actions** -** Get a relationship by its ID. -** Get a property of a relationship. -** Get an existing relationship type. -** Get a relationship type name by its ID, or its ID by its name. -* **General actions** -** Get the name of a property key by its ID, or its ID by the key name. -** Find a node or relationship through an index seek or index scan. -** Find a path in a variable-length expand. -** Find a shortest path. -** Ask the count store for a value. +* General actions +** Get the name of a property key by its ID, or its ID by the key name +** Find a node or relationship through an index seek or index scan +** Find a path in a variable-length expand +** Find a shortest path +** Ask the count store for a value -* **Schema actions** -** Add an index. -** Drop an index. -** Get the reference of an index. -** Create a constraint. -** Drop a constraint. -* Call a procedure. -* Call a user-defined function. +* Schema actions +** Add an index +** Drop an index +** Get the reference of an index +** Create a constraint +** Drop a constraint + +* Call a procedure +* Call a user-defined function [NOTE] -==== -The presented value can vary slightly depending on the xref::query-tuning/query-options.adoc#cypher-runtime[Cypher runtime] that was used to execute the query. +-- +The presented value can vary slightly depending on the xref:query-tuning/index.adoc#cypher-runtime[Cypher runtime] that was used to execute the query. In the pipelined runtime the number of _database hits_ will typically be higher since it uses a more accurate way of measuring. -==== - +-- diff --git a/modules/ROOT/pages/execution-plans/index.adoc b/modules/ROOT/pages/execution-plans/index.adoc index 0f4706541..9fc30f650 100644 --- a/modules/ROOT/pages/execution-plans/index.adoc +++ b/modules/ROOT/pages/execution-plans/index.adoc @@ -1,17 +1,9 @@ -:description: Characteristics of query execution plans and provides details about each of the operators. - [[execution-plans]] = Execution plans - -[abstract] --- -This section describes the characteristics of query execution plans and provides details about each of the operators. --- +:description: This section describes the characteristics of query execution plans and provides details about each of the operators. [NOTE] -==== -For information on replanning, see xref::query-tuning/query-options.adoc#cypher-replanning[Cypher replanning]. -==== +For information on replanning, see xref:query-tuning/index.adoc#cypher-replanning[]. [[execution-plan-introduction]] .Introduction @@ -25,7 +17,7 @@ Operators that join two branches in the tree combine input from two incoming str .Evaluation model Evaluation of the execution plan begins at the leaf nodes of the tree. Leaf nodes have no input rows and generally comprise operators such as scans and seeks. -These operators obtain the data directly from the storage engine, thus incurring xref::execution-plans/db-hits.adoc[database hits]. +These operators obtain the data directly from the storage engine, thus incurring xref:execution-plans/db-hits.adoc[database hits]. Any rows produced by leaf nodes are then piped into their parent nodes, which in turn pipe their output rows to their parent nodes and so on, all the way up to the root node. The root node produces the final results of the query. @@ -36,7 +28,7 @@ This means that a child operator may not be fully exhausted before the parent op However, some operators, such as those used for aggregation and sorting, need to aggregate all their rows before they can produce output. Such operators need to complete execution in its entirety before any rows are sent to their parents as input. -These operators are called _eager_ operators, and are denoted as such in xref::execution-plans/operator-summary.adoc[]. +These operators are called _eager_ operators, and are denoted as such in xref:execution-plans/operator-summary.adoc[]. Eagerness can cause high memory usage and may therefore be the cause of query performance issues. .Statistics @@ -45,17 +37,14 @@ Each operator is annotated with statistics. `Rows`:: The number of rows that the operator produced. This is only available if the query was profiled. - `EstimatedRows`:: This is the estimated number of rows that is expected to be produced by the operator. The estimate is an approximate number based on the available statistical information. The compiler uses this estimate to choose a suitable execution plan. - `DbHits`:: Each operator will ask the Neo4j storage engine to do work such as retrieving or updating data. A _database hit_ is an abstract unit of this storage engine work. -The actions triggering a database hit are listed in xref::execution-plans/db-hits.adoc[]. - +The actions triggering a database hit are listed in xref:execution-plans/db-hits.adoc[]. `Page Cache Hits`, `Page Cache Misses`, `Page Cache Hit Ratio`:: These metrics are only shown for some queries when using Neo4j Enterprise Edition. The page cache is used to cache data and avoid accessing disk, so having a high number of `hits` and a low number of `misses` will typically make the query run faster. @@ -79,10 +68,9 @@ The statistical information maintained by Neo4j is: Information about how the statistics are kept up to date, as well as configuration options for managing query replanning and caching, can be found in the link:{neo4j-docs-base-uri}/operations-manual/{page-version}/performance/statistics-execution-plans[Operations Manual -> Statistics and execution plans]. -xref::query-tuning/index.adoc[] describes how to tune Cypher queries. -In particular, see xref::query-tuning/query-profile.adoc[] for how to view the execution plan for a query and xref::query-tuning/using.adoc[Planner hints and the USING keyword] for how to use _hints_ to influence the decisions of the planner when building an execution plan for a query. +xref:query-tuning/index.adoc[] describes how to tune Cypher queries. +In particular, see xref:query-tuning/how-do-i-profile-a-query.adoc[] for how to view the execution plan for a query and xref:query-tuning/using.adoc[Planner hints and the USING keyword] for how to use _hints_ to influence the decisions of the planner when building an execution plan for a query. -For a deeper understanding of how each operator works, refer to xref::execution-plans/operator-summary.adoc[] and the linked sections per operator. +For a deeper understanding of how each operator works, refer to xref:execution-plans/operator-summary.adoc[] and the linked sections per operator. Please remember that the statistics of the particular database where the queries run will decide the plan used. There is no guarantee that a specific query will always be solved with the same plan. - diff --git a/modules/ROOT/pages/execution-plans/operator-summary.adoc b/modules/ROOT/pages/execution-plans/operator-summary.adoc index e2929d115..1ed47f5ac 100644 --- a/modules/ROOT/pages/execution-plans/operator-summary.adoc +++ b/modules/ROOT/pages/execution-plans/operator-summary.adoc @@ -1,12 +1,9 @@ -:description: Exection plan operators at a glance. +[[execution-plan-operators-summary]] += Execution plan operators at a glance +:description: This section contains the exection plan operators at a glance. -[[execution-plan-operators]] -= Execution plan operators - -[abstract] --- -This section contains the exection plan operators at a glance. --- +//This is being included in: +//neo4j-manual-modeling/cypherManual/docbook/content-map.xml This table comprises all the execution plan operators ordered lexicographically. @@ -14,25 +11,23 @@ This table comprises all the execution plan operators ordered lexicographically. * _Updating_ operators are used in queries that update the graph. -* _Eager_ operators xref::execution-plans/index.adoc#eagerness-laziness[accumulate all their rows] before piping them to the next operator. +* _Eager_ operators xref:execution-plans/index.adoc#eagerness-laziness[accumulate all their rows] before piping them to the next operator. [cols="35a,35a,6,10,14", options="header"] |=== -| Name | Description | Leaf? | Updating? | Considerations +| Name +| Description +| Leaf? +| Updating? +| Considerations -| xref::execution-plans/operators.adoc#query-plan-all-nodes-scan[AllNodesScan] +| xref:execution-plans/operators.adoc#query-plan-all-nodes-scan[AllNodesScan] | Reads all nodes from the node store. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-anti[Anti] -| Tests for the absence of a pattern. -| -| -| - -| xref::execution-plans/operators.adoc#query-plan-anti-semi-apply[AntiSemiApply] +| xref:execution-plans/operators.adoc#query-plan-anti-semi-apply[AntiSemiApply] a| Performs a nested loop. Tests for the absence of a pattern predicate. @@ -40,243 +35,235 @@ Tests for the absence of a pattern predicate. | | -| xref::execution-plans/operators.adoc#query-plan-apply[Apply] +| xref:execution-plans/operators.adoc#query-plan-apply[Apply] | Performs a nested loop. Yields rows from both the left-hand and right-hand side operators. | | | -| xref::execution-plans/operators.adoc#query-plan-argument[Argument] +| xref:execution-plans/operators.adoc#query-plan-argument[Argument] | Indicates the variable to be used as an argument to the right-hand side of an `Apply` operator. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-assert-same-node[AssertSameNode] +| xref:execution-plans/operators.adoc#query-plan-assert-same-node[AssertSameNode] | Used to ensure that no unique constraints are violated. | | | -| xref::execution-plans/operators.adoc#query-plan-asserting-multi-node-index-seek[AssertingMultiNodeIndexSeek] +| xref:execution-plans/operators.adoc#query-plan-asserting-multi-node-index-seek[AssertingMultiNodeIndexSeek] | Used to ensure that no unique constraints are violated. | | | -| xref::execution-plans/operators.adoc#query-plan-cache-properties[CacheProperties] +| xref:execution-plans/operators.adoc#query-plan-cache-properties[CacheProperties] | Reads node or relationship properties and caches them. | | | -| xref::execution-plans/operators.adoc#query-plan-cartesian-product[CartesianProduct] +| xref:execution-plans/operators.adoc#query-plan-cartesian-product[CartesianProduct] | Produces a cartesian product of the inputs from the left-hand and right-hand operators. | | | -| xref::execution-plans/operators.adoc#query-plan-create[Create] -| Creates nodes and relationships. +| xref:execution-plans/operators.adoc#query-plan-create-index[CreateIndex] +| Creates an index for either nodes or relationships. | | label:yes[] | -| xref::execution-plans/operators.adoc#query-plan-create-index[CreateIndex] -| Creates an index for either nodes or relationships. +| xref:execution-plans/operators.adoc#query-plan-create-node-key-constraint[CreateNodeKeyConstraint] +| Creates a node key constraint on a set of properties for all nodes having a certain label. | | label:yes[] | -| xref::execution-plans/operators.adoc#query-plan-create-node-key-constraint[CreateNodeKeyConstraint] -| Creates a node key constraint on a set of properties for all nodes with a certain label. +| xref:execution-plans/operators.adoc#query-plan-create-nodes---relationships[Create] +| Creates nodes and relationships. | | label:yes[] | -| xref::execution-plans/operators.adoc#query-plan-create-node-property-existence-constraint[CreateNodePropertyExistenceConstraint] -| Creates an existence constraint on a property for all nodes with a certain label. +| xref:execution-plans/operators.adoc#query-plan-create-node-property-existence-constraint[CreateNodePropertyExistenceConstraint] +| Creates an existence constraint on a property for all nodes having a certain label. | | label:yes[] | -| xref::execution-plans/operators.adoc#query-plan-create-relationship-property-existence-constraint[CreateRelationshipPropertyExistenceConstraint] +| xref:execution-plans/operators.adoc#query-plan-create-relationship-property-existence-constraint[CreateRelationshipPropertyExistenceConstraint] | Creates an existence constraint on a property for all relationships of a certain type. | | label:yes[] | -| xref::execution-plans/operators.adoc#query-plan-create-unique-constraint[CreateUniqueConstraint] -| Creates a unique constraint on a set of properties for all nodes with a certain label. +| xref:execution-plans/operators.adoc#query-plan-create-unique-constraint[CreateUniqueConstraint] +| Creates a unique constraint on a property for all nodes having a certain label. | | label:yes[] | -| xref::execution-plans/operators.adoc#query-plan-delete[Delete] +| xref:execution-plans/operators.adoc#query-plan-delete[Delete] | Deletes a node or relationship. | | label:yes[] | -| xref::execution-plans/operators.adoc#query-plan-detach-delete[DetachDelete] +| xref:execution-plans/operators.adoc#query-plan-detach-delete[DetachDelete] | Deletes a node and its relationships. | | label:yes[] | -| xref::execution-plans/operators.adoc#query-plan-directed-relationship-by-id-seek[DirectedRelationshipByIdSeek] +| xref:execution-plans/operators.adoc#query-plan-directed-relationship-by-id-seek[DirectedRelationshipByIdSeek] | Reads one or more relationships by id from the relationship store. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-directed-relationship-index-contains-scan[DirectedRelationshipIndexContainsScan] +| xref:execution-plans/operators.adoc#query-plan-directed-relationship-index-contains-scan[DirectedRelationshipIndexContainsScan] | Examines all values stored in an index, searching for entries containing a specific string; for example, in queries including `CONTAINS`. | | | -| xref::execution-plans/operators.adoc#query-plan-directed-relationship-index-ends-with-scan[DirectedRelationshipIndexEndsWithScan] +| xref:execution-plans/operators.adoc#query-plan-directed-relationship-index-ends-with-scan[DirectedRelationshipIndexEndsWithScan] | Examines all values stored in an index, searching for entries ending in a specific string; for example, in queries containing `ENDS WITH`. | | | -| xref::execution-plans/operators.adoc#query-plan-directed-relationship-index-scan[DirectedRelationshipIndexScan] +| xref:execution-plans/operators.adoc#query-plan-directed-relationship-index-scan[DirectedRelationshipIndexScan] | Examines all values stored in an index, returning all relationships and their start and end nodes with a particular relationship type and a specified property. | | | -| xref::execution-plans/operators.adoc#query-plan-directed-relationship-index-seek[DirectedRelationshipIndexSeek] +| xref:execution-plans/operators.adoc#query-plan-directed-relationship-index-seek[DirectedRelationshipIndexSeek] | Finds relationships and their start and end nodes using an index seek. | | | -| xref::execution-plans/operators.adoc#query-plan-directed-relationship-index-seek-by-range[DirectedRelationshipIndexSeekByRange] +| xref:execution-plans/operators.adoc#query-plan-directed-relationship-index-seek-by-range[DirectedRelationshipIndexSeekByRange] | Finds relationships and their start and end nodes using an index seek where the value of the property matches a given prefix string. | | | -| xref::execution-plans/operators.adoc#query-plan-directed-relationship-type-scan[DirectedRelationshipTypeScan] +| xref:execution-plans/operators.adoc#query-plan-directed-relationship-type-scan[DirectedRelationshipTypeScan] | Fetches all relationships and their start and end nodes with a specific type from the relationship type index. | | | -| xref::execution-plans/operators.adoc#query-plan-distinct[Distinct] +| xref:execution-plans/operators.adoc#query-plan-distinct[Distinct] | Drops duplicate rows from the incoming stream of rows. | | | label:eager[] -| xref::execution-plans/operators.adoc#query-plan-do-nothing-if-exists-constraint[DoNothingIfExists(CONSTRAINT)] +| xref:execution-plans/operators.adoc#query-plan-create-constraint-only-if-it-does-not-already-exist[DoNothingIfExists(CONSTRAINT)] | Checks if a constraint already exists, if it does then it stops the execution, if not it continues. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-do-nothing-if-exists-index[DoNothingIfExists(INDEX)] +| xref:execution-plans/operators.adoc#query-plan-create-index-only-if-it-does-not-already-exist[DoNothingIfExists(INDEX)] | Checks if an index already exists, if it does then it stops the execution, if not it continues. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-drop-constraint[DropConstraint] -| Drops a constraint using its name. +| xref:execution-plans/operators.adoc#query-plan-drop-index-by-schema[DropIndex] +| Drops an index from a property for all nodes having a certain label. | label:yes[] | label:yes[] -| +| label:deprecated[] -| xref::execution-plans/operators.adoc#query-plan-drop-index-by-schema[DropIndex] -| Drops an index from a property for all nodes with a certain label. +| xref:execution-plans/operators.adoc#query-plan-drop-index-by-name[DropIndex] +| Drops an index using its name. | label:yes[] | label:yes[] -| label:deprecated[] +| -| xref::execution-plans/operators.adoc#query-plan-drop-index[DropIndex] -| Drops an index using its name. +| xref:execution-plans/operators.adoc#query-plan-drop-constraint-by-name[DropConstraint] +| Drops a constraint using its name. | label:yes[] | label:yes[] | -| xref::execution-plans/operators.adoc#query-plan-drop-node-key-constraint[DropNodeKeyConstraint] -| Drops a node key constraint from a set of properties for all nodes with a certain label. +| xref:execution-plans/operators.adoc#query-plan-drop-node-key-constraint[DropNodeKeyConstraint] +| Drops a node key constraint from a set of properties for all nodes having a certain label. | label:yes[] | label:yes[] | label:deprecated[] -| xref::execution-plans/operators.adoc#query-plan-drop-node-property-existence-constraint[DropNodePropertyExistenceConstraint] -| Drops an existence constraint from a property for all nodes with a certain label. +| xref:execution-plans/operators.adoc#query-plan-drop-node-property-existence-constraint[DropNodePropertyExistenceConstraint] +| Drops an existence constraint from a property for all nodes having a certain label. | label:yes[] | label:yes[] | label:deprecated[] -| xref::execution-plans/operators.adoc#query-plan-drop-relationship-property-existence-constraint[DropRelationshipPropertyExistenceConstraint] +| xref:execution-plans/operators.adoc#query-plan-drop-relationship-property-existence-constraint[DropRelationshipPropertyExistenceConstraint] | Drops an existence constraint from a property for all relationships of a certain type. | label:yes[] | label:yes[] | label:deprecated[] -| xref::execution-plans/operators.adoc#query-plan-drop-unique-constraint[DropUniqueConstraint] -| Drops a unique constraint from a set of properties for all nodes with a certain label. +| xref:execution-plans/operators.adoc#query-plan-drop-unique-constraint[DropUniqueConstraint] +| Drops a unique constraint from a property for all nodes having a certain label. | label:yes[] | label:yes[] | label:deprecated[] -| xref::execution-plans/operators.adoc#query-plan-eager[Eager] +| xref:execution-plans/operators.adoc#query-plan-eager[Eager] | For isolation purposes, `Eager` ensures that operations affecting subsequent operations are executed fully for the whole dataset before continuing execution. | | | label:eager[] -| xref::execution-plans/operators.adoc#query-plan-eager-aggregation[EagerAggregation] +| xref:execution-plans/operators.adoc#query-plan-eager-aggregation[EagerAggregation] | Evaluates a grouping expression. | | | label:eager[] -| xref::execution-plans/operators.adoc#query-plan-empty-result[EmptyResult] +| xref:execution-plans/operators.adoc#query-plan-empty-result[EmptyResult] | Eagerly loads all incoming data and discards it. | | | -| xref::execution-plans/operators.adoc#query-plan-empty-row[EmptyRow] +| xref:execution-plans/operators.adoc#query-plan-empty-row[EmptyRow] | Returns a single row with no columns. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-exhaustive-limit[ExhaustiveLimit] -a| -The `ExhaustiveLimit` operator is similar to the `Limit` operator, but always exhausts the input. -Used when combining `LIMIT` and updates. -| -| -| - -| xref::execution-plans/operators.adoc#query-plan-expand-all[Expand(All)] +| xref:execution-plans/operators.adoc#query-plan-expand-all[Expand(All)] | Traverses incoming or outgoing relationships from a given node. | | | -| xref::execution-plans/operators.adoc#query-plan-expand-into[Expand(Into)] +| xref:execution-plans/operators.adoc#query-plan-expand-into[Expand(Into)] | Finds all relationships between two nodes. | | | -| xref::execution-plans/operators.adoc#query-plan-filter[Filter] +| xref:execution-plans/operators.adoc#query-plan-filter[Filter] | Filters each row coming from the child operator, only passing through rows that evaluate the predicates to `true`. | | | -| xref::execution-plans/operators.adoc#query-plan-foreach[Foreach] +| xref:execution-plans/operators.adoc#query-plan-foreach[Foreach] a| Performs a nested loop. Yields rows from the left-hand operator and discards rows from the right-hand operator. @@ -284,7 +271,7 @@ Yields rows from the left-hand operator and discards rows from the right-hand op | | -| xref::execution-plans/operators.adoc#query-plan-let-anti-semi-apply[LetAntiSemiApply] +| xref:execution-plans/operators.adoc#query-plan-let-anti-semi-apply[LetAntiSemiApply] a| Performs a nested loop. Tests for the absence of a pattern predicate in queries containing multiple pattern predicates. @@ -292,23 +279,23 @@ Tests for the absence of a pattern predicate in queries containing multiple patt | | -| xref::execution-plans/operators.adoc#query-plan-let-select-or-anti-semi-apply[LetSelectOrAntiSemiApply] +| xref:execution-plans/operators.adoc#query-plan-let-select-or-semi-apply[LetSelectOrSemiApply] a| Performs a nested loop. -Tests for the absence of a pattern predicate that is combined with other predicates. +Tests for the presence of a pattern predicate that is combined with other predicates. | | | -| xref::execution-plans/operators.adoc#query-plan-let-select-or-semi-apply[LetSelectOrSemiApply] +| xref:execution-plans/operators.adoc#query-plan-let-select-or-anti-semi-apply[LetSelectOrAntiSemiApply] a| Performs a nested loop. -Tests for the presence of a pattern predicate that is combined with other predicates. +Tests for the absence of a pattern predicate that is combined with other predicates. | | | -| xref::execution-plans/operators.adoc#query-plan-let-semi-apply[LetSemiApply] +| xref:execution-plans/operators.adoc#query-plan-let-semi-apply[LetSemiApply] a| Performs a nested loop. Tests for the presence of a pattern predicate in queries containing multiple pattern predicates. @@ -316,195 +303,189 @@ Tests for the presence of a pattern predicate in queries containing multiple pat | | -| xref::execution-plans/operators.adoc#query-plan-limit[Limit] -| Returns the first `+n+` rows from the incoming input. +| xref:execution-plans/operators.adoc#query-plan-limit[Limit] +| Returns the first 'n' rows from the incoming input. | | | -| xref::execution-plans/operators.adoc#query-plan-load-csv[LoadCSV] +| xref:execution-plans/operators.adoc#query-plan-load-csv[LoadCSV] | Loads data from a CSV source into the query. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-locking-merge[LockingMerge] +| xref:execution-plans/operators.adoc#query-plan-locking-merge[LockingMerge] | Similar to the `Merge` operator but will lock the start and end node when creating a relationship if necessary. | | | -| xref::execution-plans/operators.adoc#query-plan-merge[Merge] +| xref:execution-plans/operators.adoc#query-plan-merge[Merge] | The `Merge` operator will either read or create nodes and/or relationships. | | | -| xref::execution-plans/operators.adoc#query-plan-multi-node-index-seek[MultiNodeIndexSeek] -| Finds nodes using multiple index seeks. -| label:yes[] -| -| - -| xref::execution-plans/operators.adoc#query-plan-node-by-id-seek[NodeByIdSeek] +| xref:execution-plans/operators.adoc#query-plan-node-by-id-seek[NodeByIdSeek] | Reads one or more nodes by ID from the node store. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-node-by-label-scan[NodeByLabelScan] +| xref:execution-plans/operators.adoc#query-plan-node-by-label-scan[NodeByLabelScan] | Fetches all nodes with a specific label from the node label index. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-node-count-from-count-store[NodeCountFromCountStore] +| xref:execution-plans/operators.adoc#query-plan-node-count-from-count-store[NodeCountFromCountStore] | Uses the count store to answer questions about node counts. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-node-hash-join[NodeHashJoin] +| xref:execution-plans/operators.adoc#query-plan-node-hash-join[NodeHashJoin] | Executes a hash join on node ID. | | | label:eager[] -| xref::execution-plans/operators.adoc#query-plan-node-index-contains-scan[NodeIndexContainsScan] +| xref:execution-plans/operators.adoc#query-plan-node-index-contains-scan[NodeIndexContainsScan] | Examines all values stored in an index, searching for entries containing a specific string. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-node-index-ends-with-scan[NodeIndexEndsWithScan] +| xref:execution-plans/operators.adoc#query-plan-node-index-ends-with-scan[NodeIndexEndsWithScan] | Examines all values stored in an index, searching for entries ending in a specific string. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-node-index-scan[NodeIndexScan] -| Examines all values stored in an index, returning all nodes with a particular label with a specified property. +| xref:execution-plans/operators.adoc#query-plan-node-index-scan[NodeIndexScan] +| Examines all values stored in an index, returning all nodes with a particular label having a specified property. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-node-index-seek[NodeIndexSeek] +| xref:execution-plans/operators.adoc#query-plan-node-index-seek[NodeIndexSeek] | Finds nodes using an index seek. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-node-index-seek-by-range[NodeIndexSeekByRange] +| xref:execution-plans/operators.adoc#query-plan-node-index-seek-by-range[NodeIndexSeekByRange] | Finds nodes using an index seek where the value of the property matches the given prefix string. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-node-left-right-outer-hash-join[NodeLeftOuterHashJoin] +| xref:execution-plans/operators.adoc#query-plan-node-left-right-outer-hash-join[NodeLeftOuterHashJoin] | Executes a left outer hash join. | | | label:eager[] -| xref::execution-plans/operators.adoc#query-plan-node-left-right-outer-hash-join[NodeRightOuterHashJoin] +| xref:execution-plans/operators.adoc#query-plan-node-left-right-outer-hash-join[NodeRightOuterHashJoin] | Executes a right outer hash join. | | | label:eager[] -| xref::execution-plans/operators.adoc#query-plan-node-unique-index-seek[NodeUniqueIndexSeek] +| xref:execution-plans/operators.adoc#query-plan-node-unique-index-seek[NodeUniqueIndexSeek] | Finds nodes using an index seek within a unique index. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-node-unique-index-seek-by-range[NodeUniqueIndexSeekByRange] +| xref:execution-plans/operators.adoc#query-plan-node-unique-index-seek-by-range[NodeUniqueIndexSeekByRange] | Finds nodes using an index seek within a unique index where the value of the property matches the given prefix string. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-optional[Optional] -| Yields a single row with all columns set to `null` if no data is returned by its source. +| xref:execution-plans/operators.adoc#query-plan-ordered-aggregation[OrderedAggregation] +a| +Like `EagerAggregation` but relies on the ordering of incoming rows. +Is not eager. | | | -| xref::execution-plans/operators.adoc#query-plan-optional-expand-all[OptionalExpand(All)] -| Traverses relationships from a given node, producing a single row with the relationship and end node set to `null` if the predicates are not fulfilled. +| xref:execution-plans/operators.adoc#query-plan-ordered-distinct[OrderedDistinct] +| Like `Distinct` but relies on the ordering of incoming rows. | | | -| xref::execution-plans/operators.adoc#query-plan-optional-expand-into[OptionalExpand(Into)] -| Traverses all relationships between two nodes, producing a single row with the relationship and end node set to `null` if no matching relationships are found (the start node is the node with the smallest degree). +| xref:execution-plans/operators.adoc#query-plan-optional[Optional] +| Yields a single row with all columns set to `null` if no data is returned by its source. | | | -| xref::execution-plans/operators.adoc#query-plan-ordered-aggregation[OrderedAggregation] -a| -Like `EagerAggregation` but relies on the ordering of incoming rows. -Is not eager. +| xref:execution-plans/operators.adoc#query-plan-optional-expand-all[OptionalExpand(All)] +| Traverses relationships from a given node, producing a single row with the relationship and end node set to `null` if the predicates are not fulfilled. | | | -| xref::execution-plans/operators.adoc#query-plan-ordered-distinct[OrderedDistinct] -| Like `Distinct` but relies on the ordering of incoming rows. +| xref:execution-plans/operators.adoc#query-plan-optional-expand-into[OptionalExpand(Into)] +| Traverses all relationships between two nodes, producing a single row with the relationship and end node set to `null` if no matching relationships are found (the start node will be the node with the smallest degree). | | | -| xref::execution-plans/operators.adoc#query-plan-partial-sort[PartialSort] +| xref:execution-plans/operators.adoc#query-plan-partial-sort[PartialSort] | Sorts a row by multiple columns if there is already an ordering. | | | -| xref::execution-plans/operators.adoc#query-plan-partial-top[PartialTop] -| Returns the first `+n+` rows sorted by multiple columns if there is already an ordering. +| xref:execution-plans/operators.adoc#query-plan-partial-top[PartialTop] +| Returns the first 'n' rows sorted by multiple columns if there is already an ordering. | | | -| xref::execution-plans/operators.adoc#query-plan-procedure-call[ProcedureCall] +| xref:execution-plans/operators.adoc#query-plan-procedure-call[ProcedureCall] | Calls a procedure. | | | -| xref::execution-plans/operators.adoc#query-plan-produce-results[ProduceResults] +| xref:execution-plans/operators.adoc#query-plan-produce-results[ProduceResults] | Prepares the result so that it is consumable by the user. | | | -| xref::execution-plans/operators.adoc#query-plan-project-endpoints[ProjectEndpoints] +| xref:execution-plans/operators.adoc#query-plan-project-endpoints[ProjectEndpoints] | Projects the start and end node of a relationship. | | | -| xref::execution-plans/operators.adoc#query-plan-projection[Projection] +| xref:execution-plans/operators.adoc#query-plan-projection[Projection] | Evaluates a set of expressions, producing a row with the results thereof. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-relationship-count-from-count-store[RelationshipCountFromCountStore] +| xref:execution-plans/operators.adoc#query-plan-relationship-count-from-count-store[RelationshipCountFromCountStore] | Uses the count store to answer questions about relationship counts. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-remove-labels[RemoveLabels] +| xref:execution-plans/operators.adoc#query-plan-remove-labels[RemoveLabels] | Deletes labels from a node. | | label:yes[] | -| xref::execution-plans/operators.adoc#query-plan-roll-up-apply[RollUpApply] +| xref:execution-plans/operators.adoc#query-plan-roll-up-apply[RollUpApply] a| Performs a nested loop. Executes a pattern expression or pattern comprehension. @@ -512,7 +493,7 @@ Executes a pattern expression or pattern comprehension. | | -| xref::execution-plans/operators.adoc#query-plan-select-or-anti-semi-apply[SelectOrAntiSemiApply] +| xref:execution-plans/operators.adoc#query-plan-select-or-anti-semi-apply[SelectOrAntiSemiApply] a| Performs a nested loop. Tests for the absence of a pattern predicate if an expression predicate evaluates to `false`. @@ -520,196 +501,165 @@ Tests for the absence of a pattern predicate if an expression predicate evaluate | | -| xref::execution-plans/operators.adoc#query-plan-select-or-semi-apply[SelectOrSemiApply] +| xref:execution-plans/operators.adoc#query-plan-select-or-semi-apply[SelectOrSemiApply] | Performs a nested loop. Tests for the presence of a pattern predicate if an expression predicate evaluates to `false`. | | | -| xref::execution-plans/operators.adoc#query-plan-semi-apply[SemiApply] +| xref:execution-plans/operators.adoc#query-plan-semi-apply[SemiApply] | Performs a nested loop. Tests for the presence of a pattern predicate. | | | -| xref::execution-plans/operators.adoc#query-plan-set-labels[SetLabels] +| xref:execution-plans/operators.adoc#query-plan-set-labels[SetLabels] | Sets labels on a node. | | label:yes[] | -| xref::execution-plans/operators.adoc#query-plan-set-node-properties-from-map[SetNodePropertiesFromMap] +| xref:execution-plans/operators.adoc#query-plan-set-node-properties-from-map[SetNodePropertiesFromMap] | Sets properties from a map on a node. | | label:yes[] | -| xref::execution-plans/operators.adoc#query-plan-set-property[SetProperty] +| xref:execution-plans/operators.adoc#query-plan-set-property[SetProperty] | Sets a property on a node or relationship. | | label:yes[] | -| xref::execution-plans/operators.adoc#query-plan-set-relationship-properties-from-map[SetRelationshipPropertiesFromMap] +| xref:execution-plans/operators.adoc#query-plan-set-relationship-properties-from-map[SetRelationshipPropertiesFromMap] | Sets properties from a map on a relationship. | | label:yes[] | -| xref::execution-plans/operators.adoc#query-plan-shortest-path[ShortestPath] -| Finds one or all shortest paths between two previously matches node variables. -| -| -| - -| xref::execution-plans/operators.adoc#query-plan-show-constraints[ShowConstraints] +| xref:execution-plans/operators.adoc#query-plan-listing-constraints[ShowConstraints] | Lists the available constraints. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-show-functions[ShowFunctions] +| xref:execution-plans/operators.adoc#query-plan-listing-functions[ShowFunctions] | Lists the available functions. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-show-indexes[ShowIndexes] +| xref:execution-plans/operators.adoc#query-plan-listing-indexes[ShowIndexes] | Lists the available indexes. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-show-procedures[ShowProcedures] +| xref:execution-plans/operators.adoc#query-plan-listing-procedures[ShowProcedures] | Lists the available procedures. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-show-transactions[ShowTransactions] -| Lists the available transactions on the current server. -| label:yes[] -| -| - -| xref::execution-plans/operators.adoc#query-plan-skip[Skip] -| Skips `+n+` rows from the incoming rows. +| xref:execution-plans/operators.adoc#query-plan-skip[Skip] +| Skips 'n' rows from the incoming rows. | | | -| xref::execution-plans/operators.adoc#query-plan-sort[Sort] +| xref:execution-plans/operators.adoc#query-plan-sort[Sort] | Sorts rows by a provided key. | | | label:eager[] -| xref::execution-plans/operators.adoc#query-plan-terminate-transactions[TerminateTransactions] -| Terminate transactions with the given IDs. -| label:yes[] -| -| - -| xref::execution-plans/operators.adoc#query-plan-top[Top] +| xref:execution-plans/operators.adoc#query-plan-top[Top] | Returns the first 'n' rows sorted by a provided key. | | | label:eager[] -| xref::execution-plans/operators.adoc#query-plan-triadic-build[TriadicBuild] -| The `TriadicBuild` operator is used in conjunction with `TriadicFilter` to solve triangular queries. -| -| -| - -| xref::execution-plans/operators.adoc#query-plan-triadic-filter[TriadicFilter] -| The `TriadicFilter` operator is used in conjunction with `TriadicBuild` to solve triangular queries. -| -| -| - -| xref::execution-plans/operators.adoc#query-plan-triadic-selection[TriadicSelection] +| xref:execution-plans/operators.adoc#query-plan-triadic-selection[TriadicSelection] | Solves triangular queries, such as the very common 'find my friend-of-friends that are not already my friend'. | | | -| xref::execution-plans/operators.adoc#query-plan-undirected-relationship-by-id-seek[UndirectedRelationshipByIdSeek] +| xref:execution-plans/operators.adoc#query-plan-undirected-relationship-by-id-seek[UndirectedRelationshipByIdSeek] | Reads one or more relationships by ID from the relationship store. | label:yes[] | | -| xref::execution-plans/operators.adoc#query-plan-undirected-relationship-index-contains-scan[UndirectedRelationshipIndexContainsScan] +| xref:execution-plans/operators.adoc#query-plan-undirected-relationship-index-contains-scan[UndirectedRelationshipIndexContainsScan] | Examines all values stored in an index, searching for entries containing a specific string; for example, in queries including `CONTAINS`. | | | -| xref::execution-plans/operators.adoc#query-plan-undirected-relationship-index-ends-with-scan[UndirectedRelationshipIndexEndsWithScan] +| xref:execution-plans/operators.adoc#query-plan-undirected-relationship-index-ends-with-scan[UndirectedRelationshipIndexEndsWithScan] | Examines all values stored in an index, searching for entries ending in a specific string; for example, in queries containing `ENDS WITH`. | | | -| xref::execution-plans/operators.adoc#query-plan-undirected-relationship-index-scan[UndirectedRelationshipIndexScan] +| xref:execution-plans/operators.adoc#query-plan-undirected-relationship-index-scan[UndirectedRelationshipIndexScan] | Examines all values stored in an index, returning all relationships and their start and end nodes with a particular relationship type and a specified property. | | | -| xref::execution-plans/operators.adoc#query-plan-undirected-relationship-index-seek[UndirectedRelationshipIndexSeek] +| xref:execution-plans/operators.adoc#query-plan-undirected-relationship-index-seek[UndirectedRelationshipIndexSeek] | Finds relationships and their start and end nodes using an index seek. | | | -| xref::execution-plans/operators.adoc#query-plan-undirected-relationship-index-seek-by-range[UndirectedRelationshipIndexSeekByRange] +| xref:execution-plans/operators.adoc#query-plan-undirected-relationship-index-seek-by-range[UndirectedRelationshipIndexSeekByRange] | Finds relationships and their start and end nodes using an index seek where the value of the property matches a given prefix string. | | | -| xref::execution-plans/operators.adoc#query-plan-undirected-relationship-type-scan[UndirectedRelationshipTypeScan] +| xref:execution-plans/operators.adoc#query-plan-undirected-relationship-type-scan[UndirectedRelationshipTypeScan] | Fetches all relationships and their start and end nodes with a specific type from the relationship type index. | | | -| xref::execution-plans/operators.adoc#query-plan-union[Union] +| xref:execution-plans/operators.adoc#query-plan-union[Union] | Concatenates the results from the right-hand operator with the results from the left-hand operator. | | | -| xref::execution-plans/operators.adoc#query-plan-unwind[Unwind] +| xref:execution-plans/operators.adoc#query-plan-unwind[Unwind] | Returns one row per item in a list. | | | -| xref::execution-plans/operators.adoc#query-plan-value-hash-join[ValueHashJoin] +| xref:execution-plans/operators.adoc#query-plan-value-hash-join[ValueHashJoin] | Executes a hash join on arbitrary values. | | | label:eager[] -| xref::execution-plans/operators.adoc#query-plan-varlength-expand-all[VarLengthExpand(All)] +| xref:execution-plans/operators.adoc#query-plan-varlength-expand-all[VarLengthExpand(All)] | Traverses variable-length relationships from a given node. | | | -| xref::execution-plans/operators.adoc#query-plan-varlength-expand-into[VarLengthExpand(Into)] +| xref:execution-plans/operators.adoc#query-plan-varlength-expand-into[VarLengthExpand(Into)] | Finds all variable-length relationships between two nodes. | | | -| xref::execution-plans/operators.adoc#query-plan-varlength-expand-pruning[VarLengthExpand(Pruning)] +| xref:execution-plans/operators.adoc#query-plan-varlength-expand-pruning[VarLengthExpand(Pruning)] | Traverses variable-length relationships from a given node and only returns unique end nodes. | | | |=== - diff --git a/modules/ROOT/pages/execution-plans/operators.adoc b/modules/ROOT/pages/execution-plans/operators.adoc index 5be1ea2fb..1371f3840 100644 --- a/modules/ROOT/pages/execution-plans/operators.adoc +++ b/modules/ROOT/pages/execution-plans/operators.adoc @@ -1,929 +1,803 @@ -:description: Execution plan operators. - [[execution-plans-operators]] = Execution plan operators in detail +:description: All operators are listed here, grouped by the similarity of their characteristics. +:page-toclevels: -1 -[abstract] --- -All executin plan operators are listed here, grouped by the similarity of their characteristics. --- - -Certain operators are only used by a subset of the xref::query-tuning/index.adoc#cypher-runtime[runtimes] that Cypher can choose from. +Certain operators are only used by a subset of the xref:query-tuning/index.adoc#cypher-runtime[runtimes] that Cypher can choose from. If that is the case, the example queries will be prefixed with an option to choose one of these runtimes. - // --- scan and seek operators --- - -[[query-plan-all-nodes-scan]] -== All Nodes Scan // AllNodesScan - -The `AllNodesScan` operator reads all nodes from the node store. -The variable that will contain the nodes is seen in the arguments. +[[query-plan-all-nodes-scan]] +== All Nodes Scan == +The `AllNodesScan` operator reads all nodes from the node store. The variable that will contain the nodes is seen in the arguments. Any query using this operator is likely to encounter performance problems on a non-trivial database. - -.AllNodesScan -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (n) -RETURN n +MATCH (n) RETURN n ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+---------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-----------------+---------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | n | 35 | 35 | 0 | | | | Fused in Pipeline 0 | | | +---------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | n | 35 | 35 | 36 | 112 | 3/0 | 0.598 | Fused in Pipeline 0 | +| +AllNodesScan | n | 35 | 35 | 36 | 72 | 3/0 | 0.830 | Fused in Pipeline 0 | +-----------------+---------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 36, total allocated memory: 176 ----- - -====== +Total database accesses: 36, total allocated memory: 136 +---- -[[query-plan-directed-relationship-index-scan]] -== Directed Relationship Index Scan // DirectedRelationshipIndexScan +[[query-plan-directed-relationship-index-scan]] +== Directed Relationship Index Scan == The `DirectedRelationshipIndexScan` operator examines all values stored in an index, returning all relationships and their start and end nodes with a particular relationship type and a specified property. - -.DirectedRelationshipIndexScan -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH ()-[r: WORKS_IN]->() -WHERE r.title IS NOT NULL -RETURN r +MATCH ()-[r: WORKS_IN]->() WHERE r.title IS NOT NULL RETURN r ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+--------------------------------+----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+--------------------------------+----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | r | 15 | 15 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexScan | BTREE INDEX (anon_0)-[r:WORKS_IN(title)]->(anon_1) WHERE title IS NOT NULL | 15 | 15 | 31 | 112 | 3/1 | 0.874 | Fused in Pipeline 0 | -+--------------------------------+----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Runtime version 4.3 -Total database accesses: 31, total allocated memory: 176 ----- - -====== ++--------------------------------+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++--------------------------------+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | r | 15 | 15 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +DirectedRelationshipIndexScan | (anon_0)-[r:WORKS_IN(title)]->(anon_1) WHERE title IS NOT NULL | 15 | 15 | 31 | 72 | 3/1 | 2.512 | Fused in Pipeline 0 | ++--------------------------------+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Total database accesses: 31, total allocated memory: 136 -[[query-plan-undirected-relationship-index-scan]] -== Undirected Relationship Index Scan +---- // UndirectedRelationshipIndexScan +[[query-plan-undirected-relationship-index-scan]] +== Undirected Relationship Index Scan == The `UndirectedRelationshipIndexScan` operator examines all values stored in an index, returning all relationships and their start and end nodes with a particular relationship type and a specified property. - -.UndirectedRelationshipIndexScan -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH ()-[r: WORKS_IN]-() -WHERE r.title IS NOT NULL -RETURN r +MATCH ()-[r: WORKS_IN]-() WHERE r.title IS NOT NULL RETURN r ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+----------------------------------+---------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+----------------------------------+---------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | r | 30 | 30 | 0 | | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +UndirectedRelationshipIndexScan | BTREE INDEX (anon_0)-[r:WORKS_IN(title)]-(anon_1) WHERE title IS NOT NULL | 30 | 30 | 31 | 112 | 3/1 | 1.005 | Fused in Pipeline 0 | -+----------------------------------+---------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++----------------------------------+---------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++----------------------------------+---------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | r | 30 | 30 | 0 | | | | Fused in Pipeline 0 | +| | +---------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +UndirectedRelationshipIndexScan | (anon_0)-[r:WORKS_IN(title)]-(anon_1) WHERE title IS NOT NULL | 30 | 30 | 31 | 72 | 3/1 | 1.938 | Fused in Pipeline 0 | ++----------------------------------+---------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 31, total allocated memory: 176 ----- - -====== +Total database accesses: 31, total allocated memory: 136 +---- -[[query-plan-directed-relationship-index-seek]] -== Directed Relationship Index Seek // DirectedRelationshipIndexSeek - +[[query-plan-directed-relationship-index-seek]] +== Directed Relationship Index Seek == The `DirectedRelationshipIndexSeek` operator finds relationships and their start and end nodes using an index seek. The relationship variable and the index used are shown in the arguments of the operator. - -.DirectedRelationshipIndexSeek -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (candidate)-[r:WORKS_IN]->() -WHERE r.title = 'chief architect' -RETURN candidate +MATCH (candidate)-[r:WORKS_IN]->() WHERE r.title = 'chief architect' RETURN candidate ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+--------------------------------+-----------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+--------------------------------+-----------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | candidate | 2 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexSeek | BTREE INDEX (candidate)-[r:WORKS_IN(title)]->(anon_0) WHERE title = $autostring_0 | 2 | 1 | 3 | 112 | 3/1 | 0.416 | Fused in Pipeline 0 | -+--------------------------------+-----------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 3, total allocated memory: 176 ----- +Runtime version 4.3 -====== ++--------------------------------+-----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++--------------------------------+-----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | candidate | 2 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +DirectedRelationshipIndexSeek | (candidate)-[r:WORKS_IN(title)]->(anon_0) WHERE title = $autostring_0 | 2 | 1 | 3 | 72 | 3/1 | 1.031 | Fused in Pipeline 0 | ++--------------------------------+-----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Total database accesses: 3, total allocated memory: 136 -[[query-plan-undirected-relationship-index-seek]] -== Undirected Relationship Index Seek +---- // UndirectedRelationshipIndexSeek - +[[query-plan-undirected-relationship-index-seek]] +== Undirected Relationship Index Seek == The `UndirectedRelationshipIndexSeek` operator finds relationships and their start and end nodes using an index seek. The relationship variable and the index used are shown in the arguments of the operator. - -.UndirectedRelationshipIndexSeek -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (candidate)-[r:WORKS_IN]-() -WHERE r.title = 'chief architect' -RETURN candidate +MATCH (candidate)-[r:WORKS_IN]-() WHERE r.title = 'chief architect' RETURN candidate ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+----------------------------------+----------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+----------------------------------+----------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | candidate | 4 | 2 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +UndirectedRelationshipIndexSeek | BTREE INDEX (candidate)-[r:WORKS_IN(title)]-(anon_0) WHERE title = $autostring_0 | 4 | 2 | 3 | 112 | 3/1 | 0.472 | Fused in Pipeline 0 | -+----------------------------------+----------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++----------------------------------+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++----------------------------------+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | candidate | 4 | 2 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +UndirectedRelationshipIndexSeek | (candidate)-[r:WORKS_IN(title)]-(anon_0) WHERE title = $autostring_0 | 4 | 2 | 3 | 72 | 3/1 | 0.671 | Fused in Pipeline 0 | ++----------------------------------+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 3, total allocated memory: 176 ----- - -====== +Total database accesses: 3, total allocated memory: 136 +---- -[[query-plan-directed-relationship-by-id-seek]] -== Directed Relationship By Id Seek // DirectedRelationshipByIdSeek - +[[query-plan-directed-relationship-by-id-seek]] +== Directed Relationship By Id Seek == The `DirectedRelationshipByIdSeek` operator reads one or more relationships by id from the relationship store, and produces both the relationship and the nodes on either side. - -.DirectedRelationshipByIdSeek -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MATCH (n1)-[r]->() -WHERE id(r) = 0 -RETURN r, n1 + WHERE id(r) = 0 + RETURN r, n1 ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-------------------------------+---------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-------------------------------+---------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | r, n1 | 1 | 1 | 0 | | | | Fused in Pipeline 0 | | | +---------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipByIdSeek | (n1)-[r]->(anon_0) WHERE id(r) = $autoint_0 | 1 | 1 | 1 | 112 | 4/0 | 0.306 | Fused in Pipeline 0 | +| +DirectedRelationshipByIdSeek | (n1)-[r]->(anon_0) WHERE id(r) = $autoint_0 | 1 | 1 | 1 | 72 | 4/0 | 0.416 | Fused in Pipeline 0 | +-------------------------------+---------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 1, total allocated memory: 176 ----- - -====== +Total database accesses: 1, total allocated memory: 136 - -[[query-plan-undirected-relationship-by-id-seek]] -== Undirected Relationship By Id Seek +---- // UndirectedRelationshipByIdSeek - +[[query-plan-undirected-relationship-by-id-seek]] +== Undirected Relationship By Id Seek == The `UndirectedRelationshipByIdSeek` operator reads one or more relationships by id from the relationship store. As the direction is unspecified, two rows are produced for each relationship as a result of alternating the combination of the start and end node. - -.UndirectedRelationshipByIdSeek -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MATCH (n1)-[r]-() -WHERE id(r) = 1 -RETURN r, n1 + WHERE id(r) = 1 + RETURN r, n1 ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +---------------------------------+--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +---------------------------------+--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | r, n1 | 2 | 2 | 0 | | | | Fused in Pipeline 0 | +| +ProduceResults | r, n1 | 1 | 2 | 0 | | | | Fused in Pipeline 0 | | | +--------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +UndirectedRelationshipByIdSeek | (n1)-[r]-(anon_0) WHERE id(r) = $autoint_0 | 2 | 2 | 1 | 112 | 4/0 | 0.890 | Fused in Pipeline 0 | +| +UndirectedRelationshipByIdSeek | (n1)-[r]-(anon_0) WHERE id(r) = $autoint_0 | 1 | 2 | 1 | 72 | 4/0 | 1.461 | Fused in Pipeline 0 | +---------------------------------+--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 1, total allocated memory: 176 ----- - -====== +Total database accesses: 1, total allocated memory: 136 +---- -[[query-plan-directed-relationship-index-contains-scan]] -== Directed Relationship Index Contains Scan // DirectedRelationshipIndexContainsScan +[[query-plan-directed-relationship-index-contains-scan]] +== Directed Relationship Index Contains Scan == -The `DirectedRelationshipIndexContainsScan` operator examines all values stored in an index, searching for entries containing a specific string; for example, in queries including `CONTAINS`. -Although this is slower than an index seek (since all entries need to be examined), it is still faster than the indirection resulting from a type scan using `DirectedRelationshipTypeScan`, and a property store filter. - - -.DirectedRelationshipIndexContainsScan -====== +The `DirectedRelationshipIndexContainsScan` operator examines all values stored in an index, searching for entries + containing a specific string; for example, in queries including `CONTAINS`. + Although this is slower than an index seek (since all entries need to be + examined), it is still faster than the indirection resulting from a type scan using `DirectedRelationshipTypeScan`, and a property store + filter. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH ()-[r: WORKS_IN]->() -WHERE r.title CONTAINS 'senior' -RETURN r +MATCH ()-[r: WORKS_IN]->() WHERE r.title CONTAINS 'senior' RETURN r ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+----------------------------------------+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+----------------------------------------+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | r | 0 | 4 | 0 | | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexContainsScan | BTREE INDEX (anon_0)-[r:WORKS_IN(title)]->(anon_1) WHERE title CONTAINS $autostring_0 | 0 | 4 | 9 | 112 | 3/1 | 0.490 | Fused in Pipeline 0 | -+----------------------------------------+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 9, total allocated memory: 176 ----- +Runtime version 4.3 -====== ++----------------------------------------+---------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++----------------------------------------+---------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | r | 0 | 4 | 0 | | | | Fused in Pipeline 0 | +| | +---------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +DirectedRelationshipIndexContainsScan | (anon_0)-[r:WORKS_IN(title)]->(anon_1) WHERE title CONTAINS $autostring_0 | 0 | 4 | 9 | 72 | 3/1 | 0.919 | Fused in Pipeline 0 | ++----------------------------------------+---------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Total database accesses: 9, total allocated memory: 136 -[[query-plan-undirected-relationship-index-contains-scan]] -== Undirected Relationship Index Contains Scan +---- // UndirectedRelationshipIndexContainsScan +[[query-plan-undirected-relationship-index-contains-scan]] +== Undirected Relationship Index Contains Scan == -The `UndirectedRelationshipIndexContainsScan` operator examines all values stored in an index, searching for entries containing a specific string; for example, in queries including `CONTAINS`. -Although this is slower than an index seek (since all entries need to be examined), it is still faster than the indirection resulting from a type scan using `DirectedRelationshipTypeScan`, and a property store filter. - - -.UndirectedRelationshipIndexContainsScan -====== +The `UndirectedRelationshipIndexContainsScan` operator examines all values stored in an index, searching for entries + containing a specific string; for example, in queries including `CONTAINS`. + Although this is slower than an index seek (since all entries need to be + examined), it is still faster than the indirection resulting from a type scan using `DirectedRelationshipTypeScan`, and a property store + filter. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH ()-[r: WORKS_IN]-() -WHERE r.title CONTAINS 'senior' -RETURN r +MATCH ()-[r: WORKS_IN]-() WHERE r.title CONTAINS 'senior' RETURN r ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+------------------------------------------+--------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+------------------------------------------+--------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | r | 0 | 8 | 0 | | | | Fused in Pipeline 0 | -| | +--------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +UndirectedRelationshipIndexContainsScan | BTREE INDEX (anon_0)-[r:WORKS_IN(title)]-(anon_1) WHERE title CONTAINS $autostring_0 | 0 | 8 | 9 | 112 | 3/1 | 0.700 | Fused in Pipeline 0 | -+------------------------------------------+--------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Runtime version 4.3 -Total database accesses: 9, total allocated memory: 176 ----- ++------------------------------------------+--------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++------------------------------------------+--------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | r | 0 | 8 | 0 | | | | Fused in Pipeline 0 | +| | +--------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +UndirectedRelationshipIndexContainsScan | (anon_0)-[r:WORKS_IN(title)]-(anon_1) WHERE title CONTAINS $autostring_0 | 0 | 8 | 9 | 72 | 3/1 | 1.384 | Fused in Pipeline 0 | ++------------------------------------------+--------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -====== +Total database accesses: 9, total allocated memory: 136 +---- -[[query-plan-directed-relationship-index-ends-with-scan]] -== Directed Relationship Index Ends With Scan // DirectedRelationshipIndexEndsWithScan +[[query-plan-directed-relationship-index-ends-with-scan]] +== Directed Relationship Index Ends With Scan == -The `DirectedRelationshipIndexEndsWithScan` operator examines all values stored in an index, searching for entries ending in a specific string; for example, in queries containing `ENDS WITH`. -Although this is slower than an index seek (since all entries need to be examined), it is still faster than the indirection resulting from a label scan using `NodeByLabelScan`, and a property store filter. - - -.DirectedRelationshipIndexEndsWithScan -====== +The `DirectedRelationshipIndexEndsWithScan` operator examines all values stored in an index, searching for entries + ending in a specific string; for example, in queries containing `ENDS WITH`. + Although this is slower than an index seek (since all entries need to be + examined), it is still faster than the indirection resulting from a label scan using `NodeByLabelScan`, and a property store + filter. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH ()-[r: WORKS_IN]->() -WHERE r.title ENDS WITH 'developer' -RETURN r +MATCH ()-[r: WORKS_IN]->() WHERE r.title ENDS WITH 'developer' RETURN r ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+----------------------------------------+----------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+----------------------------------------+----------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | r | 0 | 8 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexEndsWithScan | BTREE INDEX (anon_0)-[r:WORKS_IN(title)]->(anon_1) WHERE title ENDS WITH $autostring_0 | 0 | 8 | 17 | 112 | 3/1 | 1.158 | Fused in Pipeline 0 | -+----------------------------------------+----------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 17, total allocated memory: 176 ----- +Runtime version 4.3 -====== ++----------------------------------------+----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++----------------------------------------+----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | r | 0 | 8 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +DirectedRelationshipIndexEndsWithScan | (anon_0)-[r:WORKS_IN(title)]->(anon_1) WHERE title ENDS WITH $autostring_0 | 0 | 8 | 17 | 72 | 3/1 | 1.065 | Fused in Pipeline 0 | ++----------------------------------------+----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Total database accesses: 17, total allocated memory: 136 -[[query-plan-undirected-relationship-index-ends-with-scan]] -== Undirected Relationship Index Ends With Scan +---- // UndirectedRelationshipIndexEndsWithScan +[[query-plan-undirected-relationship-index-ends-with-scan]] +== Undirected Relationship Index Ends With Scan == -The `UndirectedRelationshipIndexEndsWithScan` operator examines all values stored in an index, searching for entries ending in a specific string; for example, in queries containing `ENDS WITH`. -Although this is slower than an index seek (since all entries need to be examined), it is still faster than the indirection resulting from a label scan using `NodeByLabelScan`, and a property store filter. - - -.UndirectedRelationshipIndexEndsWithScan -====== +The `UndirectedRelationshipIndexEndsWithScan` operator examines all values stored in an index, searching for entries + ending in a specific string; for example, in queries containing `ENDS WITH`. + Although this is slower than an index seek (since all entries need to be + examined), it is still faster than the indirection resulting from a label scan using `NodeByLabelScan`, and a property store + filter. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH ()-[r: WORKS_IN]-() -WHERE r.title ENDS WITH 'developer' -RETURN r +MATCH ()-[r: WORKS_IN]-() WHERE r.title ENDS WITH 'developer' RETURN r ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+------------------------------------------+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+------------------------------------------+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | r | 0 | 16 | 0 | | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +UndirectedRelationshipIndexEndsWithScan | BTREE INDEX (anon_0)-[r:WORKS_IN(title)]-(anon_1) WHERE title ENDS WITH $autostring_0 | 0 | 16 | 17 | 112 | 3/1 | 0.655 | Fused in Pipeline 0 | -+------------------------------------------+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Runtime version 4.3 -Total database accesses: 17, total allocated memory: 176 ----- ++------------------------------------------+---------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++------------------------------------------+---------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | r | 0 | 16 | 0 | | | | Fused in Pipeline 0 | +| | +---------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +UndirectedRelationshipIndexEndsWithScan | (anon_0)-[r:WORKS_IN(title)]-(anon_1) WHERE title ENDS WITH $autostring_0 | 0 | 16 | 17 | 72 | 3/1 | 1.363 | Fused in Pipeline 0 | ++------------------------------------------+---------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -====== +Total database accesses: 17, total allocated memory: 136 +---- -[[query-plan-directed-relationship-index-seek-by-range]] -== Directed Relationship Index Seek By Range // DirectedRelationshipIndexSeekByRange - +[[query-plan-directed-relationship-index-seek-by-range]] +== Directed Relationship Index Seek By Range == The `DirectedRelationshipIndexSeekByRange` operator finds relationships and their start and end nodes using an index seek where the value of the property matches a given prefix string. -`DirectedRelationshipIndexSeekByRange` can be used for `STARTS WITH` and comparison operators such as `+<+`, `+>+`, `+<=+` and `+>=+`. - - -.DirectedRelationshipIndexSeekByRange -====== +`DirectedRelationshipIndexSeekByRange` can be used for `STARTS WITH` and comparison operators such as `<`, `>`, `\<=` and `>=`. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (candidate: Person)-[r:WORKS_IN]->(location) -WHERE r.duration > 100 -RETURN candidate +MATCH (candidate: Person)-[r:WORKS_IN]->(location) WHERE r.duration > 100 RETURN candidate ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+---------------------------------------+----------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+---------------------------------------+----------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | candidate | 4 | 15 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | candidate:Person | 4 | 15 | 15 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexSeekByRange | BTREE INDEX (candidate)-[r:WORKS_IN(duration)]->(location) WHERE duration > $autoint_0 | 4 | 15 | 31 | 112 | 4/1 | 0.495 | Fused in Pipeline 0 | -+---------------------------------------+----------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++---------------------------------------+----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++---------------------------------------+----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | candidate | 4 | 15 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | candidate:Person | 4 | 15 | 15 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +DirectedRelationshipIndexSeekByRange | (candidate)-[r:WORKS_IN(duration)]->(location) WHERE duration > $autoint_0 | 4 | 15 | 31 | 72 | 4/1 | 1.661 | Fused in Pipeline 0 | ++---------------------------------------+----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 46, total allocated memory: 176 ----- +Total database accesses: 46, total allocated memory: 136 -====== - - -[[query-plan-undirected-relationship-index-seek-by-range]] -== Undirected Relationship Index Seek By Range +---- // UndirectedRelationshipIndexSeekByRange - +[[query-plan-undirected-relationship-index-seek-by-range]] +== Undirected Relationship Index Seek By Range == The `UndirectedRelationshipIndexSeekByRange` operator finds relationships and their start and end nodes using an index seek where the value of the property matches a given prefix string. -`UndirectedRelationshipIndexSeekByRange` can be used for `STARTS WITH` and comparison operators such as `+<+`, `+>+`, `+<=+` and `+>=+`. - - -.UndirectedRelationshipIndexSeekByRange -====== +`UndirectedRelationshipIndexSeekByRange` can be used for `STARTS WITH` and comparison operators such as `<`, `>`, `\<=` and `>=`. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (candidate: Person)-[r:WORKS_IN]-(location) -WHERE r.duration > 100 -RETURN candidate +MATCH (candidate: Person)-[r:WORKS_IN]-(location) WHERE r.duration > 100 RETURN candidate ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------------------------------+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------------------------------+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | candidate | 4 | 15 | 0 | | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | candidate:Person | 4 | 15 | 30 | | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +UndirectedRelationshipIndexSeekByRange | BTREE INDEX (candidate)-[r:WORKS_IN(duration)]-(location) WHERE duration > $autoint_0 | 8 | 30 | 31 | 112 | 4/1 | 0.728 | Fused in Pipeline 0 | -+-----------------------------------------+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Runtime version 4.3 -Total database accesses: 61, total allocated memory: 176 ----- ++-----------------------------------------+---------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------------------------------+---------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | candidate | 4 | 15 | 0 | | | | Fused in Pipeline 0 | +| | +---------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | candidate:Person | 4 | 15 | 30 | | | | Fused in Pipeline 0 | +| | +---------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +UndirectedRelationshipIndexSeekByRange | (candidate)-[r:WORKS_IN(duration)]-(location) WHERE duration > $autoint_0 | 8 | 30 | 31 | 72 | 4/1 | 13.237 | Fused in Pipeline 0 | ++-----------------------------------------+---------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -====== +Total database accesses: 61, total allocated memory: 136 +---- -[[query-plan-directed-relationship-type-scan]] -== Directed Relationship Type Scan // DirectedRelationshipTypeScan - +[[query-plan-directed-relationship-type-scan]] +== Directed Relationship Type Scan == The `DirectedRelationshipTypeScan` operator fetches all relationships and their start and end nodes with a specific type from the relationship type index. - -.DirectedRelationshipTypeScan -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH ()-[r: FRIENDS_WITH]->() -RETURN r +MATCH ()-[r: FRIENDS_WITH]->() RETURN r ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-------------------------------+-------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-------------------------------+-------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | r | 2 | 2 | 0 | | | | Fused in Pipeline 0 | | | +-------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipTypeScan | (anon_0)-[r:FRIENDS_WITH]->(anon_1) | 2 | 2 | 5 | 112 | 2/1 | 0.470 | Fused in Pipeline 0 | +| +DirectedRelationshipTypeScan | (anon_0)-[r:FRIENDS_WITH]->(anon_1) | 2 | 2 | 5 | 72 | 2/1 | 0.488 | Fused in Pipeline 0 | +-------------------------------+-------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 5, total allocated memory: 176 ----- - -====== - +Total database accesses: 5, total allocated memory: 136 -[[query-plan-undirected-relationship-type-scan]] -== Undirected Relationship Type Scan +---- // UndirectedRelationshipTypeScan - +[[query-plan-undirected-relationship-type-scan]] +== Undirected Relationship Type Scan == The `UndirectedRelationshipTypeScan` operator fetches all relationships and their start and end nodes with a specific type from the relationship type index. -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH ()-[r: FRIENDS_WITH]-() -RETURN r +MATCH ()-[r: FRIENDS_WITH]-() RETURN r ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +---------------------------------+------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +---------------------------------+------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | r | 4 | 4 | 0 | | | | Fused in Pipeline 0 | | | +------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +UndirectedRelationshipTypeScan | (anon_0)-[r:FRIENDS_WITH]-(anon_1) | 4 | 4 | 5 | 112 | 2/1 | 0.821 | Fused in Pipeline 0 | +| +UndirectedRelationshipTypeScan | (anon_0)-[r:FRIENDS_WITH]-(anon_1) | 4 | 4 | 5 | 72 | 2/1 | 0.920 | Fused in Pipeline 0 | +---------------------------------+------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 5, total allocated memory: 176 ----- - -====== +Total database accesses: 5, total allocated memory: 136 +---- -[[query-plan-node-by-id-seek]] -== Node By Id Seek // NodeByIdSeek - +[[query-plan-node-by-id-seek]] +== Node By Id Seek == The `NodeByIdSeek` operator reads one or more nodes by id from the node store. - -.NodeByIdSeek -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (n) -WHERE id(n) = 0 -RETURN n +MATCH (n) WHERE id(n) = 0 RETURN n ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+----------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-----------------+----------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | n | 1 | 1 | 0 | | | | Fused in Pipeline 0 | | | +----------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeByIdSeek | n WHERE id(n) = $autoint_0 | 1 | 1 | 1 | 112 | 3/0 | 0.252 | Fused in Pipeline 0 | +| +NodeByIdSeek | n WHERE id(n) = $autoint_0 | 1 | 1 | 1 | 72 | 3/0 | 0.272 | Fused in Pipeline 0 | +-----------------+----------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 1, total allocated memory: 176 ----- - -====== +Total database accesses: 1, total allocated memory: 136 +---- -[[query-plan-node-by-label-scan]] -== Node By Label Scan // NodeByLabelScan - +[[query-plan-node-by-label-scan]] +== Node By Label Scan == The `NodeByLabelScan` operator fetches all nodes with a specific label from the node label index. - -.NodeByLabelScan -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (person:Person) -RETURN person +MATCH (person:Person) RETURN person ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +------------------+---------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +------------------+---------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person | 14 | 14 | 0 | | | | Fused in Pipeline 0 | | | +---------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeByLabelScan | person:Person | 14 | 14 | 15 | 112 | 2/1 | 0.446 | Fused in Pipeline 0 | +| +NodeByLabelScan | person:Person | 14 | 14 | 15 | 72 | 2/1 | 7.126 | Fused in Pipeline 0 | +------------------+---------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 15, total allocated memory: 176 ----- - -====== +Total database accesses: 15, total allocated memory: 136 +---- -[[query-plan-node-index-seek]] -== Node Index Seek // NodeIndexSeek - +[[query-plan-node-index-seek]] +== Node Index Seek == The `NodeIndexSeek` operator finds nodes using an index seek. The node variable and the index used are shown in the arguments of the operator. -If the index is a unique index, the operator is instead called xref::execution-plans/operators.adoc#query-plan-node-unique-index-seek[NodeUniqueIndexSeek]. - - -.NodeIndexSeek -====== +If the index is a unique index, the operator is instead called xref:execution-plans/operators.adoc#query-plan-node-unique-index-seek[NodeUniqueIndexSeek]. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (location:Location {name: 'Malmo'}) -RETURN location +MATCH (location:Location {name: 'Malmo'}) RETURN location ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | location | 1 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX location:Location(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 112 | 2/1 | 0.422 | Fused in Pipeline 0 | -+-----------------+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Runtime version 4.3 -Total database accesses: 2, total allocated memory: 176 ----- ++-----------------+----------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+----------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | location | 0 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeek | location:Location(name) WHERE name = $autostring_0 | 0 | 1 | 2 | 72 | 2/1 | 0.840 | Fused in Pipeline 0 | ++-----------------+----------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -====== +Total database accesses: 2, total allocated memory: 136 +---- -[[query-plan-node-unique-index-seek]] -== Node Unique Index Seek // NodeUniqueIndexSeek - -The `NodeUniqueIndexSeek` operator finds nodes using an index seek within a unique index. -The node variable and the index used are shown in the arguments of the operator. -If the index is not unique, the operator is instead called xref::execution-plans/operators.adoc#query-plan-node-index-seek[NodeIndexSeek]. -If the index seek is used to solve a xref::clauses/merge.adoc[MERGE] clause, it will also be marked with `(Locking)`. +[[query-plan-node-unique-index-seek]] +== Node Unique Index Seek == +The `NodeUniqueIndexSeek` operator finds nodes using an index seek within a unique index. The node variable and the index used are shown in the arguments of the operator. +If the index is not unique, the operator is instead called xref:execution-plans/operators.adoc#query-plan-node-index-seek[NodeIndexSeek]. +If the index seek is used to solve a xref:clauses/merge.adoc[MERGE] clause, it will also be marked with `(Locking)`. This makes it clear that any nodes returned from the index will be locked in order to prevent concurrent conflicting updates. - -.NodeUniqueIndexSeek -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (t:Team {name: 'Malmo'}) -RETURN t +MATCH (t:Team {name: 'Malmo'}) RETURN t ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +----------------------+------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +----------------------+------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | t | 1 | 0 | 0 | | | | Fused in Pipeline 0 | +| +ProduceResults | t | 0 | 0 | 0 | | | | Fused in Pipeline 0 | | | +------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeUniqueIndexSeek | UNIQUE t:Team(name) WHERE name = $autostring_0 | 1 | 0 | 1 | 112 | 0/1 | 0.414 | Fused in Pipeline 0 | +| +NodeUniqueIndexSeek | UNIQUE t:Team(name) WHERE name = $autostring_0 | 0 | 0 | 1 | 72 | 0/1 | 0.694 | Fused in Pipeline 0 | +----------------------+------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 1, total allocated memory: 176 ----- - -====== +Total database accesses: 1, total allocated memory: 136 +---- -[[query-plan-multi-node-index-seek]] -== Multi Node Index Seek // MultiNodeIndexSeek - +[[query-plan-multi-node-index-seek]] +== Multi Node Index Seek == The `MultiNodeIndexSeek` operator finds nodes using multiple index seeks. It supports using multiple distinct indexes for different nodes in the query. The node variables and the indexes used are shown in the arguments of the operator. The operator yields a cartesian product of all index seeks. -For example, if the operator does two seeks and the first seek finds the nodes `a1, a2` and the second `b1, b2, b3`, the `MultiNodeIndexSeek` will yield the rows `(a1, b1), (a1, b2), (a1, b3), (a2, b1), (a2, b2), (a2, b3)`. - +For example, if the operator does two seeks and the first seek finds the nodes `a1, a2` and the second `b1, b2, b3`, +the `MultiNodeIndexSeek` will yield the rows `(a1, b1), (a1, b2), (a1, b3), (a2, b1), (a2, b2), (a2, b3)`. -.MultiNodeIndexSeek -====== .Query -[source, cypher, role="noplay"] +[source,cypher] ---- CYPHER runtime=pipelined -MATCH - (location:Location {name: 'Malmo'}), - (person:Person {name: 'Bob'}) -RETURN location, person +MATCH (location:Location {name: 'Malmo'}), (person:Person {name: 'Bob'}) RETURN location, person ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+---------------------+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+---------------------+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | location, person | 1 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +MultiNodeIndexSeek | BTREE INDEX location:Location(name) WHERE name = $autostring_0, | 1 | 0 | 0 | 112 | 2/2 | 1.216 | Fused in Pipeline 0 | -| | BTREE INDEX person:Person(name) WHERE name = $autostring_1 | | | | | | | | -+---------------------+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Runtime version 4.3 -Total database accesses: 0, total allocated memory: 176 ----- ++---------------------+----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++---------------------+----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| +ProduceResults | location, person | 0 | 1 | 0 | | 2/0 | 0.176 | In Pipeline 0 | +| | +----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| +MultiNodeIndexSeek | location:Location(name) WHERE name = $autostring_0, person:Person(name) WHERE name = $autostring_1 | 0 | 1 | 4 | 72 | 0/2 | 0.512 | In Pipeline 0 | ++---------------------+----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -====== +Total database accesses: 4, total allocated memory: 136 +---- -[[query-plan-asserting-multi-node-index-seek]] -== Asserting Multi Node Index Seek // AssertingMultiNodeIndexSeek - +[[query-plan-asserting-multi-node-index-seek]] +== Asserting Multi Node Index Seek == The `AssertingMultiNodeIndexSeek` operator is used to ensure that no unique constraints are violated. -The example looks for the presence of a team with the supplied name and id, and if one does not exist, it will be created. -Owing to the existence of two unique constraints on `:Team(name)` and `:Team(id)`, any node that would be found by the `UniqueIndexSeek` must be the very same node, or the constraints would be violated. - - -.AssertingMultiNodeIndexSeek -====== +The example looks for the presence of a team with the supplied name and id, and if one does not exist, +it will be created. Owing to the existence of two unique constraints +on `:Team(name)` and `:Team(id)`, any node that would be found by the `UniqueIndexSeek` +must be the very same node, or the constraints would be violated. + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MERGE (t:Team {name: 'Engineering', id: 42}) ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +------------------------------+-----------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -932,304 +806,267 @@ Runtime version 4.4 | | +-----------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +EmptyResult | | 1 | 0 | 0 | | | | Fused in Pipeline 0 | | | +-----------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Merge | CREATE (t:Team {name: $autostring_0, id: $autoint_1}) | 1 | 1 | 0 | | | | Fused in Pipeline 0 | +| +Merge | CREATE (t:Team) | 1 | 1 | 0 | | | | Fused in Pipeline 0 | | | +-----------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AssertingMultiNodeIndexSeek | UNIQUE t:Team(name) WHERE name = $autostring_0, UNIQUE t:Team(id) WHERE id = $autoint_1 | 0 | 2 | 4 | 112 | 0/2 | 2.260 | Fused in Pipeline 0 | +| +AssertingMultiNodeIndexSeek | UNIQUE t:Team(name) WHERE name = $autostring_0, UNIQUE t:Team(id) WHERE id = $autoint_1 | 0 | 2 | 4 | 72 | 0/2 | 3.404 | Fused in Pipeline 0 | +------------------------------+-----------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 4, total allocated memory: 176 ----- - -====== +Total database accesses: 4, total allocated memory: 136 +---- -[[query-plan-node-index-seek-by-range]] -== Node Index Seek By Range // NodeIndexSeekByRange - +[[query-plan-node-index-seek-by-range]] +== Node Index Seek By Range == The `NodeIndexSeekByRange` operator finds nodes using an index seek where the value of the property matches a given prefix string. -`NodeIndexSeekByRange` can be used for `STARTS WITH` and comparison operators such as `+<+`, `+>+`, `+<=+` and `+>=+`. +`NodeIndexSeekByRange` can be used for `STARTS WITH` and comparison operators such as `<`, `>`, `\<=` and `>=`. If the index is a unique index, the operator is instead called `NodeUniqueIndexSeekByRange`. - -.NodeIndexSeekByRange -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (l:Location) -WHERE l.name STARTS WITH 'Lon' -RETURN l +MATCH (l:Location) WHERE l.name STARTS WITH 'Lon' RETURN l ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------------+-------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------------+-------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | l | 2 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +-------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeekByRange | BTREE INDEX l:Location(name) WHERE name STARTS WITH $autostring_0 | 2 | 1 | 2 | 112 | 3/0 | 1.095 | Fused in Pipeline 0 | -+-----------------------+-------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Runtime version 4.3 -Total database accesses: 2, total allocated memory: 176 ----- ++-----------------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | l | 2 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeekByRange | l:Location(name) WHERE name STARTS WITH $autostring_0 | 2 | 1 | 2 | 72 | 3/0 | 0.726 | Fused in Pipeline 0 | ++-----------------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -====== +Total database accesses: 2, total allocated memory: 136 +---- -[[query-plan-node-unique-index-seek-by-range]] -== Node Unique Index Seek By Range // NodeUniqueIndexSeekByRange - +[[query-plan-node-unique-index-seek-by-range]] +== Node Unique Index Seek By Range == The `NodeUniqueIndexSeekByRange` operator finds nodes using an index seek within a unique index, where the value of the property matches a given prefix string. -`NodeUniqueIndexSeekByRange` is used by `STARTS WITH` and comparison operators such as `+<+`, `+>+`, `+<=+`, and `+>=+`. +`NodeUniqueIndexSeekByRange` is used by `STARTS WITH` and comparison operators such as `<`, `>`, `\<=` and `>=`. If the index is not unique, the operator is instead called `NodeIndexSeekByRange`. - -.NodeUniqueIndexSeekByRange -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (t:Team) -WHERE t.name STARTS WITH 'Ma' -RETURN t +MATCH (t:Team) WHERE t.name STARTS WITH 'Ma' RETURN t ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-----------------------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | t | 2 | 0 | 0 | | | | Fused in Pipeline 0 | | | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeUniqueIndexSeekByRange | UNIQUE t:Team(name) WHERE name STARTS WITH $autostring_0 | 2 | 0 | 1 | 112 | 1/0 | 0.781 | Fused in Pipeline 0 | +| +NodeUniqueIndexSeekByRange | UNIQUE t:Team(name) WHERE name STARTS WITH $autostring_0 | 2 | 0 | 1 | 72 | 1/0 | 0.393 | Fused in Pipeline 0 | +-----------------------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 1, total allocated memory: 176 ----- - -====== +Total database accesses: 1, total allocated memory: 136 +---- +// NodeIndexContainsScan [[query-plan-node-index-contains-scan]] == Node Index Contains Scan == -// NodeIndexContainsScan - -The `NodeIndexContainsScan` operator examines all values stored in an index, searching for entries containing a specific string; for example, in queries including `CONTAINS`. -Although this is slower than an index seek (since all entries need to be examined), it is still faster than the indirection resulting from a label scan using `NodeByLabelScan`, and a property store filter. - -.NodeIndexContainsScan -====== +The `NodeIndexContainsScan` operator examines all values stored in an index, searching for entries + containing a specific string; for example, in queries including `CONTAINS`. + Although this is slower than an index seek (since all entries need to be + examined), it is still faster than the indirection resulting from a label scan using `NodeByLabelScan`, and a property store + filter. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (l:Location) -WHERE l.name CONTAINS 'al' -RETURN l +MATCH (l:Location) WHERE l.name CONTAINS 'al' RETURN l ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+------------------------+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+------------------------+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | l | 0 | 2 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexContainsScan | BTREE INDEX l:Location(name) WHERE name CONTAINS $autostring_0 | 0 | 2 | 3 | 112 | 2/1 | 0.470 | Fused in Pipeline 0 | -+------------------------+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++------------------------+----------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++------------------------+----------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | l | 0 | 2 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexContainsScan | l:Location(name) WHERE name CONTAINS $autostring_0 | 0 | 2 | 3 | 72 | 2/1 | 0.546 | Fused in Pipeline 0 | ++------------------------+----------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 3, total allocated memory: 176 ----- - -====== +Total database accesses: 3, total allocated memory: 136 +---- -[[query-plan-node-index-ends-with-scan]] -== Node Index Ends With Scan // NodeIndexEndsWithScan +[[query-plan-node-index-ends-with-scan]] +== Node Index Ends With Scan == -The `NodeIndexEndsWithScan` operator examines all values stored in an index, searching for entries ending in a specific string; for example, in queries containing `ENDS WITH`. -Although this is slower than an index seek (since all entries need to be examined), it is still faster than the indirection resulting from a label scan using `NodeByLabelScan`, and a property store filter. - - -.NodeIndexEndsWithScan -====== +The `NodeIndexEndsWithScan` operator examines all values stored in an index, searching for entries + ending in a specific string; for example, in queries containing `ENDS WITH`. + Although this is slower than an index seek (since all entries need to be + examined), it is still faster than the indirection resulting from a label scan using `NodeByLabelScan`, and a property store + filter. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (l:Location) -WHERE l.name ENDS WITH 'al' -RETURN l +MATCH (l:Location) WHERE l.name ENDS WITH 'al' RETURN l ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+------------------------+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+------------------------+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | l | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexEndsWithScan | BTREE INDEX l:Location(name) WHERE name ENDS WITH $autostring_0 | 0 | 0 | 1 | 112 | 0/1 | 0.270 | Fused in Pipeline 0 | -+------------------------+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++------------------------+-----------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++------------------------+-----------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | l | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexEndsWithScan | l:Location(name) WHERE name ENDS WITH $autostring_0 | 0 | 0 | 1 | 72 | 0/1 | 6.646 | Fused in Pipeline 0 | ++------------------------+-----------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 1, total allocated memory: 176 ----- - -====== +Total database accesses: 1, total allocated memory: 136 +---- -[[query-plan-node-index-scan]] -== Node Index Scan // NodeIndexScan +[[query-plan-node-index-scan]] +== Node Index Scan == The `NodeIndexScan` operator examines all values stored in an index, returning all nodes with a particular label and a specified property. - -.NodeIndexScan -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (l:Location) -WHERE l.name IS NOT NULL -RETURN l +MATCH (l:Location) WHERE l.name IS NOT NULL RETURN l ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+-----------------+-----------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+-----------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | l | 10 | 10 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexScan | BTREE INDEX l:Location(name) WHERE name IS NOT NULL | 10 | 10 | 11 | 112 | 2/1 | 0.486 | Fused in Pipeline 0 | -+-----------------+-----------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++-----------------+-----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+-----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | l | 10 | 10 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexScan | l:Location(name) WHERE name IS NOT NULL | 10 | 10 | 11 | 72 | 2/1 | 0.981 | Fused in Pipeline 0 | ++-----------------+-----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 11, total allocated memory: 176 ----- - -====== +Total database accesses: 11, total allocated memory: 136 +---- // --- apply operators --- -[[query-plan-apply]] -== Apply // Apply +[[query-plan-apply]] +== Apply == -All the different `Apply` operators (listed below) share the same basic functionality: they perform a nested loop by taking a single row from the left-hand side, and using the xref::execution-plans/operators.adoc#query-plan-argument[Argument] operator on the right-hand side, execute the operator tree on the right-hand side. +All the different `Apply` operators (listed below) share the same basic functionality: they perform a nested loop by taking a single row from the left-hand side, and using the xref:execution-plans/operators.adoc#query-plan-argument[Argument] operator on the right-hand side, execute the operator tree on the right-hand side. The versions of the `Apply` operators differ in how the results are managed. -The `Apply` operator (i.e. the standard version) takes the row produced by the right-hand side -- which at this point contains data from both the left-hand and right-hand sides -- and yields it. - - -.Apply -====== +The `Apply` operator (i.e. the standard version) takes the row produced by the right-hand side -- which at this point contains data from both the left-hand and right-hand sides -- and yields it.. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person {name: 'me'}) +MATCH (p:Person {name:'me'}) MATCH (q:Person {name: p.secondName}) RETURN p, q ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+------------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+------------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | p, q | 1 | 0 | 0 | | | | Fused in Pipeline 1 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Apply | | 1 | 0 | 0 | | | | | -| |\ +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +NodeIndexSeek | BTREE INDEX q:Person(name) WHERE name = p.secondName | 1 | 0 | 0 | 2152 | 0/0 | 0.173 | Fused in Pipeline 1 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +NodeIndexSeek | BTREE INDEX p:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 112 | 0/1 | 0.227 | In Pipeline 0 | -+------------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 2, total allocated memory: 2216 ----- ++------------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++------------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | p, q | 1 | 0 | 0 | | | | Fused in Pipeline 1 | +| | +-------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Apply | | 1 | 0 | 0 | | | | | +| |\ +-------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +NodeIndexSeek | q:Person(name) WHERE name = p.secondName | 1 | 0 | 0 | 80 | 0/0 | 0.959 | Fused in Pipeline 1 | +| | +-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +NodeIndexSeek | p:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 72 | 0/1 | 0.762 | In Pipeline 0 | ++------------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -====== +Total database accesses: 2, total allocated memory: 144 +---- -[[query-plan-semi-apply]] -== Semi Apply // SemiApply - -The `SemiApply` operator tests for the presence of a pattern predicate, and is a variation of the xref::execution-plans/operators.adoc#query-plan-apply[Apply] operator. +[[query-plan-semi-apply]] +== Semi Apply == +The `SemiApply` operator tests for the presence of a pattern predicate, and is a variation of the xref:execution-plans/operators.adoc#query-plan-apply[Apply] operator. If the right-hand side operator yields at least one row, the row from the left-hand side operator is yielded by the `SemiApply` operator. This makes `SemiApply` a filtering operator, used mostly for pattern predicates in queries. - -.SemiApply -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- CYPHER runtime=slotted MATCH (p:Person) @@ -1237,16 +1074,18 @@ WHERE (p)-[:FRIENDS_WITH]->(:Person) RETURN p.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime SLOTTED -Runtime version 4.4 +Runtime version 4.3 +-----------------+-------------------------------------+----------------+------+---------+------------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | @@ -1259,7 +1098,7 @@ Runtime version 4.4 | |\ +-------------------------------------+----------------+------+---------+------------------------+ | | +Filter | anon_3:Person | 2 | 0 | 2 | 0/0 | | | | +-------------------------------------+----------------+------+---------+------------------------+ -| | +Expand(All) | (p)-[anon_2:FRIENDS_WITH]->(anon_3) | 2 | 2 | 33 | 28/0 | +| | +Expand(All) | (p)-[anon_2:FRIENDS_WITH]->(anon_3) | 2 | 2 | 33 | 15/0 | | | | +-------------------------------------+----------------+------+---------+------------------------+ | | +Argument | p | 14 | 14 | 0 | 0/0 | | | +-------------------------------------+----------------+------+---------+------------------------+ @@ -1269,153 +1108,135 @@ Runtime version 4.4 +-----------------+-------------------------------------+----------------+------+---------+------------------------+ Total database accesses: 108, total allocated memory: 64 ----- - -====== +---- -[[query-plan-anti-semi-apply]] -== Anti Semi Apply // AntiSemiApply - -The `AntiSemiApply` operator tests for the absence of a pattern, and is a variation of the xref::execution-plans/operators.adoc#query-plan-apply[Apply] operator. +[[query-plan-anti-semi-apply]] +== Anti Semi Apply == +The `AntiSemiApply` operator tests for the absence of a pattern, and is a variation of the xref:execution-plans/operators.adoc#query-plan-apply[Apply] operator. If the right-hand side operator yields no rows, the row from the left-hand side operator is yielded by the `AntiSemiApply` operator. This makes `AntiSemiApply` a filtering operator, used for pattern predicates in queries. -.AntiSemiApply -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- CYPHER runtime=slotted -MATCH - (me:Person {name: 'me'}), - (other:Person) +MATCH (me:Person {name: "me"}), (other:Person) WHERE NOT (me)-[:FRIENDS_WITH]->(other) RETURN other.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime SLOTTED -Runtime version 4.4 - -+-------------------+--------------------------------------------------------+----------------+------+---------+----------------+------------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | -+-------------------+--------------------------------------------------------+----------------+------+---------+----------------+------------------------+ -| +ProduceResults | `other.name` | 4 | 13 | 0 | | 0/0 | -| | +--------------------------------------------------------+----------------+------+---------+----------------+------------------------+ -| +Projection | other.name AS `other.name` | 4 | 13 | 13 | | 1/0 | -| | +--------------------------------------------------------+----------------+------+---------+----------------+------------------------+ -| +AntiSemiApply | | 4 | 13 | 0 | | 0/0 | -| |\ +--------------------------------------------------------+----------------+------+---------+----------------+------------------------+ -| | +Expand(Into) | (me)-[anon_2:FRIENDS_WITH]->(other) | 0 | 0 | 55 | 888 | 28/0 | -| | | +--------------------------------------------------------+----------------+------+---------+----------------+------------------------+ -| | +Argument | me, other | 14 | 14 | 0 | | 0/0 | -| | +--------------------------------------------------------+----------------+------+---------+----------------+------------------------+ -| +CartesianProduct | | 14 | 14 | 0 | | 0/0 | -| |\ +--------------------------------------------------------+----------------+------+---------+----------------+------------------------+ -| | +Filter | other:Person | 14 | 14 | 35 | | 1/0 | -| | | +--------------------------------------------------------+----------------+------+---------+----------------+------------------------+ -| | +AllNodesScan | other | 35 | 35 | 36 | | 1/0 | -| | +--------------------------------------------------------+----------------+------+---------+----------------+------------------------+ -| +NodeIndexSeek | BTREE INDEX me:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | | 0/1 | -+-------------------+--------------------------------------------------------+----------------+------+---------+----------------+------------------------+ +Runtime version 4.3 + ++-------------------+--------------------------------------------+----------------+------+---------+----------------+------------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | ++-------------------+--------------------------------------------+----------------+------+---------+----------------+------------------------+ +| +ProduceResults | `other.name` | 4 | 13 | 0 | | 0/0 | +| | +--------------------------------------------+----------------+------+---------+----------------+------------------------+ +| +Projection | other.name AS `other.name` | 4 | 13 | 13 | | 1/0 | +| | +--------------------------------------------+----------------+------+---------+----------------+------------------------+ +| +AntiSemiApply | | 4 | 13 | 0 | | 0/0 | +| |\ +--------------------------------------------+----------------+------+---------+----------------+------------------------+ +| | +Expand(Into) | (me)-[anon_2:FRIENDS_WITH]->(other) | 0 | 0 | 55 | 896 | 15/0 | +| | | +--------------------------------------------+----------------+------+---------+----------------+------------------------+ +| | +Argument | me, other | 14 | 14 | 0 | | 0/0 | +| | +--------------------------------------------+----------------+------+---------+----------------+------------------------+ +| +CartesianProduct | | 14 | 14 | 0 | | 0/0 | +| |\ +--------------------------------------------+----------------+------+---------+----------------+------------------------+ +| | +Filter | other:Person | 14 | 14 | 35 | | 1/0 | +| | | +--------------------------------------------+----------------+------+---------+----------------+------------------------+ +| | +AllNodesScan | other | 35 | 35 | 36 | | 1/0 | +| | +--------------------------------------------+----------------+------+---------+----------------+------------------------+ +| +NodeIndexSeek | me:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | | 0/1 | ++-------------------+--------------------------------------------+----------------+------+---------+----------------+------------------------+ + +Total database accesses: 141, total allocated memory: 976 -Total database accesses: 141, total allocated memory: 968 ---- -====== - - -[[query-plan-anti]] -== Anti // Anti - +[[query-plan-anti]] +== Anti == The `Anti` operator tests for the absence of a pattern. If there are incoming rows, the `Anti` operator will yield no rows. If there are no incoming rows, the `Anti` operator will yield a single row. -.Anti -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- CYPHER runtime=pipelined -MATCH - (me:Person {name: 'me'}), - (other:Person) +MATCH (me:Person {name: "me"}), (other:Person) WHERE NOT (me)-[:FRIENDS_WITH]->(other) RETURN other.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-------------------+--------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-------------------+--------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | `other.name` | 4 | 13 | 0 | | 0/0 | 0.086 | In Pipeline 4 | -| | +--------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Projection | other.name AS `other.name` | 4 | 13 | 26 | | 2/0 | 0.069 | In Pipeline 4 | -| | +--------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Apply | | 4 | 13 | 0 | | 0/0 | | | -| |\ +--------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| | +Anti | | 14 | 13 | 0 | 1256 | 0/0 | 0.070 | In Pipeline 4 | -| | | +--------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| | +Limit | 1 | 14 | 1 | 0 | 752 | | | Fused in Pipeline 3 | -| | | +--------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +Expand(Into) | (me)-[anon_2:FRIENDS_WITH]->(other) | 0 | 1 | 55 | 2848 | | | Fused in Pipeline 3 | -| | | +--------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +Argument | me, other | 14 | 14 | 0 | 3184 | 1/0 | 1.158 | Fused in Pipeline 3 | -| | +--------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +CartesianProduct | | 14 | 14 | 0 | 3664 | | 0.107 | In Pipeline 2 | -| |\ +--------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| | +Filter | other:Person | 14 | 14 | 0 | | | | Fused in Pipeline 1 | -| | | +--------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +AllNodesScan | other | 35 | 35 | 36 | 128 | 1/0 | 0.158 | Fused in Pipeline 1 | -| | +--------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +NodeIndexSeek | BTREE INDEX me:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 112 | 0/1 | 0.168 | In Pipeline 0 | -+-------------------+--------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 119, total allocated memory: 6736 ----- - -====== +Runtime version 4.3 + ++-------------------+--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-------------------+--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | `other.name` | 4 | 13 | 0 | | 0/0 | 0.174 | In Pipeline 4 | +| | +--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +Projection | other.name AS `other.name` | 4 | 13 | 26 | | 2/0 | 0.057 | In Pipeline 4 | +| | +--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +Apply | | 4 | 13 | 0 | | 0/0 | | | +| |\ +--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| | +Anti | | 14 | 13 | 0 | 1256 | 0/0 | 0.253 | In Pipeline 4 | +| | | +--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| | +Limit | 1 | 14 | 1 | 0 | 752 | | | Fused in Pipeline 3 | +| | | +--------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +Expand(Into) | (me)-[anon_2:FRIENDS_WITH]->(other) | 0 | 1 | 55 | 2856 | | | Fused in Pipeline 3 | +| | | +--------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +Argument | me, other | 14 | 14 | 0 | 408 | 1/0 | 1.744 | Fused in Pipeline 3 | +| | +--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +CartesianProduct | | 14 | 14 | 0 | 1800 | | 0.300 | In Pipeline 2 | +| |\ +--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| | +Filter | other:Person | 14 | 14 | 0 | | | | Fused in Pipeline 1 | +| | | +--------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +AllNodesScan | other | 35 | 35 | 36 | 88 | 1/0 | 0.222 | Fused in Pipeline 1 | +| | +--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +NodeIndexSeek | me:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 72 | 0/1 | 0.473 | In Pipeline 0 | ++-------------------+--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ + +Total database accesses: 119, total allocated memory: 6352 +---- -[[query-plan-let-semi-apply]] -== Let Semi Apply // LetSemiApply - -The `LetSemiApply` operator tests for the presence of a pattern predicate, and is a variation of the xref::execution-plans/operators.adoc#query-plan-apply[Apply] operator. +[[query-plan-let-semi-apply]] +== Let Semi Apply == +The `LetSemiApply` operator tests for the presence of a pattern predicate, and is a variation of the xref:execution-plans/operators.adoc#query-plan-apply[Apply] operator. When a query contains multiple pattern predicates separated with `OR`, `LetSemiApply` will be used to evaluate the first of these. It will record the result of evaluating the predicate but will leave any filtering to another operator. -In the example, `LetSemiApply` will be used to check for the presence of the `FRIENDS_WITH` relationship from each person. - - -.LetSemiApply -====== +In the example, `LetSemiApply` will be used to check for the presence of the `FRIENDS_WITH` +relationship from each person. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- CYPHER runtime=slotted MATCH (other:Person) @@ -1423,16 +1244,18 @@ WHERE (other)-[:FRIENDS_WITH]->(:Person) OR (other)-[:WORKS_IN]->(:Location) RETURN other.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime SLOTTED -Runtime version 4.4 +Runtime version 4.3 +--------------------+-----------------------------------------+----------------+------+---------+------------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | @@ -1445,7 +1268,7 @@ Runtime version 4.4 | |\ +-----------------------------------------+----------------+------+---------+------------------------+ | | +Filter | anon_7:Location | 14 | 0 | 12 | 0/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ -| | +Expand(All) | (other)-[anon_6:WORKS_IN]->(anon_7) | 14 | 12 | 26 | 24/0 | +| | +Expand(All) | (other)-[anon_6:WORKS_IN]->(anon_7) | 14 | 12 | 26 | 12/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ | | +Argument | other | 14 | 12 | 0 | 0/0 | | | +-----------------------------------------+----------------+------+---------+------------------------+ @@ -1453,7 +1276,7 @@ Runtime version 4.4 | |\ +-----------------------------------------+----------------+------+---------+------------------------+ | | +Filter | anon_5:Person | 2 | 0 | 2 | 0/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ -| | +Expand(All) | (other)-[anon_4:FRIENDS_WITH]->(anon_5) | 2 | 2 | 33 | 28/0 | +| | +Expand(All) | (other)-[anon_4:FRIENDS_WITH]->(anon_5) | 2 | 2 | 33 | 15/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ | | +Argument | other | 14 | 14 | 0 | 0/0 | | | +-----------------------------------------+----------------+------+---------+------------------------+ @@ -1463,26 +1286,21 @@ Runtime version 4.4 +--------------------+-----------------------------------------+----------------+------+---------+------------------------+ Total database accesses: 158, total allocated memory: 64 ----- - -====== +---- -[[query-plan-let-anti-semi-apply]] -== Let Anti Semi Apply // LetAntiSemiApply - -The `LetAntiSemiApply` operator tests for the absence of a pattern, and is a variation of the xref::execution-plans/operators.adoc#query-plan-apply[Apply] operator. -When a query contains multiple negated pattern predicates -- i.e. predicates separated with `OR`, where at least one predicate contains `NOT` -- `LetAntiSemiApply` will be used to evaluate the first of these. +[[query-plan-let-anti-semi-apply]] +== Let Anti Semi Apply == +The `LetAntiSemiApply` operator tests for the absence of a pattern, and is a variation of the xref:execution-plans/operators.adoc#query-plan-apply[Apply] operator. +When a query contains multiple negated pattern predicates -- i.e. predicates separated with `OR`, where at +least one predicate contains `NOT` -- `LetAntiSemiApply` will be used to evaluate the first of these. It will record the result of evaluating the predicate but will leave any filtering to another operator. -In the example, `LetAntiSemiApply` will be used to check for the absence of the `FRIENDS_WITH` relationship from each person. - - -.LetAntiSemiApply -====== +In the example, `LetAntiSemiApply` will be used to check for the absence of +the `FRIENDS_WITH` relationship from each person. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- CYPHER runtime=slotted MATCH (other:Person) @@ -1490,16 +1308,18 @@ WHERE NOT ((other)-[:FRIENDS_WITH]->(:Person)) OR (other)-[:WORKS_IN]->(:Locatio RETURN other.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime SLOTTED -Runtime version 4.4 +Runtime version 4.3 +--------------------+-----------------------------------------+----------------+------+---------+------------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | @@ -1512,7 +1332,7 @@ Runtime version 4.4 | |\ +-----------------------------------------+----------------+------+---------+------------------------+ | | +Filter | anon_7:Location | 14 | 0 | 2 | 0/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ -| | +Expand(All) | (other)-[anon_6:WORKS_IN]->(anon_7) | 14 | 2 | 7 | 4/0 | +| | +Expand(All) | (other)-[anon_6:WORKS_IN]->(anon_7) | 14 | 2 | 7 | 2/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ | | +Argument | other | 14 | 2 | 0 | 0/0 | | | +-----------------------------------------+----------------+------+---------+------------------------+ @@ -1520,7 +1340,7 @@ Runtime version 4.4 | |\ +-----------------------------------------+----------------+------+---------+------------------------+ | | +Filter | anon_5:Person | 2 | 0 | 2 | 0/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ -| | +Expand(All) | (other)-[anon_4:FRIENDS_WITH]->(anon_5) | 2 | 2 | 33 | 28/0 | +| | +Expand(All) | (other)-[anon_4:FRIENDS_WITH]->(anon_5) | 2 | 2 | 33 | 15/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ | | +Argument | other | 14 | 14 | 0 | 0/0 | | | +-----------------------------------------+----------------+------+---------+------------------------+ @@ -1530,42 +1350,38 @@ Runtime version 4.4 +--------------------+-----------------------------------------+----------------+------+---------+------------------------+ Total database accesses: 129, total allocated memory: 64 ----- - -====== +---- -[[query-plan-select-or-semi-apply]] -== Select Or Semi Apply // SelectOrSemiApply - +[[query-plan-select-or-semi-apply]] +== Select Or Semi Apply == The `SelectOrSemiApply` operator tests for the presence of a pattern predicate and evaluates a predicate, -and is a variation of the xref::execution-plans/operators.adoc#query-plan-apply[Apply] operator. -This operator allows for the mixing of normal predicates and pattern predicates that check for the presence of a pattern. +and is a variation of the xref:execution-plans/operators.adoc#query-plan-apply[Apply] operator. +This operator allows for the mixing of normal predicates and pattern predicates +that check for the presence of a pattern. First, the normal expression predicate is evaluated, and, only if it returns `false`, is the costly pattern predicate evaluated. - -.SelectOrSemiApply -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MATCH (other:Person) WHERE other.age > 25 OR (other)-[:FRIENDS_WITH]->(:Person) RETURN other.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +--------------------+-----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -1574,7 +1390,7 @@ Runtime version 4.4 | | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Projection | other.name AS `other.name` | 11 | 2 | 4 | | | | Fused in Pipeline 2 | | | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +SelectOrSemiApply | other.age > $autoint_0 | 14 | 2 | 0 | 4200 | 0/0 | 0.153 | Fused in Pipeline 2 | +| +SelectOrSemiApply | other.age > $autoint_0 | 14 | 2 | 0 | 128 | 0/0 | 0.226 | Fused in Pipeline 2 | | |\ +-----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | | +Limit | 1 | 14 | 2 | 0 | 752 | | | Fused in Pipeline 1 | | | | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ @@ -1582,49 +1398,45 @@ Runtime version 4.4 | | | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | | +Expand(All) | (other)-[anon_2:FRIENDS_WITH]->(anon_3) | 2 | 2 | 32 | | | | Fused in Pipeline 1 | | | | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +Argument | other | 14 | 14 | 0 | 2160 | 2/0 | 0.376 | Fused in Pipeline 1 | +| | +Argument | other | 14 | 14 | 0 | 296 | 2/0 | 0.779 | Fused in Pipeline 1 | | | +-----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +Filter | other:Person | 14 | 14 | 0 | | | | Fused in Pipeline 0 | | | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | other | 35 | 35 | 36 | 112 | 1/0 | 0.166 | Fused in Pipeline 0 | +| +AllNodesScan | other | 35 | 35 | 36 | 72 | 1/0 | 0.224 | Fused in Pipeline 0 | +--------------------+-----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 74, total allocated memory: 5040 ----- - -====== +Total database accesses: 74, total allocated memory: 1080 +---- -[[query-plan-select-or-anti-semi-apply]] -== Select Or Anti Semi Apply // SelectOrAntiSemiApply - -The `SelectOrAntiSemiApply` operator is used to evaluate `OR` between a predicate and a negative pattern predicate (i.e. a pattern predicate preceded with `NOT`), and is a variation of the xref::execution-plans/operators.adoc#query-plan-apply[Apply] operator. +[[query-plan-select-or-anti-semi-apply]] +== Select Or Anti Semi Apply == +The `SelectOrAntiSemiApply` operator is used to evaluate `OR` between a predicate and a negative pattern predicate +(i.e. a pattern predicate preceded with `NOT`), and is a variation of the xref:execution-plans/operators.adoc#query-plan-apply[Apply] operator. If the predicate returns `true`, the pattern predicate is not tested. If the predicate returns `false` or `null`, `SelectOrAntiSemiApply` will instead test the pattern predicate. - -.SelectOrAntiSemiApply -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MATCH (other:Person) WHERE other.age > 25 OR NOT (other)-[:FRIENDS_WITH]->(:Person) RETURN other.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +------------------------+-----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -1633,9 +1445,9 @@ Runtime version 4.4 | | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Projection | other.name AS `other.name` | 4 | 12 | 24 | | | | Fused in Pipeline 3 | | | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +SelectOrAntiSemiApply | other.age > $autoint_0 | 14 | 12 | 0 | 4200 | 0/0 | 0.215 | Fused in Pipeline 3 | +| +SelectOrAntiSemiApply | other.age > $autoint_0 | 14 | 12 | 0 | 448 | 0/0 | 0.335 | Fused in Pipeline 3 | | |\ +-----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| | +Anti | | 14 | 12 | 0 | 1256 | 0/0 | 0.127 | In Pipeline 2 | +| | +Anti | | 14 | 12 | 0 | 1256 | 0/0 | 0.287 | In Pipeline 2 | | | | +-----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | | +Limit | 1 | 14 | 2 | 0 | 752 | | | Fused in Pipeline 1 | | | | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ @@ -1643,32 +1455,26 @@ Runtime version 4.4 | | | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | | +Expand(All) | (other)-[anon_2:FRIENDS_WITH]->(anon_3) | 2 | 2 | 32 | | | | Fused in Pipeline 1 | | | | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +Argument | other | 14 | 14 | 0 | 2160 | 2/0 | 1.109 | Fused in Pipeline 1 | +| | +Argument | other | 14 | 14 | 0 | 296 | 2/0 | 2.706 | Fused in Pipeline 1 | | | +-----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +Filter | other:Person | 14 | 14 | 0 | | | | Fused in Pipeline 0 | | | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | other | 35 | 35 | 36 | 112 | 1/0 | 0.160 | Fused in Pipeline 0 | +| +AllNodesScan | other | 35 | 35 | 36 | 72 | 1/0 | 0.636 | Fused in Pipeline 0 | +------------------------+-----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 94, total allocated memory: 5736 ----- - -====== +Total database accesses: 94, total allocated memory: 2336 +---- -[[query-plan-let-select-or-semi-apply]] -== Let Select Or Semi Apply // LetSelectOrSemiApply - +[[query-plan-let-select-or-semi-apply]] +== Let Select Or Semi Apply == The `LetSelectOrSemiApply` operator is planned for pattern predicates that are combined with other predicates using `OR`. -This is a variation of the xref::execution-plans/operators.adoc#query-plan-apply[Apply] operator. - - -.LetSelectOrSemiApply -====== +This is a variation of the xref:execution-plans/operators.adoc#query-plan-apply[Apply] operator. + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- CYPHER runtime=slotted MATCH (other:Person) @@ -1676,16 +1482,18 @@ WHERE (other)-[:FRIENDS_WITH]->(:Person) OR (other)-[:WORKS_IN]->(:Location) OR RETURN other.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime SLOTTED -Runtime version 4.4 +Runtime version 4.3 +-----------------------+-----------------------------------------+----------------+------+---------+------------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | @@ -1698,7 +1506,7 @@ Runtime version 4.4 | |\ +-----------------------------------------+----------------+------+---------+------------------------+ | | +Filter | anon_7:Location | 14 | 0 | 12 | 0/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ -| | +Expand(All) | (other)-[anon_6:WORKS_IN]->(anon_7) | 14 | 12 | 26 | 24/0 | +| | +Expand(All) | (other)-[anon_6:WORKS_IN]->(anon_7) | 14 | 12 | 26 | 12/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ | | +Argument | other | 14 | 12 | 0 | 0/0 | | | +-----------------------------------------+----------------+------+---------+------------------------+ @@ -1706,7 +1514,7 @@ Runtime version 4.4 | |\ +-----------------------------------------+----------------+------+---------+------------------------+ | | +Filter | anon_5:Person | 2 | 0 | 2 | 0/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ -| | +Expand(All) | (other)-[anon_4:FRIENDS_WITH]->(anon_5) | 2 | 2 | 33 | 28/0 | +| | +Expand(All) | (other)-[anon_4:FRIENDS_WITH]->(anon_5) | 2 | 2 | 33 | 15/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ | | +Argument | other | 14 | 14 | 0 | 0/0 | | | +-----------------------------------------+----------------+------+---------+------------------------+ @@ -1716,24 +1524,19 @@ Runtime version 4.4 +-----------------------+-----------------------------------------+----------------+------+---------+------------------------+ Total database accesses: 172, total allocated memory: 64 ----- - -====== +---- -[[query-plan-let-select-or-anti-semi-apply]] -== Let Select Or Anti Semi Apply // LetSelectOrAntiSemiApply - -The `LetSelectOrAntiSemiApply` operator is planned for negated pattern predicates -- i.e. pattern predicates preceded with `NOT` -- that are combined with other predicates using `OR`. -This operator is a variation of the xref::execution-plans/operators.adoc#query-plan-apply[Apply] operator. - - -.LetSelectOrAntiSemiApply -====== +[[query-plan-let-select-or-anti-semi-apply]] +== Let Select Or Anti Semi Apply == +The `LetSelectOrAntiSemiApply` operator is planned for negated pattern predicates -- i.e. pattern predicates +preceded with `NOT` -- that are combined with other predicates using `OR`. +This operator is a variation of the xref:execution-plans/operators.adoc#query-plan-apply[Apply] operator. + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- CYPHER runtime=slotted MATCH (other:Person) @@ -1741,16 +1544,18 @@ WHERE NOT (other)-[:FRIENDS_WITH]->(:Person) OR (other)-[:WORKS_IN]->(:Location) RETURN other.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime SLOTTED -Runtime version 4.4 +Runtime version 4.3 +---------------------------+-----------------------------------------+----------------+------+---------+------------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | @@ -1763,7 +1568,7 @@ Runtime version 4.4 | |\ +-----------------------------------------+----------------+------+---------+------------------------+ | | +Filter | anon_7:Location | 14 | 0 | 2 | 0/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ -| | +Expand(All) | (other)-[anon_6:WORKS_IN]->(anon_7) | 14 | 2 | 7 | 4/0 | +| | +Expand(All) | (other)-[anon_6:WORKS_IN]->(anon_7) | 14 | 2 | 7 | 2/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ | | +Argument | other | 14 | 2 | 0 | 0/0 | | | +-----------------------------------------+----------------+------+---------+------------------------+ @@ -1771,7 +1576,7 @@ Runtime version 4.4 | |\ +-----------------------------------------+----------------+------+---------+------------------------+ | | +Filter | anon_5:Person | 2 | 0 | 2 | 0/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ -| | +Expand(All) | (other)-[anon_4:FRIENDS_WITH]->(anon_5) | 2 | 2 | 33 | 28/0 | +| | +Expand(All) | (other)-[anon_4:FRIENDS_WITH]->(anon_5) | 2 | 2 | 33 | 15/0 | | | | +-----------------------------------------+----------------+------+---------+------------------------+ | | +Argument | other | 14 | 14 | 0 | 0/0 | | | +-----------------------------------------+----------------+------+---------+------------------------+ @@ -1781,145 +1586,130 @@ Runtime version 4.4 +---------------------------+-----------------------------------------+----------------+------+---------+------------------------+ Total database accesses: 143, total allocated memory: 64 ----- - -====== +---- -[[query-plan-merge]] -== Merge -// ConditionalApply -- changed in 4.3 to Merge. -// AntiConditionalApply -- removed in 4.3 (by Merge). +// ConditionalApply - changed in 4.3 to Merge. +// AntiConditionalApply - removed in 4.3 (by Merge). // Merge - +[[query-plan-merge]] +== Merge == The `Merge` operator will either read or create nodes and/or relationships. If matches are found it will execute the provided `ON MATCH` operations foreach incoming row. If no matches are found instead nodes and relationships are created and all `ON CREATE` operations are run. - - -.Merge -====== + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MERGE (p:Person {name: 'Andy'}) ON MATCH SET p.existed = true ON CREATE SET p.existed = false ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------+-------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+-------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | | 1 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +-------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +EmptyResult | | 1 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +-------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Merge | CREATE (p:Person {name: $autostring_0}), ON MATCH SET p.existed = true, | 1 | 1 | 2 | | | | Fused in Pipeline 0 | -| | | ON CREATE SET p.existed = false | | | | | | | | -| | +-------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX p:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 112 | 2/1 | 0.512 | Fused in Pipeline 0 | -+-----------------+-------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Runtime version 4.3 -Total database accesses: 4, total allocated memory: 176 ----- ++-----------------+-----------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+-----------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | | 1 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +EmptyResult | | 1 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Merge | CREATE (p:Person), ON MATCH SET p.existed = true, ON CREATE SET p.existed = false | 1 | 1 | 1 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeek | p:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 72 | 2/1 | 0.642 | Fused in Pipeline 0 | ++-----------------+-----------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -====== +Total database accesses: 3, total allocated memory: 136 +---- -[[query-plan-locking-merge]] -== Locking Merge // LockingMerge - +[[query-plan-locking-merge]] +== Locking Merge == The `LockingMerge` operator is just like a normal `Merge` but will lock the start and end node when creating a relationship if necessary. - -.LockingMerge -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (s:Person {name: 'me'}) -MERGE (s)-[:FRIENDS_WITH]->(s) +MATCH (s:Person {name: 'me'}) MERGE (s)-[:FRIENDS_WITH]->(s) ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | | 1 | 0 | 0 | | | | Fused in Pipeline 1 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +EmptyResult | | 1 | 0 | 0 | | | | Fused in Pipeline 1 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Apply | | 1 | 1 | 0 | | | | | -| |\ +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +LockingMerge | CREATE (s)-[anon_0:FRIENDS_WITH]->(s), LOCK(s) | 1 | 1 | 1 | | | | Fused in Pipeline 1 | -| | | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +Expand(Into) | (s)-[anon_0:FRIENDS_WITH]->(s) | 0 | 0 | 8 | 888 | | | Fused in Pipeline 1 | -| | | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +Argument | s | 1 | 3 | 0 | 2152 | 3/0 | 0.616 | Fused in Pipeline 1 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +NodeIndexSeek | BTREE INDEX s:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 112 | 0/1 | 0.235 | In Pipeline 0 | -+-----------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Runtime version 4.3 -Total database accesses: 11, total allocated memory: 2232 ----- - -====== - - -[[query-plan-roll-up-apply]] -== Roll Up Apply -// RollUpApply ++-----------------+------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | | 1 | 0 | 0 | | | | Fused in Pipeline 1 | +| | +------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +EmptyResult | | 1 | 0 | 0 | | | | Fused in Pipeline 1 | +| | +------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Apply | | 1 | 1 | 0 | | | | | +| |\ +------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +LockingMerge | CREATE (s)-[anon_0:FRIENDS_WITH]->(s), LOCK(s) | 1 | 1 | 1 | | | | Fused in Pipeline 1 | +| | | +------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +Expand(Into) | (s)-[anon_0:FRIENDS_WITH]->(s) | 0 | 0 | 8 | 896 | | | Fused in Pipeline 1 | +| | | +------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +Argument | s | 1 | 3 | 0 | 80 | 4/0 | 0.663 | Fused in Pipeline 1 | +| | +------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +NodeIndexSeek | s:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 72 | 0/1 | 0.322 | In Pipeline 0 | ++-----------------+------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -The `RollUpApply` operator is used to execute an expression which takes as input a pattern, and returns a list with content from the matched pattern; for example, when using a pattern expression or pattern comprehension in a query. -This operator is a variation of the xref::execution-plans/operators.adoc#query-plan-apply[Apply] operator. +Total database accesses: 11, total allocated memory: 976 +---- -.RollUpApply -====== +// RollUpApply +[[query-plan-roll-up-apply]] +== Roll Up Apply == +The `RollUpApply` operator is used to execute an expression which takes as input a pattern, and returns a list with content from the matched pattern; +for example, when using a pattern expression or pattern comprehension in a query. +This operator is a variation of the xref:execution-plans/operators.adoc#query-plan-apply[Apply] operator. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- CYPHER runtime=slotted MATCH (p:Person) -RETURN p.name, [(p)-[:WORKS_IN]->(location) | location.name] AS cities +RETURN p.name, [ (p)-[:WORKS_IN]->(location) | location.name ] AS cities ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime SLOTTED -Runtime version 4.4 +Runtime version 4.3 +-----------------+-----------------------------------+----------------+------+---------+------------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | @@ -1932,7 +1722,7 @@ Runtime version 4.4 | |\ +-----------------------------------+----------------+------+---------+------------------------+ | | +Projection | location.name AS anon_0 | 6 | 15 | 15 | 1/0 | | | | +-----------------------------------+----------------+------+---------+------------------------+ -| | +Expand(All) | (p)-[anon_2:WORKS_IN]->(location) | 6 | 15 | 33 | 28/0 | +| | +Expand(All) | (p)-[anon_2:WORKS_IN]->(location) | 6 | 15 | 33 | 15/0 | | | | +-----------------------------------+----------------+------+---------+------------------------+ | | +Argument | p | 14 | 14 | 0 | 0/0 | | | +-----------------------------------+----------------+------+---------+------------------------+ @@ -1942,288 +1732,252 @@ Runtime version 4.4 +-----------------+-----------------------------------+----------------+------+---------+------------------------+ Total database accesses: 133, total allocated memory: 64 ----- - -====== +---- -[[query-plan-argument]] -== Argument // Argument - -The `Argument` operator indicates the variable to be used as an argument to the right-hand side of an xref::execution-plans/operators.adoc#query-plan-apply[Apply] operator. - - -.Argument -====== +[[query-plan-argument]] +== Argument == +The `Argument` operator indicates the variable to be used as an argument to the right-hand side of an xref:execution-plans/operators.adoc#query-plan-apply[Apply] operator. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (s:Person {name: 'me'}) -MERGE (s)-[:FRIENDS_WITH]->(s) +MATCH (s:Person {name: 'me'}) MERGE (s)-[:FRIENDS_WITH]->(s) ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | | 1 | 0 | 0 | | | | Fused in Pipeline 1 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +EmptyResult | | 1 | 0 | 0 | | | | Fused in Pipeline 1 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Apply | | 1 | 1 | 0 | | | | | -| |\ +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +LockingMerge | CREATE (s)-[anon_0:FRIENDS_WITH]->(s), LOCK(s) | 1 | 1 | 1 | | | | Fused in Pipeline 1 | -| | | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +Expand(Into) | (s)-[anon_0:FRIENDS_WITH]->(s) | 0 | 0 | 8 | 888 | | | Fused in Pipeline 1 | -| | | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +Argument | s | 1 | 3 | 0 | 2152 | 3/0 | 0.894 | Fused in Pipeline 1 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +NodeIndexSeek | BTREE INDEX s:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 112 | 0/1 | 1.055 | In Pipeline 0 | -+-----------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Runtime version 4.3 -Total database accesses: 11, total allocated memory: 2232 ----- ++-----------------+------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | | 1 | 0 | 0 | | | | Fused in Pipeline 1 | +| | +------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +EmptyResult | | 1 | 0 | 0 | | | | Fused in Pipeline 1 | +| | +------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Apply | | 1 | 1 | 0 | | | | | +| |\ +------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +LockingMerge | CREATE (s)-[anon_0:FRIENDS_WITH]->(s), LOCK(s) | 1 | 1 | 1 | | | | Fused in Pipeline 1 | +| | | +------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +Expand(Into) | (s)-[anon_0:FRIENDS_WITH]->(s) | 0 | 0 | 8 | 896 | | | Fused in Pipeline 1 | +| | | +------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +Argument | s | 1 | 3 | 0 | 80 | 4/0 | 8.475 | Fused in Pipeline 1 | +| | +------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +NodeIndexSeek | s:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 72 | 0/1 | 10.014 | In Pipeline 0 | ++-----------------+------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -====== +Total database accesses: 11, total allocated memory: 976 +---- // --- expand operators --- -[[query-plan-expand-all]] -== Expand All // Expand(All) - +[[query-plan-expand-all]] +== Expand All == Given a start node, and depending on the pattern relationship, the `Expand(All)` operator will traverse incoming or outgoing relationships. - -.Expand(All) -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person {name: 'me'})-[:FRIENDS_WITH]->(fof) -RETURN fof +MATCH (p:Person {name: 'me'})-[:FRIENDS_WITH]->(fof) RETURN fof ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | fof | 0 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (p)-[anon_0:FRIENDS_WITH]->(fof) | 0 | 1 | 3 | | | | Fused in Pipeline 0 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX p:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 112 | 4/1 | 0.533 | Fused in Pipeline 0 | -+-----------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Runtime version 4.3 -Total database accesses: 5, total allocated memory: 176 ----- ++-----------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | fof | 0 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +-------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (p)-[anon_0:FRIENDS_WITH]->(fof) | 0 | 1 | 3 | | | | Fused in Pipeline 0 | +| | +-------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeek | p:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 72 | 4/1 | 1.109 | Fused in Pipeline 0 | ++-----------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -====== +Total database accesses: 5, total allocated memory: 136 +---- -[[query-plan-expand-into]] -== Expand Into // Expand(Into) - +[[query-plan-expand-into]] +== Expand Into == When both the start and end node have already been found, the `Expand(Into)` operator is used to find all relationships connecting the two nodes. As both the start and end node of the relationship are already in scope, the node with the smallest degree will be used. This can make a noticeable difference when dense nodes appear as end points. - -.Expand(Into) -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person {name: 'me'})-[:FRIENDS_WITH]->(fof)-->(p) -RETURN fof +MATCH (p:Person {name: 'me'})-[:FRIENDS_WITH]->(fof)-->(p) RETURN fof ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | fof | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | not anon_0 = anon_1 | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(Into) | (p)-[anon_0:FRIENDS_WITH]->(fof) | 0 | 0 | 0 | 0 | | | Fused in Pipeline 0 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (p)<-[anon_1]-(fof) | 0 | 0 | 3 | | | | Fused in Pipeline 0 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX p:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 112 | 2/1 | 0.314 | Fused in Pipeline 0 | -+-----------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Runtime version 4.3 -Total database accesses: 5, total allocated memory: 192 ----- ++-----------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | fof | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +-------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | not anon_0 = anon_1 | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +-------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(Into) | (p)-[anon_0:FRIENDS_WITH]->(fof) | 0 | 0 | 0 | 0 | | | Fused in Pipeline 0 | +| | +-------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (p)<-[anon_1]-(fof) | 0 | 0 | 3 | | | | Fused in Pipeline 0 | +| | +-------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeek | p:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 72 | 2/1 | 12.521 | Fused in Pipeline 0 | ++-----------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -====== +Total database accesses: 5, total allocated memory: 152 +---- -[[query-plan-optional-expand-all]] -== Optional Expand All // OptionalExpand(All) - -The `OptionalExpand(All)` operator is analogous to xref::execution-plans/operators.adoc#query-plan-expand-all[Expand(All)], apart from when no relationships match the direction, type and property predicates. +[[query-plan-optional-expand-all]] +== Optional Expand All == +The `OptionalExpand(All)` operator is analogous to xref:execution-plans/operators.adoc#query-plan-expand-all[Expand(All)], apart from when no relationships match the direction, type and property predicates. In this situation, `OptionalExpand(all)` will return a single row with the relationship and end node set to `null`. -.OptionalExpand(All) -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MATCH (p:Person) -OPTIONAL MATCH (p)-[works_in:WORKS_IN]->(l) - WHERE works_in.duration > 180 -RETURN p, l + OPTIONAL MATCH (p)-[works_in:WORKS_IN]->(l) WHERE works_in.duration > 180 + RETURN p, l ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +----------------------+-------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +----------------------+-------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | p, l | 14 | 15 | 1 | | | | Fused in Pipeline 0 | +| +ProduceResults | p, l | 14 | 15 | 2 | | | | Fused in Pipeline 0 | | | +-------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +OptionalExpand(All) | (p)-[works_in:WORKS_IN]->(l) WHERE works_in.duration > $autoint_0 | 14 | 15 | 33 | | | | Fused in Pipeline 0 | +| +OptionalExpand(All) | (p)-[works_in:WORKS_IN]->(l) WHERE works_in.duration > $autoint_0 | 14 | 15 | 47 | | | | Fused in Pipeline 0 | | | +-------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | p:Person | 14 | 14 | 0 | | | | Fused in Pipeline 0 | | | +-------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | p | 35 | 35 | 36 | 112 | 5/0 | 0.764 | Fused in Pipeline 0 | +| +AllNodesScan | p | 35 | 35 | 36 | 72 | 5/0 | 1.701 | Fused in Pipeline 0 | +----------------------+-------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 70, total allocated memory: 176 ----- - -====== +Total database accesses: 85, total allocated memory: 136 +---- -[[query-plan-optional-expand-into]] -== Optional Expand Into // OptionalExpand(Into) - -The `OptionalExpand(Into)` operator is analogous to xref::execution-plans/operators.adoc#query-plan-expand-into[Expand(Into)], apart from when no matching relationships are found. +[[query-plan-optional-expand-into]] +== Optional Expand Into == +The `OptionalExpand(Into)` operator is analogous to xref:execution-plans/operators.adoc#query-plan-expand-into[Expand(Into)], apart from when no matching relationships are found. In this situation, `OptionalExpand(Into)` will return a single row with the relationship and end node set to `null`. As both the start and end node of the relationship are already in scope, the node with the smallest degree will be used. This can make a noticeable difference when dense nodes appear as end points. - -.OptionalExpand(Into) -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person)-[works_in:WORKS_IN]->(l) -OPTIONAL MATCH (l)-->(p) -RETURN p +MATCH (p:Person)-[works_in:WORKS_IN]->(l) OPTIONAL MATCH (l)-->(p) RETURN p ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------------+------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-----------------------+------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | p | 15 | 15 | 0 | | | | Fused in Pipeline 0 | | | +------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +OptionalExpand(Into) | (l)-[anon_0]->(p) | 15 | 15 | 105 | 3352 | | | Fused in Pipeline 0 | +| +OptionalExpand(Into) | (l)-[anon_0]->(p) | 15 | 15 | 105 | 3360 | | | Fused in Pipeline 0 | | | +------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Expand(All) | (p)-[works_in:WORKS_IN]->(l) | 15 | 15 | 19 | | | | Fused in Pipeline 0 | | | +------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | p:Person | 14 | 14 | 0 | | | | Fused in Pipeline 0 | | | +------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | p | 35 | 35 | 36 | 112 | 6/0 | 1.131 | Fused in Pipeline 0 | +| +AllNodesScan | p | 35 | 35 | 36 | 72 | 6/0 | 1.790 | Fused in Pipeline 0 | +-----------------------+------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 160, total allocated memory: 3432 ----- - -====== +Total database accesses: 160, total allocated memory: 3440 +---- -[[query-plan-varlength-expand-all]] -== VarLength Expand All // VarLengthExpand(All) - +[[query-plan-varlength-expand-all]] +== VarLength Expand All == Given a start node, the `VarLengthExpand(All)` operator will traverse variable-length relationships. - -.VarLengthExpand(All) -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person)-[:FRIENDS_WITH *1..2]-(q:Person) -RETURN p, q +MATCH (p:Person)-[:FRIENDS_WITH *1..2]-(q:Person) RETURN p, q ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------------+-----------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -2236,42 +1990,36 @@ Runtime version 4.4 | | +-----------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | p:Person | 14 | 14 | 0 | | | | Fused in Pipeline 0 | | | +-----------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | p | 35 | 35 | 36 | 112 | 7/0 | 1.033 | Fused in Pipeline 0 | +| +AllNodesScan | p | 35 | 35 | 36 | 72 | 7/0 | 2.034 | Fused in Pipeline 0 | +-----------------------+-----------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 89, total allocated memory: 208 ----- - -====== +---- -[[query-plan-varlength-expand-into]] -== VarLength Expand Into // VarLengthExpand(Into) - +[[query-plan-varlength-expand-into]] +== VarLength Expand Into == When both the start and end node have already been found, the `VarLengthExpand(Into)` operator is used to find all variable-length relationships connecting the two nodes. - -.VarLengthExpand(Into) -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person)-[:FRIENDS_WITH *1..2]-(p:Person) -RETURN p +MATCH (p:Person)-[:FRIENDS_WITH *1..2]-(p:Person) RETURN p ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +------------------------+-----------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -2282,149 +2030,134 @@ Runtime version 4.4 | | +-----------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | p:Person | 14 | 14 | 0 | | | | Fused in Pipeline 0 | | | +-----------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | p | 35 | 35 | 36 | 112 | 4/0 | 0.622 | Fused in Pipeline 0 | +| +AllNodesScan | p | 35 | 35 | 36 | 72 | 4/0 | 0.698 | Fused in Pipeline 0 | +------------------------+-----------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 83, total allocated memory: 192 ----- - -====== +---- -[[query-plan-varlength-expand-pruning]] -== VarLength Expand Pruning // VarLengthExpand(Pruning) - -Given a start node, the `VarLengthExpand(Pruning)` operator will traverse variable-length relationships much like the xref::execution-plans/operators.adoc#query-plan-varlength-expand-all[`VarLengthExpand(All)`] operator. +[[query-plan-varlength-expand-pruning]] +== VarLength Expand Pruning == +Given a start node, the `VarLengthExpand(Pruning)` operator will traverse variable-length relationships much like the xref:execution-plans/operators.adoc#query-plan-varlength-expand-all[`VarLengthExpand(All)`] operator. However, as an optimization, some paths will not be explored if they are guaranteed to produce an end node that has already been found (by means of a previous path traversal). This will only be used in cases where the individual paths are not of interest. This operator guarantees that all the end nodes produced will be unique. - -.VarLengthExpand(Pruning) -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person)-[:FRIENDS_WITH *3..4]-(q:Person) -RETURN DISTINCT p, q +MATCH (p:Person)-[:FRIENDS_WITH *3..4]-(q:Person) RETURN DISTINCT p, q ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +---------------------------+------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +---------------------------+------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | p, q | 0 | 0 | 0 | | 0/0 | 0.049 | In Pipeline 1 | +| +ProduceResults | p, q | 0 | 0 | 0 | | 0/0 | 0.044 | In Pipeline 1 | | | +------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Distinct | p, q | 0 | 0 | 0 | 224 | 0/0 | 4.258 | In Pipeline 1 | +| +Distinct | p, q | 0 | 0 | 0 | 224 | 0/0 | 2.558 | In Pipeline 1 | | | +------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Filter | q:Person | 0 | 0 | 0 | | 0/0 | 0.165 | In Pipeline 1 | +| +Filter | q:Person | 0 | 0 | 0 | | 0/0 | 0.079 | In Pipeline 1 | | | +------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +VarLengthExpand(Pruning) | (p)-[:FRIENDS_WITH*3..4]-(q) | 0 | 0 | 32 | 1128 | | | In Pipeline 1 | +| +VarLengthExpand(Pruning) | (p)-[:FRIENDS_WITH*3..4]-(q) | 0 | 0 | 32 | 896 | | | In Pipeline 1 | | | +------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +Filter | p:Person | 14 | 14 | 0 | | | | Fused in Pipeline 0 | | | +------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | p | 35 | 35 | 36 | 112 | 1/0 | 0.224 | Fused in Pipeline 0 | +| +AllNodesScan | p | 35 | 35 | 36 | 72 | 1/0 | 0.198 | Fused in Pipeline 0 | +---------------------------+------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 68, total allocated memory: 1208 ----- - -====== +Total database accesses: 68, total allocated memory: 976 +---- -[[query-plan-assert-same-node]] -== Assert Same Node // AssertSameNode - +[[query-plan-assert-same-node]] +== Assert Same Node == The `AssertSameNode` operator is used to ensure that no unique constraints are violated in the slotted and interpreted runtime. -The example looks for the presence of a team with the supplied name and id, and if one does not exist, it will be created. -Owing to the existence of two unique constraints on `:Team(name)` and `:Team(id)`, any node that would be found by the `UniqueIndexSeek` must be the very same node, or the constraints would be violated. - - -.AssertSameNode -====== +The example looks for the presence of a team with the supplied name and id, and if one does not exist, +it will be created. Owing to the existence of two unique constraints +on `:Team(name)` and `:Team(id)`, any node that would be found by the `UniqueIndexSeek` +must be the very same node, or the constraints would be violated. + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CYPHER runtime=slotted -MERGE (t:Team {name: 'Engineering', id: 42}) +CYPHER runtime=slotted MERGE (t:Team {name: 'Engineering', id: 42}) ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime SLOTTED -Runtime version 4.4 - -+---------------------------------+-------------------------------------------------------+----------------+------+---------+------------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | -+---------------------------------+-------------------------------------------------------+----------------+------+---------+------------------------+ -| +ProduceResults | | 1 | 0 | 0 | 0/0 | -| | +-------------------------------------------------------+----------------+------+---------+------------------------+ -| +EmptyResult | | 1 | 0 | 0 | 0/0 | -| | +-------------------------------------------------------+----------------+------+---------+------------------------+ -| +Merge | CREATE (t:Team {name: $autostring_0, id: $autoint_1}) | 1 | 1 | 0 | 0/0 | -| | +-------------------------------------------------------+----------------+------+---------+------------------------+ -| +AssertSameNode | t | 0 | 1 | 0 | 0/0 | -| |\ +-------------------------------------------------------+----------------+------+---------+------------------------+ -| | +NodeUniqueIndexSeek(Locking) | UNIQUE t:Team(id) WHERE id = $autoint_1 | 1 | 1 | 1 | 0/1 | -| | +-------------------------------------------------------+----------------+------+---------+------------------------+ -| +NodeUniqueIndexSeek(Locking) | UNIQUE t:Team(name) WHERE name = $autostring_0 | 1 | 1 | 1 | 0/1 | -+---------------------------------+-------------------------------------------------------+----------------+------+---------+------------------------+ +Runtime version 4.3 + ++---------------------------------+------------------------------------------------+----------------+------+---------+------------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | ++---------------------------------+------------------------------------------------+----------------+------+---------+------------------------+ +| +ProduceResults | | 1 | 0 | 0 | 0/0 | +| | +------------------------------------------------+----------------+------+---------+------------------------+ +| +EmptyResult | | 1 | 0 | 0 | 0/0 | +| | +------------------------------------------------+----------------+------+---------+------------------------+ +| +Merge | CREATE (t:Team) | 1 | 1 | 0 | 0/0 | +| | +------------------------------------------------+----------------+------+---------+------------------------+ +| +AssertSameNode | t | 0 | 1 | 0 | 0/0 | +| |\ +------------------------------------------------+----------------+------+---------+------------------------+ +| | +NodeUniqueIndexSeek(Locking) | UNIQUE t:Team(id) WHERE id = $autoint_1 | 1 | 1 | 1 | 0/1 | +| | +------------------------------------------------+----------------+------+---------+------------------------+ +| +NodeUniqueIndexSeek(Locking) | UNIQUE t:Team(name) WHERE name = $autostring_0 | 0 | 1 | 1 | 0/1 | ++---------------------------------+------------------------------------------------+----------------+------+---------+------------------------+ Total database accesses: 2, total allocated memory: 64 ----- -====== - - -// DropResult -- removed in 4.3 +---- +//DropResult - removed in 4.3 -[[query-plan-empty-result]] -== Empty Result // EmptyResult - +[[query-plan-empty-result]] +== Empty Result == The `EmptyResult` operator eagerly loads all incoming data and discards it. - -.EmptyResult -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- CREATE (:Person) ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+-----------------+----------------+------+---------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | Time (ms) | Other | @@ -2436,96 +2169,83 @@ Runtime version 4.4 | +Create | (anon_0:Person) | 1 | 1 | 1 | 0/0 | 0.000 | Fused in Pipeline 0 | +-----------------+-----------------+----------------+------+---------+------------------------+-----------+---------------------+ -Total database accesses: 1, total allocated memory: 176 ----- - -====== +Total database accesses: 1, total allocated memory: 136 +---- -[[query-plan-produce-results]] -== Produce Results // ProduceResults - +[[query-plan-produce-results]] +== Produce Results == The `ProduceResults` operator prepares the result so that it is consumable by the user, such as transforming internal values to user values. It is present in every single query that returns data to the user, and has little bearing on performance optimisation. - -.ProduceResults -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (n) -RETURN n +MATCH (n) RETURN n ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+---------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-----------------+---------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | n | 35 | 35 | 0 | | | | Fused in Pipeline 0 | | | +---------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | n | 35 | 35 | 36 | 112 | 3/0 | 0.853 | Fused in Pipeline 0 | +| +AllNodesScan | n | 35 | 35 | 36 | 72 | 3/0 | 1.409 | Fused in Pipeline 0 | +-----------------+---------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 36, total allocated memory: 176 ----- - -====== +Total database accesses: 36, total allocated memory: 136 +---- -[[query-plan-load-csv]] -== Load CSV // LoadCSV - +[[query-plan-load-csv]] +== Load CSV == The `LoadCSV` operator loads data from a CSV source into the query. -It is used whenever the xref::clauses/load-csv.adoc[LOAD CSV] clause is used in a query. - - -.LoadCSV -====== +It is used whenever the xref:clauses/load-csv.adoc[LOAD CSV] clause is used in a query. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -LOAD CSV FROM 'https://neo4j.com/docs/cypher-refcard/3.3/csv/artists.csv' AS line -RETURN line +LOAD CSV FROM 'https://neo4j.com/docs/cypher-refcard/3.3/csv/artists.csv' AS line RETURN line ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+---------+----------------+------+---------+----------------+------------------------+-----------+---------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-----------------+---------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| +ProduceResults | line | 10 | 4 | 0 | | 0/0 | 0.414 | In Pipeline 1 | +| +ProduceResults | line | 10 | 4 | 0 | | 0/0 | 0.491 | In Pipeline 1 | | | +---------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| +LoadCSV | line | 10 | 4 | 0 | 104 | | | In Pipeline 1 | +| +LoadCSV | line | 10 | 4 | 0 | 64 | | | In Pipeline 1 | +-----------------+---------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -Total database accesses: 0, total allocated memory: 176 ----- +Total database accesses: 0, total allocated memory: 136 -====== +---- [[execution-plans-operators-hash-join-general]] @@ -2540,100 +2260,91 @@ In query plans, the build input is always the left operator, and the probe input There are four hash join operators: -* xref::execution-plans/operators.adoc#query-plan-node-hash-join[NodeHashJoin] -* xref::execution-plans/operators.adoc#query-plan-value-hash-join[ValueHashJoin] -* xref::execution-plans/operators.adoc#query-plan-node-left-right-outer-hash-join[NodeLeftOuterHashJoin] -* xref::execution-plans/operators.adoc#query-plan-node-left-right-outer-hash-join[NodeRightOuterHashJoin] - +* xref:execution-plans/operators.adoc#query-plan-node-hash-join[NodeHashJoin] +* xref:execution-plans/operators.adoc#query-plan-value-hash-join[ValueHashJoin] +* xref:execution-plans/operators.adoc#query-plan-node-left-right-outer-hash-join[NodeLeftOuterHashJoin] +* xref:execution-plans/operators.adoc#query-plan-node-left-right-outer-hash-join[NodeRightOuterHashJoin] -[[query-plan-node-hash-join]] -== Node Hash Join // NodeHashJoin +[[query-plan-node-hash-join]] +== Node Hash Join == -The `NodeHashJoin` operator is a variation of the xref::execution-plans/operators.adoc#execution-plans-operators-hash-join-general[hash join]. +The `NodeHashJoin` operator is a variation of the xref:execution-plans/operators.adoc#execution-plans-operators-hash-join-general[hash join]. `NodeHashJoin` executes the hash join on node ids. As primitive types and arrays can be used, it can be done very efficiently. - -.NodeHashJoin -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (bob:Person {name: 'Bob'})-[:WORKS_IN]->(loc)<-[:WORKS_IN]-(matt:Person {name: 'Mattis'}) +MATCH (bob:Person {name:'Bob'})-[:WORKS_IN]->(loc)<-[:WORKS_IN]-(matt:Person {name:'Mattis'}) RETURN loc.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+------------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+------------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | `loc.name` | 10 | 0 | 0 | | 0/0 | 0.000 | In Pipeline 2 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Projection | loc.name AS `loc.name` | 10 | 0 | 0 | | 0/0 | 0.000 | In Pipeline 2 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Filter | not anon_0 = anon_1 | 10 | 0 | 0 | | 0/0 | 0.000 | In Pipeline 2 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +NodeHashJoin | loc | 10 | 0 | 0 | 3680 | | 0.022 | In Pipeline 2 | -| |\ +----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| | +Expand(All) | (matt)-[anon_1:WORKS_IN]->(loc) | 19 | 0 | 0 | | | | Fused in Pipeline 1 | -| | | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +NodeIndexSeek | BTREE INDEX matt:Person(name) WHERE name = $autostring_1 | 1 | 0 | 1 | 112 | 1/0 | 0.199 | Fused in Pipeline 1 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Expand(All) | (bob)-[anon_0:WORKS_IN]->(loc) | 19 | 1 | 3 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX bob:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 112 | 3/0 | 0.349 | Fused in Pipeline 0 | -+------------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++------------------+----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++------------------+----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | `loc.name` | 10 | 0 | 0 | | 0/0 | 0.000 | In Pipeline 2 | +| | +----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +Projection | loc.name AS `loc.name` | 10 | 0 | 0 | | 0/0 | 0.000 | In Pipeline 2 | +| | +----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +Filter | not anon_0 = anon_1 | 10 | 0 | 0 | | 0/0 | 0.000 | In Pipeline 2 | +| | +----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +NodeHashJoin | loc | 10 | 0 | 0 | 592 | | 0.041 | In Pipeline 2 | +| |\ +----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| | +Expand(All) | (matt)-[anon_1:WORKS_IN]->(loc) | 19 | 0 | 0 | | | | Fused in Pipeline 1 | +| | | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +NodeIndexSeek | matt:Person(name) WHERE name = $autostring_1 | 1 | 0 | 1 | 72 | 1/0 | 0.536 | Fused in Pipeline 1 | +| | +----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +Expand(All) | (bob)-[anon_0:WORKS_IN]->(loc) | 19 | 1 | 3 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeek | bob:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 72 | 3/0 | 0.668 | Fused in Pipeline 0 | ++------------------+----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 6, total allocated memory: 3872 ----- - -====== +Total database accesses: 6, total allocated memory: 744 +---- -[[query-plan-value-hash-join]] -== Value Hash Join // ValueHashJoin - -The `ValueHashJoin` operator is a variation of the xref::execution-plans/operators.adoc#execution-plans-operators-hash-join-general[hash join]. -This operator allows for arbitrary values to be used as the join key. -It is most frequently used to solve predicates of the form: `n.prop1 = m.prop2` (i.e. equality predicates between two property columns). - - -.ValueHashJoin -====== +[[query-plan-value-hash-join]] +== Value Hash Join == +The `ValueHashJoin` operator is a variation of the xref:execution-plans/operators.adoc#execution-plans-operators-hash-join-general[hash join]. + This operator allows for arbitrary values to be used as the join key. + It is most frequently used to solve predicates of the form: `n.prop1 = m.prop2` (i.e. equality predicates between two property columns). + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH - (p:Person), - (q:Person) +MATCH (p:Person),(q:Person) WHERE p.age = q.age -RETURN p, q +RETURN p,q ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+---------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -2644,36 +2355,30 @@ Runtime version 4.4 | |\ +---------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | | +Filter | q:Person | 14 | 0 | 0 | | | | Fused in Pipeline 1 | | | | +---------------+----------------+------+---------+----------------+ | +---------------------+ -| | +AllNodesScan | q | 35 | 0 | 0 | 112 | 0/0 | 0.000 | Fused in Pipeline 1 | +| | +AllNodesScan | q | 35 | 0 | 0 | 72 | 0/0 | 0.000 | Fused in Pipeline 1 | | | +---------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +Filter | p:Person | 14 | 14 | 0 | | | | Fused in Pipeline 0 | | | +---------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | p | 35 | 35 | 36 | 112 | 1/0 | 0.340 | Fused in Pipeline 0 | +| +AllNodesScan | p | 35 | 35 | 36 | 72 | 1/0 | 0.485 | Fused in Pipeline 0 | +-----------------+---------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 36, total allocated memory: 648 ----- - -====== +Total database accesses: 36, total allocated memory: 568 +---- -[[query-plan-node-left-right-outer-hash-join]] -== Node Left/Right Outer Hash Join // NodeLeftOuterHashJoin // NodeRightOuterHashJoin +[[query-plan-node-left-right-outer-hash-join]] +== Node Left/Right Outer Hash Join == -The `NodeLeftOuterHashJoin` and `NodeRightOuterHashJoin` operators are variations of the xref::execution-plans/operators.adoc#execution-plans-operators-hash-join-general[hash join]. +The `NodeLeftOuterHashJoin` and `NodeRightOuterHashJoin` operators are variations of the xref:execution-plans/operators.adoc#execution-plans-operators-hash-join-general[hash join]. The query below can be planned with either a left or a right outer join. The decision depends on the cardinalities of the left-hand and right-hand sides; i.e. how many rows would be returned, respectively, for `(a:Person)` and `(a)-->(b:Person)`. If `(a:Person)` returns fewer results than `(a)-->(b:Person)`, a left outer join -- indicated by `NodeLeftOuterHashJoin` -- is planned. On the other hand, if `(a:Person)` returns more results than `(a)-->(b:Person)`, a right outer join -- indicated by `NodeRightOuterHashJoin` -- is planned instead. - -.NodeRightOuterHashJoin -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MATCH (a:Person) OPTIONAL MATCH (a)-->(b:Person) @@ -2681,29 +2386,31 @@ USING JOIN ON a RETURN a.name, b.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-------------------------+------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-------------------------+------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | `a.name`, `b.name` | 14 | 14 | 0 | | 0/0 | 0.122 | In Pipeline 2 | +| +ProduceResults | `a.name`, `b.name` | 14 | 14 | 0 | | 0/0 | 0.228 | In Pipeline 2 | | | +------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Projection | cache[a.name] AS `a.name`, cache[b.name] AS `b.name` | 14 | 14 | 24 | | 0/0 | 0.287 | In Pipeline 2 | +| +Projection | cache[a.name] AS `a.name`, cache[b.name] AS `b.name` | 14 | 14 | 24 | | 0/0 | 0.166 | In Pipeline 2 | | | +------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +NodeRightOuterHashJoin | a | 14 | 14 | 0 | 6176 | | 0.377 | In Pipeline 2 | +| +NodeRightOuterHashJoin | a | 14 | 14 | 0 | 1144 | | 0.765 | In Pipeline 2 | | |\ +------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | | +Filter | a:Person | 14 | 14 | 0 | | | | Fused in Pipeline 1 | | | | +------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +AllNodesScan | a | 35 | 35 | 36 | 112 | 0/0 | 0.170 | Fused in Pipeline 1 | +| | +AllNodesScan | a | 35 | 35 | 36 | 72 | 0/0 | 0.213 | Fused in Pipeline 1 | | | +------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +CacheProperties | cache[a.name], cache[b.name] | 2 | 2 | 6 | | | | Fused in Pipeline 0 | | | +------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ @@ -2711,46 +2418,42 @@ Runtime version 4.4 | | +------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | b:Person | 14 | 14 | 0 | | | | Fused in Pipeline 0 | | | +------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | b | 35 | 35 | 36 | 112 | 4/0 | 0.347 | Fused in Pipeline 0 | +| +AllNodesScan | b | 35 | 35 | 36 | 72 | 4/0 | 0.951 | Fused in Pipeline 0 | +-------------------------+------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 121, total allocated memory: 6264 ----- - -====== +Total database accesses: 121, total allocated memory: 1232 +---- -[[query-plan-triadic-selection]] -== Triadic Selection // TriadicSelection - -The `TriadicSelection` operator is used to solve triangular queries, such as the very common 'find my friend-of-friends that are not already my friend'. -It does so by putting all the friends into a set, and uses the set to check if the friend-of-friends are already connected to me. +[[query-plan-triadic-selection]] +== Triadic Selection == +The `TriadicSelection` operator is used to solve triangular queries, such as the very +common 'find my friend-of-friends that are not already my friend'. +It does so by putting all the friends into a set, and uses the set to check if the +friend-of-friends are already connected to me. The example finds the names of all friends of my friends that are not already my friends. - -.TriadicSelection -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CYPHER runtime=slotted -MATCH (me:Person)-[:FRIENDS_WITH]-()-[:FRIENDS_WITH]-(other) +CYPHER runtime=slotted MATCH (me:Person)-[:FRIENDS_WITH]-()-[:FRIENDS_WITH]-(other) WHERE NOT (me)-[:FRIENDS_WITH]-(other) RETURN other.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime SLOTTED -Runtime version 4.4 +Runtime version 4.3 +-------------------+----------------------------------------+----------------+------+---------+------------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | @@ -2763,11 +2466,11 @@ Runtime version 4.4 | |\ +----------------------------------------+----------------+------+---------+------------------------+ | | +Filter | not anon_2 = anon_4 | 0 | 2 | 0 | 0/0 | | | | +----------------------------------------+----------------+------+---------+------------------------+ -| | +Expand(All) | (anon_3)-[anon_4:FRIENDS_WITH]-(other) | 0 | 6 | 14 | 8/0 | +| | +Expand(All) | (anon_3)-[anon_4:FRIENDS_WITH]-(other) | 0 | 6 | 14 | 4/0 | | | | +----------------------------------------+----------------+------+---------+------------------------+ | | +Argument | anon_3, anon_2 | 4 | 4 | 0 | 0/0 | | | +----------------------------------------+----------------+------+---------+------------------------+ -| +Expand(All) | (me)-[anon_2:FRIENDS_WITH]-(anon_3) | 4 | 4 | 33 | 28/0 | +| +Expand(All) | (me)-[anon_2:FRIENDS_WITH]-(anon_3) | 4 | 4 | 33 | 17/0 | | | +----------------------------------------+----------------+------+---------+------------------------+ | +Filter | me:Person | 14 | 14 | 35 | 1/0 | | | +----------------------------------------+----------------+------+---------+------------------------+ @@ -2775,52 +2478,47 @@ Runtime version 4.4 +-------------------+----------------------------------------+----------------+------+---------+------------------------+ Total database accesses: 120, total allocated memory: 64 ----- - -====== +---- -[[query-plan-triadic-build]] -== Triadic Build // TriadicBuild - -The `TriadicBuild` operator is used in conjunction with `TriadicFilter` to solve triangular queries, such as the very common 'find my friend-of-friends that are not already my friend'. -These two operators are specific to Pipelined runtime and together perform the same logic as `TriadicSelection` does for other runtimes. +[[query-plan-triadic-build]] +== Triadic Build == +The `TriadicBuild` operator is used in conjunction with `TriadicFilter` to solve triangular queries, such as the very +common 'find my friend-of-friends that are not already my friend'. These two operators are specific to Pipelined +runtime and together perform the same logic as `TriadicSelection` does for other runtimes. `TriadicBuild` builds a set of all friends, which is later used by `TriadicFilter`. The example finds the names of all friends of my friends that are not already my friends. - -.TriadicBuild -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CYPHER runtime=pipelined -MATCH (me:Person)-[:FRIENDS_WITH]-()-[:FRIENDS_WITH]-(other) +CYPHER runtime=pipelined MATCH (me:Person)-[:FRIENDS_WITH]-()-[:FRIENDS_WITH]-(other) WHERE NOT (me)-[:FRIENDS_WITH]-(other) RETURN other.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-----------------+----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | `other.name` | 0 | 2 | 0 | | 0/0 | 0.121 | In Pipeline 3 | +| +ProduceResults | `other.name` | 0 | 2 | 0 | | 0/0 | 0.123 | In Pipeline 3 | | | +----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Projection | other.name AS `other.name` | 0 | 2 | 4 | | 2/0 | 0.092 | In Pipeline 3 | +| +Projection | other.name AS `other.name` | 0 | 2 | 4 | | 2/0 | 0.046 | In Pipeline 3 | | | +----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +TriadicFilter | WHERE NOT (me)--(other) | 0 | 2 | 0 | 6984 | 0/0 | 0.122 | In Pipeline 3 | +| +TriadicFilter | WHERE NOT (me)--(other) | 0 | 2 | 0 | 896 | 0/0 | 0.151 | In Pipeline 3 | | | +----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +Apply | | 0 | 2 | 0 | | 0/0 | | | | |\ +----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ @@ -2828,64 +2526,59 @@ Runtime version 4.4 | | | +----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | | +Expand(All) | (anon_3)-[anon_4:FRIENDS_WITH]-(other) | 0 | 6 | 14 | | | | Fused in Pipeline 2 | | | | +----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +Argument | anon_3, anon_2 | 4 | 4 | 0 | 4200 | 0/0 | 0.397 | Fused in Pipeline 2 | +| | +Argument | anon_3, anon_2 | 4 | 4 | 0 | 192 | 0/0 | 0.714 | Fused in Pipeline 2 | | | +----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +TriadicBuild | (me)--(anon_3) | 4 | 4 | 0 | 3392 | 0/0 | 0.481 | In Pipeline 1 | +| +TriadicBuild | (me)--(anon_3) | 4 | 4 | 0 | 376 | 0/0 | 0.588 | In Pipeline 1 | | | +----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +Expand(All) | (me)-[anon_2:FRIENDS_WITH]-(anon_3) | 4 | 4 | 19 | | | | Fused in Pipeline 0 | | | +----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | me:Person | 14 | 14 | 0 | | | | Fused in Pipeline 0 | | | +----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | me | 35 | 35 | 36 | 112 | 2/0 | 0.584 | Fused in Pipeline 0 | +| +AllNodesScan | me | 35 | 35 | 36 | 72 | 2/0 | 0.343 | Fused in Pipeline 0 | +-----------------+----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 73, total allocated memory: 7248 ----- - -====== +Total database accesses: 73, total allocated memory: 1208 +---- -[[query-plan-triadic-filter]] -== Triadic Filter // TriadicFilter - -The `TriadicFilter` operator is used in conjunction with `TriadicBuild` to solve triangular queries, such as the very common 'find my friend-of-friends that are not already my friend'. -These two operators are specific to Pipelined runtime and together perform the same logic as `TriadicSelection` does for other runtimes. +[[query-plan-triadic-filter]] +== Triadic Filter == +The `TriadicFilter` operator is used in conjunction with `TriadicBuild` to solve triangular queries, such as the very +common 'find my friend-of-friends that are not already my friend'. These two operators are specific to Pipelined +runtime and together perform the same logic as `TriadicSelection` does for other runtimes. `TriadicFilter` uses a set of friends previously built by `TriadicBuild` to check if the friend-of-friends are already connected to me. The example finds the names of all friends of my friends that are not already my friends. - -.TriadicFilter -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CYPHER runtime=pipelined -MATCH (me:Person)-[:FRIENDS_WITH]-()-[:FRIENDS_WITH]-(other) +CYPHER runtime=pipelined MATCH (me:Person)-[:FRIENDS_WITH]-()-[:FRIENDS_WITH]-(other) WHERE NOT (me)-[:FRIENDS_WITH]-(other) RETURN other.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-----------------+----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | `other.name` | 0 | 2 | 0 | | 0/0 | 0.061 | In Pipeline 3 | +| +ProduceResults | `other.name` | 0 | 2 | 0 | | 0/0 | 0.126 | In Pipeline 3 | | | +----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Projection | other.name AS `other.name` | 0 | 2 | 4 | | 2/0 | 0.148 | In Pipeline 3 | +| +Projection | other.name AS `other.name` | 0 | 2 | 4 | | 2/0 | 0.122 | In Pipeline 3 | | | +----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +TriadicFilter | WHERE NOT (me)--(other) | 0 | 2 | 0 | 6984 | 0/0 | 0.411 | In Pipeline 3 | +| +TriadicFilter | WHERE NOT (me)--(other) | 0 | 2 | 0 | 896 | 0/0 | 0.590 | In Pipeline 3 | | | +----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +Apply | | 0 | 2 | 0 | | 0/0 | | | | |\ +----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ @@ -2893,228 +2586,201 @@ Runtime version 4.4 | | | +----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | | +Expand(All) | (anon_3)-[anon_4:FRIENDS_WITH]-(other) | 0 | 6 | 14 | | | | Fused in Pipeline 2 | | | | +----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +Argument | anon_3, anon_2 | 4 | 4 | 0 | 4200 | 0/0 | 0.362 | Fused in Pipeline 2 | +| | +Argument | anon_3, anon_2 | 4 | 4 | 0 | 192 | 0/0 | 0.547 | Fused in Pipeline 2 | | | +----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +TriadicBuild | (me)--(anon_3) | 4 | 4 | 0 | 3392 | 0/0 | 3.763 | In Pipeline 1 | +| +TriadicBuild | (me)--(anon_3) | 4 | 4 | 0 | 376 | 0/0 | 7.670 | In Pipeline 1 | | | +----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +Expand(All) | (me)-[anon_2:FRIENDS_WITH]-(anon_3) | 4 | 4 | 19 | | | | Fused in Pipeline 0 | | | +----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | me:Person | 14 | 14 | 0 | | | | Fused in Pipeline 0 | | | +----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | me | 35 | 35 | 36 | 112 | 2/0 | 0.279 | Fused in Pipeline 0 | +| +AllNodesScan | me | 35 | 35 | 36 | 72 | 2/0 | 0.475 | Fused in Pipeline 0 | +-----------------+----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 73, total allocated memory: 7248 ----- - -====== +Total database accesses: 73, total allocated memory: 1208 +---- -[[query-plan-cartesian-product]] -== Cartesian Product // CartesianProduct - +[[query-plan-cartesian-product]] +== Cartesian Product == The `CartesianProduct` operator produces a cartesian product of the two inputs -- each row coming from the left child operator will be combined with all the rows from the right child operator. `CartesianProduct` generally exhibits bad performance and ought to be avoided if possible. - - -.CartesianProduct -====== + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH - (p:Person), - (t:Team) -RETURN p, t +MATCH (p:Person), (t:Team) RETURN p, t ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-------------------+----------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-------------------+----------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | p, t | 140 | 140 | 0 | | 2/0 | 1.868 | In Pipeline 2 | +| +ProduceResults | p, t | 140 | 140 | 0 | | 2/0 | 8.824 | In Pipeline 2 | | | +----------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +CartesianProduct | | 140 | 140 | 0 | 3656 | | 0.528 | In Pipeline 2 | +| +CartesianProduct | | 140 | 140 | 0 | 1728 | | 0.871 | In Pipeline 2 | | |\ +----------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | | +Filter | t:Team | 10 | 10 | 0 | | | | Fused in Pipeline 1 | | | | +----------+----------------+------+---------+----------------+ | +---------------------+ -| | +AllNodesScan | t | 35 | 35 | 36 | 128 | 0/0 | 0.123 | Fused in Pipeline 1 | +| | +AllNodesScan | t | 35 | 35 | 36 | 88 | 0/0 | 0.159 | Fused in Pipeline 1 | | | +----------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +Filter | p:Person | 14 | 14 | 0 | | | | Fused in Pipeline 0 | | | +----------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | p | 35 | 35 | 36 | 112 | 1/0 | 0.164 | Fused in Pipeline 0 | +| +AllNodesScan | p | 35 | 35 | 36 | 72 | 1/0 | 0.629 | Fused in Pipeline 0 | +-------------------+----------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 72, total allocated memory: 3736 ----- - -====== +Total database accesses: 72, total allocated memory: 1808 +---- -[[query-plan-foreach]] -== Foreach // Foreach - +[[query-plan-foreach]] +== Foreach == The `Foreach` operator executes a nested loop between the left child operator and the right child operator. -In an analogous manner to the xref::execution-plans/operators.adoc#query-plan-apply[Apply] operator, it takes a row from the left-hand side and, using the xref::execution-plans/operators.adoc#query-plan-argument[Argument] operator, provides it to the operator tree on the right-hand side. -`Foreach` will yield all the rows coming in from the left-hand side; all results from the right-hand side are pulled in and discarded. - - -.Foreach -====== + In an analogous manner to the xref:execution-plans/operators.adoc#query-plan-apply[Apply] operator, it takes a row from the left-hand side and, using the xref:execution-plans/operators.adoc#query-plan-argument[Argument] operator, provides it to the operator tree on the right-hand side. + `Foreach` will yield all the rows coming in from the left-hand side; all results from the right-hand side are pulled in and discarded. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CYPHER runtime=slotted -FOREACH (value IN [1,2,3] | CREATE (:Person {age: value})) +CYPHER runtime=slotted FOREACH (value IN [1,2,3] | +CREATE (:Person {age: value}) +) ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime SLOTTED -Runtime version 4.4 +Runtime version 4.3 -+-----------------+---------------------------------------------------------+----------------+------+---------+------------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | -+-----------------+---------------------------------------------------------+----------------+------+---------+------------------------+ -| +ProduceResults | | 1 | 0 | 0 | 0/0 | -| | +---------------------------------------------------------+----------------+------+---------+------------------------+ -| +EmptyResult | | 1 | 0 | 0 | 0/0 | -| | +---------------------------------------------------------+----------------+------+---------+------------------------+ -| +Foreach | value IN [1, 2, 3], CREATE (anon_0:Person {age: value}) | 1 | 1 | 9 | 0/0 | -+-----------------+---------------------------------------------------------+----------------+------+---------+------------------------+ ++-----------------+--------------------------------------------+----------------+------+---------+------------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | ++-----------------+--------------------------------------------+----------------+------+---------+------------------------+ +| +ProduceResults | | 1 | 0 | 0 | 0/0 | +| | +--------------------------------------------+----------------+------+---------+------------------------+ +| +EmptyResult | | 1 | 0 | 0 | 0/0 | +| | +--------------------------------------------+----------------+------+---------+------------------------+ +| +Foreach | value IN [1, 2, 3], CREATE (anon_0:Person) | 1 | 1 | 9 | 0/0 | ++-----------------+--------------------------------------------+----------------+------+---------+------------------------+ Total database accesses: 9, total allocated memory: 64 ----- - -====== +---- -[[query-plan-eager]] -== Eager // Eager - +[[query-plan-eager]] +== Eager == For isolation purposes, the `Eager` operator ensures that operations affecting subsequent operations are executed fully for the whole dataset before continuing execution. - -Information from the stores is fetched in a lazy manner; i.e. the pattern matching might not be fully exhausted before updates are applied. -To guarantee reasonable semantics, the query planner will insert `Eager` operators into the query plan to prevent updates from influencing pattern matching; this scenario is exemplified by the query below, where the `DELETE` clause influences the `MATCH` clause. - -The `Eager` operator can cause high memory usage when importing data or migrating graph structures. -In such cases, the operations should be split into simpler steps; e.g. importing nodes and relationships separately. -Alternatively, the records to be updated can be returned, followed by an update statement. - - -.Eager -====== + Information from the stores is fetched in a lazy manner; i.e. the pattern matching might not be fully exhausted before updates are applied. + To guarantee reasonable semantics, the query planner will insert `Eager` operators into the query plan to prevent updates from influencing pattern matching; + this scenario is exemplified by the query below, where the `DELETE` clause influences the `MATCH` clause. + The `Eager` operator can cause high memory usage when importing data or migrating graph structures. + In such cases, the operations should be split into simpler steps; e.g. importing nodes and relationships separately. + Alternatively, the records to be updated can be returned, followed by an update statement. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (a)-[r]-(b) -DELETE r, a, b -MERGE () +MATCH (a)-[r]-(b) DELETE r,a,b MERGE () ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------+----------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+----------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | | 36 | 0 | 0 | | | | Fused in Pipeline 3 | -| | +----------------------+----------------+------+---------+----------------+ | +---------------------+ -| +EmptyResult | | 36 | 0 | 0 | | | | Fused in Pipeline 3 | -| | +----------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Apply | | 36 | 504 | 0 | | | | | -| |\ +----------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +Merge | CREATE (anon_0) | 36 | 504 | 0 | | | | Fused in Pipeline 3 | -| | | +----------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +AllNodesScan | anon_0 | 1260 | 504 | 540 | 32872 | 0/0 | 0.794 | Fused in Pipeline 3 | -| | +----------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Eager | | 36 | 36 | 0 | 24696 | 0/0 | 0.081 | In Pipeline 2 | -| | +----------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Delete | b | 36 | 36 | 9 | | | | Fused in Pipeline 1 | -| | +----------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Delete | a | 36 | 36 | 12 | | | | Fused in Pipeline 1 | -| | +----------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Delete | r | 36 | 36 | 18 | | | | Fused in Pipeline 1 | -| | +----------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Eager | delete overlap: b, r | 36 | 36 | 0 | 24696 | 2/0 | 1.629 | Fused in Pipeline 1 | -| | +----------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Expand(All) | (a)-[r]-(b) | 36 | 36 | 36 | | | | Fused in Pipeline 0 | -| | +----------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | a | 35 | 35 | 36 | 112 | 2/0 | 0.245 | Fused in Pipeline 0 | -+-----------------+----------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 651, total allocated memory: 33000 ----- - -====== +Runtime version 4.3 + ++-----------------+-----------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+-----------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | | 36 | 0 | 0 | | | | Fused in Pipeline 3 | +| | +-----------------+----------------+------+---------+----------------+ | +---------------------+ +| +EmptyResult | | 36 | 0 | 0 | | | | Fused in Pipeline 3 | +| | +-----------------+----------------+------+---------+----------------+ | +---------------------+ +| +Apply | | 36 | 504 | 0 | | | | | +| |\ +-----------------+----------------+------+---------+----------------+ | +---------------------+ +| | +Merge | CREATE (anon_0) | 36 | 504 | 0 | | | | Fused in Pipeline 3 | +| | | +-----------------+----------------+------+---------+----------------+ | +---------------------+ +| | +AllNodesScan | anon_0 | 1260 | 504 | 540 | 1216 | 0/0 | 4.271 | Fused in Pipeline 3 | +| | +-----------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +Eager | | 36 | 36 | 0 | 944 | 0/0 | 0.289 | In Pipeline 2 | +| | +-----------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +Delete | b | 36 | 36 | 9 | | | | Fused in Pipeline 1 | +| | +-----------------+----------------+------+---------+----------------+ | +---------------------+ +| +Delete | a | 36 | 36 | 12 | | | | Fused in Pipeline 1 | +| | +-----------------+----------------+------+---------+----------------+ | +---------------------+ +| +Delete | r | 36 | 36 | 18 | | | | Fused in Pipeline 1 | +| | +-----------------+----------------+------+---------+----------------+ | +---------------------+ +| +Eager | | 36 | 36 | 0 | 944 | 19/0 | 2.109 | Fused in Pipeline 1 | +| | +-----------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +Expand(All) | (a)-[r]-(b) | 36 | 36 | 36 | | | | Fused in Pipeline 0 | +| | +-----------------+----------------+------+---------+----------------+ | +---------------------+ +| +AllNodesScan | a | 35 | 35 | 36 | 72 | 2/0 | 0.573 | Fused in Pipeline 0 | ++-----------------+-----------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ + +Total database accesses: 651, total allocated memory: 1344 +---- -[[query-plan-eager-aggregation]] -== Eager Aggregation // EagerAggregation - +[[query-plan-eager-aggregation]] +== Eager Aggregation == The `EagerAggregation` operator evaluates a grouping expression and uses the result to group rows into different groupings. For each of these groupings, `EagerAggregation` will then evaluate all aggregation functions and return the result. To do this, `EagerAggregation`, as the name implies, needs to pull in all data eagerly from its source and build up state, which leads to increased memory pressure in the system. - -.EagerAggregation -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (l:Location)<-[:WORKS_IN]-(p:Person) -RETURN - l.name AS location, - collect(p.name) AS people +MATCH (l:Location)<-[:WORKS_IN]-(p:Person) RETURN l.name AS location, collect(p.name) AS people ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-------------------+------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-------------------+------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | location, people | 4 | 6 | 0 | | 0/0 | 0.138 | In Pipeline 1 | +| +ProduceResults | location, people | 4 | 6 | 0 | | 0/0 | 0.454 | In Pipeline 1 | | | +------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +EagerAggregation | cache[l.name] AS location, collect(p.name) AS people | 4 | 6 | 30 | 3168 | | | Fused in Pipeline 0 | | | +------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ @@ -3126,180 +2792,156 @@ Runtime version 4.4 | | +------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | l:Location | 10 | 10 | 0 | | | | Fused in Pipeline 0 | | | +------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | l | 35 | 35 | 36 | 112 | 4/0 | 0.484 | Fused in Pipeline 0 | +| +AllNodesScan | l | 35 | 35 | 36 | 72 | 4/0 | 1.122 | Fused in Pipeline 0 | +-------------------+------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 107, total allocated memory: 3248 ----- - -====== +---- -[[query-plan-ordered-aggregation]] -== Ordered Aggregation // OrderedAggregation - +[[query-plan-ordered-aggregation]] +== Ordered Aggregation == The `OrderedAggregation` operator is an optimization of the `EagerAggregation` operator that takes advantage of the ordering of the incoming rows. This operator uses lazy evaluation and has a lower memory pressure in the system than the `EagerAggregation` operator. - - -.OrderedAggregation -====== + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person) -WHERE p.name STARTS WITH 'P' -RETURN p.name, count(*) AS count +MATCH (p:Person) WHERE p.name STARTS WITH 'P' RETURN p.name, count(*) AS count ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Other | -+-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -| +ProduceResults | `p.name`, count | 0 | 2 | 0 | | 0/0 | 0.060 | p.name ASC | In Pipeline 1 | -| | +--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -| +OrderedAggregation | cache[p.name] AS `p.name`, count(*) AS count | 0 | 2 | 0 | 1832 | 0/0 | 0.154 | p.name ASC | In Pipeline 1 | -| | +--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -| +NodeIndexSeekByRange | BTREE INDEX p:Person(name) WHERE name STARTS WITH $autostring_0, cache[p.name] | 0 | 2 | 3 | 112 | 0/1 | 0.711 | p.name ASC | In Pipeline 0 | -+-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ +Runtime version 4.3 -Total database accesses: 3, total allocated memory: 1896 ----- ++-----------------------+--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Other | ++-----------------------+--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ +| +ProduceResults | `p.name`, count | 0 | 2 | 0 | | 0/0 | 0.122 | p.name ASC | In Pipeline 1 | +| | +--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ +| +OrderedAggregation | cache[p.name] AS `p.name`, count(*) AS count | 0 | 2 | 0 | 280 | 0/0 | 0.278 | p.name ASC | In Pipeline 1 | +| | +--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ +| +NodeIndexSeekByRange | p:Person(name) WHERE name STARTS WITH $autostring_0, cache[p.name] | 0 | 2 | 3 | 72 | 0/1 | 1.675 | p.name ASC | In Pipeline 0 | ++-----------------------+--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -====== +Total database accesses: 3, total allocated memory: 344 +---- -[[query-plan-node-count-from-count-store]] -== Node Count From Count Store // NodeCountFromCountStore - +[[query-plan-node-count-from-count-store]] +== Node Count From Count Store == The `NodeCountFromCountStore` operator uses the count store to answer questions about node counts. -This is much faster than the `EagerAggregation` operator which achieves the same result by actually counting. -However, as the count store only stores a limited range of combinations, `EagerAggregation` will still be used for more complex queries. -For example, we can get counts for all nodes, and nodes with a label, but not nodes with more than one label. - - -.NodeCountFromCountStore -====== + This is much faster than the `EagerAggregation` operator which achieves the same result by actually counting. + However, as the count store only stores a limited range of combinations, `EagerAggregation` will still be used for more complex queries. + For example, we can get counts for all nodes, and nodes with a label, but not nodes with more than one label. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person) -RETURN count(p) AS people +MATCH (p:Person) RETURN count(p) AS people ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +--------------------------+------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +--------------------------+------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | people | 1 | 1 | 0 | | | | Fused in Pipeline 0 | | | +------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeCountFromCountStore | count( (:Person) ) AS people | 1 | 1 | 1 | 112 | 0/0 | 0.187 | Fused in Pipeline 0 | +| +NodeCountFromCountStore | count( (:Person) ) AS people | 1 | 1 | 1 | 72 | 0/0 | 0.335 | Fused in Pipeline 0 | +--------------------------+------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 1, total allocated memory: 176 ----- - -====== +Total database accesses: 1, total allocated memory: 136 +---- +// RelationshipCountFromCountStore [[query-plan-relationship-count-from-count-store]] == Relationship Count From Count Store == -// RelationshipCountFromCountStore - The `RelationshipCountFromCountStore` operator uses the count store to answer questions about relationship counts. -This is much faster than the `EagerAggregation` operator which achieves the same result by actually counting. -However, as the count store only stores a limited range of combinations, `EagerAggregation` will still be used for more complex queries. -For example, we can get counts for all relationships, relationships with a type, relationships with a label on one end, but not relationships with labels on both end nodes. - - -.RelationshipCountFromCountStore -====== + This is much faster than the `EagerAggregation` operator which achieves the same result by actually counting. + However, as the count store only stores a limited range of combinations, `EagerAggregation` will still be used for more complex queries. + For example, we can get counts for all relationships, relationships with a type, relationships with a label on one end, but not relationships with labels on both end nodes. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person)-[r:WORKS_IN]->() -RETURN count(r) AS jobs +MATCH (p:Person)-[r:WORKS_IN]->() RETURN count(r) AS jobs ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +----------------------------------+--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +----------------------------------+--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | jobs | 1 | 1 | 0 | | | | Fused in Pipeline 0 | | | +--------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +RelationshipCountFromCountStore | count( (:Person)-[:WORKS_IN]->() ) AS jobs | 1 | 1 | 1 | 112 | 0/0 | 0.159 | Fused in Pipeline 0 | +| +RelationshipCountFromCountStore | count( (:Person)-[:WORKS_IN]->() ) AS jobs | 1 | 1 | 1 | 72 | 0/0 | 0.233 | Fused in Pipeline 0 | +----------------------------------+--------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 1, total allocated memory: 176 ----- - -====== +Total database accesses: 1, total allocated memory: 136 +---- -[[query-plan-distinct]] -== Distinct // Distinct - +[[query-plan-distinct]] +== Distinct == The `Distinct` operator removes duplicate rows from the incoming stream of rows. To ensure only distinct elements are returned, `Distinct` will pull in data lazily from its source and build up state. This may lead to increased memory pressure in the system. - -.Distinct -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (l:Location)<-[:WORKS_IN]-(p:Person) -RETURN DISTINCT l +MATCH (l:Location)<-[:WORKS_IN]-(p:Person) RETURN DISTINCT l ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+----------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -3314,134 +2956,114 @@ Runtime version 4.4 | | +----------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | l:Location | 10 | 10 | 0 | | | | Fused in Pipeline 0 | | | +----------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | l | 35 | 35 | 36 | 112 | 5/0 | 0.628 | Fused in Pipeline 0 | +| +AllNodesScan | l | 35 | 35 | 36 | 72 | 5/0 | 0.698 | Fused in Pipeline 0 | +-----------------+----------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ Total database accesses: 67, total allocated memory: 304 ----- - -====== +---- -[[query-plan-ordered-distinct]] -== Ordered Distinct // OrderedDistinct - +[[query-plan-ordered-distinct]] +== Ordered Distinct == The `OrderedDistinct` operator is an optimization of the `Distinct` operator that takes advantage of the ordering of the incoming rows. This operator has a lower memory pressure in the system than the `Distinct` operator. - - -.OrderedDistinct -====== + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person) -WHERE p.name STARTS WITH 'P' -RETURN DISTINCT p.name +MATCH (p:Person) WHERE p.name STARTS WITH 'P' RETURN DISTINCT p.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Other | -+-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -| +ProduceResults | `p.name` | 0 | 2 | 0 | | 0/0 | 0.071 | p.name ASC | In Pipeline 0 | -| | +--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -| +OrderedDistinct | cache[p.name] AS `p.name` | 0 | 2 | 0 | 32 | 0/0 | 1.923 | p.name ASC | In Pipeline 0 | -| | +--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -| +NodeIndexSeekByRange | BTREE INDEX p:Person(name) WHERE name STARTS WITH $autostring_0, cache[p.name] | 0 | 2 | 3 | 112 | 0/1 | 0.239 | p.name ASC | In Pipeline 0 | -+-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ +Runtime version 4.3 -Total database accesses: 3, total allocated memory: 176 ----- ++-----------------------+--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Other | ++-----------------------+--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ +| +ProduceResults | `p.name` | 0 | 2 | 0 | | 0/0 | 0.078 | p.name ASC | In Pipeline 0 | +| | +--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ +| +OrderedDistinct | cache[p.name] AS `p.name` | 0 | 2 | 0 | 32 | 0/0 | 3.111 | p.name ASC | In Pipeline 0 | +| | +--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ +| +NodeIndexSeekByRange | p:Person(name) WHERE name STARTS WITH $autostring_0, cache[p.name] | 0 | 2 | 3 | 72 | 0/1 | 0.402 | p.name ASC | In Pipeline 0 | ++-----------------------+--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -====== +Total database accesses: 3, total allocated memory: 136 +---- -[[query-plan-filter]] -== Filter // Filter - +[[query-plan-filter]] +== Filter == The `Filter` operator filters each row coming from the child operator, only passing through rows that evaluate the predicates to `true`. - -.Filter -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person) -WHERE p.name =~ '^a.*' -RETURN p +MATCH (p:Person) WHERE p.name =~ '^a.*' RETURN p ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------+------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | p | 14 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | cache[p.name] =~ $autostring_0 | 14 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexScan | BTREE INDEX p:Person(name) WHERE name IS NOT NULL, cache[p.name] | 14 | 14 | 15 | 112 | 0/1 | 0.373 | Fused in Pipeline 0 | -+-----------------+------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Runtime version 4.3 -Total database accesses: 15, total allocated memory: 176 ----- ++-----------------+------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | p | 14 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | cache[p.name] =~ $autostring_0 | 14 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexScan | p:Person(name) WHERE name IS NOT NULL, cache[p.name] | 14 | 14 | 15 | 72 | 0/1 | 1.033 | Fused in Pipeline 0 | ++-----------------+------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -====== +Total database accesses: 15, total allocated memory: 136 +---- -[[query-plan-limit]] -== Limit // Limit - -The `Limit` operator returns the first `+n+` rows from the incoming input. - - -.Limit -====== +[[query-plan-limit]] +== Limit == +The `Limit` operator returns the first 'n' rows from the incoming input. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person) -RETURN p -LIMIT 3 +MATCH (p:Person) RETURN p LIMIT 3 ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+----------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -3452,306 +3074,266 @@ Runtime version 4.4 | | +----------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | p:Person | 3 | 3 | 0 | | | | Fused in Pipeline 0 | | | +----------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | p | 8 | 4 | 5 | 112 | 3/0 | 0.320 | Fused in Pipeline 0 | +| +AllNodesScan | p | 8 | 4 | 5 | 72 | 3/0 | 0.769 | Fused in Pipeline 0 | +-----------------+----------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 5, total allocated memory: 176 ----- - -====== +Total database accesses: 5, total allocated memory: 136 +---- -[[query-plan-skip]] -== Skip // Skip - -The `Skip` operator skips `+n+` rows from the incoming rows. - - -.Skip -====== +[[query-plan-skip]] +== Skip == +The `Skip` operator skips 'n' rows from the incoming rows. + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MATCH (p:Person) -RETURN p -ORDER BY p.id -SKIP 1 + RETURN p + ORDER BY p.id + SKIP 1 ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+----------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Other | +-----------------+----------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ -| +ProduceResults | p | 13 | 13 | 0 | | 2/0 | 0.698 | p.id ASC | In Pipeline 1 | +| +ProduceResults | p | 13 | 13 | 0 | | 2/0 | 0.551 | p.id ASC | In Pipeline 1 | | | +----------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ -| +Skip | $autoint_0 | 13 | 13 | 0 | 32 | 0/0 | 2.206 | p.id ASC | In Pipeline 1 | +| +Skip | $autoint_0 | 13 | 13 | 0 | 32 | 0/0 | 2.429 | p.id ASC | In Pipeline 1 | | | +----------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ -| +Sort | `p.id` ASC | 14 | 14 | 0 | 1800 | 0/0 | 0.708 | p.id ASC | In Pipeline 1 | +| +Sort | `p.id` ASC | 14 | 14 | 0 | 392 | 0/0 | 0.238 | p.id ASC | In Pipeline 1 | | | +----------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ -| +Projection | p.id AS `p.id` | 14 | 14 | 0 | | | | | Fused in Pipeline 0 | +| +Projection | p.id AS `p.id` | 14 | 14 | 14 | | | | | Fused in Pipeline 0 | | | +----------------+----------------+------+---------+----------------+ | +------------+---------------------+ | +Filter | p:Person | 14 | 14 | 0 | | | | | Fused in Pipeline 0 | | | +----------------+----------------+------+---------+----------------+ | +------------+---------------------+ -| +AllNodesScan | p | 35 | 35 | 36 | 112 | 2/0 | 1.153 | | Fused in Pipeline 0 | +| +AllNodesScan | p | 35 | 35 | 36 | 72 | 2/0 | 1.180 | | Fused in Pipeline 0 | +-----------------+----------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ -Total database accesses: 36, total allocated memory: 1912 ----- - -====== +Total database accesses: 50, total allocated memory: 504 +---- -[[query-plan-sort]] -== Sort // Sort - +[[query-plan-sort]] +== Sort == The `Sort` operator sorts rows by a provided key. In order to sort the data, all data from the source operator needs to be pulled in eagerly and kept in the query state, which will lead to increased memory pressure in the system. - -.Sort -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person) -RETURN p -ORDER BY p.name +MATCH (p:Person) RETURN p ORDER BY p.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+--------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Other | +-----------------+--------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ -| +ProduceResults | p | 14 | 14 | 0 | | 2/0 | 1.224 | p.name ASC | In Pipeline 1 | +| +ProduceResults | p | 14 | 14 | 0 | | 2/0 | 0.366 | p.name ASC | In Pipeline 1 | | | +--------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ -| +Sort | `p.name` ASC | 14 | 14 | 0 | 2592 | 0/0 | 0.922 | p.name ASC | In Pipeline 1 | +| +Sort | `p.name` ASC | 14 | 14 | 0 | 1184 | 0/0 | 0.190 | p.name ASC | In Pipeline 1 | | | +--------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ | +Projection | p.name AS `p.name` | 14 | 14 | 14 | | | | | Fused in Pipeline 0 | | | +--------------------+----------------+------+---------+----------------+ | +------------+---------------------+ | +Filter | p:Person | 14 | 14 | 0 | | | | | Fused in Pipeline 0 | | | +--------------------+----------------+------+---------+----------------+ | +------------+---------------------+ -| +AllNodesScan | p | 35 | 35 | 36 | 112 | 2/0 | 0.990 | | Fused in Pipeline 0 | +| +AllNodesScan | p | 35 | 35 | 36 | 72 | 2/0 | 0.409 | | Fused in Pipeline 0 | +-----------------+--------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ -Total database accesses: 50, total allocated memory: 2672 ----- - -====== +Total database accesses: 50, total allocated memory: 1264 +---- -[[query-plan-partial-sort]] -== Partial Sort // PartialSort - +[[query-plan-partial-sort]] +== Partial Sort == The `PartialSort` operator is an optimization of the `Sort` operator that takes advantage of the ordering of the incoming rows. This operator uses lazy evaluation and has a lower memory pressure in the system than the `Sort` operator. Partial sort is only applicable when sorting on multiple columns. - - -.PartialSort -====== + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person) -WHERE p.name STARTS WITH 'P' -RETURN p -ORDER BY p.name, p.age +MATCH (p:Person) WHERE p.name STARTS WITH 'P' RETURN p ORDER BY p.name, p.age ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Other | -+-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ -| +ProduceResults | p | 0 | 2 | 0 | | 2/0 | 0.550 | p.name ASC, p.age ASC | In Pipeline 1 | -| | +--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ -| +PartialSort | `p.name` ASC, `p.age` ASC | 0 | 2 | 0 | 3096 | 0/0 | 0.535 | p.name ASC, p.age ASC | In Pipeline 1 | -| | +--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ -| +Projection | cache[p.name] AS `p.name`, p.age AS `p.age` | 0 | 2 | 0 | | | | p.name ASC | Fused in Pipeline 0 | -| | +--------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +-----------------------+---------------------+ -| +NodeIndexSeekByRange | BTREE INDEX p:Person(name) WHERE name STARTS WITH $autostring_0, cache[p.name] | 0 | 2 | 3 | 112 | 0/1 | 1.068 | p.name ASC | Fused in Pipeline 0 | -+-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ - -Total database accesses: 3, total allocated memory: 3160 ----- ++-----------------------+--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Other | ++-----------------------+--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ +| +ProduceResults | p | 0 | 2 | 0 | | 2/0 | 0.414 | p.name ASC, p.age ASC | In Pipeline 1 | +| | +--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ +| +PartialSort | `p.name` ASC, `p.age` ASC | 0 | 2 | 0 | 536 | 0/0 | 0.687 | p.name ASC, p.age ASC | In Pipeline 1 | +| | +--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ +| +Projection | cache[p.name] AS `p.name`, p.age AS `p.age` | 0 | 2 | 0 | | | | p.name ASC | Fused in Pipeline 0 | +| | +--------------------------------------------------------------------+----------------+------+---------+----------------+ | +-----------------------+---------------------+ +| +NodeIndexSeekByRange | p:Person(name) WHERE name STARTS WITH $autostring_0, cache[p.name] | 0 | 2 | 3 | 72 | 0/1 | 0.818 | p.name ASC | Fused in Pipeline 0 | ++-----------------------+--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ -====== +Total database accesses: 3, total allocated memory: 600 +---- -[[query-plan-top]] -== Top // Top - -The `Top` operator returns the first `+n+` rows sorted by a provided key. -Instead of sorting the entire input, only the top `+n+` rows are retained. - - -.Top -====== +[[query-plan-top]] +== Top == +The `Top` operator returns the first 'n' rows sorted by a provided key. Instead of sorting the entire input, only the top 'n' rows are retained. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person) -RETURN p -ORDER BY p.name -LIMIT 2 +MATCH (p:Person) RETURN p ORDER BY p.name LIMIT 2 ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+----------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Other | +-----------------+----------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ -| +ProduceResults | p | 2 | 2 | 0 | | 2/0 | 0.093 | p.name ASC | In Pipeline 1 | +| +ProduceResults | p | 2 | 2 | 0 | | 2/0 | 0.261 | p.name ASC | In Pipeline 1 | | | +----------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ -| +Top | `p.name` ASC LIMIT 2 | 2 | 2 | 0 | 2584 | 0/0 | 0.638 | p.name ASC | In Pipeline 1 | +| +Top | `p.name` ASC LIMIT 2 | 2 | 2 | 0 | 1176 | 0/0 | 1.151 | p.name ASC | In Pipeline 1 | | | +----------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ | +Projection | p.name AS `p.name` | 14 | 14 | 14 | | | | | Fused in Pipeline 0 | | | +----------------------+----------------+------+---------+----------------+ | +------------+---------------------+ | +Filter | p:Person | 14 | 14 | 0 | | | | | Fused in Pipeline 0 | | | +----------------------+----------------+------+---------+----------------+ | +------------+---------------------+ -| +AllNodesScan | p | 35 | 35 | 36 | 112 | 2/0 | 0.173 | | Fused in Pipeline 0 | +| +AllNodesScan | p | 35 | 35 | 36 | 72 | 2/0 | 0.395 | | Fused in Pipeline 0 | +-----------------+----------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ -Total database accesses: 50, total allocated memory: 2664 ----- - -====== +Total database accesses: 50, total allocated memory: 1256 +---- -[[query-plan-partial-top]] -== Partial Top // PartialTop - +[[query-plan-partial-top]] +== Partial Top == The `PartialTop` operator is an optimization of the `Top` operator that takes advantage of the ordering of the incoming rows. This operator uses lazy evaluation and has a lower memory pressure in the system than the `Top` operator. Partial top is only applicable when sorting on multiple columns. - - -.PartialTop -====== + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person) -WHERE p.name STARTS WITH 'P' -RETURN p -ORDER BY p.name, p.age -LIMIT 2 +MATCH (p:Person) WHERE p.name STARTS WITH 'P' RETURN p ORDER BY p.name, p.age LIMIT 2 ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Other | -+-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ -| +ProduceResults | p | 0 | 2 | 0 | | 2/0 | 0.101 | p.name ASC, p.age ASC | In Pipeline 1 | -| | +--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ -| +PartialTop | `p.name` ASC, `p.age` ASC LIMIT 2 | 0 | 2 | 0 | 632 | 0/0 | 0.650 | p.name ASC, p.age ASC | In Pipeline 1 | -| | +--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ -| +Projection | cache[p.name] AS `p.name`, p.age AS `p.age` | 0 | 2 | 0 | | | | p.name ASC | Fused in Pipeline 0 | -| | +--------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +-----------------------+---------------------+ -| +NodeIndexSeekByRange | BTREE INDEX p:Person(name) WHERE name STARTS WITH $autostring_0, cache[p.name] | 0 | 2 | 3 | 112 | 0/1 | 0.492 | p.name ASC | Fused in Pipeline 0 | -+-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ - -Total database accesses: 3, total allocated memory: 696 ----- ++-----------------------+--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Other | ++-----------------------+--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ +| +ProduceResults | p | 0 | 2 | 0 | | 2/0 | 0.148 | p.name ASC, p.age ASC | In Pipeline 1 | +| | +--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ +| +PartialTop | `p.name` ASC, `p.age` ASC LIMIT 2 | 0 | 2 | 0 | 432 | 0/0 | 0.950 | p.name ASC, p.age ASC | In Pipeline 1 | +| | +--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ +| +Projection | cache[p.name] AS `p.name`, p.age AS `p.age` | 0 | 2 | 0 | | | | p.name ASC | Fused in Pipeline 0 | +| | +--------------------------------------------------------------------+----------------+------+---------+----------------+ | +-----------------------+---------------------+ +| +NodeIndexSeekByRange | p:Person(name) WHERE name STARTS WITH $autostring_0, cache[p.name] | 0 | 2 | 3 | 72 | 0/1 | 0.519 | p.name ASC | Fused in Pipeline 0 | ++-----------------------+--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+-----------------------+---------------------+ -====== +Total database accesses: 3, total allocated memory: 496 +---- -[[query-plan-union]] -== Union // Union - +[[query-plan-union]] +== Union == The `Union` operator concatenates the results from the right child operator with the results from the left child operator. - -.Union -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MATCH (p:Location) - RETURN p.name -UNION ALL -MATCH (p:Country) - RETURN p.name + RETURN p.name + UNION ALL + MATCH (p:Country) + RETURN p.name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+--------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-----------------+--------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | `p.name` | 20 | 11 | 0 | | | | Fused in Pipeline 2 | | | +--------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Union | | 20 | 11 | 0 | 2224 | 0/0 | 0.158 | Fused in Pipeline 2 | +| +Union | | 20 | 11 | 0 | 768 | 0/0 | 0.552 | Fused in Pipeline 2 | | |\ +--------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | | +Projection | `p.name` | 10 | 1 | 0 | | | | Fused in Pipeline 1 | | | | +--------------------+----------------+------+---------+----------------+ | +---------------------+ @@ -3759,7 +3341,7 @@ Runtime version 4.4 | | | +--------------------+----------------+------+---------+----------------+ | +---------------------+ | | +Filter | p:Country | 10 | 1 | 0 | | | | Fused in Pipeline 1 | | | | +--------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +AllNodesScan | p | 35 | 35 | 36 | 112 | 0/0 | 0.106 | Fused in Pipeline 1 | +| | +AllNodesScan | p | 35 | 35 | 36 | 72 | 0/0 | 0.293 | Fused in Pipeline 1 | | | +--------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +Projection | `p.name` | 10 | 10 | 0 | | | | Fused in Pipeline 0 | | | +--------------------+----------------+------+---------+----------------+ | +---------------------+ @@ -3767,42 +3349,36 @@ Runtime version 4.4 | | +--------------------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | p:Location | 10 | 10 | 0 | | | | Fused in Pipeline 0 | | | +--------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | p | 35 | 35 | 36 | 112 | 2/0 | 0.243 | Fused in Pipeline 0 | +| +AllNodesScan | p | 35 | 35 | 36 | 72 | 2/0 | 0.325 | Fused in Pipeline 0 | +-----------------+--------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 83, total allocated memory: 2424 ----- - -====== +Total database accesses: 83, total allocated memory: 928 +---- -[[query-plan-unwind]] -== Unwind // Unwind - +[[query-plan-unwind]] +== Unwind == The `Unwind` operator returns one row per item in a list. - -.Unwind -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -UNWIND range(1, 5) AS value -RETURN value +UNWIND range(1, 5) as value return value ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+----------------------------------------+----------------+------+---------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | Time (ms) | Other | @@ -3812,43 +3388,35 @@ Runtime version 4.4 | +Unwind | range($autoint_0, $autoint_1) AS value | 10 | 5 | 0 | 0/0 | 0.000 | Fused in Pipeline 0 | +-----------------+----------------------------------------+----------------+------+---------+------------------------+-----------+---------------------+ -Total database accesses: 0, total allocated memory: 176 ----- - -====== +Total database accesses: 0, total allocated memory: 136 +---- -[[query-plan-exhaustive-limit]] -== Exhaustive Limit // LockNodes - changed in 4.3 // ExhaustiveLimit - +[[query-plan-exhaustive-limit]] +== Exhaustive Limit == The `ExhaustiveLimit` operator is just like a normal `Limit` but will always exhaust the input. Used when combining `LIMIT` and updates - -.ExhaustiveLimit -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person) -SET p.seen = true -RETURN p -LIMIT 3 +MATCH (p:Person) SET p.seen=true RETURN p LIMIT 3 ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +------------------+---------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -3857,99 +3425,84 @@ Runtime version 4.4 | | +---------------+----------------+------+---------+----------------+ | +---------------------+ | +ExhaustiveLimit | 3 | 3 | 3 | 0 | 32 | | | Fused in Pipeline 0 | | | +---------------+----------------+------+---------+----------------+ | +---------------------+ -| +SetProperty | p.seen = true | 14 | 14 | 28 | | | | Fused in Pipeline 0 | +| +SetProperty | p.seen = true | 14 | 14 | 14 | | | | Fused in Pipeline 0 | | | +---------------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | p:Person | 14 | 14 | 0 | | | | Fused in Pipeline 0 | | | +---------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | p | 35 | 35 | 36 | 112 | 3/0 | 1.062 | Fused in Pipeline 0 | +| +AllNodesScan | p | 35 | 35 | 36 | 72 | 3/0 | 2.630 | Fused in Pipeline 0 | +------------------+---------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 64, total allocated memory: 192 ----- - -====== +Total database accesses: 50, total allocated memory: 152 +---- -[[query-plan-optional]] -== Optional // Optional - -The `Optional` operator is used to solve some xref::clauses/optional-match.adoc[OPTIONAL MATCH] queries. +[[query-plan-optional]] +== Optional == +The `Optional` operator is used to solve some xref:clauses/optional-match.adoc[OPTIONAL MATCH] queries. It will pull data from its source, simply passing it through if any data exists. However, if no data is returned by its source, `Optional` will yield a single row with all columns set to `null`. - -.Optional -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (p:Person {name: 'me'}) -OPTIONAL MATCH (q:Person {name: 'Lulu'}) -RETURN p, q +MATCH (p:Person {name:'me'}) OPTIONAL MATCH (q:Person {name: 'Lulu'}) RETURN p, q ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+------------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+------------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| +ProduceResults | p, q | 1 | 1 | 0 | | 2/0 | 0.097 | In Pipeline 2 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| +Apply | | 1 | 1 | 0 | | 0/0 | 0.012 | | -| |\ +-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| | +Optional | p | 1 | 1 | 0 | 768 | 0/0 | 0.339 | In Pipeline 2 | -| | | +-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| | +NodeIndexSeek | BTREE INDEX q:Person(name) WHERE name = $autostring_1 | 1 | 0 | 1 | 2152 | 1/0 | 0.483 | In Pipeline 1 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| +NodeIndexSeek | BTREE INDEX p:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 112 | 0/1 | 0.188 | In Pipeline 0 | -+------------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +Runtime version 4.3 -Total database accesses: 3, total allocated memory: 3000 ----- ++------------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++------------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| +ProduceResults | p, q | 1 | 1 | 0 | | 2/0 | 0.144 | In Pipeline 2 | +| | +-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| +Apply | | 1 | 1 | 0 | | 0/0 | 0.021 | | +| |\ +-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| | +Optional | p | 1 | 1 | 0 | 768 | 0/0 | 0.622 | In Pipeline 2 | +| | | +-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| | +NodeIndexSeek | q:Person(name) WHERE name = $autostring_1 | 1 | 0 | 1 | 80 | 1/0 | 2.817 | In Pipeline 1 | +| | +-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| +NodeIndexSeek | p:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 72 | 0/1 | 0.326 | In Pipeline 0 | ++------------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -====== +Total database accesses: 3, total allocated memory: 928 +---- -[[query-plan-project-endpoints]] -== Project Endpoints // ProjectEndpoints - +[[query-plan-project-endpoints]] +== Project Endpoints == The `ProjectEndpoints` operator projects the start and end node of a relationship. - -.ProjectEndpoints -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CREATE (n)-[p:KNOWS]->(m) -WITH p AS r -MATCH (u)-[r]->(v) -RETURN u, v +CREATE (n)-[p:KNOWS]->(m) WITH p AS r MATCH (u)-[r]->(v) RETURN u, v ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +---------------------+------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -3960,45 +3513,40 @@ Runtime version 4.4 | |\ +------------------------------+----------------+------+---------+----------------+ | +---------------------+ | | +ProjectEndpoints | (u)-[r*]->(v) | 1 | 1 | 0 | | | | Fused in Pipeline 1 | | | | +------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +Argument | r | 1 | 1 | 0 | 4200 | 0/0 | 0.160 | Fused in Pipeline 1 | +| | +Argument | r | 1 | 1 | 0 | 96 | 0/0 | 0.205 | Fused in Pipeline 1 | | | +------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +Projection | p AS r | 1 | 1 | 0 | | | | Fused in Pipeline 0 | | | +------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Create | (n), (m), (n)-[p:KNOWS]->(m) | 1 | 1 | 4 | | 0/0 | 0.000 | Fused in Pipeline 0 | +---------------------+------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 4, total allocated memory: 4280 ----- - -====== +Total database accesses: 4, total allocated memory: 176 +---- -[[query-plan-projection]] -== Projection // Projection - +[[query-plan-projection]] +== Projection == For each incoming row, the `Projection` operator evaluates a set of expressions and produces a row with the results of the expressions. - -.Projection -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- RETURN 'hello' AS greeting ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+---------------------------+----------------+------+---------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | Time (ms) | Other | @@ -4008,87 +3556,76 @@ Runtime version 4.4 | +Projection | $autostring_0 AS greeting | 1 | 1 | 0 | 0/0 | 0.000 | Fused in Pipeline 0 | +-----------------+---------------------------+----------------+------+---------+------------------------+-----------+---------------------+ -Total database accesses: 0, total allocated memory: 176 ----- - -====== +Total database accesses: 0, total allocated memory: 136 +---- -[[query-plan-shortest-path]] -== Shortest path // ShortestPath - +[[query-plan-shortest-path]] +== Shortest path == The `ShortestPath` operator finds one or all shortest paths between two previously matches node variables. - - -.ShortestPath -====== + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH - (andy:Person {name: 'Andy'}), - (mattias:Person {name: 'Mattias'}), +MATCH (andy:Person {name: 'Andy'}),(mattias:Person {name: 'Mattias'}), p = shortestPath((andy)-[*]-(mattias)) RETURN p ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+---------------------+-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+---------------------+-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| +ProduceResults | p | 1 | 1 | 0 | | 1/0 | 0.169 | In Pipeline 1 | -| | +-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| +ShortestPath | p = (andy)-[anon_0*]-(mattias) | 1 | 1 | 1 | 3176 | | | In Pipeline 1 | -| | +-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| +MultiNodeIndexSeek | BTREE INDEX andy:Person(name) WHERE name = $autostring_0, | 1 | 1 | 4 | 112 | 1/1 | 0.277 | In Pipeline 0 | -| | BTREE INDEX mattias:Person(name) WHERE name = $autostring_1 | | | | | | | | -+---------------------+-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ ++---------------------+-----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++---------------------+-----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| +ProduceResults | p | 1 | 1 | 0 | | 1/0 | 1.235 | In Pipeline 1 | +| | +-----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| +ShortestPath | p = (andy)-[anon_0*]-(mattias) | 1 | 1 | 1 | 1280 | | | In Pipeline 1 | +| | +-----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| +MultiNodeIndexSeek | andy:Person(name) WHERE name = $autostring_0, mattias:Person(name) WHERE name = $autostring_1 | 1 | 1 | 4 | 72 | 1/1 | 2.255 | In Pipeline 0 | ++---------------------+-----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -Total database accesses: 5, total allocated memory: 3240 ----- - -====== +Total database accesses: 5, total allocated memory: 1344 +---- -[[query-plan-empty-row]] -== Empty Row // EmptyRow - +[[query-plan-empty-row]] +== Empty Row == The `EmptyRow` operator returns a single row with no columns. - -.EmptyRow -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CYPHER runtime=slotted -FOREACH (value IN [1,2,3] | MERGE (:Person {age: value})) +CYPHER runtime=slotted FOREACH (value IN [1,2,3] | +MERGE (:Person {age: value}) +) ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime SLOTTED -Runtime version 4.4 +Runtime version 4.3 +-----------------+--------------------------------------+----------------+------+---------+------------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | @@ -4099,97 +3636,83 @@ Runtime version 4.4 | | +--------------------------------------+----------------+------+---------+------------------------+ | +Foreach | value IN [1, 2, 3] | 1 | 1 | 0 | 0/0 | | |\ +--------------------------------------+----------------+------+---------+------------------------+ -| | +Merge | CREATE (anon_0:Person {age: value}) | 1 | 3 | 9 | 0/0 | +| | +Merge | CREATE (anon_0:Person) | 1 | 3 | 9 | 0/0 | | | | +--------------------------------------+----------------+------+---------+------------------------+ | | +Filter | anon_0:Person AND anon_0.age = value | 1 | 0 | 184 | 2/0 | | | | +--------------------------------------+----------------+------+---------+------------------------+ -| | +AllNodesScan | anon_0 | 35 | 108 | 111 | 3/0 | +| | +AllNodesScan | anon_0 | 35 | 108 | 111 | 1/0 | | | +--------------------------------------+----------------+------+---------+------------------------+ | +EmptyRow | | 1 | 1 | 0 | 0/0 | +-----------------+--------------------------------------+----------------+------+---------+------------------------+ Total database accesses: 304, total allocated memory: 64 ----- - -====== +---- -[[query-plan-procedure-call]] -== Procedure Call // ProcedureCall - +[[query-plan-procedure-call]] +== Procedure Call == The `ProcedureCall` operator indicates an invocation to a procedure. - -.ProcedureCall -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CALL db.labels() YIELD label -RETURN * -ORDER BY label +CALL db.labels() YIELD label RETURN * ORDER BY label ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+-----------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Other | +-----------------+-----------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ -| +ProduceResults | label | 10 | 4 | 0 | | 0/0 | 0.062 | label ASC | In Pipeline 1 | +| +ProduceResults | label | 10 | 4 | 0 | | 0/0 | 0.131 | label ASC | In Pipeline 1 | | | +-----------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ -| +Sort | label ASC | 10 | 4 | 0 | 1064 | 0/0 | 0.177 | label ASC | In Pipeline 1 | +| +Sort | label ASC | 10 | 4 | 0 | 528 | 0/0 | 0.678 | label ASC | In Pipeline 1 | | | +-----------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ | +ProcedureCall | db.labels() :: (label :: STRING?) | 10 | 4 | | | | | | Fused in Pipeline 0 | +-----------------+-----------------------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------------+ -Total database accesses: ?, total allocated memory: 1128 ----- - -====== +Total database accesses: ?, total allocated memory: 592 +---- -[[query-plan-cache-properties]] -== Cache Properties // CacheProperties - +[[query-plan-cache-properties]] +== Cache Properties == The `CacheProperties` operator reads nodes and relationship properties and caches them in the current row. Future accesses to these properties can avoid reading from the store which will speed up the query. In the plan below we will cache `l.name` before `Expand(All)` where there are fewer rows. - - -.CacheProperties -====== + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -MATCH (l:Location)<-[:WORKS_IN]-(p:Person) -RETURN - l.name AS location, - p.name AS name +MATCH (l:Location)<-[:WORKS_IN]-(p:Person) RETURN l.name AS location, p.name AS name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +------------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -4206,141 +3729,123 @@ Runtime version 4.4 | | +-------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | l:Location | 10 | 10 | 0 | | | | Fused in Pipeline 0 | | | +-------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | l | 35 | 35 | 36 | 112 | 4/0 | 0.410 | Fused in Pipeline 0 | +| +AllNodesScan | l | 35 | 35 | 36 | 72 | 4/0 | 1.255 | Fused in Pipeline 0 | +------------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 107, total allocated memory: 192 ----- - -====== +Total database accesses: 107, total allocated memory: 152 +---- -[[query-plan-create]] -== Create (nodes and relationships) // Create - +[[query-plan-create-nodes---relationships]] +== Create Nodes / Relationships == The `Create` operator is used to create nodes and relationships. - -.Create -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CREATE - (max:Person {name: 'Max'}), - (chris:Person {name: 'Chris'}) +CREATE (max:Person {name: 'Max'}), (chris:Person {name: 'Chris'}) CREATE (max)-[:FRIENDS_WITH]->(chris) ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------+---------------------------------------------------------------------------+----------------+------+---------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+---------------------------------------------------------------------------+----------------+------+---------+------------------------+-----------+---------------------+ -| +ProduceResults | | 1 | 0 | 0 | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------------+----------------+------+---------+ | +---------------------+ -| +EmptyResult | | 1 | 0 | 0 | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------------+----------------+------+---------+ | +---------------------+ -| +Create | (max:Person {name: $autostring_0}), (chris:Person {name: $autostring_1}), | 1 | 1 | 7 | 0/0 | 0.000 | Fused in Pipeline 0 | -| | (max)-[anon_0:FRIENDS_WITH]->(chris) | | | | | | | -+-----------------+---------------------------------------------------------------------------+----------------+------+---------+------------------------+-----------+---------------------+ +Runtime version 4.3 -Total database accesses: 7, total allocated memory: 176 ----- ++-----------------+--------------------------------------------------------------------+----------------+------+---------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+--------------------------------------------------------------------+----------------+------+---------+------------------------+-----------+---------------------+ +| +ProduceResults | | 1 | 0 | 0 | | | Fused in Pipeline 0 | +| | +--------------------------------------------------------------------+----------------+------+---------+ | +---------------------+ +| +EmptyResult | | 1 | 0 | 0 | | | Fused in Pipeline 0 | +| | +--------------------------------------------------------------------+----------------+------+---------+ | +---------------------+ +| +Create | (max:Person), (chris:Person), (max)-[anon_0:FRIENDS_WITH]->(chris) | 1 | 1 | 7 | 0/0 | 0.000 | Fused in Pipeline 0 | ++-----------------+--------------------------------------------------------------------+----------------+------+---------+------------------------+-----------+---------------------+ -====== +Total database accesses: 7, total allocated memory: 136 +---- -[[query-plan-delete]] -== Delete (nodes and relationships) // Delete - +[[query-plan-delete]] +== Delete == The `Delete` operator is used to delete a node or a relationship. - -.Delete -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MATCH (me:Person {name: 'me'})-[w:WORKS_IN {duration: 190}]->(london:Location {name: 'London'}) DELETE w ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------+-----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+-----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | | 0 | 0 | 0 | | | | Fused in Pipeline 1 | -| | +-----------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +EmptyResult | | 0 | 0 | 0 | | | | Fused in Pipeline 1 | -| | +-----------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Delete | w | 0 | 1 | 1 | | | | Fused in Pipeline 1 | -| | +-----------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Eager | delete overlap: w | 0 | 1 | 0 | 3192 | 1/0 | 0.215 | Fused in Pipeline 1 | -| | +-----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Filter | london.name = $autostring_2 AND w.duration = $autoint_1 AND london:Location | 0 | 1 | 4 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (me)-[w:WORKS_IN]->(london) | 1 | 1 | 3 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX me:Person(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 112 | 4/1 | 0.442 | Fused in Pipeline 0 | -+-----------------+-----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Runtime version 4.3 -Total database accesses: 10, total allocated memory: 3272 ----- ++-----------------+-------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+-------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | | 0 | 0 | 0 | | | | Fused in Pipeline 1 | +| | +-------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +EmptyResult | | 0 | 0 | 0 | | | | Fused in Pipeline 1 | +| | +-------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Delete | w | 0 | 1 | 1 | | | | Fused in Pipeline 1 | +| | +-------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Eager | | 0 | 1 | 0 | 104 | 1/0 | 3.883 | Fused in Pipeline 1 | +| | +-------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +Filter | me.name = $autostring_0 AND w.duration = $autoint_1 AND me:Person | 0 | 1 | 17 | | | | Fused in Pipeline 0 | +| | +-------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (london)<-[w:WORKS_IN]-(me) | 0 | 7 | 9 | | | | Fused in Pipeline 0 | +| | +-------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeek | london:Location(name) WHERE name = $autostring_2 | 0 | 1 | 2 | 72 | 4/1 | 4.253 | Fused in Pipeline 0 | ++-----------------+-------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -====== +Total database accesses: 29, total allocated memory: 184 +---- -[[query-plan-detach-delete]] -== Detach Delete // DetachDelete - -The `DetachDelete` operator is used in all queries containing the xref::clauses/delete.adoc[DETACH DELETE] clause, when deleting nodes and their relationships. - - -.DetachDelete -====== +[[query-plan-detach-delete]] +== Detach Delete == +The `DetachDelete` operator is used in all queries containing the xref:clauses/delete.adoc[DETACH DELETE] clause, when deleting nodes and their relationships. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MATCH (p:Person) DETACH DELETE p ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+----------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -4353,47 +3858,43 @@ Runtime version 4.4 | | +----------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | p:Person | 14 | 14 | 0 | | | | Fused in Pipeline 0 | | | +----------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | p | 35 | 35 | 36 | 112 | 19/0 | 1.332 | Fused in Pipeline 0 | +| +AllNodesScan | p | 35 | 35 | 36 | 72 | 35/0 | 6.049 | Fused in Pipeline 0 | +-----------------+----------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 67, total allocated memory: 192 ----- - -====== - - -// MergeCreateNode -- removed in 4.3 - -// MergeCreateRelationship -- removed in 4.3 +Total database accesses: 67, total allocated memory: 152 +---- -[[query-plan-set-labels]] -== Set Labels -// SetLabels - -The `SetLabels` operator is used when setting labels on a node. +// include::../ql/query-plan/merge-create-node.adoc[] +// Removed in 4.3 +// include::../ql/query-plan/merge-create-relationship.adoc[] +// Removed in 4.3 -.SetLabels -====== +// SetLabels +[[query-plan-set-labels]] +== Set Labels == +The `SetLabels` operator is used when setting labels on a node. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MATCH (n) SET n:Person ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+----------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -4404,42 +3905,37 @@ Runtime version 4.4 | | +----------+----------------+------+---------+----------------+ | +---------------------+ | +SetLabels | n:Person | 35 | 35 | 22 | | | | Fused in Pipeline 0 | | | +----------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | n | 35 | 35 | 36 | 112 | 3/0 | 1.188 | Fused in Pipeline 0 | +| +AllNodesScan | n | 35 | 35 | 36 | 72 | 3/0 | 3.342 | Fused in Pipeline 0 | +-----------------+----------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 58, total allocated memory: 176 ----- - -====== +Total database accesses: 58, total allocated memory: 136 +---- -[[query-plan-remove-labels]] -== Remove Labels // RemoveLabels - +[[query-plan-remove-labels]] +== Remove Labels == The `RemoveLabels` operator is used when deleting labels from a node. - -.RemoveLabels -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MATCH (n) REMOVE n:Person ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+----------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -4450,42 +3946,37 @@ Runtime version 4.4 | | +----------+----------------+------+---------+----------------+ | +---------------------+ | +RemoveLabels | n:Person | 35 | 35 | 15 | | | | Fused in Pipeline 0 | | | +----------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | n | 35 | 35 | 36 | 112 | 3/0 | 2.081 | Fused in Pipeline 0 | +| +AllNodesScan | n | 35 | 35 | 36 | 72 | 3/0 | 1.984 | Fused in Pipeline 0 | +-----------------+----------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 51, total allocated memory: 176 ----- - -====== +Total database accesses: 51, total allocated memory: 136 +---- -[[query-plan-set-node-properties-from-map]] -== Set Node Properties From Map // SetNodePropertiesFromMap - +[[query-plan-set-node-properties-from-map]] +== Set Node Properties From Map == The `SetNodePropertiesFromMap` operator is used when setting properties from a map on a node. - -.SetNodePropertiesFromMap -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MATCH (n) SET n = {weekday: 'Monday', meal: 'Lunch'} ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +---------------------------+---------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -4494,44 +3985,39 @@ Runtime version 4.4 | | +---------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +EmptyResult | | 35 | 0 | 0 | | | | Fused in Pipeline 0 | | | +---------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +SetNodePropertiesFromMap | n = {weekday: $autostring_0, meal: $autostring_1} | 35 | 35 | 105 | | | | Fused in Pipeline 0 | +| +SetNodePropertiesFromMap | n = {weekday: $autostring_0, meal: $autostring_1} | 35 | 35 | 35 | | | | Fused in Pipeline 0 | | | +---------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | n | 35 | 35 | 36 | 112 | 5/0 | 3.873 | Fused in Pipeline 0 | +| +AllNodesScan | n | 35 | 35 | 36 | 72 | 5/0 | 10.565 | Fused in Pipeline 0 | +---------------------------+---------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 141, total allocated memory: 176 ----- - -====== +Total database accesses: 71, total allocated memory: 136 +---- -[[query-plan-set-relationship-properties-from-map]] -== Set Relationship Properties From Map // SetRelationshipPropertiesFromMap - +[[query-plan-set-relationship-properties-from-map]] +== Set Relationship Properties From Map == The `SetRelationshipPropertiesFromMap` operator is used when setting properties from a map on a relationship. - -.SetRelationshipPropertiesFromMap -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MATCH (n)-[r]->(m) SET r = {weight: 5, unit: 'kg'} ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------------------------+-----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -4540,46 +4026,41 @@ Runtime version 4.4 | | +-----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +EmptyResult | | 18 | 0 | 0 | | | | Fused in Pipeline 0 | | | +-----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +SetRelationshipPropertiesFromMap | r = {weight: $autoint_0, unit: $autostring_1} | 18 | 18 | 54 | | | | Fused in Pipeline 0 | +| +SetRelationshipPropertiesFromMap | r = {weight: $autoint_0, unit: $autostring_1} | 18 | 18 | 18 | | | | Fused in Pipeline 0 | | | +-----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Expand(All) | (m)<-[r]-(n) | 18 | 18 | 36 | | | | Fused in Pipeline 0 | | | +-----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | m | 35 | 35 | 36 | 112 | 6/0 | 4.009 | Fused in Pipeline 0 | +| +AllNodesScan | m | 35 | 35 | 36 | 72 | 6/0 | 5.737 | Fused in Pipeline 0 | +-----------------------------------+-----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 126, total allocated memory: 192 ----- - -====== +Total database accesses: 90, total allocated memory: 152 +---- -[[query-plan-set-property]] -== Set Property // SetProperty - +[[query-plan-set-property]] +== Set Property == The `SetProperty` operator is used when setting a property on a node or relationship. - -.SetProperty -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- MATCH (n) SET n.checked = true ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -4588,86 +4069,75 @@ Runtime version 4.4 | | +------------------+----------------+------+---------+----------------+ | +---------------------+ | +EmptyResult | | 35 | 0 | 0 | | | | Fused in Pipeline 0 | | | +------------------+----------------+------+---------+----------------+ | +---------------------+ -| +SetProperty | n.checked = true | 35 | 35 | 70 | | | | Fused in Pipeline 0 | +| +SetProperty | n.checked = true | 35 | 35 | 35 | | | | Fused in Pipeline 0 | | | +------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AllNodesScan | n | 35 | 35 | 36 | 112 | 3/0 | 0.529 | Fused in Pipeline 0 | +| +AllNodesScan | n | 35 | 35 | 36 | 72 | 3/0 | 0.552 | Fused in Pipeline 0 | +-----------------+------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 106, total allocated memory: 176 ----- - -====== +Total database accesses: 71, total allocated memory: 136 +---- -[[query-plan-create-unique-constraint]] -== Create Unique Constraint // CreateUniqueConstraint - -The `CreateUniqueConstraint` operator creates a unique constraint on a set of properties for all nodes having a certain label. +[[query-plan-create-unique-constraint]] +== Create Unique Constraint == +The `CreateUniqueConstraint` operator creates a unique constraint on a property for all nodes having a certain label. The following query will create a unique constraint with the name `uniqueness` on the `name` property of nodes with the `Country` label. - -.CreateUniqueConstraint -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CREATE CONSTRAINT uniqueness -FOR (c:Country) REQUIRE c.name is UNIQUE +CREATE CONSTRAINT uniqueness ON (c:Country) ASSERT c.name is UNIQUE ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner ADMINISTRATION Runtime SCHEMA -Runtime version 4.4 +Runtime version 4.3 -+-------------------+------------------------------------------------------------------+ -| Operator | Details | -+-------------------+------------------------------------------------------------------+ -| +CreateConstraint | CONSTRAINT uniqueness FOR (c:Country) REQUIRE (c.name) IS UNIQUE | -+-------------------+------------------------------------------------------------------+ ++-------------------+----------------------------------------------------------------+ +| Operator | Details | ++-------------------+----------------------------------------------------------------+ +| +CreateConstraint | CONSTRAINT uniqueness ON (c:Country) ASSERT (c.name) IS UNIQUE | ++-------------------+----------------------------------------------------------------+ Total database accesses: ? ----- - -====== +---- +// DropUniqueConstraint (deprecated) [role=deprecated] [[query-plan-drop-unique-constraint]] -== Drop Unique Constraint -// DropUniqueConstraint (deprecated) - -The `DropUniqueConstraint` operator removes a unique constraint from all nodes having a certain set of properties and label. +== Drop Unique Constraint == +The `DropUniqueConstraint` operator removes a unique constraint from a property for all nodes having a certain label. The following query will drop a unique constraint on the `name` property of nodes with the `Country` label. - -.DropUniqueConstraint -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- DROP CONSTRAINT ON (c:Country) ASSERT c.name is UNIQUE ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner ADMINISTRATION Runtime SCHEMA -Runtime version 4.4 +Runtime version 4.3 +-----------------+-----------------------------------------------------+ | Operator | Details | @@ -4676,125 +4146,111 @@ Runtime version 4.4 +-----------------+-----------------------------------------------------+ Total database accesses: ? ----- - -====== +---- -[[query-plan-do-nothing-if-exists-constraint]] -== Do Nothing If Exists (constraint) // DoNothingIfExists(CONSTRAINT) - +[[query-plan-create-constraint-only-if-it-does-not-already-exist]] +== Create Constraint only if it does not already exist == To not get an error creating the same constraint twice, we use the `DoNothingIfExists` operator for constraints. This will make sure no other constraint with the given name or another constraint of the same type and schema already exists before the specific `CreateConstraint` operator creates the constraint. If it finds a constraint with the given name or with the same type and schema it will stop the execution and no new constraint is created. -The following query will create a unique constraint with the name `uniqueness` on the `name` property of nodes with the `Country` label only if no constraint named `uniqueness` or unique constraint on `+(:Country {name})+` already exists. - - -.DoNothingIfExists(CONSTRAINT) -====== +The following query will create a unique constraint with the name `uniqueness` on the `name` property of nodes with the `Country` label only if +no constraint named `uniqueness` or unique constraint on `(:Country \{name})` already exists. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CREATE CONSTRAINT uniqueness IF NOT EXISTS -FOR (c:Country) REQUIRE c.name is UNIQUE +CREATE CONSTRAINT uniqueness IF NOT EXISTS ON (c:Country) ASSERT c.name is UNIQUE ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner ADMINISTRATION Runtime SCHEMA -Runtime version 4.4 +Runtime version 4.3 -+--------------------------------+------------------------------------------------------------------+ -| Operator | Details | -+--------------------------------+------------------------------------------------------------------+ -| +CreateConstraint | CONSTRAINT uniqueness FOR (c:Country) REQUIRE (c.name) IS UNIQUE | -| | +------------------------------------------------------------------+ -| +DoNothingIfExists(CONSTRAINT) | CONSTRAINT uniqueness FOR (c:Country) REQUIRE (c.name) IS UNIQUE | -+--------------------------------+------------------------------------------------------------------+ ++--------------------------------+----------------------------------------------------------------+ +| Operator | Details | ++--------------------------------+----------------------------------------------------------------+ +| +CreateConstraint | CONSTRAINT uniqueness ON (c:Country) ASSERT (c.name) IS UNIQUE | +| | +----------------------------------------------------------------+ +| +DoNothingIfExists(CONSTRAINT) | CONSTRAINT uniqueness ON (c:Country) ASSERT (c.name) IS UNIQUE | ++--------------------------------+----------------------------------------------------------------+ Total database accesses: ? ----- - -====== +---- -[[query-plan-create-node-property-existence-constraint]] -== Create Node Property Existence Constraint // CreateNodePropertyExistenceConstraint - +[[query-plan-create-node-property-existence-constraint]] +== Create Node Property Existence Constraint == The `CreateNodePropertyExistenceConstraint` operator creates an existence constraint with the name `existence` on a property for all nodes having a certain label. This will only appear in Enterprise Edition. - - -.CreateNodePropertyExistenceConstraint -====== + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CREATE CONSTRAINT existence -FOR (p:Person) REQUIRE p.name IS NOT NULL +CREATE CONSTRAINT existence ON (p:Person) ASSERT p.name IS NOT NULL ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner ADMINISTRATION Runtime SCHEMA -Runtime version 4.4 +Runtime version 4.3 -+-------------------+------------------------------------------------------------------+ -| Operator | Details | -+-------------------+------------------------------------------------------------------+ -| +CreateConstraint | CONSTRAINT existence FOR (p:Person) REQUIRE (p.name) IS NOT NULL | -+-------------------+------------------------------------------------------------------+ ++-------------------+----------------------------------------------------------------+ +| Operator | Details | ++-------------------+----------------------------------------------------------------+ +| +CreateConstraint | CONSTRAINT existence ON (p:Person) ASSERT (p.name) IS NOT NULL | ++-------------------+----------------------------------------------------------------+ Total database accesses: ? ----- - -====== +---- +// DropNodePropertyExistenceConstraint (deprecated) [role=deprecated] [[query-plan-drop-node-property-existence-constraint]] -== Drop Node Property Existence Constraint -// DropNodePropertyExistenceConstraint (deprecated) - +== Drop Node Property Existence Constraint == The `DropNodePropertyExistenceConstraint` operator removes an existence constraint from a property for all nodes having a certain label. This will only appear in Enterprise Edition. - - -.DropNodePropertyExistenceConstraint -====== + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- DROP CONSTRAINT ON (p:Person) ASSERT exists(p.name) ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner ADMINISTRATION Runtime SCHEMA -Runtime version 4.4 +Runtime version 4.3 +-----------------+------------------------------------------------+ | Operator | Details | @@ -4803,81 +4259,72 @@ Runtime version 4.4 +-----------------+------------------------------------------------+ Total database accesses: ? ----- - -====== +---- -[[query-plan-create-node-key-constraint]] -== Create Node Key Constraint // CreateNodeKeyConstraint - +[[query-plan-create-node-key-constraint]] +== Create Node Key Constraint == The `CreateNodeKeyConstraint` operator creates a node key constraint with the name `node_key` which ensures that all nodes with a particular label have a set of defined properties whose combined value is unique, and where all properties in the set are present. This will only appear in Enterprise Edition. - - -.CreateNodeKeyConstraint -====== + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CREATE CONSTRAINT node_key -FOR (e:Employee) REQUIRE (e.firstname, e.surname) IS NODE KEY +CREATE CONSTRAINT node_key ON (e:Employee) ASSERT (e.firstname, e.surname) IS NODE KEY ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner ADMINISTRATION Runtime SCHEMA -Runtime version 4.4 +Runtime version 4.3 -+-------------------+-----------------------------------------------------------------------------------+ -| Operator | Details | -+-------------------+-----------------------------------------------------------------------------------+ -| +CreateConstraint | CONSTRAINT node_key FOR (e:Employee) REQUIRE (e.firstname, e.surname) IS NODE KEY | -+-------------------+-----------------------------------------------------------------------------------+ ++-------------------+---------------------------------------------------------------------------------+ +| Operator | Details | ++-------------------+---------------------------------------------------------------------------------+ +| +CreateConstraint | CONSTRAINT node_key ON (e:Employee) ASSERT (e.firstname, e.surname) IS NODE KEY | ++-------------------+---------------------------------------------------------------------------------+ Total database accesses: ? ----- - -====== +---- +// DropNodeKeyConstraint (deprecated) [role=deprecated] [[query-plan-drop-node-key-constraint]] -== Drop Node Key Constraint -// DropNodeKeyConstraint (deprecated) - +== Drop Node Key Constraint == The `DropNodeKeyConstraint` operator removes a node key constraint from a set of properties for all nodes having a certain label. This will only appear in Enterprise Edition. - - -.DropNodeKeyConstraint -====== + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- DROP CONSTRAINT ON (e:Employee) ASSERT (e.firstname, e.surname) IS NODE KEY ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner ADMINISTRATION Runtime SCHEMA -Runtime version 4.4 +Runtime version 4.3 +-----------------+------------------------------------------------------------------------+ | Operator | Details | @@ -4886,80 +4333,70 @@ Runtime version 4.4 +-----------------+------------------------------------------------------------------------+ Total database accesses: ? ----- - -====== +---- -[[query-plan-create-relationship-property-existence-constraint]] -== Create Relationship Property Existence Constraint // CreateRelationshipPropertyExistenceConstraint - +[[query-plan-create-relationship-property-existence-constraint]] +== Create Relationship Property Existence Constraint == The `CreateRelationshipPropertyExistenceConstraint` operator creates an existence constraint with the name `existence` on a property for all relationships of a certain type. This will only appear in Enterprise Edition. - - -.CreateRelationshipPropertyExistenceConstraint -====== + .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CREATE CONSTRAINT existence -FOR ()-[l:LIKED]-() REQUIRE l.when IS NOT NULL +CREATE CONSTRAINT existence ON ()-[l:LIKED]-() ASSERT l.when IS NOT NULL ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner ADMINISTRATION Runtime SCHEMA -Runtime version 4.4 +Runtime version 4.3 -+-------------------+-----------------------------------------------------------------------+ -| Operator | Details | -+-------------------+-----------------------------------------------------------------------+ -| +CreateConstraint | CONSTRAINT existence FOR ()-[l:LIKED]-() REQUIRE (l.when) IS NOT NULL | -+-------------------+-----------------------------------------------------------------------+ ++-------------------+---------------------------------------------------------------------+ +| Operator | Details | ++-------------------+---------------------------------------------------------------------+ +| +CreateConstraint | CONSTRAINT existence ON ()-[l:LIKED]-() ASSERT (l.when) IS NOT NULL | ++-------------------+---------------------------------------------------------------------+ Total database accesses: ? ----- - -====== +---- +// DropRelationshipPropertyExistenceConstraint (deprecated) [role=deprecated] [[query-plan-drop-relationship-property-existence-constraint]] -== Drop Relationship Property Existence Constraint -// DropRelationshipPropertyExistenceConstraint (deprecated) - +== Drop Relationship Property Existence Constraint == The `DropRelationshipPropertyExistenceConstraint` operator removes an existence constraint from a property for all relationships of a certain type. This will only appear in Enterprise Edition. - -.DropRelationshipPropertyExistenceConstraint -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- DROP CONSTRAINT ON ()-[l:LIKED]-() ASSERT exists(l.when) ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner ADMINISTRATION Runtime SCHEMA -Runtime version 4.4 +Runtime version 4.3 +-----------------+-----------------------------------------------------+ | Operator | Details | @@ -4968,37 +4405,32 @@ Runtime version 4.4 +-----------------+-----------------------------------------------------+ Total database accesses: ? ----- - -====== +---- -[[query-plan-drop-constraint]] -== Drop Constraint // DropConstraint - +[[query-plan-drop-constraint-by-name]] +== Drop Constraint by name == The `DropConstraint` operator removes a constraint using the name of the constraint, no matter the type. - -.DropConstraint -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- DROP CONSTRAINT name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner ADMINISTRATION Runtime SCHEMA -Runtime version 4.4 +Runtime version 4.3 +-----------------+-----------------+ | Operator | Details | @@ -5007,38 +4439,32 @@ Runtime version 4.4 +-----------------+-----------------+ Total database accesses: ? ----- - -====== +---- -[[query-plan-show-constraints]] -== Show Constraints // ShowConstraints - -The `ShowConstraints` operator lists constraints. -It may include filtering on constraint type and can have either default or full output. - - -.ShowConstraints -====== +[[query-plan-listing-constraints]] +== Listing constraints == +The `ShowConstraints` operator lists constraints. It may include filtering on constraint type and can have either default or full output. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- SHOW CONSTRAINTS ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime SLOTTED -Runtime version 4.4 +Runtime version 4.3 +------------------+---------------------------------------------------------------------+----------------+------+---------+------------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | @@ -5049,165 +4475,142 @@ Runtime version 4.4 +------------------+---------------------------------------------------------------------+----------------+------+---------+------------------------+ Total database accesses: 1, total allocated memory: 64 ----- - -====== +---- -[[query-plan-create-index]] -== Create Index // CreateIndex - -The `CreateIndex` operator creates an index. This index can either be a b-tree, fulltext, text, or token lookup index. +[[query-plan-create-index]] +== Create Index == +The `CreateIndex` operator creates an index. This index can either be a b-tree, fulltext, or token lookup index. The following query will create an index with the name `my_index` on the `name` property of nodes with the `Country` label. - -.CreateIndex -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CREATE INDEX my_index -FOR (c:Country) ON (c.name) +CREATE INDEX my_index FOR (c:Country) ON (c.name) ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner ADMINISTRATION Runtime SCHEMA -Runtime version 4.4 +Runtime version 4.3 -+--------------+-----------------------------------------------+ -| Operator | Details | -+--------------+-----------------------------------------------+ -| +CreateIndex | BTREE INDEX my_index FOR (:Country) ON (name) | -+--------------+-----------------------------------------------+ ++--------------+-----------------------------------------+ +| Operator | Details | ++--------------+-----------------------------------------+ +| +CreateIndex | INDEX my_index FOR (:Country) ON (name) | ++--------------+-----------------------------------------+ Total database accesses: ? ----- - -====== +---- -[[query-plan-do-nothing-if-exists-index]] -== Do Nothing If Exits (index) // DoNothingIfExists(INDEX) - +[[query-plan-create-index-only-if-it-does-not-already-exist]] +== Create Index only if it does not already exist == To not get an error creating the same index twice, we use the `DoNothingIfExists` operator for indexes. This will make sure no other index with the given name or schema already exists before the `CreateIndex` operator creates an index. If it finds an index with the given name or schema it will stop the execution and no new index is created. The following query will create an index with the name `my_index` on the `since` property of relationships with the `KNOWS` relationship type only if no such index already exists. - -.DoNothingIfExists(INDEX) -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- -CREATE INDEX my_index IF NOT EXISTS -FOR ()-[k:KNOWS]-() ON (k.since) +CREATE INDEX my_index IF NOT EXISTS FOR ()-[k:KNOWS]-() ON (k.since) ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner ADMINISTRATION Runtime SCHEMA -Runtime version 4.4 +Runtime version 4.3 -+---------------------------+----------------------------------------------------+ -| Operator | Details | -+---------------------------+----------------------------------------------------+ -| +CreateIndex | BTREE INDEX my_index FOR ()-[:KNOWS]-() ON (since) | -| | +----------------------------------------------------+ -| +DoNothingIfExists(INDEX) | BTREE INDEX my_index FOR ()-[:KNOWS]-() ON (since) | -+---------------------------+----------------------------------------------------+ ++---------------------------+----------------------------------------------+ +| Operator | Details | ++---------------------------+----------------------------------------------+ +| +CreateIndex | INDEX my_index FOR ()-[:KNOWS]-() ON (since) | +| | +----------------------------------------------+ +| +DoNothingIfExists(INDEX) | INDEX my_index FOR ()-[:KNOWS]-() ON (since) | ++---------------------------+----------------------------------------------+ Total database accesses: ? ----- - -====== +---- +// DropIndex (deprecated) [role=deprecated] [[query-plan-drop-index-by-schema]] -== Drop Index by schema -// DropIndex (deprecated) - +== Drop Index by schema == The `DropIndex` operator removes an index from a property for all nodes having a certain label. - - -.DropIndex -====== - The following query will drop an index on the `name` property of nodes with the `Country` label. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- DROP INDEX ON :Country(name) ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner ADMINISTRATION Runtime SCHEMA -Runtime version 4.4 +Runtime version 4.3 -+------------+--------------------------------------+ -| Operator | Details | -+------------+--------------------------------------+ -| +DropIndex | BTREE INDEX FOR (:Country) ON (name) | -+------------+--------------------------------------+ ++------------+--------------------------------+ +| Operator | Details | ++------------+--------------------------------+ +| +DropIndex | INDEX FOR (:Country) ON (name) | ++------------+--------------------------------+ Total database accesses: ? ----- - -====== +---- -[[query-plan-drop-index]] -== Drop Index // DropIndex - +[[query-plan-drop-index-by-name]] +== Drop Index by name == The `DropIndex` operator removes an index using the name of the index. - -.DropIndex -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- DROP INDEX name ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner ADMINISTRATION Runtime SCHEMA -Runtime version 4.4 +Runtime version 4.3 +------------+------------+ | Operator | Details | @@ -5216,38 +4619,32 @@ Runtime version 4.4 +------------+------------+ Total database accesses: ? ----- - -====== +---- -[[query-plan-show-indexes]] -== Show Indexes // ShowIndexes - -The `ShowIndexes` operator lists indexes. -It may include filtering on index type and can have either default or full output. - - -.ShowIndexes -====== +[[query-plan-listing-indexes]] +== Listing indexes == +The `ShowIndexes` operator lists indexes. It may include filtering on index type and can have either default or full output. .Query -[source, cypher, role="noplay"] +[source,cypher] ---- SHOW INDEXES ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime SLOTTED -Runtime version 4.4 +Runtime version 4.3 +-----------------+----------------------------------------------------------------------------------------------+----------------+------+---------+------------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | @@ -5259,81 +4656,71 @@ Runtime version 4.4 +-----------------+----------------------------------------------------------------------------------------------+----------------+------+---------+------------------------+ Total database accesses: 1, total allocated memory: 64 ----- - -====== +---- -[[query-plan-show-functions]] -== Show Functions // ShowFunctions - +[[query-plan-listing-functions]] +== Listing functions == The `ShowFunctions` operator lists functions. It may include filtering on built-in vs user-defined functions as well as if a given user can execute the function. The output can either be default or full output. - -.ShowFunctions -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- SHOW FUNCTIONS ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime SLOTTED -Runtime version 4.4 +Runtime version 4.3 +-----------------+-----------------------------------------------------+----------------+------+---------+------------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | +-----------------+-----------------------------------------------------+----------------+------+---------+------------------------+ -| +ProduceResults | name, category, description | 10 | 143 | 0 | 0/0 | +| +ProduceResults | name, category, description | 10 | 142 | 0 | 0/0 | | | +-----------------------------------------------------+----------------+------+---------+------------------------+ -| +ShowFunctions | allFunctions, functionsForUser(all), defaultColumns | 10 | 143 | 0 | 0/0 | +| +ShowFunctions | allFunctions, functionsForUser(all), defaultColumns | 10 | 142 | 0 | 0/0 | +-----------------+-----------------------------------------------------+----------------+------+---------+------------------------+ Total database accesses: 0, total allocated memory: 64 ----- - -====== +---- -[[query-plan-show-procedures]] -== Show Procedures // ShowProcedures - +[[query-plan-listing-procedures]] +== Listing procedures == The `ShowProcedures` operator lists procedures. It may include filtering on whether a given user can execute the procedure and can have either default or full output. - -.ShowProcedures -====== - .Query -[source, cypher, role="noplay"] +[source,cypher] ---- SHOW PROCEDURES ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime SLOTTED -Runtime version 4.4 +Runtime version 4.3 +-----------------+----------------------------------------+----------------+------+---------+------------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | @@ -5344,91 +4731,5 @@ Runtime version 4.4 +-----------------+----------------------------------------+----------------+------+---------+------------------------+ Total database accesses: 0, total allocated memory: 64 ----- - -====== - - -[[query-plan-show-transactions]] -== Show Transactions -// ShowTransactions - -The `ShowTransactions` operator lists transactions. -It may include filtering on given ids and can have either default or full output. - - -.ShowTransactions -====== - -.Query -[source, cypher, role="noplay"] ----- -SHOW TRANSACTIONS ----- - -.Query Plan -[source, query plan, role="noheader"] ----- -Compiler CYPHER 4.4 - -Planner COST - -Runtime SLOTTED - -Runtime version 4.4 - -+-------------------+-----------------------------------------------------------------------------------------------+----------------+------+---------+------------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | -+-------------------+-----------------------------------------------------------------------------------------------+----------------+------+---------+------------------------+ -| +ProduceResults | database, transactionId, currentQueryId, connectionId, clientAddress, username, currentQuery, | 10 | 1 | 0 | 0/0 | -| | | startTime, status, elapsedTime, allocatedBytes | | | | | -| | +-----------------------------------------------------------------------------------------------+----------------+------+---------+------------------------+ -| +ShowTransactions | defaultColumns, allTransactions | 10 | 1 | 0 | 0/0 | -+-------------------+-----------------------------------------------------------------------------------------------+----------------+------+---------+------------------------+ - -Total database accesses: 0, total allocated memory: 64 ----- - -====== - - -[[query-plan-terminate-transactions]] -== Terminate Transactions -// TerminateTransactions - -The `TerminateTransactions` operator terminates transactions by ID. - - -.TerminateTransactions -====== -.Query -[source, cypher, role="noplay"] ----- -TERMINATE TRANSACTIONS 'database-transaction-123' ----- - -.Query Plan -[source, query plan, role="noheader"] ----- -Compiler CYPHER 4.4 - -Planner COST - -Runtime SLOTTED - -Runtime version 4.4 - -+------------------------+----------------------------------------+----------------+------+---------+------------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Page Cache Hits/Misses | -+------------------------+----------------------------------------+----------------+------+---------+------------------------+ -| +ProduceResults | transactionId, username, message | 10 | 1 | 0 | 0/0 | -| | +----------------------------------------+----------------+------+---------+------------------------+ -| +TerminateTransactions | transactions: database-transaction-123 | 10 | 1 | 0 | 0/0 | -+------------------------+----------------------------------------+----------------+------+---------+------------------------+ - -Total database accesses: 0, total allocated memory: 64 ---- - -====== - diff --git a/modules/ROOT/pages/execution-plans/shortestpath-planning.adoc b/modules/ROOT/pages/execution-plans/shortestpath-planning.adoc index 139613b0c..bc5034371 100644 --- a/modules/ROOT/pages/execution-plans/shortestpath-planning.adoc +++ b/modules/ROOT/pages/execution-plans/shortestpath-planning.adoc @@ -1,284 +1,314 @@ -:description: Shortest path and how it is planned. - [[query-shortestpath-planning]] = Shortest path planning +:description: Shortest path finding in Cypher and how it is planned. -[abstract] --- -Shortest path finding in Cypher and how it is planned. --- - -Planning shortest paths in Cypher can lead to different query plans depending on the predicates that need to be evaluated. -Internally, Neo4j will use a fast bidirectional breadth-first search algorithm if the predicates can be evaluated whilst searching for the path. -Therefore, this fast algorithm will always be certain to return the right answer when there are universal predicates on the path; for example, when searching for the shortest path where all nodes have the `Person` label, or where there are no nodes with a `name` property. - -If the predicates need to inspect the whole path before deciding on whether it is valid or not, this fast algorithm cannot be relied on to find the shortest path, and Neo4j may have to resort to using a slower exhaustive depth-first search algorithm to find the path. -This means that query plans for shortest path queries with non-universal predicates will include a fallback to running the exhaustive search to find the path should the fast algorithm not succeed. -For example, depending on the data, an answer to a shortest path query with existential predicates -- such as the requirement that at least one node contains the property `name='Kevin Bacon'` -- may not be able to be found by the fast algorithm. -In this case, Neo4j will fall back to using the exhaustive search to enumerate all paths and potentially return an answer. - -The running times of these two algorithms may differ by orders of magnitude, so it is important to ensure that the fast approach is used for time-critical queries. - -When the exhaustive search is planned, it is still only executed when the fast algorithm fails to find any matching paths. -The fast algorithm is always executed first, since it is possible that it can find a valid path even though that could not be guaranteed at planning time. - -Please note that falling back to the exhaustive search may prove to be a very time consuming strategy in some cases; such as when there is no shortest path between two nodes. -Therefore, in these cases, it is recommended to set `cypher.forbid_exhaustive_shortestpath` to `true`, as explained in link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_cypher.forbid_exhaustive_shortestpath[Operations Manual -> Configuration settings]. - - -== Shortest path -- fast algorithm - +Planning shortest paths in Cypher can lead to different query plans depending on the predicates that need +to be evaluated. Internally, Neo4j will use a fast bidirectional breadth-first search algorithm if the +predicates can be evaluated whilst searching for the path. Therefore, this fast algorithm will always +be certain to return the right answer when there are universal predicates on the path; for example, when +searching for the shortest path where all nodes have the `Person` label, or where there are no nodes with +a `name` property. -.Query evaluated with the fast algorith -====== +If the predicates need to inspect the whole path before deciding on whether it is valid or not, this fast +algorithm cannot be relied on to find the shortest path, and Neo4j may have to resort to using a slower +exhaustive depth-first search algorithm to find the path. This means that query plans for shortest path +queries with non-universal predicates will include a fallback to running the exhaustive search to find +the path should the fast algorithm not succeed. For example, depending on the data, an answer to a shortest +path query with existential predicates -- such as the requirement that at least one node contains the property +`name='Kevin Bacon'` -- may not be able to be found by the fast algorithm. In this case, Neo4j will fall back to using +the exhaustive search to enumerate all paths and potentially return an answer. -//// -CREATE - (KevinB:Person {name: 'Kevin Bacon'}), - (JackN:Person {name: 'Jack Nicholson'}), - (Keanu:Person {name: 'Keanu Reeves'}), - (Al:Person {name: 'Al Pacino'}), - (NancyM:Person {name: 'Nancy Meyers'}), - (RobR:Person {name: 'Rob Reiner'}), - (Taylor:Person {name: 'Taylor Hackford'}), +The running times of these two algorithms may differ by orders of magnitude, so it is important to ensure +that the fast approach is used for time-critical queries. - (AFewGoodMen:Movie {title: 'A Few Good Men'}), - (JackN)-[:ACTED_IN {role: 'Col. Nathan R. Jessup'}]->(AFewGoodMen), - (KevinB)-[:ACTED_IN {role: 'Capt. Jack Ross'}]->(AFewGoodMen), - (RobR)-[:DIRECTED]->(AFewGoodMen), +When the exhaustive search is planned, it is still only executed when the fast algorithm fails to find any +matching paths. The fast algorithm is always executed first, since it is possible that it can find a valid +path even though that could not be guaranteed at planning time. - (SomethingsGottaGive:Movie {title: 'Something´s Gotta Give'}), - (JackN)-[:ACTED_IN {role: 'Harry Sanborn'}]->(SomethingsGottaGive), - (Keanu)-[:ACTED_IN {role: 'Julian Mercer'}]->(SomethingsGottaGive), - (NancyM)-[:DIRECTED]->(SomethingsGottaGive), +Please note that falling back to the exhaustive search may prove to be a very time consuming strategy in some +cases; such as when there is no shortest path between two nodes. +Therefore, in these cases, it is recommended to set `cypher.forbid_exhaustive_shortestpath` to `true`, +as explained in link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_cypher.forbid_exhaustive_shortestpath[Operations Manual -> Configuration settings] - (TheDevilsAdvocate:Movie {title: 'The Devil´s Advocate'}), - (Keanu)-[:ACTED_IN {role: 'Kevin Lomax'}]->(TheDevilsAdvocate), - (Al)-[:ACTED_IN {role: 'John Milton'}]->(TheDevilsAdvocate) +== Shortest path with fast algorithm -CREATE INDEX FOR (n:Person) -ON (n.name) -//// - -This query can be evaluated with the fast algorithm -- there are no predicates that need to see the whole path before being evaluated. .Query -[source, cypher, role="noplay"] +[source, cypher] ---- -MATCH - (KevinB:Person {name: 'Kevin Bacon'}), - (Al:Person {name: 'Al Pacino'}), - p = shortestPath((KevinB)-[:ACTED_IN*]-(Al)) +MATCH (KevinB:Person {name: 'Kevin Bacon'} ), + (Al:Person {name: 'Al Pacino'}), + p = shortestPath((KevinB)-[:ACTED_IN*]-(Al)) WHERE all(r IN relationships(p) WHERE r.role IS NOT NULL) RETURN p ---- +This query can be evaluated with the fast algorithm -- there are no predicates that need to see the whole +path before being evaluated. + .Query plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +---------------------+------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +---------------------+------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| +ProduceResults | p | 2 | 1 | 0 | | 1/0 | 0.159 | In Pipeline 1 | +| +ProduceResults | p | 0 | 1 | 0 | | 0/0 | 0.217 | In Pipeline 1 | | | +------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| +ShortestPath | p = (KevinB)-[anon_0:ACTED_IN*]-(Al) WHERE all(r IN relationships(p) WHERE r.role IS NOT NULL) | 2 | 1 | 23 | 3176 | | | In Pipeline 1 | +| +ShortestPath | p = (KevinB)-[anon_0:ACTED_IN*]-(Al) WHERE all(r IN relationships(p) WHERE r.role IS NOT NULL) | 0 | 1 | 23 | 1704 | | | In Pipeline 1 | | | +------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| +MultiNodeIndexSeek | BTREE INDEX KevinB:Person(name) WHERE name = $autostring_0, | 2 | 1 | 4 | 112 | 1/1 | 0.233 | In Pipeline 0 | -| | BTREE INDEX Al:Person(name) WHERE name = $autostring_1 | | | | | | | | +| +MultiNodeIndexSeek | KevinB:Person(name) WHERE name = $autostring_0, Al:Person(name) WHERE name = $autostring_1 | 0 | 1 | 4 | 72 | 1/1 | 0.327 | In Pipeline 0 | +---------------------+------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -Total database accesses: 27, total allocated memory: 3240 ----- - -====== - +Total database accesses: 27, total allocated memory: 1768 -== Shortest path -- additional predicate checks on the paths - -Predicates used in the `WHERE` clause that apply to the shortest path pattern are evaluated before deciding what the shortest matching path is. - - -.Consider using the exhaustive search as a fallback -====== +---- -//// -CREATE - (KevinB:Person {name: 'Kevin Bacon'}), - (JackN:Person {name: 'Jack Nicholson'}), - (Keanu:Person {name: 'Keanu Reeves'}), - (Al:Person {name: 'Al Pacino'}), - (NancyM:Person {name: 'Nancy Meyers'}), - (RobR:Person {name: 'Rob Reiner'}), - (Taylor:Person {name: 'Taylor Hackford'}), +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(AFewGoodMen), + (KevinB)-[:ACTED_IN {role: 'Capt. Jack Ross'}]->(AFewGoodMen), + (RobR)-[:DIRECTED]->(AFewGoodMen), + + (SomethingsGottaGive:Movie {title: 'Something´s Gotta Give'}), + (JackN)-[:ACTED_IN {role: 'Harry Sanborn'}]->(SomethingsGottaGive), + (Keanu)-[:ACTED_IN {role: 'Julian Mercer'}]->(SomethingsGottaGive), + (NancyM)-[:DIRECTED]->(SomethingsGottaGive), + + (TheDevilsAdvocate:Movie {title: 'The Devil´s Advocate'}), + (Keanu)-[:ACTED_IN {role: 'Kevin Lomax'}]->(TheDevilsAdvocate), + (Al)-[:ACTED_IN {role: 'John Milton'}]->(TheDevilsAdvocate) +CREATE INDEX FOR (n:Person) ON (n.name) + +]]> +++++ +endif::nonhtmloutput[] - (AFewGoodMen:Movie {title: 'A Few Good Men'}), - (JackN)-[:ACTED_IN {role: 'Col. Nathan R. Jessup'}]->(AFewGoodMen), - (KevinB)-[:ACTED_IN {role: 'Capt. Jack Ross'}]->(AFewGoodMen), - (RobR)-[:DIRECTED]->(AFewGoodMen), +== Shortest path with additional predicate checks on the paths - (SomethingsGottaGive:Movie {title: 'Something´s Gotta Give'}), - (JackN)-[:ACTED_IN {role: 'Harry Sanborn'}]->(SomethingsGottaGive), - (Keanu)-[:ACTED_IN {role: 'Julian Mercer'}]->(SomethingsGottaGive), - (NancyM)-[:DIRECTED]->(SomethingsGottaGive), +=== Consider using the exhaustive search as a fallback - (TheDevilsAdvocate:Movie {title: 'The Devil´s Advocate'}), - (Keanu)-[:ACTED_IN {role: 'Kevin Lomax'}]->(TheDevilsAdvocate), - (Al)-[:ACTED_IN {role: 'John Milton'}]->(TheDevilsAdvocate) +Predicates used in the `WHERE` clause that apply to the shortest path pattern are evaluated before deciding +what the shortest matching path is. -CREATE INDEX FOR (n:Person) -ON (n.name) -//// .Query -[source, cypher, role="noplay"] +[source, cypher] ---- -MATCH - (KevinB:Person {name: 'Kevin Bacon'}), - (Al:Person {name: 'Al Pacino'}), - p = shortestPath((KevinB)-[*]-(Al)) +MATCH (KevinB:Person {name: 'Kevin Bacon'}), + (Al:Person {name: 'Al Pacino'}), + p = shortestPath((KevinB)-[*]-(Al)) WHERE length(p) > 1 RETURN p ---- -This query, in contrast with the one above, needs to check that the whole path follows the predicate before we know if it is valid or not, and so the query plan will also include the fallback to the slower exhaustive search algorithm. +This query, in contrast with the one above, needs to check that the whole path follows the predicate +before we know if it is valid or not, and so the query plan will also include the fallback to the slower +exhaustive search algorithm. .Query plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+--------------------------+-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+--------------------------+-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | p | 1 | 1 | 0 | | | | Fused in Pipeline 6 | -| | +-------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +AntiConditionalApply | | 1 | 1 | 0 | 41456 | 0/0 | 0.496 | Fused in Pipeline 6 | -| |\ +-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| | +Top | anon_1 ASC LIMIT 1 | 2 | 0 | 0 | 4280 | 0/0 | 0.000 | In Pipeline 5 | -| | | +-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| | +Projection | length(p) AS anon_1 | 7966 | 0 | 0 | | | | Fused in Pipeline 4 | -| | | +-------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +Filter | length(p) > $autoint_2 | 7966 | 0 | 0 | | | | Fused in Pipeline 4 | -| | | +-------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +Projection | (KevinB)-[anon_0*]-(Al) AS p | 26554 | 0 | 0 | | | | Fused in Pipeline 4 | -| | | +-------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +VarLengthExpand(Into) | (KevinB)-[anon_0*]-(Al) | 26554 | 0 | 0 | | | | Fused in Pipeline 4 | -| | | +-------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +Argument | KevinB, Al | 2 | 0 | 0 | 0 | 0/0 | 0.000 | Fused in Pipeline 4 | -| | +-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Apply | | 2 | 1 | 0 | | 0/0 | 0.021 | | -| |\ +-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| | +Optional | KevinB, Al | 2 | 1 | 0 | 37608 | 0/0 | 0.148 | In Pipeline 3 | -| | | +-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| | +ShortestPath | p = (KevinB)-[anon_0*]-(Al) WHERE length(p) > $autoint_2 | 1 | 1 | 1 | 32872 | | | In Pipeline 2 | -| | | +-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| | +Argument | KevinB, Al | 2 | 1 | 0 | 24680 | 0/0 | 0.032 | In Pipeline 1 | -| | +-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +MultiNodeIndexSeek | BTREE INDEX KevinB:Person(name) WHERE name = $autostring_0, | 2 | 1 | 4 | 112 | 2/0 | 0.330 | In Pipeline 0 | -| | BTREE INDEX Al:Person(name) WHERE name = $autostring_1 | | | | | | | | -+--------------------------+-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 5, total allocated memory: 50144 ----- - -====== - -The way the bigger exhaustive query plan works is by using `Apply`/`Optional` to ensure that when the fast algorithm does not find any results, a `null` result is generated instead of simply stopping the result stream. -On top of this, the planner will issue an `AntiConditionalApply`, which will run the exhaustive search if the path variable is pointing to `null` instead of a path. +Runtime version 4.3 + ++--------------------------+--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++--------------------------+--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | p | 0 | 1 | 0 | | | | Fused in Pipeline 6 | +| | +--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +AntiConditionalApply | | 0 | 1 | 0 | 1808 | 0/0 | 0.356 | Fused in Pipeline 6 | +| |\ +--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| | +Top | anon_1 ASC LIMIT 1 | 0 | 0 | 0 | 696 | 0/0 | 0.000 | In Pipeline 5 | +| | | +--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| | +Projection | length(p) AS anon_1 | 80 | 0 | 0 | | | | Fused in Pipeline 4 | +| | | +--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +Filter | length(p) > $autoint_2 | 80 | 0 | 0 | | | | Fused in Pipeline 4 | +| | | +--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +Projection | (KevinB)-[anon_0*]-(Al) AS p | 266 | 0 | 0 | | | | Fused in Pipeline 4 | +| | | +--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +VarLengthExpand(Into) | (KevinB)-[anon_0*]-(Al) | 266 | 0 | 0 | | | | Fused in Pipeline 4 | +| | | +--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +Argument | KevinB, Al | 0 | 0 | 0 | 0 | 0/0 | 0.000 | Fused in Pipeline 4 | +| | +--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +Apply | | 0 | 1 | 0 | | 0/0 | 0.025 | | +| |\ +--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| | +Optional | KevinB, Al | 0 | 1 | 0 | 2560 | 0/0 | 0.157 | In Pipeline 3 | +| | | +--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| | +ShortestPath | p = (KevinB)-[anon_0*]-(Al) WHERE length(p) > $autoint_2 | 0 | 1 | 1 | 1776 | | | In Pipeline 2 | +| | | +--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| | +Argument | KevinB, Al | 0 | 1 | 0 | 88 | 0/0 | 0.035 | In Pipeline 1 | +| | +--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +MultiNodeIndexSeek | KevinB:Person(name) WHERE name = $autostring_0, Al:Person(name) WHERE name = $autostring_1 | 0 | 1 | 4 | 72 | 2/0 | 0.368 | In Pipeline 0 | ++--------------------------+--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ + +Total database accesses: 5, total allocated memory: 3384 -An `ErrorPlan` operator will appear in the execution plan in cases where: - -* `cypher.forbid_exhaustive_shortestpath` is set to `true`. -* The fast algorithm is not able to find the shortest path. - - -.Prevent the exhaustive search from being used as a fallback -====== +---- -//// -CREATE - (KevinB:Person {name: 'Kevin Bacon'}), - (JackN:Person {name: 'Jack Nicholson'}), - (Keanu:Person {name: 'Keanu Reeves'}), - (Al:Person {name: 'Al Pacino'}), - (NancyM:Person {name: 'Nancy Meyers'}), - (RobR:Person {name: 'Rob Reiner'}), - (Taylor:Person {name: 'Taylor Hackford'}), +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(AFewGoodMen), + (KevinB)-[:ACTED_IN {role: 'Capt. Jack Ross'}]->(AFewGoodMen), + (RobR)-[:DIRECTED]->(AFewGoodMen), + + (SomethingsGottaGive:Movie {title: 'Something´s Gotta Give'}), + (JackN)-[:ACTED_IN {role: 'Harry Sanborn'}]->(SomethingsGottaGive), + (Keanu)-[:ACTED_IN {role: 'Julian Mercer'}]->(SomethingsGottaGive), + (NancyM)-[:DIRECTED]->(SomethingsGottaGive), + + (TheDevilsAdvocate:Movie {title: 'The Devil´s Advocate'}), + (Keanu)-[:ACTED_IN {role: 'Kevin Lomax'}]->(TheDevilsAdvocate), + (Al)-[:ACTED_IN {role: 'John Milton'}]->(TheDevilsAdvocate) +CREATE INDEX FOR (n:Person) ON (n.name) + +]]> 1 +RETURN p +]]> +++++ +endif::nonhtmloutput[] - (AFewGoodMen:Movie {title: 'A Few Good Men'}), - (JackN)-[:ACTED_IN {role: 'Col. Nathan R. Jessup'}]->(AFewGoodMen), - (KevinB)-[:ACTED_IN {role: 'Capt. Jack Ross'}]->(AFewGoodMen), - (RobR)-[:DIRECTED]->(AFewGoodMen), +The way the bigger exhaustive query plan works is by using `Apply`/`Optional` to ensure that when the +fast algorithm does not find any results, a `null` result is generated instead of simply stopping the result +stream. +On top of this, the planner will issue an `AntiConditionalApply`, which will run the exhaustive search +if the path variable is pointing to `null` instead of a path. - (SomethingsGottaGive:Movie {title: 'Something´s Gotta Give'}), - (JackN)-[:ACTED_IN {role: 'Harry Sanborn'}]->(SomethingsGottaGive), - (Keanu)-[:ACTED_IN {role: 'Julian Mercer'}]->(SomethingsGottaGive), - (NancyM)-[:DIRECTED]->(SomethingsGottaGive), +An `ErrorPlan` operator will appear in the execution plan in cases where (i) +`cypher.forbid_exhaustive_shortestpath` is set to `true`, and (ii) the fast algorithm is not able to find the shortest path. - (TheDevilsAdvocate:Movie {title: 'The Devil´s Advocate'}), - (Keanu)-[:ACTED_IN {role: 'Kevin Lomax'}]->(TheDevilsAdvocate), - (Al)-[:ACTED_IN {role: 'John Milton'}]->(TheDevilsAdvocate) +=== Prevent the exhaustive search from being used as a fallback -CREATE INDEX FOR (n:Person) -ON (n.name) -//// .Query -[source, cypher, role="noplay"] +[source, cypher] ---- -MATCH - (KevinB:Person {name: 'Kevin Bacon'}), - (Al:Person {name: 'Al Pacino'}), - p = shortestPath((KevinB)-[*]-(Al)) +MATCH (KevinB:Person {name: 'Kevin Bacon'}), + (Al:Person {name: 'Al Pacino'}), + p = shortestPath((KevinB)-[*]-(Al)) WITH p WHERE length(p) > 1 RETURN p ---- -This query, just like the one above, needs to check that the whole path follows the predicate before we know if it is valid or not. -However, the inclusion of the `WITH` clause means that the query plan will not include the fallback to the slower exhaustive search algorithm. -Instead, any paths found by the fast algorithm will subsequently be filtered, which may result in no answers being returned. +This query, just like the one above, needs to check that the whole path follows the predicate +before we know if it is valid or not. However, the inclusion of the `WITH` clause means that the query +plan will not include the fallback to the slower exhaustive search algorithm. Instead, any +paths found by the fast algorithm will subsequently be filtered, which may result in no answers + being returned. .Query plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+---------------------+-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+---------------------+-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| +ProduceResults | p | 1 | 1 | 0 | | 1/0 | 0.118 | In Pipeline 1 | -| | +-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| +Filter | length(p) > $autoint_2 | 1 | 1 | 0 | | 0/0 | 0.063 | In Pipeline 1 | -| | +-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| +ShortestPath | p = (KevinB)-[anon_0*]-(Al) | 2 | 1 | 1 | 3176 | | | In Pipeline 1 | -| | +-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ -| +MultiNodeIndexSeek | BTREE INDEX KevinB:Person(name) WHERE name = $autostring_0, | 2 | 1 | 4 | 112 | 2/0 | 0.226 | In Pipeline 0 | -| | BTREE INDEX Al:Person(name) WHERE name = $autostring_1 | | | | | | | | -+---------------------+-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ - -Total database accesses: 5, total allocated memory: 3240 +Runtime version 4.3 + ++---------------------+--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++---------------------+--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| +ProduceResults | p | 0 | 1 | 0 | | 0/0 | 0.496 | In Pipeline 1 | +| | +--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| +Filter | length(p) > $autoint_2 | 0 | 1 | 0 | | 0/0 | 0.149 | In Pipeline 1 | +| | +--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| +ShortestPath | p = (KevinB)-[anon_0*]-(Al) | 0 | 1 | 1 | 1776 | | | In Pipeline 1 | +| | +--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ +| +MultiNodeIndexSeek | KevinB:Person(name) WHERE name = $autostring_0, Al:Person(name) WHERE name = $autostring_1 | 0 | 1 | 4 | 72 | 2/0 | 0.276 | In Pipeline 0 | ++---------------------+--------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------+ + +Total database accesses: 5, total allocated memory: 1840 + ---- -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(AFewGoodMen), + (KevinB)-[:ACTED_IN {role: 'Capt. Jack Ross'}]->(AFewGoodMen), + (RobR)-[:DIRECTED]->(AFewGoodMen), + + (SomethingsGottaGive:Movie {title: 'Something´s Gotta Give'}), + (JackN)-[:ACTED_IN {role: 'Harry Sanborn'}]->(SomethingsGottaGive), + (Keanu)-[:ACTED_IN {role: 'Julian Mercer'}]->(SomethingsGottaGive), + (NancyM)-[:DIRECTED]->(SomethingsGottaGive), + + (TheDevilsAdvocate:Movie {title: 'The Devil´s Advocate'}), + (Keanu)-[:ACTED_IN {role: 'Kevin Lomax'}]->(TheDevilsAdvocate), + (Al)-[:ACTED_IN {role: 'John Milton'}]->(TheDevilsAdvocate) +CREATE INDEX FOR (n:Person) ON (n.name) + +]]> 1 +RETURN p +]]> +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/functions/aggregating.adoc b/modules/ROOT/pages/functions/aggregating.adoc index 6605c7257..944b325de 100644 --- a/modules/ROOT/pages/functions/aggregating.adoc +++ b/modules/ROOT/pages/functions/aggregating.adoc @@ -1,34 +1,28 @@ -:description: Aggregating functions take a set of values and calculate an aggregated value over them. - [[query-functions-aggregating]] = Aggregating functions - -[abstract] --- -Aggregating functions take a set of values and calculate an aggregated value over them. --- +:description: Aggregating functions take a set of values and calculate an aggregated value over them. Functions: -* xref::functions/aggregating.adoc#functions-avg[avg() - Numeric values] -* xref::functions/aggregating.adoc#functions-avg-duration[avg() - Durations] -* xref::functions/aggregating.adoc#functions-collect[collect()] -* xref::functions/aggregating.adoc#functions-count[count()] -* xref::functions/aggregating.adoc#functions-max[max()] -* xref::functions/aggregating.adoc#functions-min[min()] -* xref::functions/aggregating.adoc#functions-percentilecont[percentileCont()] -* xref::functions/aggregating.adoc#functions-percentiledisc[percentileDisc()] -* xref::functions/aggregating.adoc#functions-stdev[stDev()] -* xref::functions/aggregating.adoc#functions-stdevp[stDevP()] -* xref::functions/aggregating.adoc#functions-sum[sum() - Numeric values] -* xref::functions/aggregating.adoc#functions-sum-duration[sum() - Durations] +* xref:functions/aggregating.adoc#functions-avg[avg() - Numeric values] +* xref:functions/aggregating.adoc#functions-avg-duration[avg() - Durations] +* xref:functions/aggregating.adoc#functions-collect[collect()] +* xref:functions/aggregating.adoc#functions-count[count()] +* xref:functions/aggregating.adoc#functions-max[max()] +* xref:functions/aggregating.adoc#functions-min[min()] +* xref:functions/aggregating.adoc#functions-percentilecont[percentileCont()] +* xref:functions/aggregating.adoc#functions-percentiledisc[percentileDisc()] +* xref:functions/aggregating.adoc#functions-stdev[stDev()] +* xref:functions/aggregating.adoc#functions-stdevp[stDevP()] +* xref:functions/aggregating.adoc#functions-sum[sum() - Numeric values] +* xref:functions/aggregating.adoc#functions-sum-duration[sum() - Durations] Aggregation can be computed over all the matching paths, or it can be further divided by introducing grouping keys. Grouping keys are non-aggregate expressions, that are used to group the values going into the aggregate functions. Assume we have the following return statement: -[source, cypher, indent=0] +[source, cypher] ---- RETURN n, count(*) ---- @@ -43,75 +37,96 @@ To use aggregations to sort the result set, the aggregation must be included in The `DISTINCT` operator works in conjunction with aggregation. It is used to make all values unique before running them through an aggregate function. -More information about `DISTINCT` may be found in xref::syntax/operators.adoc#query-operators-aggregation[Syntax -> Aggregation operators]. +More information about `DISTINCT` may be found in xref:syntax/operators.adoc#query-operators-aggregation[Syntax -> Aggregation operators]. The following graph is used for the examples below: -image:graph_aggregating_functions.svg[] - -//// -CREATE - (a:Person {name: 'A', age: 13}), - (b:Person {name: 'B', age: 33, eyes: 'blue'}), - (c:Person {name: 'C', age: 44, eyes: 'blue'}), - (d1:Person {name: 'D', eyes: 'brown'}), - (d2:Person {name: 'D'}), - (book:Book {name: 'Cypher'}), - (a)-[:READS]->(book), - (a)-[:KNOWS]->(d1), - (a)-[:KNOWS]->(c), - (a)-[:KNOWS]->(b), - (c)-[:KNOWS]->(d2), - (b)-[:KNOWS]->(d2) -//// - +.Graph +["dot", "Aggregating functions-1.svg", "neoviz", ""] +---- + N0 [ + label = "{Person|age = 13\lname = \'A\'\l}" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N5 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "READS\n" + ] + N1 [ + label = "{Person|eyes = \'blue\'\lage = 33\lname = \'B\'\l}" + ] + N1 -> N4 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N2 [ + label = "{Person|eyes = \'blue\'\lage = 44\lname = \'C\'\l}" + ] + N2 -> N4 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N3 [ + label = "{Person|eyes = \'brown\'\lname = \'D\'\l}" + ] + N4 [ + label = "{Person|name = \'D\'\l}" + ] + N5 [ + label = "{Book|name = \'Cypher\'\l}" + ] + +---- + [[functions-avg]] == avg() - Numeric values The function `avg()` returns the average of a set of numeric values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -avg(expression) ----- +*Syntax:* `avg(expression)` *Returns:* - |=== - -| Either an Integer or a Float, depending on the values returned by `expression` and whether or not the calculation overflows. - +| +Either an Integer or a Float, depending on the values returned by `expression` and whether or not the calculation overflows. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| An expression returning a set of numeric values. - +| `expression` | An expression returning a set of numeric values. |=== *Considerations:* |=== - -| Any `null` values are excluded from the calculation. -| `avg(null)` returns `null`. - +|Any `null` values are excluded from the calculation. +|`avg(null)` returns `null`. |=== -.+avg()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) RETURN avg(n.age) @@ -122,64 +137,69 @@ The average of all the values in the property `age` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]> +++++ +endif::nonhtmloutput[] [[functions-avg-duration]] == avg() - Durations The function `avg()` returns the average of a set of Durations. -*Syntax:* - -[source, syntax, role="noheader"] ----- -avg(expression) ----- +*Syntax:* `avg(expression)` *Returns:* - |=== - -| A Duration. - +| +A Duration. |=== *Arguments:* - [options="header"] |=== | Name | Description - -| `expression` -| An expression returning a set of Durations. - +| `expression` | An expression returning a set of Durations. |=== *Considerations:* - |=== - -| Any `null` values are excluded from the calculation. -| `avg(null)` returns `null`. - +|Any `null` values are excluded from the calculation. +|`avg(null)` returns `null`. |=== -.+avg()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [duration('P2DT3H'), duration('PT1H45S')] AS dur RETURN avg(dur) @@ -190,64 +210,69 @@ The average of the two supplied Durations is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]> +++++ +endif::nonhtmloutput[] [[functions-collect]] == collect() The function `collect()` returns a single aggregated list containing the values returned by an expression. -*Syntax:* - -[source, syntax, role="noheader"] ----- -collect(expression) ----- +*Syntax:* `collect(expression)` *Returns:* - |=== - -| A list containing heterogeneous elements; the types of the elements are determined by the values returned by `expression`. - +| +A list containing heterogeneous elements; the types of the elements are determined by the values returned by `expression`. |=== *Arguments:* - [options="header"] |=== | Name | Description - -| `expression` -| An expression returning a set of values. - +| `expression` | An expression returning a set of values. |=== *Considerations:* - |=== - -| Any `null` values are ignored and will not be added to the list. -| `collect(null)` returns an empty list. - +|Any `null` values are ignored and will not be added to the list. +|`collect(null)` returns an empty list. |=== -.+collect()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) RETURN collect(n.age) @@ -258,15 +283,37 @@ All the values are collected and returned in a single list. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]> +++++ +endif::nonhtmloutput[] [[functions-count]] == count() @@ -276,52 +323,37 @@ The function `count()` returns the number of values or rows, and appears in two `count(*)`:: returns the number of matching rows. `count(expr)`:: returns the number of non-`null` values returned by an expression. -*Syntax:* - -[source, syntax, role="noheader"] ----- -count(expression) ----- +*Syntax:* `count(expression)` *Returns:* - |=== - -| An Integer. - +| +An Integer. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| An expression. - +| `expression` | An expression. |=== -*Considerations:* +*Considerations:* |=== - -| `count(*)` includes rows returning `null`. -| `count(expr)` ignores `null` values. -| `count(null)` returns `0`. - +|`count(*)` includes rows returning `null`. +|`count(expr)` ignores `null` values. +|`count(null)` returns `0`. |=== - === Using `count(*)` to return the number of nodes The function `count(*)` can be used to return the number of nodes; for example, the number of nodes connected to some node `n`. -.+count()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'A'})-->(x) RETURN labels(n), n.age, count(*) @@ -332,25 +364,45 @@ The labels and `age` property of the start node `n` and the number of nodes rela .Result [role="queryresult",options="header,footer",cols="3* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]>(x) +RETURN labels(n), n.age, count(*) +]]> +++++ +endif::nonhtmloutput[] === Using `count(*)` to group and count relationship types The function `count(*)` can be used to group the type of matched relationships and return the number. -.+count()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'A'})-[r]->() RETURN type(r), count(*) @@ -361,26 +413,95 @@ The type of matched relationships are grouped and the group count are returned. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) + +]]>() +RETURN type(r), count(*) +]]> +++++ +endif::nonhtmloutput[] + +=== Using `count(expression)` to return the number of values + +Instead of simply returning the number of rows with `count(*)`, it may be more useful to return the actual number of values returned by an expression. + +.Query +[source, cypher] +---- +MATCH (n {name: 'A'})-->(x) +RETURN count(x) +---- + +The number of nodes that are connected directly (one relationship) to the node, with the name `'A'`, is returned. + +.Result +[role="queryresult",options="header,footer",cols="1* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]>(x) +RETURN count(x) +]]> +++++ +endif::nonhtmloutput[] === Counting non-`null` values -Instead of simply returning the number of rows with `count(*)`, the function `count(expression)` can be used to return the number of non-`null` values returned by the expression. +The function `count(expression)` can be used to return the number of non-`null` values returned by the expression. -.+count()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) RETURN count(n.age) @@ -391,15 +512,37 @@ The number of nodes with the label `Person` and a property `age` is returned. (I .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]> +++++ +endif::nonhtmloutput[] === Counting with and without duplicates @@ -408,11 +551,9 @@ In this example we are trying to find all our friends of friends, and count them `count(DISTINCT friend_of_friend)`:: Will only count a `friend_of_friend` once, as `DISTINCT` removes the duplicates. `count(friend_of_friend)`:: Will consider the same `friend_of_friend` multiple times. -.+count()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (me:Person)-->(friend:Person)-->(friend_of_friend:Person) WHERE me.name = 'A' @@ -424,64 +565,72 @@ Both `B` and `C` know `D` and thus `D` will get counted twice when not using `DI .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]>(friend:Person)-->(friend_of_friend:Person) +WHERE me.name = 'A' +RETURN count(DISTINCT friend_of_friend), count(friend_of_friend) +]]> +++++ +endif::nonhtmloutput[] [[functions-max]] == max() The function `max()` returns the maximum value in a set of values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -max(expression) ----- +*Syntax:* `max(expression)` *Returns:* - |=== - -| A xref::syntax/values.adoc#property-types[property type], or a list, depending on the values returned by `expression`. - +| +A xref:syntax/values.adoc#property-types[property type], or a list, depending on the values returned by `expression`. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| An expression returning a set containing any combination of xref::syntax/values.adoc#property-types[property types] and lists thereof. - +| `expression` | An expression returning a set containing any combination of xref:syntax/values.adoc#property-types[property types] and lists thereof. |=== -*Considerations:* +*Considerations:* |=== - -| Any `null` values are excluded from the calculation. -| In a mixed set, any numeric value is always considered to be higher than any string value, and any string value is always considered to be higher than any list. -| Lists are compared in dictionary order, i.e. list elements are compared pairwise in ascending order from the start of the list to the end. -| `max(null)` returns `null`. - +|Any `null` values are excluded from the calculation. +|In a mixed set, any numeric value is always considered to be higher than any string value, and any string value is always considered to be higher than any list. +|Lists are compared in dictionary order, i.e. list elements are compared pairwise in ascending order from the start of the list to the end. +|`max(null)` returns `null`. |=== -.+max()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [1, 'a', null, 0.2, 'b', '1', '99'] AS val RETURN max(val) @@ -492,6 +641,8 @@ The highest of all the values in the mixed set -- in this case, the numeric valu [NOTE] ==== The value `'99'` (a string), is considered to be a lower value than `1` (an integer), because `'99'` is a string. + + ==== .Result @@ -502,14 +653,36 @@ The value `'99'` (a string), is considered to be a lower value than `1` (an inte 1+d|Rows: 1 |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]> +++++ +endif::nonhtmloutput[] -.+max()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [[1, 'a', 89], [1, 2]] AS val RETURN max(val) @@ -520,21 +693,41 @@ The highest of all the lists in the set -- in this case, the list `[1, 2]` -- is .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) + +]]> +++++ +endif::nonhtmloutput[] + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) RETURN max(n.age) @@ -545,65 +738,71 @@ The highest of all the values in the property `age` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]> +++++ +endif::nonhtmloutput[] [[functions-min]] == min() The function `min()` returns the minimum value in a set of values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -min(expression) ----- +*Syntax:* `min(expression)` *Returns:* - |=== - -| A xref::syntax/values.adoc#property-types[property type], or a list, depending on the values returned by `expression`. - +| +A xref:syntax/values.adoc#property-types[property type], or a list, depending on the values returned by `expression`. |=== -*Arguments:* +*Arguments:* [options="header"] |=== - | Name | Description - -| `expression` -| An expression returning a set containing any combination of xref::syntax/values.adoc#property-types[property types] and lists thereof. - +| `expression` | An expression returning a set containing any combination of xref:syntax/values.adoc#property-types[property types] and lists thereof. |=== -*Considerations:* +*Considerations:* |=== - -| Any `null` values are excluded from the calculation. -| In a mixed set, any string value is always considered to be lower than any numeric value, and any list is always considered to be lower than any string. -| Lists are compared in dictionary order, i.e. list elements are compared pairwise in ascending order from the start of the list to the end. -| `min(null)` returns `null`. - +|Any `null` values are excluded from the calculation. +|In a mixed set, any string value is always considered to be lower than any numeric value, and any list is always considered to be lower than any string. +|Lists are compared in dictionary order, i.e. list elements are compared pairwise in ascending order from the start of the list to the end. +|`min(null)` returns `null`. |=== -.+min()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [1, 'a', null, 0.2, 'b', '1', '99'] AS val RETURN min(val) @@ -620,14 +819,36 @@ Note that the (numeric) value `0.2`, which may _appear_ at first glance to be th 1+d|Rows: 1 |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]> +++++ +endif::nonhtmloutput[] -.+min()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND ['d', [1, 2], ['a', 'c', 23]] AS val RETURN min(val) @@ -638,21 +859,41 @@ The lowest of all the values in the set -- in this case, the list `['a', 'c', 23 .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]> +++++ +endif::nonhtmloutput[] -.+min()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) RETURN min(n.age) @@ -663,66 +904,72 @@ The lowest of all the values in the property `age` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]> +++++ +endif::nonhtmloutput[] [[functions-percentilecont]] == percentileCont() -The function `percentileCont()` returns the percentile of the given value over a group, with a percentile from `0.0` to `1.0`. +The function `percentileCont()` returns the percentile of the given value over a group, with a percentile from 0.0 to 1.0. It uses a linear interpolation method, calculating a weighted average between two values if the desired percentile lies between them. For nearest values using a rounding method, see `percentileDisc`. -*Syntax:* - -[source, syntax, role="noheader"] ----- -percentileCont(expression, percentile) ----- +*Syntax:* `percentileCont(expression, percentile)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression. - -| `percentile` -| A numeric value between `0.0` and `1.0`. - +| `expression` | A numeric expression. +| `percentile` | A numeric value between 0.0 and 1.0 |=== -*Considerations:* +*Considerations:* |=== - -| Any `null` values are excluded from the calculation. -| `percentileCont(null, percentile)` returns `null`. - +|Any `null` values are excluded from the calculation. +|`percentileCont(null, percentile)` returns `null`. |=== -.+percentileCont()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) RETURN percentileCont(n.age, 0.4) @@ -733,67 +980,72 @@ The 40th percentile of the values in the property `age` is returned, calculated .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]> +++++ +endif::nonhtmloutput[] [[functions-percentiledisc]] == percentileDisc() -The function `percentileDisc()` returns the percentile of the given value over a group, with a percentile from `0.0` to `1.0`. +The function `percentileDisc()` returns the percentile of the given value over a group, with a percentile from 0.0 to 1.0. It uses a rounding method and calculates the nearest value to the percentile. For interpolated values, see `percentileCont`. -*Syntax:* - -[source, syntax, role="noheader"] ----- -percentileDisc(expression, percentile) ----- +*Syntax:* `percentileDisc(expression, percentile)` *Returns:* - |=== - -| Either an Integer or a Float, depending on the values returned by `expression` and whether or not the calculation overflows. - +| +Either an Integer or a Float, depending on the values returned by `expression` and whether or not the calculation overflows. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression. - -| `percentile` -| A numeric value between `0.0` and `1.0`. - +| `expression` | A numeric expression. +| `percentile` | A numeric value between 0.0 and 1.0 |=== -*Considerations:* +*Considerations:* |=== - -| Any `null` values are excluded from the calculation. -| `percentileDisc(null, percentile)` returns `null`. - +|Any `null` values are excluded from the calculation. +|`percentileDisc(null, percentile)` returns `null`. |=== -.+percentileDisc()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) RETURN percentileDisc(n.age, 0.5) @@ -804,15 +1056,37 @@ The 50th percentile of the values in the property `age` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]> +++++ +endif::nonhtmloutput[] [[functions-stdev]] == stDev() @@ -821,46 +1095,32 @@ The function `stDev()` returns the standard deviation for the given value over a It uses a standard two-pass method, with `N - 1` as the denominator, and should be used when taking a sample of the population for an unbiased estimate. When the standard variation of the entire population is being calculated, `stdDevP` should be used. -*Syntax:* - -[source, syntax, role="noheader"] ----- -stDev(expression) ----- +*Syntax:* `stDev(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression. - +| `expression` | A numeric expression. |=== + *Considerations:* |=== - -| Any `null` values are excluded from the calculation. -| `stDev(null)` returns `0`. - +|Any `null` values are excluded from the calculation. +|`stDev(null)` returns `0`. |=== -.+stDev()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE n.name IN ['A', 'B', 'C'] @@ -872,15 +1132,38 @@ The standard deviation of the values in the property `age` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]> +++++ +endif::nonhtmloutput[] [[functions-stdevp]] == stDevP() @@ -889,47 +1172,32 @@ The function `stDevP()` returns the standard deviation for the given value over It uses a standard two-pass method, with `N` as the denominator, and should be used when calculating the standard deviation for an entire population. When the standard variation of only a sample of the population is being calculated, `stDev` should be used. -*Syntax:* - -[source, syntax, role="noheader"] ----- -stDevP(expression) ----- +*Syntax:* `stDevP(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression. - +| `expression` | A numeric expression. |=== -*Considerations:* +*Considerations:* |=== - -| Any `null` values are excluded from the calculation. -| `stDevP(null)` returns `0`. - +|Any `null` values are excluded from the calculation. +|`stDevP(null)` returns `0`. |=== -.+stDevP()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE n.name IN ['A', 'B', 'C'] @@ -941,62 +1209,70 @@ The population standard deviation of the values in the property `age` is returne .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]> +++++ +endif::nonhtmloutput[] [[functions-sum]] == sum() - Numeric values The function `sum()` returns the sum of a set of numeric values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -sum(expression) ----- +*Syntax:* `sum(expression)` *Returns:* - |=== - -| Either an Integer or a Float, depending on the values returned by `expression`. - +| +Either an Integer or a Float, depending on the values returned by `expression`. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| An expression returning a set of numeric values. - +| `expression` | An expression returning a set of numeric values. |=== -*Considerations:* +*Considerations:* |=== - -| Any `null` values are excluded from the calculation. -| `sum(null)` returns `0`. - +|Any `null` values are excluded from the calculation. +|`sum(null)` returns `0`. |=== -.+sum()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Person) RETURN sum(n.age) @@ -1007,60 +1283,68 @@ The sum of all the values in the property `age` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) +]]> +++++ +endif::nonhtmloutput[] [[functions-sum-duration]] == sum() - Durations The function `sum()` returns the sum of a set of durations. -*Syntax:* - -[source, syntax, role="noheader"] ----- -sum(expression) ----- +*Syntax:* `sum(expression)` *Returns:* - |=== - -| A Duration. - +| +A Duration. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| An expression returning a set of Durations. - +| `expression` | An expression returning a set of Durations. |=== -*Considerations:* +*Considerations:* |=== - -| Any `null` values are excluded from the calculation. - +|Any `null` values are excluded from the calculation. |=== -.+sum()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [duration('P2DT3H'), duration('PT1H45S')] AS dur RETURN sum(dur) @@ -1071,12 +1355,35 @@ The sum of the two supplied Durations is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(book), + (a)-[:KNOWS]->(d1), + (a)-[:KNOWS]->(c), + (a)-[:KNOWS]->(b), + (c)-[:KNOWS]->(d2), + (b)-[:KNOWS]->(d2) + +]]> +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/functions/index.adoc b/modules/ROOT/pages/functions/index.adoc index 90ca95657..1cb7d143d 100644 --- a/modules/ROOT/pages/functions/index.adoc +++ b/modules/ROOT/pages/functions/index.adoc @@ -1,402 +1,181 @@ -:description: This section contains information on all functions in the Cypher query language. - [[query-function]] = Functions - -[abstract] --- -This section contains information on all functions in the Cypher query language. --- - -* Predicate functions [xref::functions/index.adoc#header-query-functions-predicate[Summary]|xref::functions/predicate.adoc[Detail]] -* Scalar functions [xref::functions/index.adoc#header-query-functions-scalar[Summary]|xref::functions/scalar.adoc[Detail]] -* Aggregating functions [xref::functions/index.adoc#header-query-functions-aggregating[Summary]|xref::functions/aggregating.adoc[Detail]] -* List functions [xref::functions/index.adoc#header-query-functions-list[Summary]|xref::functions/list.adoc[Detail]] -* Mathematical functions - numeric [xref::functions/index.adoc#header-query-functions-numeric[Summary]|xref::functions/mathematical-numeric.adoc[Detail]] -* Mathematical functions - logarithmic [xref::functions/index.adoc#header-query-functions-logarithmic[Summary]|xref::functions/mathematical-logarithmic.adoc[Detail]] -* Mathematical functions - trigonometric [xref::functions/index.adoc#header-query-functions-trigonometric[Summary]|xref::functions/mathematical-trigonometric.adoc[Detail]] -* String functions [xref::functions/index.adoc#header-query-functions-string[Summary]|xref::functions/string.adoc[Detail]] -* Temporal functions - instant types [xref::functions/index.adoc#header-query-functions-temporal-instant-types[Summary]|xref::functions/temporal/index.adoc[Detail]] -* Temporal functions - duration [xref::functions/index.adoc#header-query-functions-temporal-duration[Summary]|xref::functions/temporal/duration.adoc[Detail]] -* Spatial functions [xref::functions/index.adoc#header-query-functions-spatial[Summary]|xref::functions/spatial.adoc[Detail]] -* LOAD CSV functions [xref::functions/index.adoc#header-query-functions-load-csv[Summary]|xref::functions/load-csv.adoc[Detail]] -* User-defined functions [xref::functions/index.adoc#header-query-functions-user-defined[Summary]|xref::functions/user-defined.adoc[Detail]] - -Related information may be found in xref::syntax/operators.adoc[Operators]. - -[NOTE] -==== +:description: This section contains information on all functions in the Cypher query language. + + +* Predicate functions [xref:functions/index.adoc#header-query-functions-predicate[Summary]|xref:functions/predicate.adoc[Detail]] +* Scalar functions [xref:functions/index.adoc#header-query-functions-scalar[Summary]|xref:functions/scalar.adoc[Detail]] +* Aggregating functions [xref:functions/index.adoc#header-query-functions-aggregating[Summary]|xref:functions/aggregating.adoc[Detail]] +* List functions [xref:functions/index.adoc#header-query-functions-list[Summary]|xref:functions/list.adoc[Detail]] +* Mathematical functions - numeric [xref:functions/index.adoc#header-query-functions-numeric[Summary]|xref:functions/mathematical-numeric.adoc[Detail]] +* Mathematical functions - logarithmic [xref:functions/index.adoc#header-query-functions-logarithmic[Summary]|xref:functions/mathematical-logarithmic.adoc[Detail]] +* Mathematical functions - trigonometric [xref:functions/index.adoc#header-query-functions-trigonometric[Summary]|xref:functions/mathematical-trigonometric.adoc[Detail]] +* String functions [xref:functions/index.adoc#header-query-functions-string[Summary]|xref:functions/string.adoc[Detail]] +* Temporal functions - instant types [xref:functions/index.adoc#header-query-functions-temporal-instant-types[Summary]|xref:functions/temporal/index.adoc[Detail]] +* Temporal functions - duration [xref:functions/index.adoc#header-query-functions-temporal-duration[Summary]|xref:functions/temporal/duration.adoc[Detail]] +* Spatial functions [xref:functions/index.adoc#header-query-functions-spatial[Summary]|xref:functions/spatial.adoc[Detail]] +* LOAD CSV functions [xref:functions/index.adoc#header-query-functions-load-csv[Summary]|xref:functions/load-csv.adoc[Detail]] +* User-defined functions [xref:functions/index.adoc#header-query-functions-user-defined[Summary]|xref:functions/user-defined.adoc[Detail]] + +Related information may be found in xref:syntax/operators.adoc[Operators]. + +.Please note * Functions in Cypher return `null` if an input parameter is `null`. * Functions taking a string as input all operate on _Unicode characters_ rather than on a standard `char[]`. - For example, the `size()` function applied to any _Unicode character_ will return `1`, even if the character does not fit in the 16 bits of one `char`. -==== - + For example, the `size()` function applied to any _Unicode character_ will return *1*, even if the character does not fit in the 16 bits of one `char`. .List available functions -====== -To list the available functions, run the following xref::clauses/listing-functions.adoc[Cypher query]: +==== +To list the available functions, run the following xref:clauses/listing-functions.adoc[Cypher query]: -[source, cypher, indent=0] +[source, cypher] ---- SHOW FUNCTIONS ---- -====== - +==== [[header-query-functions-predicate]] -**xref::functions/predicate.adoc[Predicate functions]** +**xref:functions/predicate.adoc[Predicate functions]** These functions return either true or false for the given arguments. [options="header"] |=== | Function | Signature | Description - -1.1+| xref::functions/predicate.adoc#functions-all[`all()`] -| `all(variable :: VARIABLE IN list :: LIST OF ANY? 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?)` -| 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?)` -| Returns true if a match for the pattern exists in the graph, or if the specified property exists in the node, relationship or map. - -1.3+| xref::functions/predicate.adoc#functions-isempty[`isEmpty()`] -| `isEmpty(input :: LIST? OF ANY?) :: (BOOLEAN?)` -| Checks whether a list is empty. -| `isEmpty(input :: MAP?) :: (BOOLEAN?)` -| Checks whether a map is empty. -| `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?)` -| 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?)` -| Returns true if the predicate holds for exactly one of the elements in the given list. - +1.1+| xref:functions/predicate.adoc#functions-all[`all()`] | `all(variable :: VARIABLE IN list :: LIST OF ANY? 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?)` | 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?)` | Returns true if a match for the pattern exists in the graph, or if the specified property exists in the node, relationship or map. +1.3+| xref:functions/predicate.adoc#functions-isempty[`isEmpty()`] | `isEmpty(input :: LIST? OF ANY?) :: (BOOLEAN?)` | Checks whether a list is empty. +| `isEmpty(input :: MAP?) :: (BOOLEAN?)` | Checks whether a map is empty. +| `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?)` | 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?)` | Returns true if the predicate holds for exactly one of the elements in the given list. |=== - [[header-query-functions-scalar]] -**xref::functions/scalar.adoc[Scalar functions]** +**xref:functions/scalar.adoc[Scalar functions]** These functions return a single value. [options="header"] |=== | Function | Signature | Description - -1.1+| xref::functions/scalar.adoc#functions-coalesce[`coalesce()`] -| `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?)` -| Returns the end node of a relationship. - -1.1+| xref::functions/scalar.adoc#functions-head[`head()`] -| `head(list :: LIST? OF ANY?) :: (ANY?)` -| Returns the first element in a list. - -1.2+| xref::functions/scalar.adoc#functions-id[`id()`] -| `id(input :: NODE?) :: (INTEGER?)` -| Returns the id of a node. -| `id(input :: RELATIONSHIP?) :: (INTEGER?)` -| Returns the id of a relationship. - -1.1+| xref::functions/scalar.adoc#functions-last[`last()`] -| `last(list :: LIST? OF ANY?) :: (ANY?)` -| Returns the last element in a list. - -1.1+| xref::functions/scalar.adoc#functions-length[`length()`] -| `length(input :: PATH?) :: (INTEGER?)` -| Returns the length of a path. - -1.3+| xref::functions/scalar.adoc#functions-properties[`properties()`] -| `properties(input :: MAP?) :: (MAP?)` -| Returns a map containing all the properties of a map. -| `properties(input :: NODE?) :: (MAP?)` -| Returns a map containing all the properties of a node. -| `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?)` -| Generates a random UUID. - -1.2+| xref::functions/scalar.adoc#functions-size[`size()`] -| `size(input :: LIST? OF ANY?) :: (INTEGER?)` -| Returns the number of items in a list. -| `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?)` -| Returns the start node of a relationship. - -1.3+| xref::functions/scalar.adoc#functions-toboolean[`toBoolean()`] -| `toBoolean(input :: STRING?) :: (BOOLEAN?)` -| Converts a string value to a boolean value. -| `toBoolean(input :: BOOLEAN?) :: (BOOLEAN?)` -| Converts a boolean value to a boolean value. -| `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?)` -| 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?)` -| Converts a number value to a floating point value. -| `toFloat(input :: STRING?) :: (FLOAT?)` -| Converts a string value to a floating point value. - -1.1+| xref::functions/scalar.adoc#functions-tofloatornull[`toFloatOrNull()`] -| `toFloatOrNull(input :: ANY?) :: (FLOAT?)` -| Converts a value to a floating point value, or null if the value cannot be converted. - -1.3+| xref::functions/scalar.adoc#functions-tointeger[`toInteger()`] -| `toInteger(input :: NUMBER?) :: (INTEGER?)` -| Converts a number value to an integer value. -| `toInteger(input :: BOOLEAN?) :: (INTEGER?)` -| Converts a boolean value to an integer value. -| `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?)` -| 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?)` -| Returns the string representation of the relationship type. - +1.1+| xref:functions/scalar.adoc#functions-coalesce[`coalesce()`] | `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?)` | Returns the end node of a relationship. +1.1+| xref:functions/scalar.adoc#functions-head[`head()`] | `head(list :: LIST? OF ANY?) :: (ANY?)` | Returns the first element in a list. +1.2+| xref:functions/scalar.adoc#functions-id[`id()`] | `id(input :: NODE?) :: (INTEGER?)` | Returns the id of a node. +| `id(input :: RELATIONSHIP?) :: (INTEGER?)` | Returns the id of a relationship. +1.1+| xref:functions/scalar.adoc#functions-last[`last()`] | `last(list :: LIST? OF ANY?) :: (ANY?)` | Returns the last element in a list. +1.1+| xref:functions/scalar.adoc#functions-length[`length()`] | `length(input :: PATH?) :: (INTEGER?)` | Returns the length of a path. +1.3+| xref:functions/scalar.adoc#functions-properties[`properties()`] | `properties(input :: MAP?) :: (MAP?)` | Returns a map containing all the properties of a map. +| `properties(input :: NODE?) :: (MAP?)` | Returns a map containing all the properties of a node. +| `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?)` | Generates a random UUID. +1.2+| xref:functions/scalar.adoc#functions-size[`size()`] | `size(input :: LIST? OF ANY?) :: (INTEGER?)` | Returns the number of items in a list. +| `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?)` | Returns the start node of a relationship. +1.3+| xref:functions/scalar.adoc#functions-toboolean[`toBoolean()`] | `toBoolean(input :: STRING?) :: (BOOLEAN?)` | Converts a string value to a boolean value. +| `toBoolean(input :: BOOLEAN?) :: (BOOLEAN?)` | Converts a boolean value to a boolean value. +| `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?)` | 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?)` | Converts a number value to a floating point value. +| `toFloat(input :: STRING?) :: (FLOAT?)` | Converts a string value to a floating point value. +1.1+| xref:functions/scalar.adoc#functions-tofloatornull[`toFloatOrNull()`] | `toFloatOrNull(input :: ANY?) :: (FLOAT?)` | Converts a value to a floating point value, or null if the value cannot be converted. +1.3+| xref:functions/scalar.adoc#functions-tointeger[`toInteger()`] | `toInteger(input :: NUMBER?) :: (INTEGER?)` | Converts a number value to an integer value. +| `toInteger(input :: BOOLEAN?) :: (INTEGER?)` | Converts a boolean value to an integer value. +| `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?)` | 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?)` | Returns the string representation of the relationship type. |=== - [[header-query-functions-aggregating]] -**xref::functions/aggregating.adoc[Aggregating functions]** +**xref:functions/aggregating.adoc[Aggregating functions]** These functions take multiple values as arguments, and calculate and return an aggregated value from them. [options="header"] |=== | Function | Signature | Description - -1.3+| xref::functions/aggregating.adoc#functions-avg[`avg()`] -| `avg(input :: DURATION?) :: (DURATION?)` -| Returns the average of a set of duration values. -| `avg(input :: FLOAT?) :: (FLOAT?)` -| Returns the average of a set of floating point values. -| `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?)` -| Returns a list containing the values returned by an expression. - -1.1+| xref::functions/aggregating.adoc#functions-count[`count()`] -| `count(input :: ANY?) :: (INTEGER?)` -| Returns the number of values or rows. - -1.1+| xref::functions/aggregating.adoc#functions-max[`max()`] -| `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?)` -| Returns the minimum value in a set of values. - -1.1+| xref::functions/aggregating.adoc#functions-percentilecont[`percentileCont()`] -| `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?)` -| Returns the nearest floating point value to the given percentile over a group using a rounding method. -| `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?)` -| 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?)` -| 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?)` -| Returns the sum of a set of durations -| `sum(input :: FLOAT?) :: (FLOAT?)` -| Returns the sum of a set of floats -| `sum(input :: INTEGER?) :: (INTEGER?)` -| Returns the sum of a set of integers - +1.3+| xref:functions/aggregating.adoc#functions-avg[`avg()`] | `avg(input :: DURATION?) :: (DURATION?)` | Returns the average of a set of duration values. +| `avg(input :: FLOAT?) :: (FLOAT?)` | Returns the average of a set of floating point values. +| `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?)` | Returns a list containing the values returned by an expression. +1.1+| xref:functions/aggregating.adoc#functions-count[`count()`] | `count(input :: ANY?) :: (INTEGER?)` | Returns the number of values or rows. +1.1+| xref:functions/aggregating.adoc#functions-max[`max()`] | `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?)` | Returns the minimum value in a set of values. +1.1+| xref:functions/aggregating.adoc#functions-percentilecont[`percentileCont()`] | `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?)` | Returns the nearest floating point value to the given percentile over a group using a rounding method. +| `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?)` | 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?)` | 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?)` | Returns the sum of a set of durations +| `sum(input :: FLOAT?) :: (FLOAT?)` | Returns the sum of a set of floats +| `sum(input :: INTEGER?) :: (INTEGER?)` | Returns the sum of a set of integers |=== - [[header-query-functions-list]] -**xref::functions/list.adoc[List functions]** +**xref:functions/list.adoc[List functions]** These functions return lists of other values. -Further details and examples of lists may be found in xref::syntax/lists.adoc[Lists]. +Further details and examples of lists may be found in xref:syntax/lists.adoc[Lists]. [options="header"] |=== - | Function | Signature | Description - -1.3+| xref::functions/list.adoc#functions-keys[`keys()`] -| `keys(input :: MAP?) :: (LIST? OF STRING?)` -| Returns a list containing the string representations for all the property names of a map. -| `keys(input :: NODE?) :: (LIST? OF STRING?)` -| Returns a list containing the string representations for all the property names of a node. -| `keys(input :: RELATIONSHIP?) :: (LIST? OF STRING?)` -| 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?)` -| 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?)` -| Returns a list containing all the nodes in a path. - -1.2+| xref::functions/list.adoc#functions-range[`range()`] -| `range(start :: INTEGER?, end :: INTEGER?) :: (LIST? OF INTEGER?)` -| Returns a list comprising all integer values within a specified range. -| `range(start :: INTEGER?, end :: INTEGER?, step :: INTEGER?) :: (LIST? OF INTEGER?)` -| 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?)` -| 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?)` -| Returns a list containing all the relationships in a path. - -1.1+| xref::functions/string.adoc#functions-reverse[`reverse()`] -| `reverse(input :: LIST? OF ANY?) :: (LIST? OF ANY?)` -| Returns a list in which the order of all elements in the original list have been reversed. - -1.1+| xref::functions/list.adoc#functions-tail[`tail()`] -| `tail(input :: LIST? OF ANY?) :: (LIST? OF ANY?)` -| 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?)` -a| -Converts a list of values to a list of boolean 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?)` -a| -Converts a list of values to a list of floating point values. -If any values are not convertible to floating point 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?)` -a| -Converts a list of values to a list of integer 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?)` -a| -Converts a list of values to a list of string values. -If any values are not convertible to string they will be null in the list returned. - +1.3+| xref:functions/list.adoc#functions-keys[`keys()`] | `keys(input :: MAP?) :: (LIST? OF STRING?)` | Returns a list containing the string representations for all the property names of a map. +| `keys(input :: NODE?) :: (LIST? OF STRING?)` | Returns a list containing the string representations for all the property names of a node. +| `keys(input :: RELATIONSHIP?) :: (LIST? OF STRING?)` | 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?)` | 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?)` | Returns a list containing all the nodes in a path. +1.2+| xref:functions/list.adoc#functions-range[`range()`] | `range(start :: INTEGER?, end :: INTEGER?) :: (LIST? OF INTEGER?)` | Returns a list comprising all integer values within a specified range. +| `range(start :: INTEGER?, end :: INTEGER?, step :: INTEGER?) :: (LIST? OF INTEGER?)` | 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?)` | 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?)` | Returns a list containing all the relationships in a path. +1.1+| xref:functions/string.adoc#functions-reverse[`reverse()`] | `reverse(input :: LIST? OF ANY?) :: (LIST? OF ANY?)` | Returns a list in which the order of all elements in the original list have been reversed. +1.1+| xref:functions/list.adoc#functions-tail[`tail()`] | `tail(input :: LIST? OF ANY?) :: (LIST? OF ANY?)` | 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?)` | Converts a list of values to a list of boolean 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?)` | Converts a list of values to a list of floating point values. If any values are not convertible to floating point 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?)` | Converts a list of values to a list of integer 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?)` | Converts a list of values to a list of string values. If any values are not convertible to string they will be null in the list returned. |=== - [[header-query-functions-numeric]] -**xref::functions/mathematical-numeric.adoc[Numeric functions]** +**xref:functions/mathematical-numeric.adoc[Numeric functions]** These functions all operate on numerical expressions only, and will return an error if used on any other values. [options="header"] |=== | Function | Signature | Description - -1.2+| xref::functions/mathematical-numeric.adoc#functions-abs[`abs()`] -| `abs(input :: FLOAT?) :: (FLOAT?)` -| Returns the absolute value of a floating point number. -| `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?)` -| Returns the smallest floating point number that is greater than or equal to a number and equal to a mathematical integer. - -1.1+| xref::functions/mathematical-numeric.adoc#functions-floor[`floor()`] -| `floor(input :: FLOAT?) :: (FLOAT?)` -| Returns the largest floating point number that is less than or equal to a number and equal to a mathematical integer. - -1.1+| xref::functions/mathematical-numeric.adoc#functions-rand[`rand()`] -| `rand() :: (FLOAT?)` -| Returns a random floating point number in the range from 0 (inclusive) to 1 (exclusive); i.e. [0,1). - -1.3+| xref::functions/mathematical-numeric.adoc#functions-round[`round()`] -| `round(input :: FLOAT?) :: (FLOAT?)` -| Returns the value of a number rounded to the nearest integer. -| `round(value :: FLOAT?, precision :: NUMBER?) :: (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?)` -| 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?)` -| Returns the signum of a floating point number: 0 if the number is 0, -1 for any negative number, and 1 for any positive number. -| `sign(input :: INTEGER?) :: (INTEGER?)` -| Returns the signum of an integer number: 0 if the number is 0, -1 for any negative number, and 1 for any positive number. - +1.2+| xref:functions/mathematical-numeric.adoc#functions-abs[`abs()`] | `abs(input :: FLOAT?) :: (FLOAT?)` | Returns the absolute value of a floating point number. +| `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?)` | Returns the smallest floating point number that is greater than or equal to a number and equal to a mathematical integer. +1.1+| xref:functions/mathematical-numeric.adoc#functions-floor[`floor()`] | `floor(input :: FLOAT?) :: (FLOAT?)` | Returns the largest floating point number that is less than or equal to a number and equal to a mathematical integer. +1.1+| xref:functions/mathematical-numeric.adoc#functions-rand[`rand()`] | `rand() :: (FLOAT?)` | Returns a random floating point number in the range from 0 (inclusive) to 1 (exclusive); i.e. [0,1). +1.3+| xref:functions/mathematical-numeric.adoc#functions-round[`round()`] | `round(input :: FLOAT?) :: (FLOAT?)` | Returns the value of a number rounded to the nearest integer. +| `round(value :: FLOAT?, precision :: NUMBER?) :: (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?)` | 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?)` | Returns the signum of a floating point number: 0 if the number is 0, -1 for any negative number, and 1 for any positive number. +| `sign(input :: INTEGER?) :: (INTEGER?)` | Returns the signum of an integer number: 0 if the number is 0, -1 for any negative number, and 1 for any positive number. |=== - [[header-query-functions-logarithmic]] -**xref::functions/mathematical-logarithmic.adoc[Logarithmic functions]** +**xref:functions/mathematical-logarithmic.adoc[Logarithmic functions]** These functions all operate on numerical expressions only, and will return an error if used on any other values. [options="header"] |=== | Function | Signature | Description - -1.1+| xref::functions/mathematical-logarithmic.adoc#functions-e[`e()`] -| `e() :: (FLOAT?)` -| Returns the base of the natural logarithm, e. - -1.1+| xref::functions/mathematical-logarithmic.adoc#functions-exp[`exp()`] -| `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?)` -| Returns the natural logarithm of a number. - -1.1+| xref::functions/mathematical-logarithmic.adoc#functions-log10[`log10()`] -| `log10(input :: FLOAT?) :: (FLOAT?)` -| Returns the common logarithm (base 10) of a number. - -1.1+| xref::functions/mathematical-logarithmic.adoc#functions-sqrt[`sqrt()`] -| `sqrt(input :: FLOAT?) :: (FLOAT?)` -| Returns the square root of a number. - +1.1+| xref:functions/mathematical-logarithmic.adoc#functions-e[`e()`] | `e() :: (FLOAT?)` | Returns the base of the natural logarithm, e. +1.1+| xref:functions/mathematical-logarithmic.adoc#functions-exp[`exp()`] | `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?)` | Returns the natural logarithm of a number. +1.1+| xref:functions/mathematical-logarithmic.adoc#functions-log10[`log10()`] | `log10(input :: FLOAT?) :: (FLOAT?)` | Returns the common logarithm (base 10) of a number. +1.1+| xref:functions/mathematical-logarithmic.adoc#functions-sqrt[`sqrt()`] | `sqrt(input :: FLOAT?) :: (FLOAT?)` | Returns the square root of a number. |=== - [[header-query-functions-trigonometric]] -**xref::functions/mathematical-trigonometric.adoc[Trigonometric functions]** +**xref:functions/mathematical-trigonometric.adoc[Trigonometric functions]** These functions all operate on numerical expressions only, and will return an error if used on any other values. @@ -405,353 +184,174 @@ All trigonometric functions operate on radians, unless otherwise specified. [options="header"] |=== | Function | Signature | Description - -1.1+| xref::functions/mathematical-trigonometric.adoc#functions-acos[`acos()`] -| `acos(input :: FLOAT?) :: (FLOAT?)` -| Returns the arccosine of a number in radians. - -1.1+| xref::functions/mathematical-trigonometric.adoc#functions-asin[`asin()`] -| `asin(input :: FLOAT?) :: (FLOAT?)` -| Returns the arcsine of a number in radians. - -1.1+| xref::functions/mathematical-trigonometric.adoc#functions-atan[`atan()`] -| `atan(input :: FLOAT?) :: (FLOAT?)` -| Returns the arctangent of a number in radians. - -1.1+| xref::functions/mathematical-trigonometric.adoc#functions-atan2[`atan2()`] -| `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?)` -| Returns the cosine of a number. - -1.1+| xref::functions/mathematical-trigonometric.adoc#functions-cot[`cot()`] -| `cot(input :: FLOAT?) :: (FLOAT?)` -| Returns the cotangent of a number. - -1.1+| xref::functions/mathematical-trigonometric.adoc#functions-degrees[`degrees()`] -| `degrees(input :: FLOAT?) :: (FLOAT?)` -| Converts radians to degrees. - -1.1+| xref::functions/mathematical-trigonometric.adoc#functions-haversin[`haversin()`] -| `haversin(input :: FLOAT?) :: (FLOAT?)` -| Returns half the versine of a number. - -1.1+| xref::functions/mathematical-trigonometric.adoc#functions-pi[`pi()`] -| `pi() :: (FLOAT?)` -| Returns the mathematical constant pi. - -1.1+| xref::functions/mathematical-trigonometric.adoc#functions-radians[`radians()`] -| `radians(input :: FLOAT?) :: (FLOAT?)` -| Converts degrees to radians. - -1.1+| xref::functions/mathematical-trigonometric.adoc#functions-sin[`sin()`] -| `sin(input :: FLOAT?) :: (FLOAT?)` -| Returns the sine of a number. - -1.1+| xref::functions/mathematical-trigonometric.adoc#functions-tan[`tan()`] -| `tan(input :: FLOAT?) :: (FLOAT?)` -| Returns the tangent of a number. - +1.1+| xref:functions/mathematical-trigonometric.adoc#functions-acos[`acos()`] | `acos(input :: FLOAT?) :: (FLOAT?)` | Returns the arccosine of a number in radians. +1.1+| xref:functions/mathematical-trigonometric.adoc#functions-asin[`asin()`] | `asin(input :: FLOAT?) :: (FLOAT?)` | Returns the arcsine of a number in radians. +1.1+| xref:functions/mathematical-trigonometric.adoc#functions-atan[`atan()`] | `atan(input :: FLOAT?) :: (FLOAT?)` | Returns the arctangent of a number in radians. +1.1+| xref:functions/mathematical-trigonometric.adoc#functions-atan2[`atan2()`] | `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?)` | Returns the cosine of a number. +1.1+| xref:functions/mathematical-trigonometric.adoc#functions-cot[`cot()`] | `cot(input :: FLOAT?) :: (FLOAT?)` | Returns the cotangent of a number. +1.1+| xref:functions/mathematical-trigonometric.adoc#functions-degrees[`degrees()`] | `degrees(input :: FLOAT?) :: (FLOAT?)` | Converts radians to degrees. +1.1+| xref:functions/mathematical-trigonometric.adoc#functions-haversin[`haversin()`] | `haversin(input :: FLOAT?) :: (FLOAT?)` | Returns half the versine of a number. +1.1+| xref:functions/mathematical-trigonometric.adoc#functions-pi[`pi()`] | `pi() :: (FLOAT?)` | Returns the mathematical constant pi. +1.1+| xref:functions/mathematical-trigonometric.adoc#functions-radians[`radians()`] | `radians(input :: FLOAT?) :: (FLOAT?)` | Converts degrees to radians. +1.1+| xref:functions/mathematical-trigonometric.adoc#functions-sin[`sin()`] | `sin(input :: FLOAT?) :: (FLOAT?)` | Returns the sine of a number. +1.1+| xref:functions/mathematical-trigonometric.adoc#functions-tan[`tan()`] | `tan(input :: FLOAT?) :: (FLOAT?)` | Returns the tangent of a number. |=== - [[header-query-functions-string]] -**xref::functions/string.adoc[String functions]** +**xref:functions/string.adoc[String functions]** These functions are used to manipulate strings or to create a string representation of another value. [options="header"] |=== | Function | Signature | Description - -1.1+| xref::functions/string.adoc#functions-left[`left()`] -| `left(original :: STRING?, length :: INTEGER?) :: (STRING?)` -| Returns a string containing the specified number of leftmost characters of the original string. - -1.1+| xref::functions/string.adoc#functions-ltrim[`ltrim()`] -| `ltrim(input :: STRING?) :: (STRING?)` -| Returns the original string with leading whitespace removed. - -1.1+| xref::functions/string.adoc#functions-replace[`replace()`] -| `replace(original :: STRING?, search :: STRING?, replace :: STRING?) :: (STRING?)` -| Returns a string in which all occurrences of a specified search string in the original string have been replaced by another (specified) replace string. - -1.1+| xref::functions/string.adoc#functions-reverse[`reverse()`] -| `reverse(input :: STRING?) :: (STRING?)` -| Returns a string in which the order of all characters in the original string have been reversed. - -1.1+| xref::functions/string.adoc#functions-right[`right()`] -| `right(original :: STRING?, length :: INTEGER?) :: (STRING?)` -| Returns a string containing the specified number of rightmost characters of the original string. - -1.1+| xref::functions/string.adoc#functions-rtrim[`rtrim()`] -| `rtrim(input :: STRING?) :: (STRING?)` -| Returns the original string with trailing whitespace removed. - -1.2+| xref::functions/string.adoc#functions-split[`split()`] -| `split(original :: STRING?, splitDelimiter :: STRING?) :: (LIST? OF STRING?)` -| Returns a list of strings resulting from the splitting of the original string around matches of the given delimiter. -| `split(original :: STRING?, splitDelimiters :: LIST? OF STRING?) :: (LIST? OF STRING?)` -| Returns a list of strings resulting from the splitting of the original string around matches of any of the given delimiters. - -1.2+| xref::functions/string.adoc#functions-substring[`substring()`] -| `substring(original :: STRING?, start :: INTEGER?) :: (STRING?)` -| Returns a substring of the original string, beginning with a 0-based index start. -| `substring(original :: STRING?, start :: INTEGER?, length :: INTEGER?) :: (STRING?)` -| Returns a substring of length 'length' of the original string, beginning with a 0-based index start. - -1.1+| xref::functions/string.adoc#functions-tolower[`toLower()`] -| `toLower(input :: STRING?) :: (STRING?)` -| Returns the original string in lowercase. - -1.1+| xref::functions/string.adoc#functions-tostring[`toString()`] -| `toString(input :: ANY?) :: (STRING?)` -| Converts an integer, float, boolean, point or temporal type (i.e. Date, Time, LocalTime, DateTime, LocalDateTime or Duration) value to a string. - -1.1+| xref::functions/string.adoc#functions-tostringornull[`toStringOrNull()`] -| `toStringOrNull(input :: ANY?) :: (STRING?)` -| Converts an integer, float, boolean, point or temporal type (i.e. Date, Time, LocalTime, DateTime, LocalDateTime 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?)` -| Returns the original string in uppercase. - -1.1+| xref::functions/string.adoc#functions-trim[`trim()`] -| `trim(input :: STRING?) :: (STRING?)` -| Returns the original string with leading and trailing whitespace removed. - +1.1+| xref:functions/string.adoc#functions-left[`left()`] | `left(original :: STRING?, length :: INTEGER?) :: (STRING?)` | Returns a string containing the specified number of leftmost characters of the original string. +1.1+| xref:functions/string.adoc#functions-ltrim[`ltrim()`] | `ltrim(input :: STRING?) :: (STRING?)` | Returns the original string with leading whitespace removed. +1.1+| xref:functions/string.adoc#functions-replace[`replace()`] | `replace(original :: STRING?, search :: STRING?, replace :: STRING?) :: (STRING?)` | Returns a string in which all occurrences of a specified search string in the original string have been replaced by another (specified) replace string. +1.1+| xref:functions/string.adoc#functions-reverse[`reverse()`] | `reverse(input :: STRING?) :: (STRING?)` | Returns a string in which the order of all characters in the original string have been reversed. +1.1+| xref:functions/string.adoc#functions-right[`right()`] | `right(original :: STRING?, length :: INTEGER?) :: (STRING?)` | Returns a string containing the specified number of rightmost characters of the original string. +1.1+| xref:functions/string.adoc#functions-rtrim[`rtrim()`] | `rtrim(input :: STRING?) :: (STRING?)` | Returns the original string with trailing whitespace removed. +1.2+| xref:functions/string.adoc#functions-split[`split()`] | `split(original :: STRING?, splitDelimiter :: STRING?) :: (LIST? OF STRING?)` | Returns a list of strings resulting from the splitting of the original string around matches of the given delimiter. +| `split(original :: STRING?, splitDelimiters :: LIST? OF STRING?) :: (LIST? OF STRING?)` | Returns a list of strings resulting from the splitting of the original string around matches of any of the given delimiters. +1.2+| xref:functions/string.adoc#functions-substring[`substring()`] | `substring(original :: STRING?, start :: INTEGER?) :: (STRING?)` | Returns a substring of the original string, beginning with a 0-based index start. +| `substring(original :: STRING?, start :: INTEGER?, length :: INTEGER?) :: (STRING?)` | Returns a substring of length 'length' of the original string, beginning with a 0-based index start. +1.1+| xref:functions/string.adoc#functions-tolower[`toLower()`] | `toLower(input :: STRING?) :: (STRING?)` | Returns the original string in lowercase. +1.1+| xref:functions/string.adoc#functions-tostring[`toString()`] | `toString(input :: ANY?) :: (STRING?)` | Converts an integer, float, boolean, point or temporal type (i.e. Date, Time, LocalTime, DateTime, LocalDateTime or Duration) value to a string. +1.1+| xref:functions/string.adoc#functions-tostringornull[`toStringOrNull()`] | `toStringOrNull(input :: ANY?) :: (STRING?)` | Converts an integer, float, boolean, point or temporal type (i.e. Date, Time, LocalTime, DateTime, LocalDateTime 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?)` | Returns the original string in uppercase. +1.1+| xref:functions/string.adoc#functions-trim[`trim()`] | `trim(input :: STRING?) :: (STRING?)` | Returns the original string with leading and trailing whitespace removed. |=== - [[header-query-functions-temporal-instant-types]] -**xref::functions/temporal/index.adoc[Temporal instant types functions]** +**xref:functions/temporal/index.adoc[Temporal instant types functions]** -Values of the xref::syntax/temporal.adoc[temporal types] -- _Date_, _Time_, _LocalTime_, _DateTime_, and _LocalDateTime_ -- can be created manipulated using the following functions: +Values of the xref:syntax/temporal.adoc[temporal types] -- _Date_, _Time_, _LocalTime_, _DateTime_, and _LocalDateTime_ -- can be created manipulated using the following functions: [options="header"] |=== | Function | Signature | Description +1.1+| xref:functions/temporal/index.adoc#functions-date[`date()`] | `date(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (DATE?)` | Create a Date instant. +1.1+| xref:functions/temporal/index.adoc#functions-date-realtime[`date.realtime()`] | `date.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (DATE?)` | Get 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?)` | Get 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?)` | Get 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?)` | Truncate the input 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?)` | Create a DateTime instant. +1.1+| xref:functions/temporal/index.adoc#functions-datetime-timestamp[`datetime.fromepoch()`] | `datetime.fromepoch(seconds :: NUMBER?, nanoseconds :: NUMBER?) :: (DATETIME?)` | Create a 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?)` | Create a 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?)` | Get the current 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?)` | Get the current 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?)` | Get the current 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?)` | Truncate the input temporal value to a DateTime instant using the specified unit. +1.1+| xref:functions/temporal/index.adoc#functions-localdatetime[`localdatetime()`] | `localdatetime(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALDATETIME?)` | Create a LocalDateTime instant. +1.1+| xref:functions/temporal/index.adoc#functions-localdatetime-realtime[`localdatetime.realtime()`] | `localdatetime.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALDATETIME?)` | Get the current LocalDateTime 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?)` | Get the current LocalDateTime 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?)` | Get the current LocalDateTime 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?)` | Truncate the input temporal value to a LocalDateTime instant using the specified unit. +1.1+| xref:functions/temporal/index.adoc#functions-localtime[`localtime()`] | `localtime(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALTIME?)` | Create a LocalTime instant. +1.1+| xref:functions/temporal/index.adoc#functions-localtime-realtime[`localtime.realtime()`] | `localtime.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALTIME?)` | Get the current LocalTime 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?)` | Get the current LocalTime 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?)` | Get the current LocalTime 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?)` | Truncate the input temporal value to a LocalTime instant using the specified unit. +1.1+| xref:functions/temporal/index.adoc#functions-time[`time()`] | `time(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (TIME?)` | Create a Time instant. +1.1+| xref:functions/temporal/index.adoc#functions-time-realtime[`time.realtime()`] | `time.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (TIME?)` | Get the current 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?)` | Get the current 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?)` | Get the current 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?)` | Truncate the input temporal value to a Time instant using the specified unit. +|=== -1.1+| xref::functions/temporal/index.adoc#functions-date[`date()`] -| `date(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (DATE?)` -| Create a Date instant. - -1.1+| xref::functions/temporal/index.adoc#functions-date-realtime[`date.realtime()`] -| `date.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (DATE?)` -| Get 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?)` -| Get 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?)` -| Get 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?)` -| Truncate the input 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?)` -| Create a DateTime instant. - -1.1+| xref::functions/temporal/index.adoc#functions-datetime-timestamp[`datetime.fromepoch()`] -| `datetime.fromepoch(seconds :: NUMBER?, nanoseconds :: NUMBER?) :: (DATETIME?)` -| Create a 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?)` -| Create a 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?)` -| Get the current 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?)` -| Get the current 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?)` -| Get the current 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?)` -| Truncate the input temporal value to a DateTime instant using the specified unit. - -1.1+| xref::functions/temporal/index.adoc#functions-localdatetime[`localdatetime()`] -| `localdatetime(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALDATETIME?)` -| Create a LocalDateTime instant. - -1.1+| xref::functions/temporal/index.adoc#functions-localdatetime-realtime[`localdatetime.realtime()`] -| `localdatetime.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALDATETIME?)` -| Get the current LocalDateTime 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?)` -| Get the current LocalDateTime 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?)` -| Get the current LocalDateTime 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?)` -| Truncate the input temporal value to a LocalDateTime instant using the specified unit. - -1.1+| xref::functions/temporal/index.adoc#functions-localtime[`localtime()`] -| `localtime(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALTIME?)` -| Create a LocalTime instant. - -1.1+| xref::functions/temporal/index.adoc#functions-localtime-realtime[`localtime.realtime()`] -| `localtime.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALTIME?)` -| Get the current LocalTime 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?)` -| Get the current LocalTime 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?)` -| Get the current LocalTime 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?)` -| Truncate the input temporal value to a LocalTime instant using the specified unit. - -1.1+| xref::functions/temporal/index.adoc#functions-time[`time()`] -| `time(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (TIME?)` -| Create a Time instant. +[[header-query-functions-temporal-duration]] +**xref:functions/temporal/duration.adoc[Temporal duration functions]** -1.1+| xref::functions/temporal/index.adoc#functions-time-realtime[`time.realtime()`] -| `time.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (TIME?)` -| Get the current Time instant using the realtime clock. +Duration values of the xref:syntax/temporal.adoc[temporal types] can be created manipulated using the following functions: -1.1+| xref::functions/temporal/index.adoc#functions-time-statement[`time.statement()`] -| `time.statement(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (TIME?)` -| Get the current Time instant using the statement clock. +[options="header"] +|=== +| Function | Signature | Description +1.1+| xref:functions/temporal/duration.adoc#functions-duration[`duration()`] | `duration(input :: ANY?) :: (DURATION?)` | Construct a Duration value. +1.1+| xref:functions/temporal/duration.adoc#functions-duration-between[`duration.between()`] | `duration.between(from :: ANY?, to :: ANY?) :: (DURATION?)` | Compute 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?)` | Compute 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?)` | Compute 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?)` | Compute the duration between the 'from' instant (inclusive) and the 'to' instant (exclusive) in seconds. +|=== -1.1+| xref::functions/temporal/index.adoc#functions-time-transaction[`time.transaction()`] -| `time.transaction(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (TIME?)` -| Get the current Time instant using the transaction clock. +[[header-query-functions-spatial]] +**xref:functions/spatial.adoc[Spatial functions]** -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?)` -| Truncate the input temporal value to a Time instant using the specified unit. +These functions are used to specify 2D or 3D points in a geographic or cartesian Coordinate Reference System and to calculate the geodesic distance between two points. +[options="header"] |=== +| Function | Signature | Description +1.1+| xref:functions/spatial.adoc#functions-distance[`distance()`] | `distance(from :: POINT?, to :: POINT?) :: (FLOAT?)` | Returns a floating point number 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?)` | 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?)` | 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?)` | 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?)` | Returns a 3D point object, given three coordinate values in the WGS 84 geographic coordinate system. +|=== -[[header-query-functions-temporal-duration]] -**xref::functions/temporal/duration.adoc[Temporal duration functions]** +[[header-query-functions-load-csv]] +**xref:functions/load-csv.adoc[LOAD CSV functions]** -Duration values of the xref::syntax/temporal.adoc[temporal types] can be created manipulated using the following functions: +LOAD CSV functions can be used to get information about the file that is processed by `LOAD CSV`. [options="header"] |=== | Function | Signature | Description +1.1+| xref:functions/load-csv.adoc#functions-file[`file()`] | `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?)` | Returns the line number that LOAD CSV is currently using. +|=== -1.1+| xref::functions/temporal/duration.adoc#functions-duration[`duration()`] -| `duration(input :: ANY?) :: (DURATION?)` -| Construct a Duration value. - -1.1+| xref::functions/temporal/duration.adoc#functions-duration-between[`duration.between()`] -| `duration.between(from :: ANY?, to :: ANY?) :: (DURATION?)` -| Compute 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?)` -| Compute 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?)` -| Compute the duration between the 'from' instant (inclusive) and the 'to' instant (exclusive) in months. +[[header-query-functions-user-defined]] +**xref:functions/user-defined.adoc[User-defined functions]** -1.1+| xref::functions/temporal/duration.adoc#functions-duration-inseconds[`duration.inSeconds()`] -| `duration.inSeconds(from :: ANY?, to :: ANY?) :: (DURATION?)` -| Compute the duration between the 'from' instant (inclusive) and the 'to' instant (exclusive) in seconds. +User-defined functions are written in Java, deployed into the database and are called in the same way as any other Cypher function. +There are two main types of functions that can be developed and used: +[options="header"] +|=== +|Type | Description | Usage | Developing +|Scalar | For each row the function takes parameters and returns a result | xref:functions/user-defined.adoc#query-functions-udf[Using UDF] | link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/functions#extending-neo4j-functions[Extending Neo4j (UDF)] +|Aggregating | Consumes many rows and produces an aggregated result | xref:functions/user-defined.adoc#query-functions-user-defined-aggregation[Using aggregating UDF] | link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/aggregation-functions#extending-neo4j-aggregation-functions[Extending Neo4j (Aggregating UDF)] |=== +//Predicate functions -[[header-query-functions-spatial]] -**xref::functions/spatial.adoc[Spatial functions]** -These functions are used to specify 2D or 3D points in a geographic or cartesian Coordinate Reference System and to calculate the geodesic distance between two points. +//Scalar functions -[options="header"] -|=== -| Function | Signature | Description -1.1+| xref::functions/spatial.adoc#functions-distance[`point.distance()`] -| `point.distance(from :: POINT?, to :: POINT?) :: (FLOAT?)` -| Returns a floating point number representing the geodesic distance between any two points in the same CRS. +//Aggregating functions -1.1+| xref::functions/spatial.adoc#functions-point-cartesian-2d[`point()` - Cartesian 2D] -| `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?)` -| Returns a 3D point object, given three coordinate values in the Cartesian coordinate system. +//List functions -1.1+| xref::functions/spatial.adoc#functions-point-wgs84-2d[`point()` - WGS 84 2D] -| `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?)` -| Returns a 3D point object, given three coordinate values in the WGS 84 geographic coordinate system. +//Mathematical functions - numeric -1.1+| xref::functions/spatial.adoc#functions-withinBBox[`point.withinBBox()`] -| `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`. -|=== +//Mathematical functions - logarithmic -[[header-query-functions-load-csv]] -**xref::functions/load-csv.adoc[LOAD CSV functions]** +//Mathematical functions - trigonometric -LOAD CSV functions can be used to get information about the file that is processed by `LOAD CSV`. -[options="header"] -|=== -| Function | Signature | Description +//String functions -1.1+| xref::functions/load-csv.adoc#functions-file[`file()`] -| `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?)` -| Returns the line number that LOAD CSV is currently using. +//Temporal functions - instant types -|=== +//Temporal functions - duration -[[header-query-functions-user-defined]] -**xref::functions/user-defined.adoc[User-defined functions]** -User-defined functions are written in Java, deployed into the database and are called in the same way as any other Cypher function. -There are two main types of functions that can be developed and used: +//Spatial functions -[options="header"] -|=== -| Type | Description | Usage | Developing -| Scalar -| For each row the function takes parameters and returns a result. -| xref::functions/user-defined.adoc#query-functions-udf[Using UDF] -| link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/functions#extending-neo4j-functions[Extending Neo4j (UDF)] +// User-defined functions -| Aggregating -| Consumes many rows and produces an aggregated result. -| xref::functions/user-defined.adoc#query-functions-user-defined-aggregation[Using aggregating UDF] -| link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/aggregation-functions#extending-neo4j-aggregation-functions[Extending Neo4j (Aggregating UDF)] -|=== +//Load csv functions diff --git a/modules/ROOT/pages/functions/list.adoc b/modules/ROOT/pages/functions/list.adoc index 4fea1b211..64dd2a88c 100644 --- a/modules/ROOT/pages/functions/list.adoc +++ b/modules/ROOT/pages/functions/list.adoc @@ -1,92 +1,102 @@ -:description: List functions return lists of things -- nodes in a path, and so on. - [[query-functions-list]] = List functions +:description: List functions return lists of things -- nodes in a path, and so on. -[abstract] --- -List functions return lists of things -- nodes in a path, and so on. --- - -Further details and examples of lists may be found in xref::syntax/lists.adoc[Lists] and xref::syntax/operators.adoc#query-operators-list[List operators]. +Further details and examples of lists may be found in xref:syntax/lists.adoc[Lists] and xref:syntax/operators.adoc#query-operators-list[List operators]. Functions: -* xref::functions/list.adoc#functions-keys[keys()] -* xref::functions/list.adoc#functions-labels[labels()] -* xref::functions/list.adoc#functions-nodes[nodes()] -* xref::functions/list.adoc#functions-range[range()] -* xref::functions/list.adoc#functions-reduce[reduce()] -* xref::functions/list.adoc#functions-relationships[relationships()] -* xref::functions/list.adoc#functions-reverse-list[reverse()] -* xref::functions/list.adoc#functions-tail[tail()] -* xref::functions/list.adoc#functions-tobooleanlist[toBooleanList()] -* xref::functions/list.adoc#functions-tofloatlist[toFloatList()] -* xref::functions/list.adoc#functions-tointegerlist[toIntegerList()] -* xref::functions/list.adoc#functions-tostringlist[toStringList()] - -image:graph_list_functions.svg[] - -//// -CREATE - (alice:Person:Developer {name:'Alice', age: 38, eyes: 'brown'}), - (bob {name: 'Bob', age: 25, eyes: 'blue'}), - (charlie {name: 'Charlie', age: 53, eyes: 'green'}), - (daniel {name: 'Daniel', age: 54, eyes: 'brown'}), - (eskil {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) -//// - +* xref:functions/list.adoc#functions-keys[keys()] +* xref:functions/list.adoc#functions-labels[labels()] +* xref:functions/list.adoc#functions-nodes[nodes()] +* xref:functions/list.adoc#functions-range[range()] +* xref:functions/list.adoc#functions-reduce[reduce()] +* xref:functions/list.adoc#functions-relationships[relationships()] +* xref:functions/list.adoc#functions-reverse-list[reverse()] +* xref:functions/list.adoc#functions-tail[tail()] +* xref:functions/list.adoc#functions-tobooleanlist[toBooleanList()] +* xref:functions/list.adoc#functions-tofloatlist[toFloatList()] +* xref:functions/list.adoc#functions-tointegerlist[toIntegerList()] +* xref:functions/list.adoc#functions-tostringlist[toStringList()] + + +.Graph +["dot", "List functions-1.svg", "neoviz", ""] +---- + N0 [ + label = "{Person, Developer|name = \'Alice\'\lage = 38\leyes = \'brown\'\l}" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 [ + label = "name = \'Bob\'\lage = 25\leyes = \'blue\'\l" + ] + N1 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 -> N4 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "MARRIED\n" + ] + N2 [ + label = "name = \'Charlie\'\lage = 53\leyes = \'green\'\l" + ] + N2 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N3 [ + label = "name = \'Daniel\'\lage = 54\leyes = \'brown\'\l" + ] + N4 [ + label = "eyes = \'blue\'\larray = \[\'one\', \'two\', \'three\'\]\lname = \'Eskil\'\lage = 41\l" + ] + +---- + [[functions-keys]] == keys() `keys` returns a list containing the string representations for all the property names of a node, relationship, or map. -*Syntax:* - -[source, syntax, role="noheader"] ----- -keys(expression) ----- +*Syntax:* `keys(expression)` *Returns:* - |=== - -| A list containing String elements. - +| +A list containing String elements. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| An expression that returns a node, a relationship, or a map. - +| `expression` | An expression that returns a node, a relationship, or a map. |=== -*Considerations:* +*Considerations:* |=== - -| `keys(null)` returns `null`. - +|`keys(null)` returns `null`. |=== -.+keys()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a) WHERE a.name = 'Alice' RETURN keys(a) @@ -97,61 +107,66 @@ A list containing the names of all the properties on the node bound to `a` is re .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-labels]] == labels() `labels` returns a list containing the string representations for all the labels of a node. -*Syntax:* - -[source, syntax, role="noheader"] ----- -labels(node) ----- +*Syntax:* `labels(node)` *Returns:* - |=== - -| A list containing String elements. - +| +A list containing String elements. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `node` -| An expression that returns a single node. - +| `node` | An expression that returns a single node. |=== -*Considerations:* +*Considerations:* |=== - -| `labels(null)` returns `null`. - +|`labels(null)` returns `null`. |=== -.+labels()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a) WHERE a.name = 'Alice' RETURN labels(a) @@ -162,61 +177,66 @@ A list containing all the labels of the node bound to `a` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-nodes]] == nodes() `nodes()` returns a list containing all the nodes in a path. -*Syntax:* - -[source, syntax, role="noheader"] ----- -nodes(path) ----- +*Syntax:* `nodes(path)` *Returns:* - |=== - -| A list containing Node elements. - +| +A list containing Node elements. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `path` -| An expression that returns a path. - +| `path` | An expression that returns a path. |=== -*Considerations:* +*Considerations:* |=== - -| `nodes(null)` returns `null`. - +|`nodes(null)` returns `null`. |=== -.+nodes()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH p = (a)-->(b)-->(c) WHERE a.name = 'Alice' AND c.name = 'Eskil' @@ -228,15 +248,36 @@ A list containing all the nodes in the path `p` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]>(b)-->(c) +WHERE a.name = 'Alice' AND c.name = 'Eskil' +RETURN nodes(p) +]]> +++++ +endif::nonhtmloutput[] [[functions-range]] == range() @@ -247,44 +288,28 @@ The range is inclusive for non-empty ranges, and the arithmetic progression will The only exception where the range does not contain `start` are empty ranges. An empty range will be returned if the value `step` is negative and `start - end` is positive, or vice versa, e.g. `range(0, 5, -1)`. -*Syntax:* -[source, syntax, role="noheader"] ----- -range(start, end [, step]) ----- +*Syntax:* `range(start, end [, step])` *Returns:* - |=== - -| A list of Integer elements. - +| +A list of Integer elements. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `start` -| An expression that returns an integer value. - -| `end` -| An expression that returns an integer value. - -| `step` -| A numeric expression defining the difference between any two consecutive values, with a default of `1`. - +| `start` | An expression that returns an integer value. +| `end` | An expression that returns an integer value. +| `step` | A numeric expression defining the difference between any two consecutive values, with a default of `1`. |=== -.+range()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN range(0, 10), range(2, 18, 3), range(0, 5, -1) ---- @@ -294,67 +319,65 @@ Three lists of numbers in the given ranges are returned. .Result [role="queryresult",options="header,footer",cols="3* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-reduce]] == reduce() `reduce()` returns the value resulting from the application of an expression on each successive element in a list in conjunction with the result of the computation thus far. -This function will iterate through each element `e` in the given list, run the expression on `e` -- taking into account the current partial result -- and store the new partial result in the accumulator. -This function is analogous to the `fold` or `reduce` method in functional languages such as Lisp and Scala. + This function will iterate through each element `e` in the given list, run the expression on `e` -- taking into account the current partial result -- and store the new partial result in the accumulator. + This function is analogous to the `fold` or `reduce` method in functional languages such as Lisp and Scala. -*Syntax:* - -[source, syntax, role="noheader"] ----- -reduce(accumulator = initial, variable IN list | expression) ----- +*Syntax:* `reduce(accumulator = initial, variable IN list | expression)` *Returns:* - |=== - -| The type of the value returned depends on the arguments provided, along with the semantics of `expression`. - +| +The type of the value returned depends on the arguments provided, along with the semantics of `expression`. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `accumulator` -| A variable that will hold the result and the partial results as the list is iterated. - -| `initial` -| An expression that runs once to give a starting value to the accumulator. - -| `list` -| An expression that returns a list. - -| `variable` -| The closure will have a variable introduced in its context. We decide here which variable to use. - -| `expression` -| This expression will run once per value in the list, and produce the result value. - +| `accumulator` | A variable that will hold the result and the partial results as the list is iterated. +| `initial` | An expression that runs once to give a starting value to the accumulator. +| `list` | An expression that returns a list. +| `variable` | The closure will have a variable introduced in its context. We decide here which variable to use. +| `expression` | This expression will run once per value in the list, and produce the result value. |=== -.+reduce()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH p = (a)-->(b)-->(c) WHERE a.name = 'Alice' AND b.name = 'Bob' AND c.name = 'Daniel' @@ -366,61 +389,67 @@ The `age` property of all nodes in the path are summed and returned as a single .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]>(b)-->(c) +WHERE a.name = 'Alice' AND b.name = 'Bob' AND c.name = 'Daniel' +RETURN reduce(totalAge = 0, n IN nodes(p) | totalAge + n.age) AS reduction +]]> +++++ +endif::nonhtmloutput[] [[functions-relationships]] == relationships() `relationships()` returns a list containing all the relationships in a path. -*Syntax:* - -[source, syntax, role="noheader"] ----- -relationships(path) ----- +*Syntax:* `relationships(path)` *Returns:* - |=== - -| A list containing Relationship elements. - +| +A list containing Relationship elements. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `path` -| An expression that returns a path. - +| `path` | An expression that returns a path. |=== -*Considerations:* +*Considerations:* |=== - -| `relationships(null)` returns `null`. - +|`relationships(null)` returns `null`. |=== -.+relationships()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH p = (a)-->(b)-->(c) WHERE a.name = 'Alice' AND c.name = 'Eskil' @@ -432,60 +461,67 @@ A list containing all the relationships in the path `p` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]>(b)-->(c) +WHERE a.name = 'Alice' AND c.name = 'Eskil' +RETURN relationships(p) +]]> +++++ +endif::nonhtmloutput[] [[functions-reverse-list]] == reverse() `reverse()` returns a list in which the order of all elements in the original list have been reversed. -*Syntax:* - -[source, syntax, role="noheader"] ----- -reverse(original) ----- +*Syntax:* `reverse(original)` *Returns:* - |=== - -| A list containing homogeneous or heterogeneous elements; the types of the elements are determined by the elements within `original`. - +| +A list containing homogeneous or heterogeneous elements; the types of the elements are determined by the elements within `original`. |=== + *Arguments:* [options="header"] |=== | Name | Description - -| `original` -| An expression that returns a list. - +| `original` | An expression that returns a list. |=== -*Considerations:* +*Considerations:* |=== - -| Any `null` element in `original` is preserved. - +|Any `null` element in `original` is preserved. |=== -.+reverse()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH [4923,'abc',521, null, 487] AS ids RETURN reverse(ids) @@ -494,52 +530,60 @@ RETURN reverse(ids) .Result [role="queryresult",options="header,footer",cols="1*,521,"abc",4923]+ 1+d|Rows: 1 - |=== -====== - +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-tail]] == tail() `tail()` returns a list `l~result~` containing all the elements, excluding the first one, from a list `list`. -*Syntax:* - -[source, syntax, role="noheader"] ----- -tail(list) ----- +*Syntax:* `tail(list)` *Returns:* - |=== - -| A list containing heterogeneous elements; the types of the elements are determined by the elements in `list`. - +| +A list containing heterogeneous elements; the types of the elements are determined by the elements in `list`. |=== + *Arguments:* [options="header"] |=== | Name | Description - -| `list` -| An expression that returns a list. - +| `list` | An expression that returns a list. |=== -.+tail()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a) WHERE a.name = 'Eskil' RETURN a.array, tail(a.array) @@ -550,65 +594,70 @@ The property named `array` and a list comprising all but the first element of th .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-tobooleanlist]] == toBooleanList() -`toBooleanList()` converts a list of values and returns a list of boolean values. -If any values are not convertible to boolean they will be null in the list returned. +`toBooleanList()` converts a list of values and returns a list of boolean values. If any values are not convertible to boolean they will be null in the list returned. -*Syntax:* - -[source, syntax, role="noheader"] ----- -toBooleanList(list) ----- +*Syntax:* `toBooleanList(list)` *Returns:* - |=== - -| A list containing the converted elements; depending on the input value a converted value is either a boolean value or `null`. - +| +A list containing the converted elements; depending on the input value a converted value is either a boolean value or `null`. |=== + *Arguments:* [options="header"] |=== | Name | Description - -| `list` -| An expression that returns a list. - +| `list` | An expression that returns a list. |=== -*Considerations:* +*Considerations:* |=== - -| Any `null` element in `list` is preserved. -| Any boolean value in `list` is preserved. -| If the `list` is `null`, `null` will be returned. -| If the `list` is not a list, an error will be returned. -| The conversion for each value in `list` is done according to the xref::functions/scalar.adoc#functions-tobooleanornull[`toBooleanOrNull()` function]. - +|Any `null` element in `list` is preserved. +|Any boolean value in `list` is preserved. +|If the `list` is `null`, `null` will be returned. +|If the `list` is not a list, an error will be returned. +|The conversion for each value in `list` is done according to the xref:functions/scalar.adoc#functions-tobooleanornull[`toBooleanOrNull()` function]. |=== -.+toBooleanList()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN toBooleanList(null) as noList, toBooleanList([null, null]) as nullsInList, @@ -618,65 +667,71 @@ toBooleanList(['a string', true, 'false', null, ['A','B']]) as mixedList .Result [role="queryresult",options="header,footer",cols="3*+ | +[,]+ | +[,true,false,,]+ 3+d|Rows: 1 - |=== -====== - +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-tofloatlist]] == toFloatList() -`toFloatList()` converts a list of values and returns a list of floating point values. -If any values are not convertible to floating point they will be `null` in the list returned. - -*Syntax:* +`toFloatList()` converts a list of values and returns a list of floating point values. If any values are not convertible to floating point they will be `null` in the list returned. -[source, syntax, role="noheader"] ----- -toFloatList(list) ----- +*Syntax:* `toFloatList(list)` *Returns:* - |=== - -| A list containing the converted elements; depending on the input value a converted value is either a floating point value or `null`. - +| +A list containing the converted elements; depending on the input value a converted value is either a floating point value or `null`. |=== + *Arguments:* [options="header"] |=== | Name | Description - -| `list` -| An expression that returns a list. - +| `list` | An expression that returns a list. |=== -*Considerations:* +*Considerations:* |=== - -| Any `null` element in `list` is preserved. -| Any floating point value in `list` is preserved. -| If the `list` is `null`, `null` will be returned. -| If the `list` is not a list, an error will be returned. -| The conversion for each value in `list` is done according to the xref::functions/scalar.adoc#functions-tofloatornull[`toFloatOrNull()` function]. - +|Any `null` element in `list` is preserved. +|Any floating point value in `list` is preserved. +|If the `list` is `null`, `null` will be returned. +|If the `list` is not a list, an error will be returned. +|The conversion for each value in `list` is done according to the xref:functions/scalar.adoc#functions-tofloatornull[`toFloatOrNull()` function]. |=== -.+toFloatList()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN toFloatList(null) as noList, toFloatList([null, null]) as nullsInList, @@ -686,65 +741,71 @@ toFloatList(['a string', 2.5, '3.14159', null, ['A','B']]) as mixedList .Result [role="queryresult",options="header,footer",cols="3*+ | +[,]+ | +[,2.5,3.14159,,]+ 3+d|Rows: 1 - |=== -====== - +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-tointegerlist]] == toIntegerList() -`toIntegerList()` converts a list of values and returns a list of integer values. -If any values are not convertible to integer they will be `null` in the list returned. +`toIntegerList()` converts a list of values and returns a list of integer values. If any values are not convertible to integer they will be `null` in the list returned. -*Syntax:* - -[source, syntax, role="noheader"] ----- -toIntegerList(list) ----- +*Syntax:* `toIntegerList(list)` *Returns:* - |=== - -| A list containing the converted elements; depending on the input value a converted value is either a integer value or `null`. - +| +A list containing the converted elements; depending on the input value a converted value is either a integer value or `null`. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `list` -| An expression that returns a list. - +| `list` | An expression that returns a list. |=== + *Considerations:* |=== - -| Any `null` element in `list` is preserved. -| Any integer value in `list` is preserved. -| If the `list` is `null`, `null` will be returned. -| If the `list` is not a list, an error will be returned. -| The conversion for each value in `list` is done according to the xref::functions/scalar.adoc#functions-tointegerornull[`toIntegerOrNull()` function]. - +|Any `null` element in `list` is preserved. +|Any integer value in `list` is preserved. +|If the `list` is `null`, `null` will be returned. +|If the `list` is not a list, an error will be returned. +|The conversion for each value in `list` is done according to the xref:functions/scalar.adoc#functions-tointegerornull[`toIntegerOrNull()` function]. |=== -.+toIntegerList()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN toIntegerList(null) as noList, toIntegerList([null, null]) as nullsInList, @@ -754,66 +815,71 @@ toIntegerList(['a string', 2, '5', null, ['A','B']]) as mixedList .Result [role="queryresult",options="header,footer",cols="3*+ | +[,]+ | +[,2,5,,]+ 3+d|Rows: 1 - |=== -====== - +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-tostringlist]] == toStringList() -`toStringList()` converts a list of values and returns a list of string values. -If any values are not convertible to string they will be `null` in the list returned. - -*Syntax:* +`toStringList()` converts a list of values and returns a list of string values. If any values are not convertible to string they will be `null` in the list returned. -[source, syntax, role="noheader"] ----- -toStringList(list) ----- +*Syntax:* `toStringList(list)` *Returns:* - |=== - -| A list containing the converted elements; depending on the input value a converted value is either a string value or `null`. - +| +A list containing the converted elements; depending on the input value a converted value is either a string value or `null`. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `list` -| An expression that returns a list. - +| `list` | An expression that returns a list. |=== -*Considerations:* +*Considerations:* |=== - -| Any `null` element in `list` is preserved. -| Any string value in `list` is preserved. -| If the `list` is `null`, `null` will be returned. -| If the `list` is not a list, an error will be returned. -| The conversion for each value in `list` is done according to the xref::functions/string.adoc#functions-tostringornull[`toStringOrNull()` function]. - +|Any `null` element in `list` is preserved. +|Any string value in `list` is preserved. +|If the `list` is `null`, `null` will be returned. +|If the `list` is not a list, an error will be returned. +|The conversion for each value in `list` is done according to the xref:functions/string.adoc#functions-tostringornull[`toStringOrNull()` function]. |=== -.+toStringList()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN toStringList(null) as noList, toStringList([null, null]) as nullsInList, @@ -823,12 +889,34 @@ toStringList(['already a string', 2, date({year:1955, month:11, day:5}), null, [ .Result [role="queryresult",options="header,footer",cols="3*+ | +[,]+ | +["already a string","2","1955-11-05",,]+ 3+d|Rows: 1 - |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/functions/load-csv.adoc b/modules/ROOT/pages/functions/load-csv.adoc index c177ff67a..93c23a504 100644 --- a/modules/ROOT/pages/functions/load-csv.adoc +++ b/modules/ROOT/pages/functions/load-csv.adoc @@ -1,52 +1,38 @@ -:description: LOAD CSV functions can be used to get information about the file that is processed by `LOAD CSV`. - [[query-functions-load-csv]] = LOAD CSV functions - -[abstract] --- -LOAD CSV functions can be used to get information about the file that is processed by `LOAD CSV`. --- +:description: LOAD CSV functions can be used to get information about the file that is processed by `LOAD CSV`. [IMPORTANT] ==== The functions described on this page are only useful when run on a query that uses `LOAD CSV`. In all other contexts they will always return `null`. + + ==== Functions: -* xref::functions/load-csv.adoc#functions-linenumber[linenumber()] -* xref::functions/load-csv.adoc#functions-file[file()] - +* xref:functions/load-csv.adoc#functions-linenumber[linenumber()] +* xref:functions/load-csv.adoc#functions-file[file()] [[functions-linenumber]] == linenumber() `linenumber()` returns the line number that `LOAD CSV` is currently using. -*Syntax:* - -[source, syntax, role="noheader"] ----- -linenumber() ----- +*Syntax:* `linenumber()` *Returns:* - |=== - -| An Integer. - +| +An Integer. |=== -*Considerations:* +*Considerations:* |=== - -| `null` will be returned if this function is called without a `LOAD CSV` context. -| If the CSV file contains headers, the headers will be `linenumber` 1 and the 1st row of data will have a `linenumber` of 2. - +|`null` will be returned if this function is called without a `LOAD CSV` context. +|If the CSV file contains headers, the headers will be `linenumber` 1 and the 1st row of data will have a `linenumber` of 2. |=== [[functions-file]] @@ -54,26 +40,17 @@ linenumber() `file()` returns the absolute path of the file that `LOAD CSV` is using. -*Syntax:* - -[source, syntax, role="noheader"] ----- -file() ----- +*Syntax:* `file()` *Returns:* - |=== - -| A String. - +| +A String. |=== -*Considerations:* +*Considerations:* |=== - -| `null` will be returned if this function is called without a `LOAD CSV` context. - +|`null` will be returned if this function is called without a `LOAD CSV` context. |=== diff --git a/modules/ROOT/pages/functions/mathematical-logarithmic.adoc b/modules/ROOT/pages/functions/mathematical-logarithmic.adoc index 9ddbf8f0a..e1706dc83 100644 --- a/modules/ROOT/pages/functions/mathematical-logarithmic.adoc +++ b/modules/ROOT/pages/functions/mathematical-logarithmic.adoc @@ -1,48 +1,32 @@ -:description: Logarithmic functions operate on numeric expressions only, and will return an error if used on any other values. - [[query-functions-logarithmic]] = Mathematical functions - logarithmic - -[abstract] --- -These functions all operate on numeric expressions only, and will return an error if used on any other values. See also xref::syntax/operators.adoc#query-operators-mathematical[Mathematical operators]. --- +:description: These functions all operate on numeric expressions only, and will return an error if used on any other values. See also xref:syntax/operators.adoc#query-operators-mathematical[Mathematical operators]. Functions: -* xref::functions/mathematical-logarithmic.adoc#functions-e[e()] -* xref::functions/mathematical-logarithmic.adoc#functions-exp[exp()] -* xref::functions/mathematical-logarithmic.adoc#functions-log[log()] -* xref::functions/mathematical-logarithmic.adoc#functions-log10[log10()] -* xref::functions/mathematical-logarithmic.adoc#functions-sqrt[sqrt()] - +* xref:functions/mathematical-logarithmic.adoc#functions-e[e()] +* xref:functions/mathematical-logarithmic.adoc#functions-exp[exp()] +* xref:functions/mathematical-logarithmic.adoc#functions-log[log()] +* xref:functions/mathematical-logarithmic.adoc#functions-log10[log10()] +* xref:functions/mathematical-logarithmic.adoc#functions-sqrt[sqrt()] + [[functions-e]] == e() `e()` returns the base of the natural logarithm, `e`. -*Syntax:* - -[source, syntax, role="noheader"] ----- -e() ----- +*Syntax:* `e()` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -.+e()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN e() ---- @@ -52,61 +36,54 @@ The base of the natural logarithm, `e`, is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-exp]] == exp() -`exp()` returns `e^n^`, where `e` is the base of the natural logarithm, and `n` is the value of the argument expression. +`exp()` returns `e^n`, where `e` is the base of the natural logarithm, and `n` is the value of the argument expression. -*Syntax:* - -[source, syntax, role="noheader"] ----- -e(expression) ----- +*Syntax:* `e(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression. - +| `expression` | A numeric expression. |=== -*Considerations:* +*Considerations:* |=== - -| `exp(null)` returns `null`. - +|`exp(null)` returns `null`. |=== -.+exp()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN exp(2) ---- @@ -116,61 +93,55 @@ RETURN exp(2) .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-log]] == log() `log()` returns the natural logarithm of a number. -*Syntax:* - -[source, syntax, role="noheader"] ----- -log(expression) ----- +*Syntax:* `log(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression. - +| `expression` | A numeric expression. |=== + *Considerations:* |=== - -| `log(null)` returns `null`. -| `log(0)` returns `null`. - +|`log(null)` returns `null`. +|`log(0)` returns `null`. |=== -.+log()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN log(27) ---- @@ -180,62 +151,55 @@ The natural logarithm of `27` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-log10]] == log10() `log10()` returns the common logarithm (base 10) of a number. -*Syntax:* - -[source, syntax, role="noheader"] ----- -log10(expression) ----- +*Syntax:* `log10(expression)` *Returns:* - |=== - -|A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression. - +| `expression` | A numeric expression. |=== -*Considerations:* +*Considerations:* |=== - -| `log10(null)` returns `null`. -| `log10(0)` returns `null`. - +|`log10(null)` returns `null`. +|`log10(0)` returns `null`. |=== -.+log10()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN log10(27) ---- @@ -245,62 +209,55 @@ The common logarithm of `27` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-sqrt]] == sqrt() `sqrt()` returns the square root of a number. -*Syntax:* - -[source, syntax, role="noheader"] ----- -sqrt(expression) ----- +*Syntax:* `sqrt(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression. - +| `expression` | A numeric expression. |=== -*Considerations:* +*Considerations:* |=== - -| `sqrt(null)` returns `null`. -| `sqrt()` returns `null` - +|`sqrt(null)` returns `null`. +|`sqrt()` returns `null` |=== -.+sqrt()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN sqrt(256) ---- @@ -310,12 +267,21 @@ The square root of `256` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/functions/mathematical-numeric.adoc b/modules/ROOT/pages/functions/mathematical-numeric.adoc index d3457f98c..d1d6f79d5 100644 --- a/modules/ROOT/pages/functions/mathematical-numeric.adoc +++ b/modules/ROOT/pages/functions/mathematical-numeric.adoc @@ -1,89 +1,99 @@ -:description: Functions that operate on numeric expressions only, and will return an error if used on any other values. - [[query-functions-numeric]] = Mathematical functions - numeric - -[abstract] --- -These functions all operate on numeric expressions only, and will return an error if used on any other values. -See also xref::syntax/operators.adoc#query-operators-mathematical[Mathematical operators]. --- +:description: These functions all operate on numeric expressions only, and will return an error if used on any other values. See also xref:syntax/operators.adoc#query-operators-mathematical[Mathematical operators]. Functions: -* xref::functions/mathematical-numeric.adoc#functions-abs[abs()] -* xref::functions/mathematical-numeric.adoc#functions-ceil[ceil()] -* xref::functions/mathematical-numeric.adoc#functions-floor[floor()] -* xref::functions/mathematical-numeric.adoc#functions-rand[rand()] -* xref::functions/mathematical-numeric.adoc#functions-round[round()] -* xref::functions/mathematical-numeric.adoc#functions-round2[round(), with precision] -* xref::functions/mathematical-numeric.adoc#functions-round3[round(), with precision and rounding mode] -* xref::functions/mathematical-numeric.adoc#functions-sign[sign()] +* xref:functions/mathematical-numeric.adoc#functions-abs[abs()] +* xref:functions/mathematical-numeric.adoc#functions-ceil[ceil()] +* xref:functions/mathematical-numeric.adoc#functions-floor[floor()] +* xref:functions/mathematical-numeric.adoc#functions-rand[rand()] +* xref:functions/mathematical-numeric.adoc#functions-round[round()] +* xref:functions/mathematical-numeric.adoc#functions-round2[round(), with precision] +* xref:functions/mathematical-numeric.adoc#functions-round3[round(), with precision and rounding mode] +* xref:functions/mathematical-numeric.adoc#functions-sign[sign()] + The following graph is used for the examples below: -image:graph_numeric_functions.svg[] - -//// -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', age: 54, 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) -//// - +.Graph +["dot", "Mathematical functions - numeric-1.svg", "neoviz", ""] +---- + N0 [ + label = "{A|name = \'Alice\'\lage = 38\leyes = \'brown\'\l}" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 [ + label = "{B|name = \'Bob\'\lage = 25\leyes = \'blue\'\l}" + ] + N1 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 -> N4 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "MARRIED\n" + ] + N2 [ + label = "{C|name = \'Charlie\'\lage = 53\leyes = \'green\'\l}" + ] + N2 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N3 [ + label = "{D|name = \'Daniel\'\lage = 54\leyes = \'brown\'\l}" + ] + N4 [ + label = "{E|eyes = \'blue\'\larray = \[\'one\', \'two\', \'three\'\]\lname = \'Eskil\'\lage = 41\l}" + ] + +---- + [[functions-abs]] == abs() `abs()` returns the absolute value of the given number. -*Syntax:* - -[source, syntax, role="noheader"] ----- -abs(expression) ----- +*Syntax:* `abs(expression)` *Returns:* - |=== - -| The type of the value returned will be that of `expression`. - +| +The type of the value returned will be that of `expression`. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression. - +| `expression` | A numeric expression. |=== -*Considerations:* +*Considerations:* |=== - -| `abs(null)` returns `null`. -| If `expression` is negative, `-(expression)` (i.e. the _negation_ of `expression`) is returned. - +|`abs(null)` returns `null`. +|If `expression` is negative, `-(expression)` (i.e. the _negation_ of `expression`) is returned. |=== -.+abs()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a), (e) WHERE a.name = 'Alice' AND e.name = 'Eskil' RETURN a.age, e.age, abs(a.age - e.age) ---- @@ -93,61 +103,65 @@ The absolute value of the age difference is returned. .Result [role="queryresult",options="header,footer",cols="3* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-ceil]] == ceil() `ceil()` returns the smallest floating point number that is greater than or equal to the given number and equal to a mathematical integer. -*Syntax:* - -[source, syntax, role="noheader"] ----- -ceil(expression) ----- +*Syntax:* `ceil(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression. - +| `expression` | A numeric expression. |=== -*Considerations:* +*Considerations:* |=== - -| `ceil(null)` returns `null`. - +|`ceil(null)` returns `null`. |=== -.+ceil()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN ceil(0.1) ---- @@ -157,61 +171,65 @@ The ceil of `0.1` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-floor]] == floor() `floor()` returns the largest floating point number that is less than or equal to the given number and equal to a mathematical integer. -*Syntax:* - -[source, syntax, role="noheader"] ----- -floor(expression) ----- +*Syntax:* `floor(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression. - +| `expression` | A numeric expression. |=== -*Considerations:* +*Considerations:* |=== - -| `floor(null)` returns `null`. - +|`floor(null)` returns `null`. |=== -.+floor()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN floor(0.9) ---- @@ -226,35 +244,46 @@ The floor of `0.9` is returned. 1+d|Rows: 1 |=== -====== - +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-rand]] == rand() `rand()` returns a random floating point number in the range from 0 (inclusive) to 1 (exclusive); i.e. `[0,1)`. The numbers returned follow an approximate uniform distribution. -*Syntax:* - -[source, syntax, role="noheader"] ----- -rand() ----- +*Syntax:* `rand()` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -.+rand()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN rand() ---- @@ -264,61 +293,65 @@ A random number is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-round]] == round() `round()` returns the value of the given number rounded to the nearest integer, with half-way values always rounded up. -*Syntax:* - -[source, syntax, role="noheader"] ----- -round(expression) ----- +*Syntax:* `round(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression to be rounded. - +| `expression` | A numeric expression to be rounded. |=== -*Considerations:* +*Considerations:* |=== - -| `round(null)` returns `null`. - +|`round(null)` returns `null`. |=== -.+round()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN round(3.141592) ---- @@ -333,56 +366,61 @@ RETURN round(3.141592) 1+d|Rows: 1 |=== -====== - +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-round2]] == round(), with precision `round()` returns the value of the given number rounded with the specified precision, with half-values always being rounded up. -*Syntax:* - -[source, syntax, role="noheader"] ----- -round(expression, precision) ----- +*Syntax:* `round(expression, precision)` *Returns:* |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression to be rounded. - -| `precision` -| A numeric expression specifying precision. - +| `expression` | A numeric expression to be rounded. +| `precision` | A numeric expression specifying precision. |=== -*Considerations:* +*Considerations:* |=== - -| `round(null)` returns `null`. - +|`round(null)` returns `null`. |=== -.+round()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN round(3.141592, 3) ---- @@ -392,95 +430,81 @@ RETURN round(3.141592, 3) .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-round3]] == round(), with precision and rounding mode `round()` returns the value of the given number rounded with the specified precision and the specified rounding mode. -*Syntax:* - -[source, syntax, role="noheader"] ----- -round(expression, precision, mode) ----- +*Syntax:* `round(expression, precision, mode)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression to be rounded. - -| `precision` -| A numeric expression specifying precision. - -| `mode` -| A string expression specifying rounding mode. - +| `expression` | A numeric expression to be rounded. +| `precision` | A numeric expression specifying precision. +| `mode` | A string expression specifying rounding mode. |=== + *Modes:* [options="header"] |=== | `Mode` | Description - -| `CEILING` -| Round towards positive infinity. - -| `DOWN` -| Round towards zero. - -| `FLOOR` -| Round towards zero. - -| `HALF_DOWN` -| Round towards closest value of given precision, with half-values always being rounded down. - -| `HALF_EVEN` -| Round towards closest value of given precision, with half-values always being rounded to the even neighbor. - -| `HALF_UP` -| Round towards closest value of given precision, with half-values always being rounded up. - -| `UP` -| Round away from zero. - +| `CEILING` | Round towards positive infinity. +| `DOWN` | Round towards zero. +| `FLOOR` | Round towards zero. +| `HALF_DOWN` | Round towards closest value of given precision, with half-values always being rounded down. +| `HALF_EVEN` | Round towards closest value of given precision, with half-values always being rounded to the even neighbor. +| `HALF_UP` | Round towards closest value of given precision, with half-values always being rounded up. +| `UP` | Round away from zero. |=== -*Considerations:* +*Considerations:* |=== - -| `round(null)` returns `null`. - +|`round(null)` returns `null`. |=== -.+round()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN round(3.141592, 2, 'CEILING') ---- @@ -490,61 +514,65 @@ RETURN round(3.141592, 2, 'CEILING') .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-sign]] == sign() `sign()` returns the signum of the given number: `0` if the number is `0`, `-1` for any negative number, and `1` for any positive number. -*Syntax:* - -[source, syntax, role="noheader"] ----- -sign(expression) ----- +*Syntax:* `sign(expression)` *Returns:* - |=== - -| An Integer. - +| +An Integer. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression. - +| `expression` | A numeric expression. |=== -*Considerations:* +*Considerations:* |=== - -| `sign(null)` returns `null`. - +|`sign(null)` returns `null`. |=== -.+sign()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN sign(-17), sign(0.1) ---- @@ -554,12 +582,32 @@ The signs of `-17` and `0.1` are returned. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/functions/mathematical-trigonometric.adoc b/modules/ROOT/pages/functions/mathematical-trigonometric.adoc index 7dda7f0e9..e3054f7a0 100644 --- a/modules/ROOT/pages/functions/mathematical-trigonometric.adoc +++ b/modules/ROOT/pages/functions/mathematical-trigonometric.adoc @@ -1,74 +1,55 @@ -:description: Trigonometric functions operate on numeric expressions only, and will return an error if used on any other values. - [[query-functions-trigonometric]] = Mathematical functions - trigonometric - -[abstract] --- -These functions all operate on numeric expressions only, and will return an error if used on any other values. See also xref::syntax/operators.adoc#query-operators-mathematical[Mathematical operators]. --- +:description: These functions all operate on numeric expressions only, and will return an error if used on any other values. See also xref:syntax/operators.adoc#query-operators-mathematical[Mathematical operators]. Functions: -* xref::functions/mathematical-trigonometric.adoc#functions-acos[acos()] -* xref::functions/mathematical-trigonometric.adoc#functions-asin[asin()] -* xref::functions/mathematical-trigonometric.adoc#functions-atan[atan()] -* xref::functions/mathematical-trigonometric.adoc#functions-atan2[atan2()] -* xref::functions/mathematical-trigonometric.adoc#functions-cos[cos()] -* xref::functions/mathematical-trigonometric.adoc#functions-cot[cot()] -* xref::functions/mathematical-trigonometric.adoc#functions-degrees[degrees()] -* xref::functions/mathematical-trigonometric.adoc#functions-haversin[haversin()] -* xref::functions/mathematical-trigonometric.adoc#functions-spherical-distance-using-haversin[Spherical distance using the `haversin()` function] -* xref::functions/mathematical-trigonometric.adoc#functions-pi[pi()] -* xref::functions/mathematical-trigonometric.adoc#functions-radians[radians()] -* xref::functions/mathematical-trigonometric.adoc#functions-sin[sin()] -* xref::functions/mathematical-trigonometric.adoc#functions-tan[tan()] - +* xref:functions/mathematical-trigonometric.adoc#functions-acos[acos()] +* xref:functions/mathematical-trigonometric.adoc#functions-asin[asin()] +* xref:functions/mathematical-trigonometric.adoc#functions-atan[atan()] +* xref:functions/mathematical-trigonometric.adoc#functions-atan2[atan2()] +* xref:functions/mathematical-trigonometric.adoc#functions-cos[cos()] +* xref:functions/mathematical-trigonometric.adoc#functions-cot[cot()] +* xref:functions/mathematical-trigonometric.adoc#functions-degrees[degrees()] +* xref:functions/mathematical-trigonometric.adoc#functions-haversin[haversin()] +* xref:functions/mathematical-trigonometric.adoc#functions-spherical-distance-using-haversin[Spherical distance using the `haversin()` function] +* xref:functions/mathematical-trigonometric.adoc#functions-pi[pi()] +* xref:functions/mathematical-trigonometric.adoc#functions-radians[radians()] +* xref:functions/mathematical-trigonometric.adoc#functions-sin[sin()] +* xref:functions/mathematical-trigonometric.adoc#functions-tan[tan()] + [[functions-acos]] == acos() `acos()` returns the arccosine of a number in radians. -*Syntax:* - -[source, syntax, role="noheader"] ----- -acos(expression) ----- +*Syntax:* `acos(expression)` *Returns:* |=== - -|A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression that represents the angle in radians. - +| `expression` | A numeric expression that represents the angle in radians. |=== + *Considerations:* |=== - -| `acos(null)` returns `null`. -| If (`expression` < -1) or (`expression` > 1), then (`acos(expression)`) returns `null`. - +|`acos(null)` returns `null`. +|If (`expression` < -1) or (`expression` > 1), then (`acos(expression)`) returns `null`. |=== -.+acos()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN acos(0.5) ---- @@ -83,54 +64,50 @@ The arccosine of `0.5` is returned. 1+d|Rows: 1 |=== -====== - +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-asin]] == asin() `asin()` returns the arcsine of a number in radians. -*Syntax:* - -[source, syntax, role="noheader"] ----- -asin(expression) ----- +*Syntax:* `asin(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression that represents the angle in radians. - +| `expression` | A numeric expression that represents the angle in radians. |=== + *Considerations:* |=== - -| `asin(null)` returns `null`. -| If (`expression` < -1) or (`expression` > 1), then (`asin(expression)`) returns `null`. - +|`asin(null)` returns `null`. +|If (`expression` < -1) or (`expression` > 1), then (`asin(expression)`) returns `null`. |=== -.+asin()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN asin(0.5) ---- @@ -145,54 +122,49 @@ The arcsine of `0.5` is returned. 1+d|Rows: 1 |=== -====== - +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-atan]] == atan() `atan()` returns the arctangent of a number in radians. -*Syntax:* - -[source, syntax, role="noheader"] ----- -atan(expression) ----- +*Syntax:* `atan(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression that represents the angle in radians. - +| `expression` | A numeric expression that represents the angle in radians. |=== -*Considerations:* +*Considerations:* |=== - -| `atan(null)` returns `null`. - +|`atan(null)` returns `null`. |=== -.+atan()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN atan(0.5) ---- @@ -202,63 +174,55 @@ The arctangent of `0.5` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-atan2]] == atan2() `atan2()` returns the arctangent2 of a set of coordinates in radians. -*Syntax:* - -[source, syntax, role="noheader"] ----- -atan2(expression1, expression2) ----- +*Syntax:* `atan2(expression1, expression2)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== + *Arguments:* [options="header"] |=== | Name | Description - -| `expression1` -| A numeric expression for y that represents the angle in radians. - -| `expression2` -| A numeric expression for x that represents the angle in radians. - +| `expression1` | A numeric expression for y that represents the angle in radians. +| `expression2` | A numeric expression for x that represents the angle in radians. |=== -*Considerations:* +*Considerations:* |=== - -| `atan2(null, null)`, `atan2(null, expression2)` and `atan(expression1, null)` all return `null`. - +|`atan2(null, null)`, `atan2(null, expression2)` and `atan(expression1, null)` all return `null`. |=== -.+atan2()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN atan2(0.5, 0.6) ---- @@ -268,61 +232,54 @@ The arctangent2 of `0.5` and `0.6` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-cos]] == cos() `cos()` returns the cosine of a number. -*Syntax:* - -[source, syntax, role="noheader"] ----- -cos(expression) ----- +*Syntax:* `cos(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression that represents the angle in radians. - +| `expression` | A numeric expression that represents the angle in radians. |=== -*Considerations:* +*Considerations:* |=== - -| `cos(null)` returns `null`. - +|`cos(null)` returns `null`. |=== -.+cos()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN cos(0.5) ---- @@ -332,61 +289,55 @@ The cosine of `0.5` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-cot]] == cot() `cot()` returns the cotangent of a number. -*Syntax:* - -[source, syntax, role="noheader"] ----- -cot(expression) ----- +*Syntax:* `cot(expression)` *Returns:* - |=== - -|A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression that represents the angle in radians. - +| `expression` | A numeric expression that represents the angle in radians. |=== -*Considerations:* +*Considerations:* |=== - -| `cot(null)` returns `null`. -| `cot(0)` returns `null`. - +|`cot(null)` returns `null`. +|`cot(0)` returns `null`. |=== -.+cot()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN cot(0.5) ---- @@ -396,60 +347,54 @@ The cotangent of `0.5` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-degrees]] == degrees() `degrees()` converts radians to degrees. -*Syntax:* - -[source, syntax, role="noheader"] ----- -degrees(expression) ----- +*Syntax:* `degrees(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression that represents the angle in radians. - +| `expression` | A numeric expression that represents the angle in radians. |=== + *Considerations:* |=== - -| `degrees(null)` returns `null`. - +|`degrees(null)` returns `null`. |=== -.+degrees+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN degrees(3.14159) ---- @@ -459,61 +404,54 @@ The number of degrees in something close to _pi_ is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-haversin]] == haversin() `haversin()` returns half the versine of a number. -*Syntax:* - -[source, syntax, role="noheader"] ----- -haversin(expression) ----- +*Syntax:* `haversin(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== + *Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression that represents the angle in radians. - +| `expression` | A numeric expression that represents the angle in radians. |=== *Considerations:* - |=== - -| `haversin(null)` returns `null`. - +|`haversin(null)` returns `null`. |=== -.+haversin()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN haversin(0.5) ---- @@ -523,33 +461,40 @@ The haversine of `0.5` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-spherical-distance-using-haversin]] == Spherical distance using the `haversin()` function -The `haversin()` function may be used to compute the distance on the surface of a sphere between two points (each given by their latitude and longitude). +The `haversin()` function may be used to compute the distance on the surface of a sphere between two +points (each given by their latitude and longitude). In this example the spherical distance (in km) +between Berlin in Germany (at lat 52.5, lon 13.4) and San Mateo in California (at lat 37.5, lon -122.3) +is calculated using an average earth radius of 6371 km. -.+haversin()+ -====== - -In this example the spherical distance (in km) between Berlin in Germany (at lat 52.5, lon 13.4) and San Mateo in California (at lat 37.5, lon -122.3) is calculated using an average earth radius of 6371 km. .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (ber:City {lat: 52.5, lon: 13.4}), (sm:City {lat: 37.5, lon: -122.3}) RETURN 2 * 6371 * asin(sqrt(haversin(radians( sm.lat - ber.lat )) - + cos(radians( sm.lat )) * cos(radians( ber.lat )) * - haversin(radians( sm.lon - ber.lon )))) AS dist + + cos(radians( sm.lat )) * cos(radians( ber.lat )) * + haversin(radians( sm.lon - ber.lon )))) AS dist ---- The estimated distance between *'Berlin'* and *'San Mateo'* is returned. @@ -557,45 +502,46 @@ The estimated distance between *'Berlin'* and *'San Mateo'* is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-pi]] == pi() `pi()` returns the mathematical constant _pi_. -*Syntax:* - -[source, syntax, role="noheader"] ----- -pi() ----- +*Syntax:* `pi()` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -.+pi()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN pi() ---- @@ -605,61 +551,54 @@ The constant _pi_ is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-radians]] == radians() `radians()` converts degrees to radians. -*Syntax:* - -[source, syntax, role="noheader"] ----- -radians(expression) ----- +*Syntax:* `radians(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression that represents the angle in degrees. - +| `expression` | A numeric expression that represents the angle in degrees. |=== -*Considerations:* +*Considerations:* |=== - -| `radians(null)` returns `null`. - +|`radians(null)` returns `null`. |=== -.+radians()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN radians(180) ---- @@ -669,60 +608,54 @@ The number of radians in `180` degrees is returned (pi). .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-sin]] == sin() `sin()` returns the sine of a number. -*Syntax:* - -[source, syntax, role="noheader"] ----- -sin(expression) ----- +*Syntax:* `sin(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== + *Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression that represents the angle in radians. - +| `expression` | A numeric expression that represents the angle in radians. |=== -*Considerations:* +*Considerations:* |=== - -| `sin(null)` returns `null`. - +|`sin(null)` returns `null`. |=== -.+sin()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN sin(0.5) ---- @@ -732,61 +665,54 @@ The sine of `0.5` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-tan]] == tan() `tan()` returns the tangent of a number. -*Syntax:* - -[source, syntax, role="noheader"] ----- -tan(expression) ----- +*Syntax:* `tan(expression)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| A numeric expression that represents the angle in radians. - +| `expression` | A numeric expression that represents the angle in radians. |=== -*Considerations:* +*Considerations:* |=== - -| `tan(null)` returns `null`. - +|`tan(null)` returns `null`. |=== -.+tan()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN tan(0.5) ---- @@ -796,12 +722,21 @@ The tangent of `0.5` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/functions/predicate.adoc b/modules/ROOT/pages/functions/predicate.adoc index 4a85bc6ac..b68049167 100644 --- a/modules/ROOT/pages/functions/predicate.adoc +++ b/modules/ROOT/pages/functions/predicate.adoc @@ -1,41 +1,68 @@ -:description: Predicates are boolean functions that return `true` or `false` for a given set of non-`null` input. - [[query-functions-predicate]] = Predicate functions - -[abstract] --- -Predicates are boolean functions that return `true` or `false` for a given set of non-`null` input. -They are most commonly used to filter out paths in the `WHERE` part of a query. --- +:description: Predicates are boolean functions that return `true` or `false` for a given set of non-`null` input. They are most commonly used to filter out paths in the `WHERE` part of a query. Functions: -* xref::functions/predicate.adoc#functions-all[all()] -* xref::functions/predicate.adoc#functions-any[any()] -* xref::functions/predicate.adoc#functions-exists[exists()] -* xref::functions/predicate.adoc#functions-isempty[isEmpty()] -* xref::functions/predicate.adoc#functions-none[none()] -* xref::functions/predicate.adoc#functions-single[single()] - -image:graph_predicate_functions.svg[] - -//// -CREATE - (alice {name:'Alice', age: 38, eyes: 'brown'}), - (bob {name: 'Bob', age: 25, eyes: 'blue'}), - (charlie {name: 'Charlie', age: 53, eyes: 'green'}), - (daniel {name: 'Daniel', age: 54, eyes: 'brown', liked_colors: []}), - (eskil {name: 'Eskil', age: 41, eyes: 'blue', liked_colors: ['pink', 'yellow', 'black']}), - (frank {alias: 'Frank', age: 61, eyes: '', liked_colors: ['blue', 'green']}), - (alice)-[:KNOWS]->(bob), - (grace:Person), - (alice)-[:KNOWS]->(charlie), - (bob)-[:KNOWS]->(daniel), - (charlie)-[:KNOWS]->(daniel), - (bob)-[:MARRIED]->(eskil) -//// - +* xref:functions/predicate.adoc#functions-all[all()] +* xref:functions/predicate.adoc#functions-any[any()] +* xref:functions/predicate.adoc#functions-exists[exists()] +* xref:functions/predicate.adoc#functions-isempty[isEmpty()] +* xref:functions/predicate.adoc#functions-none[none()] +* xref:functions/predicate.adoc#functions-single[single()] + +.Graph +["dot", "Predicate functions-1.svg", "neoviz", ""] +---- + N0 [ + label = "name = \'Alice\'\lage = 38\leyes = \'brown\'\l" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 [ + label = "name = \'Bob\'\lage = 25\leyes = \'blue\'\l" + ] + N1 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 -> N4 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "MARRIED\n" + ] + N2 [ + label = "name = \'Charlie\'\lage = 53\leyes = \'green\'\l" + ] + N2 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N3 [ + label = "eyes = \'brown\'\lliked_colors = \[\]\lname = \'Daniel\'\lage = 54\l" + ] + N4 [ + label = "eyes = \'blue\'\lliked_colors = \[\'pink\', \'yellow\', \'black\'\]\lname = \'Eskil\'\lage = 41\l" + ] + N5 [ + label = "eyes = \'\'\lliked_colors = \[\'blue\', \'green\'\]\lalias = \'Frank\'\lage = 61\l" + ] + N6 [ + label = "{Person|}" + ] + +---- + [[functions-all]] == all() @@ -43,48 +70,29 @@ CREATE The function `all()` returns `true` if the predicate holds for all elements in the given list. `null` is returned if the list is `null` or all of its elements are `null`. -*Syntax:* - -[source, syntax, role="noheader"] ----- -all(variable IN list WHERE predicate) ----- +*Syntax:* `all(variable IN list WHERE predicate)` *Returns:* - |=== - -| A Boolean. - +| +A Boolean. |=== -*Arguments:* +*Arguments:* [options="header"] |=== - | Name | Description - -| `list` -a| -An expression that returns a list. +| `list` | An expression that returns a list. A single element cannot be explicitly passed as a literal in the cypher statement. However, an implicit conversion will happen for single elements when passing node properties during cypher execution. - -| `variable` -| A variable that can be used from within the predicate. - -| `predicate` -| A predicate that is tested against all items in the list. - +| `variable` | A variable that can be used from within the predicate. +| `predicate` | A predicate that is tested against all items in the list. |=== -.+all()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH p = (a)-[*1..3]->(b) WHERE @@ -99,15 +107,41 @@ All nodes in the returned paths will have a property `age` with a value larger t .Result [role="queryresult",options="header,footer",cols="1*(2)-[KNOWS,3]->(3)+ 1+d|Rows: 1 - |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (grace:Person), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]>(b) +WHERE + a.name = 'Alice' + AND b.name = 'Daniel' + AND all(x IN nodes(p) WHERE x.age > 30) +RETURN p +]]> +++++ +endif::nonhtmloutput[] [[functions-any]] == any() @@ -115,47 +149,29 @@ All nodes in the returned paths will have a property `age` with a value larger t The function `any()` returns `true` if the predicate holds for at least one element in the given list. `null` is returned if the list is `null` or all of its elements are `null`. -*Syntax:* - -[source, syntax, role="noheader"] ----- -any(variable IN list WHERE predicate) ----- +*Syntax:* `any(variable IN list WHERE predicate)` *Returns:* - |=== - -| A Boolean. - +| +A Boolean. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `list` -a| -An expression that returns a list. +| `list` | An expression that returns a list. A single element cannot be explicitly passed as a literal in the cypher statement. However, an implicit conversion will happen for single elements when passing node properties during cypher execution. - -| `variable` -| A variable that can be used from within the predicate. - -| `predicate` -| A predicate that is tested against all items in the list. - +| `variable` | A variable that can be used from within the predicate. +| `predicate` | A predicate that is tested against all items in the list. |=== -.+any()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE any(color IN n.liked_colors WHERE color = 'yellow') @@ -167,15 +183,38 @@ The query returns nodes with the property `liked_colors` (as a list), where at l .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (grace:Person), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] [[functions-exists]] == exists() @@ -183,38 +222,25 @@ The query returns nodes with the property `liked_colors` (as a list), where at l The function `exists()` returns `true` if a match for the given pattern exists in the graph, or if the specified property exists in the node, relationship or map. `null` is returned if the input argument is `null`. -*Syntax:* - -[source, syntax, role="noheader"] ----- -exists(pattern-or-property) ----- +*Syntax:* `exists(pattern-or-property)` *Returns:* - |=== - -| A Boolean. - +| +A Boolean. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `pattern-or-property` -| A pattern or a property (in the form 'variable.prop'). - +| `pattern-or-property` | A pattern or a property (in the form 'variable.prop'). |=== -.+exists()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE n.name IS NOT NULL @@ -228,7 +254,6 @@ The names of all nodes with the `name` property are returned, along with a boole .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(bob), + (grace:Person), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]>()) AS is_married +]]> +++++ +endif::nonhtmloutput[] -.+exists()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a), @@ -271,59 +320,82 @@ This query exemplifies the behavior of `exists()` when operating on `null` nodes .Result [role="queryresult",options="header,footer",cols="5*+ | +false+ | ++ | ++ 5+d|Rows: 1 - |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (grace:Person), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [NOTE] ==== -Note that the `exists()` function is deprecated for property input. -Please use the xref::syntax/operators.adoc#cypher-comparison[`IS NOT NULL` predicate] instead. -==== +Note that the `exists()` function is deprecated for property input. Please use the xref:syntax/operators.adoc#cypher-comparison[`IS NOT NULL` predicate] instead. +==== + [[functions-isempty]] == isEmpty() The function `isEmpty()` returns `true` if the given list or map contains no elements or if the given string contains no characters. -*Syntax:* - -[source, syntax, role="noheader"] ----- -isEmpty(list) ----- +*Syntax:* `isEmpty(list)` *Returns:* - |=== - -| A Boolean. - +| +A Boolean. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `list` -| An expression that returns a list. - +| `list` | An expression that returns a list. |=== -.+isEmpty(list)+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE NOT isEmpty(n.liked_colors) @@ -341,41 +413,53 @@ The nodes with the property `liked_colors` being non-empty are returned. 1+d|Rows: 2 |=== -====== - +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (grace:Person), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) -*Syntax:* +]]> +++++ +endif::nonhtmloutput[] -[source, syntax, role="noheader"] ----- -isEmpty(map) ----- +*Syntax:* `isEmpty(map)` *Returns:* - |=== - -| A Boolean. - +| +A Boolean. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `map` -| An expression that returns a map. - +| `map` | An expression that returns a map. |=== -.+isEmpty(map)+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE isEmpty(properties(n)) @@ -387,47 +471,58 @@ Nodes that does not have any properties are returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (grace:Person), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) -*Syntax:* +]]> +++++ +endif::nonhtmloutput[] -[source, syntax, role="noheader"] ----- -isEmpty(string) ----- +*Syntax:* `isEmpty(string)` *Returns:* |=== - -| A Boolean. - +| +A Boolean. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `string` -| An expression that returns a string. - +| `string` | An expression that returns a string. |=== -.+isEmpty(string)+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE isEmpty(n.eyes) @@ -439,70 +534,78 @@ The age are returned for each node that has a property `eyes` where the value ev .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (grace:Person), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [NOTE] ==== -The function `isEmpty()`, like most other Cypher functions, returns `null` if `null` is passed in to the function. +The function `isEmpty()`, like most other Cypher functions, returns `null` if `null is passed in to the function. That means that a predicate `isEmpty(n.eyes)` will filter out all nodes where the `eyes` property is not set. -Thus, `isEmpty()` is not suited to test for `null`-values. +Thus, `isEmpty()` is not suited to test for null values. `IS NULL` or `IS NOT NULL` should be used for that purpose. -==== +==== + [[functions-none]] == none() The function `none()` returns `true` if the predicate does _not_ hold for any element in the given list. `null` is returned if the list is `null` or all of its elements are `null`. -*Syntax:* - -[source, syntax, role="noheader"] ----- -none(variable IN list WHERE predicate) ----- +*Syntax:* `none(variable IN list WHERE predicate)` *Returns:* |=== - -|A Boolean. - +| +A Boolean. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `list` -a| -An expression that returns a list. +| `list` | An expression that returns a list. A single element cannot be explicitly passed as a literal in the cypher statement. However, an implicit conversion will happen for single elements when passing node properties during cypher execution. - -| `variable` -| A variable that can be used from within the predicate. - -| `predicate` -| A predicate that is tested against all items in the list. - +| `variable` | A variable that can be used from within the predicate. +| `predicate` | A predicate that is tested against all items in the list. |=== -.+none()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH p = (n)-[*1..3]->(b) WHERE @@ -516,16 +619,41 @@ No node in the returned paths has a property `age` with the value `25`. .Result [role="queryresult",options="header,footer",cols="1*(2)+ | +(0)-[KNOWS,1]->(2)-[KNOWS,3]->(3)+ 1+d|Rows: 2 - |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (grace:Person), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]>(b) +WHERE + n.name = 'Alice' + AND none(x IN nodes(p) WHERE x.age = 25) +RETURN p +]]> +++++ +endif::nonhtmloutput[] [[functions-single]] == single() @@ -533,42 +661,27 @@ No node in the returned paths has a property `age` with the value `25`. The function `single()` returns `true` if the predicate holds for exactly _one_ of the elements in the given list. `null` is returned if the list is `null` or all of its elements are `null`. -*Syntax:* - -[source, syntax, role="noheader"] ----- -single(variable IN list WHERE predicate) ----- +*Syntax:* `single(variable IN list WHERE predicate)` *Returns:* |=== - -| A Boolean. - +| +A Boolean. |=== + *Arguments:* [options="header"] |=== | Name | Description - -| `list` -| An expression that returns a list. - -| `variable` -| A variable that can be used from within the predicate. - -| `predicate` -| A predicate that is tested against all items in the list. - +| `list` | An expression that returns a list. +| `variable` | A variable that can be used from within the predicate. +| `predicate` | A predicate that is tested against all items in the list. |=== -.+single()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH p = (n)-->(b) WHERE @@ -582,12 +695,38 @@ In every returned path there is exactly one node that has a property `eyes` with .Result [role="queryresult",options="header,footer",cols="1*(1)+ 1+d|Rows: 1 - |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (grace:Person), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]>(b) +WHERE + n.name = 'Alice' + AND single(var IN nodes(p) WHERE var.eyes = 'blue') +RETURN p +]]> +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/functions/scalar.adoc b/modules/ROOT/pages/functions/scalar.adoc index 7b28c08d9..ffe38cb3b 100644 --- a/modules/ROOT/pages/functions/scalar.adoc +++ b/modules/ROOT/pages/functions/scalar.adoc @@ -1,107 +1,117 @@ -:description: Scalar functions return a single value. - [[query-functions-scalar]] = Scalar functions - -[abstract] --- -Scalar functions return a single value. --- +:description: Scalar functions return a single value. Functions: -* xref::functions/scalar.adoc#functions-coalesce[coalesce()] -* xref::functions/scalar.adoc#functions-endnode[endNode()] -* xref::functions/scalar.adoc#functions-head[head()] -* xref::functions/scalar.adoc#functions-id[id()] -* xref::functions/scalar.adoc#functions-last[last()] -* xref::functions/scalar.adoc#functions-length[length()] -* xref::functions/scalar.adoc#functions-properties[properties()] -* xref::functions/scalar.adoc#functions-randomuuid[randomUUID()] -* xref::functions/scalar.adoc#functions-size[size()] -* xref::functions/scalar.adoc#functions-size-of-pattern-comprehension[Size of pattern comprehension] -* xref::functions/scalar.adoc#functions-size-of-string[Size of string] -* xref::functions/scalar.adoc#functions-startnode[startNode()] -* xref::functions/scalar.adoc#functions-timestamp[timestamp()] -* xref::functions/scalar.adoc#functions-toboolean[toBoolean()] -* xref::functions/scalar.adoc#functions-tobooleanornull[toBooleanOrNull()] -* xref::functions/scalar.adoc#functions-tofloat[toFloat()] -* xref::functions/scalar.adoc#functions-tofloatornull[toFloatOrNull()] -* xref::functions/scalar.adoc#functions-tointeger[toInteger()] -* xref::functions/scalar.adoc#functions-tointegerornull[toIntegerOrNull()] -* xref::functions/scalar.adoc#functions-type[type()] - +* xref:functions/scalar.adoc#functions-coalesce[coalesce()] +* xref:functions/scalar.adoc#functions-endnode[endNode()] +* xref:functions/scalar.adoc#functions-head[head()] +* xref:functions/scalar.adoc#functions-id[id()] +* xref:functions/scalar.adoc#functions-last[last()] +* xref:functions/scalar.adoc#functions-length[length()] +* xref:functions/scalar.adoc#functions-properties[properties()] +* xref:functions/scalar.adoc#functions-randomuuid[randomUUID()] +* xref:functions/scalar.adoc#functions-size[size()] +* xref:functions/scalar.adoc#functions-size-of-pattern-expression[Size of pattern expression] +* xref:functions/scalar.adoc#functions-size-of-string[Size of string] +* xref:functions/scalar.adoc#functions-startnode[startNode()] +* xref:functions/scalar.adoc#functions-timestamp[timestamp()] +* xref:functions/scalar.adoc#functions-toboolean[toBoolean()] +* xref:functions/scalar.adoc#functions-tobooleanornull[toBooleanOrNull()] +* xref:functions/scalar.adoc#functions-tofloat[toFloat()] +* xref:functions/scalar.adoc#functions-tofloatornull[toFloatOrNull()] +* xref:functions/scalar.adoc#functions-tointeger[toInteger()] +* xref:functions/scalar.adoc#functions-tointegerornull[toIntegerOrNull()] +* xref:functions/scalar.adoc#functions-type[type()] [IMPORTANT] ==== The `length()` and `size()` functions are quite similar, and so it is important to take note of the difference. -Function `length()`:: Only works for xref::functions/scalar.adoc#functions-length[paths]. -Function `size()`:: Only works for the three types: xref::functions/scalar.adoc#functions-size-of-string[strings], xref::functions/scalar.adoc#functions-size[lists], xref::functions/scalar.adoc#functions-size-of-pattern-comprehension[pattern comprehension]. -==== +Function `length()`:: Only works for xref:functions/scalar.adoc#functions-length[paths]. +Function `size()`:: Only works for the three types: xref:functions/scalar.adoc#functions-size-of-string[strings], xref:functions/scalar.adoc#functions-size[lists], and xref:functions/scalar.adoc#functions-size-of-pattern-expression[pattern expressions]. -image:graph_scalar_functions.svg[] -//// -CREATE - (alice:Developer {name:'Alice', age: 38, eyes: 'brown'}), - (bob {name: 'Bob', age: 25, eyes: 'blue'}), - (charlie {name: 'Charlie', age: 53, eyes: 'green'}), - (daniel {name: 'Daniel', age: 54, eyes: 'brown'}), - (eskil {name: 'Eskil', age: 41, eyes: 'blue', liked_colors: ['pink', 'yellow', 'black']}), - (alice)-[:KNOWS]->(bob), - (alice)-[:KNOWS]->(charlie), - (bob)-[:KNOWS]->(daniel), - (charlie)-[:KNOWS]->(daniel), - (bob)-[:MARRIED]->(eskil) -//// +==== +.Graph +["dot", "Scalar functions-1.svg", "neoviz", ""] +---- + N0 [ + label = "{Developer|name = \'Alice\'\lage = 38\leyes = \'brown\'\l}" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 [ + label = "name = \'Bob\'\lage = 25\leyes = \'blue\'\l" + ] + N1 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 -> N4 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "MARRIED\n" + ] + N2 [ + label = "name = \'Charlie\'\lage = 53\leyes = \'green\'\l" + ] + N2 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N3 [ + label = "name = \'Daniel\'\lage = 54\leyes = \'brown\'\l" + ] + N4 [ + label = "eyes = \'blue\'\lliked_colors = \[\'pink\', \'yellow\', \'black\'\]\lname = \'Eskil\'\lage = 41\l" + ] + +---- + [[functions-coalesce]] == coalesce() The function `coalesce()` returns the first non-`null` value in the given list of expressions. -*Syntax:* - -[source, syntax, role="noheader"] ----- -coalesce(expression [, expression]*) ----- +*Syntax:* `coalesce(expression [, expression]*)` *Returns:* - |=== - -| The type of the value returned will be that of the first non-`null` expression. - +| +The type of the value returned will be that of the first non-`null` expression. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| An expression that may return `null`. - +| `expression` | An expression that may return `null`. |=== -*Considerations:* +*Considerations:* |=== - -| `null` will be returned if all the arguments are `null`. - +|`null` will be returned if all the arguments are `null`. |=== -.+coalesce()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a) WHERE a.name = 'Alice' @@ -111,61 +121,67 @@ RETURN coalesce(a.hairColor, a.eyes) .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] [[functions-endnode]] == endNode() The function `endNode()` returns the end node of a relationship. -*Syntax:* - -[source, syntax, role="noheader"] ----- -endNode(relationship) ----- +*Syntax:* `endNode(relationship)` *Returns:* - |=== - -| A Node. - +| +A Node. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `relationship` -| An expression that returns a relationship. - +| `relationship` | An expression that returns a relationship. |=== -*Considerations:* +*Considerations:* |=== - -| `endNode(null)` returns `null`. - +|`endNode(null)` returns `null`. |=== -.+endNode()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (x:Developer)-[r]-() RETURN endNode(r) @@ -180,55 +196,63 @@ RETURN endNode(r) 1+d|Rows: 2 |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] [[functions-head]] == head() The function `head()` returns the first element in a list. -*Syntax:* - -[source, syntax, role="noheader"] ----- -head(expression) ----- +*Syntax:* `head(expression)` *Returns:* - |=== - -| The type of the value returned will be that of the first element of the list. - +| +The type of the value returned will be that of the first element of the list. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| An expression that returns a list. - +| `expression` | An expression that returns a list. |=== + *Considerations:* |=== - -| `head(null)` returns `null`. -| `head([])` returns `null`. -| If the first element in `list` is `null`, `head(list)` will return `null`. - +|`head(null)` returns `null`. +|`head([])` returns `null`. +|If the first element in `list` is `null`, `head(list)` will return `null`. |=== -.+head()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a) WHERE a.name = 'Eskil' @@ -240,22 +264,43 @@ The first element in the list is returned. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] [[functions-id]] == id() The function `id()` returns a node or a relationship identifier, unique by an object type and a database. Therefore, it is perfectly allowable for `id()` to return the same value for both nodes and relationships in the same database. -For examples on how to get a node and a relationship by ID, see xref::clauses/match.adoc#get-node-rel-by-id[Get node or relationship by ID]. +For examples on how to get a node and a relationship by ID, see xref:clauses/match.adoc#get-node-rel-by-id[Get node or relationship by id]. [NOTE] ==== @@ -268,46 +313,35 @@ The identifier for a node is guaranteed to be unique among other nodes' identifi Relationship:: Every relationship in a database has an identifier. The identifier for a relationship is guaranteed to be unique among other relationships' identifiers in the same database, within the scope of a single transaction. -==== -*Syntax:* -[source, syntax, role="noheader"] ----- -id(expression) ----- +==== -*Returns:* +*Syntax:* `id(expression)` +*Returns:* |=== - -| An Integer. - +| +An Integer. |=== + *Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| An expression that returns a node or a relationship. - +| `expression` | An expression that returns a node or a relationship. |=== + *Considerations:* |=== - -| `id(null)` returns `null`. - +|`id(null)` returns `null`. |=== -.+id()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a) RETURN id(a) @@ -318,7 +352,6 @@ The node identifier for each of the nodes is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] [[functions-last]] == last() The function `last()` returns the last element in a list. -*Syntax:* - -[source, syntax, role="noheader"] ----- -last(expression) ----- +*Syntax:* `last(expression)` *Returns:* - |=== - -| The type of the value returned will be that of the last element of the list. - +| +The type of the value returned will be that of the last element of the list. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| An expression that returns a list. - +| `expression` | An expression that returns a list. |=== -*Considerations:* +*Considerations:* |=== - -| `last(null)` returns `null`. -| `last([])` returns `null`. -| If the last element in `list` is `null`, `last(list)` will return `null`. - +|`last(null)` returns `null`. +|`last([])` returns `null`. +|If the last element in `list` is `null`, `last(list)` will return `null`. |=== -.+last()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a) WHERE a.name = 'Eskil' @@ -390,61 +429,67 @@ The last element in the list is returned. .Result [role="queryresult",options="header,footer",cols="2* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] [[functions-length]] == length() The function `length()` returns the length of a path. -*Syntax:* - -[source, syntax, role="noheader"] ----- -length(path) ----- +*Syntax:* `length(path)` *Returns:* - |=== - -| An Integer. - +| +An Integer. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `path` -| An expression that returns a path. - +| `path` | An expression that returns a path. |=== -*Considerations:* +*Considerations:* |=== - -| `length(null)` returns `null`. - +|`length(null)` returns `null`. |=== -.+length()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH p = (a)-->(b)-->(c) WHERE a.name = 'Alice' @@ -456,17 +501,38 @@ The length of the path `p` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]>(b)-->(c) +WHERE a.name = 'Alice' +RETURN length(p) +]]> +++++ +endif::nonhtmloutput[] [[functions-properties]] == properties() @@ -474,46 +540,31 @@ The length of the path `p` is returned. The function `properties()` returns a map containing all the properties; the function can be utilized for a relationship or a node. If the argument is already a map, it is returned unchanged. -*Syntax:* - -[source, syntax, role="noheader"] ----- -properties(expression) ----- +*Syntax:* `properties(expression)` *Returns:* - |=== - -| A Map. - +| +A Map. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| An expression that returns a relationship, a node, or a map. - +| `expression` | An expression that returns a relationship, a node, or a map. |=== -*Considerations:* +*Considerations:* |=== - -| `properties(null)` returns `null`. - +|`properties(null)` returns `null`. |=== -.+properties()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (p:Person {name: 'Stefan', city: 'Berlin'}) RETURN properties(p) @@ -522,18 +573,38 @@ RETURN properties(p) .Result [role="queryresult",options="header,footer",cols="1* "Berlin", name -> "Stefan"}+ 1+d|Rows: 1 + Nodes created: 1 + Properties set: 2 + Labels added: 1 - |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] [[functions-randomuuid]] == randomUUID() @@ -541,27 +612,17 @@ Labels added: 1 The function `randomUUID()` returns a randomly-generated Universally Unique Identifier (UUID), also known as a Globally Unique Identifier (GUID). This is a 128-bit value with strong guarantees of uniqueness. -*Syntax:* - -[source, syntax, role="noheader"] ----- -randomUUID() ----- +*Syntax:* `randomUUID()` *Returns:* - |=== - -| A String. - +| +A String. |=== -.+randomUUID()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN randomUUID() AS uuid ---- @@ -570,59 +631,66 @@ RETURN randomUUID() AS uuid [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] [[functions-size]] == size() The function `size()` returns the number of elements in a list. -*Syntax:* - -[source, syntax, role="noheader"] ----- -size(list) ----- +*Syntax:* `size(list)` *Returns:* - |=== - -| An Integer. - +| +An Integer. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description +| `list` | An expression that returns a list. +|=== -| `list` -| An expression that returns a list. +*Considerations:* |=== - -*Considerations:* -|=== - -| `size(null)` returns `null`. - +|`size(null)` returns `null`. |=== -.+size()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN size(['Alice', 'Bob']) ---- @@ -630,114 +698,128 @@ RETURN size(['Alice', 'Bob']) .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] -[[functions-size-of-pattern-comprehension]] -== size() applied to pattern comprehension +[[functions-size-of-pattern-expression]] +== size() applied to pattern expression -This is the same function `size()` as described above, but you pass in a pattern comprehension. +This is the same function `size()` as described above, but you pass in a pattern expression, instead of a list. The function size will then calculate on a _list_ of paths. -*Syntax:* +*Syntax:* `size(pattern expression)` -[source, syntax, role="noheader"] ----- -size(pattern expression) ----- *Arguments:* - [options="header"] |=== | Name | Description - -| `pattern expression` -| A pattern expression that returns a list. - +| `pattern expression` | A pattern expression that returns a list. |=== -.+size()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a) WHERE a.name = 'Alice' -RETURN size([p=(a)-->()-->() | p]) AS fof +RETURN size((a)-->()-->()) AS fof ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]>()-->()) AS fof +]]> +++++ +endif::nonhtmloutput[] [[functions-size-of-string]] == size() applied to string The function `size()` returns the number of Unicode characters in a string. -*Syntax:* - -[source, syntax, role="noheader"] ----- -size(string) ----- +*Syntax:* `size(string)` *Returns:* - |=== - -| An Integer. - +| +An Integer. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `string` -| An expression that returns a string value. - +| `string` | An expression that returns a string value. |=== -*Considerations:* +*Considerations:* |=== - -| `size(null)` returns `null`. - +|`size(null)` returns `null`. |=== -.+size()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a) WHERE size(a.name) > 6 @@ -747,63 +829,69 @@ RETURN size(a.name) .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> 6 +RETURN size(a.name) +]]> +++++ +endif::nonhtmloutput[] [[functions-startnode]] == startNode() The function `startNode()` returns the start node of a relationship. -*Syntax:* - -[source, syntax, role="noheader"] ----- -startNode(relationship) ----- +*Syntax:* `startNode(relationship)` *Returns:* - |=== - -| A Node. - +| +A Node. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `relationship` -| An expression that returns a relationship. - +| `relationship` | An expression that returns a relationship. |=== -*Considerations:* +*Considerations:* |=== - -| `startNode(null)` returns `null`. - +|`startNode(null)` returns `null`. |=== -.+startNode()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (x:Developer)-[r]-() RETURN startNode(r) @@ -812,16 +900,36 @@ RETURN startNode(r) .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] [[functions-timestamp]] == timestamp() @@ -831,37 +939,27 @@ The function `timestamp()` returns the difference, measured in milliseconds, bet [NOTE] ==== It is the equivalent of `datetime().epochMillis`. -==== -*Syntax:* -[source, syntax, role="noheader"] ----- -timestamp() ----- +==== -*Returns:* +*Syntax:* `timestamp()` +*Returns:* |=== - -| An Integer. - +| +An Integer. |=== -*Considerations:* +*Considerations:* |=== - |`timestamp()` will return the same value during one entire query, even for long-running queries. - |=== -.+timestamp()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN timestamp() ---- @@ -871,64 +969,69 @@ The time in milliseconds is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] [[functions-toboolean]] == toBoolean() The function `toBoolean()` converts a string, integer or boolean value to a boolean value. -*Syntax:* - -[source, syntax, role="noheader"] ----- -toBoolean(expression) ----- +*Syntax:* `toBoolean(expression)` *Returns:* - |=== - -| A Boolean. - +| +A Boolean. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| An expression that returns a boolean, string or integer value. - +| `expression` | An expression that returns a boolean, string or integer value. |=== -*Considerations:* +*Considerations:* |=== - -| `toBoolean(null)` returns `null`. -| If `expression` is a boolean value, it will be returned unchanged. -| If the parsing fails, `null` will be returned. -| If `expression` is the integer value `0`, `false` will be returned. For any other integer value `true` will be returned. -| This function will return an error if provided with an expression that is not a string, integer or boolean value. - +|`toBoolean(null)` returns `null`. +|If `expression` is a boolean value, it will be returned unchanged. +|If the parsing fails, `null` will be returned. +|If `expression` is the integer value `0`, `false` will be returned. For any other integer value `true` will be returned. +|This function will return an error if provided with an expression that is not a string, integer or boolean value. |=== -.+toBoolean()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN toBoolean('true'), toBoolean('not a boolean'), toBoolean(0) ---- @@ -936,64 +1039,69 @@ RETURN toBoolean('true'), toBoolean('not a boolean'), toBoolean(0) .Result [role="queryresult",options="header,footer",cols="3*+ | +false+ 3+d|Rows: 1 - |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] [[functions-tobooleanornull]] == toBooleanOrNull() The function `toBooleanOrNull()` converts a string, integer or boolean value to a boolean value. For any other input value, `null` will be returned. -*Syntax:* - -[source, syntax, role="noheader"] ----- -toBooleanOrNull(expression) ----- +*Syntax:* `toBooleanOrNull(expression)` *Returns:* - |=== - -| A Boolean or `null`. - +| +A Boolean or `null`. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| Any expression that returns a value. - +| `expression` | Any expression that returns a value. |=== -*Considerations:* +*Considerations:* |=== - -| `toBooleanOrNull(null)` returns `null`. -| If `expression` is a boolean value, it will be returned unchanged. -| If the parsing fails, `null` will be returned. -| If `expression` is the integer value `0`, `false` will be returned. For any other integer value `true` will be returned. -| If the `expression` is not a string, integer or boolean value, `null` will be returned. - +|`toBooleanOrNull(null)` returns `null`. +|If `expression` is a boolean value, it will be returned unchanged. +|If the parsing fails, `null` will be returned. +|If `expression` is the integer value `0`, `false` will be returned. For any other integer value `true` will be returned. +|If the `expression` is not a string, integer or boolean value, `null` will be returned. |=== -.+toBooleanOrNull()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN toBooleanOrNull('true'), toBooleanOrNull('not a boolean'), toBooleanOrNull(0), toBooleanOrNull(1.5) ---- @@ -1006,120 +1114,132 @@ RETURN toBooleanOrNull('true'), toBooleanOrNull('not a boolean'), toBooleanOrNul 4+d|Rows: 1 |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] [[functions-tofloat]] == toFloat() The function `toFloat()` converts an integer, floating point or a string value to a floating point number. -*Syntax:* - -[source, syntax, role="noheader"] ----- -toFloat(expression) ----- +*Syntax:* `toFloat(expression)` *Returns:* |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| An expression that returns a numeric or a string value. - +| `expression` | An expression that returns a numeric or a string value. |=== -*Considerations:* +*Considerations:* |=== - -| `toFloat(null)` returns `null`. -| If `expression` is a floating point number, it will be returned unchanged. -| If the parsing fails, `null` will be returned. -| This function will return an error if provided with an expression that is not an integer, floating point or a string value. - +|`toFloat(null)` returns `null`. +|If `expression` is a floating point number, it will be returned unchanged. +|If the parsing fails, `null` will be returned. +|This function will return an error if provided with an expression that is not an integer, floating point or a string value. |=== -.+toFloat()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN toFloat('11.5'), toFloat('not a number') ---- .Result [role="queryresult",options="header,footer",cols="2*+ 2+d|Rows: 1 - |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[functions-tofloatornull]] == toFloatOrNull() -The function `toFloatOrNull()` converts an integer, floating point or a string value to a floating point number. -For any other input value, `null` will be returned. +The function `toFloatOrNull()` converts an integer, floating point or a string value to a floating point number. For any other input value, `null` will be returned. -*Syntax:* - -[source, syntax, role="noheader"] ----- -toFloatOrNull(expression) ----- +*Syntax:* `toFloatOrNull(expression)` *Returns:* |=== - -| A Float or `null`. - +| +A Float or `null`. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| Any expression that returns a value. - +| `expression` | Any expression that returns a value. |=== -*Considerations:* +*Considerations:* |=== - |`toFloatOrNull(null)` returns `null`. |If `expression` is a floating point number, it will be returned unchanged. |If the parsing fails, `null` will be returned. |If the `expression` is not an integer, floating point or a string value, `null` will be returned. - |=== -.+toFloatOrNull()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN toFloatOrNull('11.5'), toFloatOrNull('not a number'), toFloatOrNull(true) ---- @@ -1127,66 +1247,69 @@ RETURN toFloatOrNull('11.5'), toFloatOrNull('not a number'), toFloatOrNull(true) .Result [role="queryresult",options="header,footer",cols="3*+ | ++ 3+d|Rows: 1 - |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] [[functions-tointeger]] == toInteger() The function `toInteger()` converts a boolean, integer, floating point or a string value to an integer value. -*Syntax:* - -[source, syntax, role="noheader"] ----- -toInteger(expression) ----- +*Syntax:* `toInteger(expression)` *Returns:* - |=== - -| An Integer. - +| +An Integer. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| An expression that returns a boolean, numeric or a string value. - +| `expression` | An expression that returns a boolean, numeric or a string value. |=== -*Considerations:* +*Considerations:* |=== - -| `toInteger(null)` returns `null`. -| If `expression` is an integer value, it will be returned unchanged. -| If the parsing fails, `null` will be returned. -| If `expression` is the boolean value `false`, `0` will be returned. -| If `expression` is the boolean value `true`, `1` will be returned. -| This function will return an error if provided with an expression that is not a boolean, floating point, integer or a string value. - +|`toInteger(null)` returns `null`. +|If `expression` is an integer value, it will be returned unchanged. +|If the parsing fails, `null` will be returned. +|If `expression` is the boolean value `false`, `0` will be returned. If `expression` is the boolean value `true`, `1` will be returned. +|This function will return an error if provided with an expression that is not a boolean, floating point, integer or a string value. |=== -.+toInteger()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN toInteger('42'), toInteger('not a number'), toInteger(true) ---- @@ -1194,64 +1317,69 @@ RETURN toInteger('42'), toInteger('not a number'), toInteger(true) .Result [role="queryresult",options="header,footer",cols="3*+ | +1+ 3+d|Rows: 1 - |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] [[functions-tointegerornull]] == toIntegerOrNull() The function `toIntegerOrNull()` converts a boolean, integer, floating point or a string value to an integer value. For any other input value, `null` will be returned. -*Syntax:* - -[source, syntax, role="noheader"] ----- -toIntegerOrNull(expression) ----- +*Syntax:* `toIntegerOrNull(expression)` *Returns:* |=== - -| An Integer or `null`. - +| +An Integer or `null`. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - | `expression` | Any expression that returns a value. - |=== -*Considerations:* +*Considerations:* |=== - -| `toIntegerOrNull(null)` returns `null`. -| If `expression` is an integer value, it will be returned unchanged. -| If the parsing fails, `null` will be returned. -| If `expression` is the boolean value `false`, `0` will be returned. -| If `expression` is the boolean value `true`, `1` will be returned. -| If the `expression` is not a boolean, floating point, integer or a string value, `null` will be returned. - +|`toIntegerOrNull(null)` returns `null`. +|If `expression` is an integer value, it will be returned unchanged. +|If the parsing fails, `null` will be returned. +|If `expression` is the boolean value `false`, `0` will be returned. If `expression` is the boolean value `true`, `1` will be returned. +|If the `expression` is not a boolean, floating point, integer or a string value, `null` will be returned. |=== -.+toIntegerOrNull()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN toIntegerOrNull('42'), toIntegerOrNull('not a number'), toIntegerOrNull(true), toIntegerOrNull(['A', 'B', 'C']) ---- @@ -1259,61 +1387,65 @@ RETURN toIntegerOrNull('42'), toIntegerOrNull('not a number'), toIntegerOrNull(t .Result [role="queryresult",options="header,footer",cols="4*+ | +1+ | ++ 4+d|Rows: 1 - |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +]]> +++++ +endif::nonhtmloutput[] [[functions-type]] == type() The function `type()` returns the string representation of the relationship type. -*Syntax:* - -[source, syntax, role="noheader"] ----- -type(relationship) ----- +*Syntax:* `type(relationship)` *Returns:* - |=== - -| A String. - +| +A String. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `relationship` -| An expression that returns a relationship. - +| `relationship` | An expression that returns a relationship. |=== -*Considerations:* +*Considerations:* |=== - -| `type(null)` returns `null`. - +|`type(null)` returns `null`. |=== -.+type()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n)-[r]->() WHERE n.name = 'Alice' @@ -1325,13 +1457,35 @@ The relationship type of `r` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]>() +WHERE n.name = 'Alice' +RETURN type(r) +]]> +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/functions/spatial.adoc b/modules/ROOT/pages/functions/spatial.adoc index 449478084..b21b54922 100644 --- a/modules/ROOT/pages/functions/spatial.adoc +++ b/modules/ROOT/pages/functions/spatial.adoc @@ -1,38 +1,40 @@ -:description: Spatial functions are used to specify 2D or 3D points in a Coordinate Reference System (CRS) and to calculate the geodesic distance between two points. - [[query-functions-spatial]] = Spatial functions - -[abstract] --- -These functions are used to specify 2D or 3D points in a Coordinate Reference System (CRS) and to calculate the geodesic distance between two points. --- +:description: These functions are used to specify 2D or 3D points in a Coordinate Reference System (CRS) and to calculate the geodesic distance between two points. Functions: -* xref::functions/spatial.adoc#functions-distance[point.distance()] -* xref::functions/spatial.adoc#functions-withinBBox[point.withinBBox()] -* xref::functions/spatial.adoc#functions-point-wgs84-2d[point() - WGS 84 2D] -* xref::functions/spatial.adoc#functions-point-wgs84-3d[point() - WGS 84 3D] -* xref::functions/spatial.adoc#functions-point-cartesian-2d[point() - Cartesian 2D] -* xref::functions/spatial.adoc#functions-point-cartesian-3d[point() - Cartesian 3D] +* xref:functions/spatial.adoc#functions-distance[distance()] +* xref:functions/spatial.adoc#functions-point-wgs84-2d[point() - WGS 84 2D] +* xref:functions/spatial.adoc#functions-point-wgs84-3d[point() - WGS 84 3D] +* xref:functions/spatial.adoc#functions-point-cartesian-2d[point() - Cartesian 2D] +* xref:functions/spatial.adoc#functions-point-cartesian-3d[point() - Cartesian 3D] + The following graph is used for some of the examples below. -image:graph_spatial_functions.svg[] - -//// -CREATE - (copenhagen:TrainStation {longitude: 12.564590, latitude: 55.672874, city: 'Copenhagen'}), - (malmo:Office {longitude: 12.994341, latitude: 55.611784, city: 'Malmo'}), - (copenhagen)-[:TRAVEL_ROUTE]->(malmo) -//// +.Graph +["dot", "Spatial functions-1.svg", "neoviz", ""] +---- + N0 [ + label = "{TrainStation|latitude = 55.672874\llongitude = 12.56459\lcity = \'Copenhagen\'\l}" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "TRAVEL_ROUTE\n" + ] + N1 [ + label = "{Office|latitude = 55.611784\llongitude = 12.994341\lcity = \'Malmo\'\l}" + ] +---- + [[functions-distance]] -== point.distance() +== distance() -`point.distance()` returns a floating point number representing the geodesic distance between two points in the same Coordinate Reference System (CRS). +`distance()` returns a floating point number representing the geodesic distance between two points in the same Coordinate Reference System (CRS). * If the points are in the _Cartesian_ CRS (2D or 3D), then the units of the returned distance will be the same as the units of the points, calculated using Pythagoras' theorem. * If the points are in the _WGS-84_ CRS (2D), then the units of the returned distance will be meters, based on the haversine formula over a spherical earth approximation. @@ -42,57 +44,38 @@ CREATE *** To account for the difference in height, Pythagoras' theorem is used, combining the previously calculated spherical distance with the height difference. ** This formula works well for points close to the earth's surface; for instance, it is well-suited for calculating the distance of an airplane flight. It is less suitable for greater heights, however, such as when calculating the distance between two satellites. + -*Syntax:* - -[source, syntax, role="noheader"] ----- -point.distance(point1, point2) ----- +*Syntax:* `distance(point1, point2)` *Returns:* - |=== - -| A Float. - +| +A Float. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `point1` -| A point in either a geographic or cartesian coordinate system. - -| `point2` -| A point in the same CRS as `point1`. - +| `point1` | A point in either a geographic or cartesian coordinate system. +| `point2` | A point in the same CRS as 'point1'. |=== + *Considerations:* |=== - -| `point.distance(null, null)` return `null`. -| `point.distance(null, point2)` return `null`. -| `point.distance(point1, null)` return `null`. -| Attempting to use points with different Coordinate Reference Systems (such as WGS 84 2D and WGS 84 3D) will return `null`. - +|`distance(null, null)`, `distance(null, point2)` and `distance(point1, null)` all return `null`. +|Attempting to use points with different Coordinate Reference Systems (such as WGS 84 2D and WGS 84 3D) will return `null`. |=== -.+point.distance()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- -WITH - point({x: 2.3, y: 4.5, crs: 'cartesian'}) AS p1, - point({x: 1.1, y: 5.4, crs: 'cartesian'}) AS p2 -RETURN point.distance(p1,p2) AS dist +WITH point({x: 2.3, y: 4.5, crs: 'cartesian'}) AS p1, point({x: 1.1, y: 5.4, crs: 'cartesian'}) AS p2 +RETURN distance(p1,p2) AS dist ---- The distance between two 2D points in the _Cartesian_ CRS is returned. @@ -100,26 +83,35 @@ The distance between two 2D points in the _Cartesian_ CRS is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(malmo) +]]> +++++ +endif::nonhtmloutput[] -.+point.distance()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- -WITH - point({longitude: 12.78, latitude: 56.7, height: 100}) AS p1, - point({latitude: 56.71, longitude: 12.79, height: 100}) AS p2 -RETURN point.distance(p1, p2) AS dist +WITH point({longitude: 12.78, latitude: 56.7, height: 100}) as p1, point({latitude: 56.71, longitude: 12.79, height: 100}) as p2 +RETURN distance(p1,p2) as dist ---- The distance between two 3D points in the _WGS 84_ CRS is returned. @@ -127,27 +119,36 @@ The distance between two 3D points in the _WGS 84_ CRS is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(malmo) + +]]> +++++ +endif::nonhtmloutput[] -.+point.distance()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (t:TrainStation)-[:TRAVEL_ROUTE]->(o:Office) -WITH - point({longitude: t.longitude, latitude: t.latitude}) AS trainPoint, - point({longitude: o.longitude, latitude: o.latitude}) AS officePoint -RETURN round(point.distance(trainPoint, officePoint)) AS travelDistance +WITH point({longitude: t.longitude, latitude: t.latitude}) AS trainPoint, point({longitude: o.longitude, latitude: o.latitude}) AS officePoint +RETURN round(distance(trainPoint, officePoint)) AS travelDistance ---- The distance between the train station in Copenhagen and the Neo4j office in Malmo is returned. @@ -155,23 +156,35 @@ The distance between the train station in Copenhagen and the Neo4j office in Mal .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(malmo) +]]>(o:Office) +WITH point({longitude: t.longitude, latitude: t.latitude}) AS trainPoint, point({longitude: o.longitude, latitude: o.latitude}) AS officePoint +RETURN round(distance(trainPoint, officePoint)) AS travelDistance +]]> +++++ +endif::nonhtmloutput[] -.+point.distance()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- -RETURN point.distance(null, point({longitude: 56.7, latitude: 12.78})) AS d +RETURN distance(null, point({longitude: 56.7, latitude: 12.78})) AS d ---- If `null` is provided as one or both of the arguments, `null` is returned. @@ -179,243 +192,64 @@ If `null` is provided as one or both of the arguments, `null` is returned. .Result [role="queryresult",options="header,footer",cols="1*+ 1+d|Rows: 1 - -|=== - -====== - - -[[functions-withinBBox]] -== point.withinBBox() - -`point.withinBBox()` takes the following arguments: - -* The point to check. -* The lower-left (south-west) point of a bounding box. -* The upper-right (or north-east) point of a bounding box. - -The return value will be true if the provided point is contained in the bounding box (boundary included), otherwise the return value will be false. - -*Syntax:* - -[source, syntax, role="noheader"] ----- -point.withinBBox(point, lowerLeft, upperRight) ----- - -*Returns:* - -|=== - -| A Boolean. - -|=== - -*Arguments:* - -[options="header"] -|=== -| Name | Description - -| `point` -| A point in either a geographic or cartesian coordinate system. - -| `lowerLeft` -| A point in the same CRS as 'point'. - -| `upperRight` -| A point in the same CRS as 'point'. - -|=== - -*Considerations:* - -|=== - -| `point.withinBBox(p1, p2, p3)` will return `null` if any of the arguments evaluate to `null`. -| Attempting to use points with different Coordinate Reference Systems (such as WGS 84 2D and WGS 84 3D) will return `null`. -| `point.withinBBox` will handle crossing the 180th meridian in geographic coordinates. -| Switching the longitude of the `lowerLeft` and `upperRight` in geographic coordinates will switch the direction of the resulting bounding box. -| Switching the latitude of the `lowerLeft` and `upperRight` in geographic coordinates so that the former is north of the latter will result in an empty range. - -|=== - - -.+point.withinBBox()+ -====== - -.Query -[source, cypher, indent=0] ----- -WITH - point({x: 0, y: 0, crs: 'cartesian'}) AS lowerLeft, - point({x: 10, y: 10, crs: 'cartesian'}) AS upperRight -RETURN point.withinBBox(point({x: 5, y: 5, crs: 'cartesian'}), lowerLeft, upperRight) AS result ----- - -Checking if a point in _Cartesian_ CRS is contained in the bounding box. - -.Result -[role="queryresult",options="header,footer",cols="1* +Try this query live ++ -1+d|Rows: 1 - -|=== - -====== + (copenhagen)-[:TRAVEL_ROUTE]->(malmo) +]]> +++++ +endif::nonhtmloutput[] [[functions-point-wgs84-2d]] == point() - WGS 84 2D `point({longitude | x, latitude | y [, crs][, srid]})` returns a 2D point in the _WGS 84_ CRS corresponding to the given coordinate values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -point({longitude | x, latitude | y [, crs][, srid]}) ----- +*Syntax:* `point({longitude | x, latitude | y [, crs][, srid]})` *Returns:* - |=== - -| A 2D point in _WGS 84_. - +| +A 2D point in _WGS 84_. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `longitude/x` -| A numeric expression that represents the longitude/x value in decimal degrees. - -| `latitude/y` -| A numeric expression that represents the latitude/y value in decimal degrees. - -| `crs` -| The optional string `'WGS-84'`. - -| `srid` -| The optional number `4326`. - +| `A single map consisting of the following:` | +| `longitude/x` | A numeric expression that represents the longitude/x value in decimal degrees +| `latitude/y` | A numeric expression that represents the latitude/y value in decimal degrees +| `crs` | The optional string 'WGS-84' +| `srid` | The optional number 4326 |=== -*Considerations:* +*Considerations:* |=== - -| If any argument provided to `point()` is `null`, `null` will be returned. -| If the coordinates are specified using `latitude` and `longitude`, the `crs` or `srid` fields are optional and inferred to be `'WGS-84'` (`srid:4326`). -| If the coordinates are specified using `x` and `y`, then either the `crs` or `srid` field is required if a geographic CRS is desired. - +|If any argument provided to `point()` is `null`, `null` will be returned. +|If the coordinates are specified using `latitude` and `longitude`, the `crs` or `srid` fields are optional and inferred to be `'WGS-84'` (srid=4326). +|If the coordinates are specified using `x` and `y`, then either the `crs` or `srid` field is required if a geographic CRS is desired. |=== -.+point()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN point({longitude: 56.7, latitude: 12.78}) AS point ---- @@ -425,21 +259,31 @@ A 2D point with a `longitude` of `56.7` and a `latitude` of `12.78` in the _WGS .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(malmo) +]]> +++++ +endif::nonhtmloutput[] -.+point()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN point({x: 2.3, y: 4.5, crs: 'WGS-84'}) AS point ---- @@ -449,21 +293,31 @@ RETURN point({x: 2.3, y: 4.5, crs: 'WGS-84'}) AS point .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(malmo) +]]> +++++ +endif::nonhtmloutput[] -.+point()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (p:Office) RETURN point({longitude: p.longitude, latitude: p.latitude}) AS officePoint @@ -474,21 +328,32 @@ A 2D point representing the coordinates of the city of Malmo in the _WGS 84_ CRS .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(malmo) +]]> +++++ +endif::nonhtmloutput[] -.+point()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN point(null) AS p ---- @@ -498,79 +363,66 @@ If `null` is provided as the argument, `null` is returned. .Result [role="queryresult",options="header,footer",cols="1*+ 1+d|Rows: 1 - |=== -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(malmo) + +]]> +++++ +endif::nonhtmloutput[] [[functions-point-wgs84-3d]] == point() - WGS 84 3D `point({longitude | x, latitude | y, height | z, [, crs][, srid]})` returns a 3D point in the _WGS 84_ CRS corresponding to the given coordinate values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -point({longitude | x, latitude | y, height | z, [, crs][, srid]}) ----- +*Syntax:* `point({longitude | x, latitude | y, height | z, [, crs][, srid]})` *Returns:* - |=== - -| A 3D point in _WGS 84_. - +| +A 3D point in _WGS 84_. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `longitude/x` -| A numeric expression that represents the longitude/x value in decimal degrees. - -| `latitude/y` -| A numeric expression that represents the latitude/y value in decimal degrees. - -| `height/z` -| A numeric expression that represents the height/z value in meters. - -| `crs` -| The optional string `'WGS-84-3D'`. - -| `srid` -| The optional number `4979`. - +| `A single map consisting of the following:` | +| `longitude/x` | A numeric expression that represents the longitude/x value in decimal degrees +| `latitude/y` | A numeric expression that represents the latitude/y value in decimal degrees +| `height/z` | A numeric expression that represents the height/z value in meters +| `crs` | The optional string 'WGS-84-3D' +| `srid` | The optional number 4979 |=== -*Considerations:* +*Considerations:* |=== - -| If any argument provided to `point()` is `null`, `null` will be returned. -| If the `height/z` key and value is not provided, a 2D point in the _WGS 84_ CRS will be returned. -| If the coordinates are specified using `latitude` and `longitude`, the `crs` or `srid` fields are optional and inferred to be `'WGS-84-3D'` (`srid:4979`). -| If the coordinates are specified using `x` and `y`, then either the `crs` or `srid` field is required if a geographic CRS is desired. - +|If any argument provided to `point()` is `null`, `null` will be returned. +|If the `height/z` key and value is not provided, a 2D point in the _WGS 84_ CRS will be returned. +|If the coordinates are specified using `latitude` and `longitude`, the `crs` or `srid` fields are optional and inferred to be `'WGS-84-3D'` (srid=4979). +|If the coordinates are specified using `x` and `y`, then either the `crs` or `srid` field is required if a geographic CRS is desired. |=== -.+point()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN point({longitude: 56.7, latitude: 12.78, height: 8}) AS point ---- @@ -580,74 +432,63 @@ A 3D point with a `longitude` of `56.7`, a `latitude` of `12.78` and a height of .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(malmo) +]]> +++++ +endif::nonhtmloutput[] [[functions-point-cartesian-2d]] == point() - Cartesian 2D `point({x, y [, crs][, srid]})` returns a 2D point in the _Cartesian_ CRS corresponding to the given coordinate values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -point({x, y [, crs][, srid]}) ----- +*Syntax:* `point({x, y [, crs][, srid]})` *Returns:* - |=== - -| A 2D point in _Cartesian_. - +| +A 2D point in _Cartesian_. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `x` -| A numeric expression. - -| `y` -| A numeric expression. - -| `crs` -| The optional string `'cartesian'`. - -| `srid` -| The optional number `7203`. - +| `A single map consisting of the following:` | +| `x` | A numeric expression +| `y` | A numeric expression +| `crs` | The optional string 'cartesian' +| `srid` | The optional number 7203 |=== -*Considerations:* +*Considerations:* |=== - -| If any argument provided to `point()` is `null`, `null` will be returned. -| The `crs` or `srid` fields are optional and default to the _Cartesian_ CRS (which means `srid:7203`). - +|If any argument provided to `point()` is `null`, `null` will be returned. +|The `crs` or `srid` fields are optional and default to the _Cartesian_ CRS (which means `srid:7203`). |=== -.+point()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN point({x: 2.3, y: 4.5}) AS point ---- @@ -657,78 +498,65 @@ A 2D point with an `x` coordinate of `2.3` and a `y` coordinate of `4.5` in the .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(malmo) +]]> +++++ +endif::nonhtmloutput[] [[functions-point-cartesian-3d]] == point() - Cartesian 3D `point({x, y, z, [, crs][, srid]})` returns a 3D point in the _Cartesian_ CRS corresponding to the given coordinate values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -point({x, y, z, [, crs][, srid]}) ----- +*Syntax:* `point({x, y, z, [, crs][, srid]})` *Returns:* - |=== - -| A 3D point in _Cartesian_. - +| +A 3D point in _Cartesian_. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `x` -| A numeric expression. - -| `y` -| A numeric expression. - -| `z` -| A numeric expression. - -| `crs` -| The optional string `'cartesian-3D'`. - -| `srid` -| The optional number `9157`. - +| `A single map consisting of the following:` | +| `x` | A numeric expression +| `y` | A numeric expression +| `z` | A numeric expression +| `crs` | The optional string 'cartesian-3D' +| `srid` | The optional number 9157 |=== -*Considerations:* +*Considerations:* |=== - -| If any argument provided to `point()` is `null`, `null` will be returned. -| If the `z` key and value is not provided, a 2D point in the _Cartesian_ CRS will be returned. -| The `crs` or `srid` fields are optional and default to the _3D Cartesian_ CRS (which means `srid:9157`). - +|If any argument provided to `point()` is `null`, `null` will be returned. +|If the `z` key and value is not provided, a 2D point in the _Cartesian_ CRS will be returned. +|The `crs` or `srid` fields are optional and default to the _3D Cartesian_ CRS (which means `srid:9157`). |=== -.+point()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN point({x: 2.3, y: 4.5, z: 2}) AS point ---- @@ -738,12 +566,25 @@ A 3D point with an `x` coordinate of `2.3`, a `y` coordinate of `4.5` and a `z` .Result [role="queryresult",options="header,footer",cols="1* +Try this query live +(malmo) + +]]> +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/functions/string.adoc b/modules/ROOT/pages/functions/string.adoc index f49c427a0..ff25c3bab 100644 --- a/modules/ROOT/pages/functions/string.adoc +++ b/modules/ROOT/pages/functions/string.adoc @@ -1,94 +1,69 @@ -:description: String functions all operate on string expressions only, and will return an error if used on any other values. - [[query-functions-string]] = String functions +:description: These functions all operate on string expressions only, and will return an error if used on any other values. The exception to this rule is `toString()`, which also accepts numbers, booleans and temporal values (i.e. _Date_, _Time_. _LocalTime_, _DateTime_, _LocalDateTime_ or _Duration_ values). -[abstract] --- -These functions all operate on string expressions only, and will return an error if used on any other values. -The exception to this rule is `toString()`, which also accepts numbers, booleans and temporal values (i.e. _Date_, _Time_. _LocalTime_, _DateTime_, _LocalDateTime_ or _Duration_ values). --- - -Functions taking a string as input all operate on _Unicode characters_ rather than on a standard `char[]`. -For example, the `size()` function applied to any _Unicode character_ will return `1`, even if the character does not fit in the 16 bits of one `char`. +Functions taking a string as input all operate on _Unicode characters_ rather than on a standard `char[]`. For example, the `size()` function applied to any _Unicode character_ will return *1*, even if the character does not fit in the 16 bits of one `char`. [NOTE] ==== -When `toString()` is applied to a temporal value, it returns a string representation suitable for parsing by the corresponding xref::functions/temporal/index.adoc[temporal functions]. +When `toString()` is applied to a temporal value, it returns a string representation suitable for parsing by the corresponding xref:functions/temporal/index.adoc[temporal functions]. This string will therefore be formatted according to the https://en.wikipedia.org/wiki/ISO_8601[ISO 8601] format. + + ==== -See also xref::syntax/operators.adoc#query-operators-string[String operators]. +See also xref:syntax/operators.adoc#query-operators-string[String operators]. Functions: -* xref::functions/string.adoc#functions-left[left()] -* xref::functions/string.adoc#functions-ltrim[ltrim()] -* xref::functions/string.adoc#functions-replace[replace()] -* xref::functions/string.adoc#functions-reverse[reverse()] -* xref::functions/string.adoc#functions-right[right()] -* xref::functions/string.adoc#functions-rtrim[rtrim()] -* xref::functions/string.adoc#functions-split[split()] -* xref::functions/string.adoc#functions-substring[substring()] -* xref::functions/string.adoc#functions-tolower[toLower()] -* xref::functions/string.adoc#functions-tostring[toString()] -* xref::functions/string.adoc#functions-tostringornull[toStringOrNull()] -* xref::functions/string.adoc#functions-toupper[toUpper()] -* xref::functions/string.adoc#functions-trim[trim()] - +* xref:functions/string.adoc#functions-left[left()] +* xref:functions/string.adoc#functions-ltrim[lTrim()] +* xref:functions/string.adoc#functions-replace[replace()] +* xref:functions/string.adoc#functions-reverse[reverse()] +* xref:functions/string.adoc#functions-right[right()] +* xref:functions/string.adoc#functions-rtrim[rTrim()] +* xref:functions/string.adoc#functions-split[split()] +* xref:functions/string.adoc#functions-substring[substring()] +* xref:functions/string.adoc#functions-tolower[toLower()] +* xref:functions/string.adoc#functions-tostring[toString()] +* xref:functions/string.adoc#functions-tostringornull[toStringOrNull()] +* xref:functions/string.adoc#functions-toupper[toUpper()] +* xref:functions/string.adoc#functions-trim[trim()] [[functions-left]] == left() `left()` returns a string containing the specified number of leftmost characters of the original string. -*Syntax:* - -[source, syntax, role="noheader"] ----- -left(original, length) ----- +*Syntax:* `left(original, length)` *Returns:* - |=== - -| A String. - +| +A String. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `original` -| An expression that returns a string. - -| `length` -| An expression that returns a positive integer. - +| `original` | An expression that returns a string. +| `n` | An expression that returns a positive integer. |=== -*Considerations:* +*Considerations:* |=== - -| `left(null, length)` return `null`. -| `left(null, null)` return `null`. -| `left(original, null)` will raise an error. -// Should be: If `length` is a negative integer, an error is raised. -| If `length` is not a positive integer, an error is raised. -| If `length` exceeds the size of `original`, `original` is returned. - +|`left(null, length)` and `left(null, null)` both return `null` +|`left(original, null)` will raise an error. +|If `length` is not a positive integer, an error is raised. +|If `length` exceeds the size of `original`, `original` is returned. |=== -.+left()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN left('hello', 3) ---- @@ -96,129 +71,112 @@ RETURN left('hello', 3) .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-ltrim]] == ltrim() -`ltrim()` returns the original string with leading whitespace removed. +`lTrim()` returns the original string with leading whitespace removed. -*Syntax:* - -[source, syntax, role="noheader"] ----- -ltrim(original) ----- +*Syntax:* `lTrim(original)` *Returns:* - |=== - -| A String. - +| +A String. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `original` -| An expression that returns a string. - +| `original` | An expression that returns a string. |=== -*Considerations:* +*Considerations:* |=== - -| `ltrim(null)` returns `null`. - +|`lTrim(null)` returns `null` |=== -.+ltrim()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- -RETURN ltrim(' hello') +RETURN lTrim(' hello') ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-replace]] == replace() `replace()` returns a string in which all occurrences of a specified string in the original string have been replaced by another (specified) string. -*Syntax:* - -[source, syntax, role="noheader"] ----- -replace(original, search, replace) ----- +*Syntax:* `replace(original, search, replace)` *Returns:* - |=== - -| A String. - +| +A String. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `original` -| An expression that returns a string. - -| `search` -| An expression that specifies the string to be replaced in `original`. - -| `replace` -| An expression that specifies the replacement string. - +| `original` | An expression that returns a string. +| `search` | An expression that specifies the string to be replaced in `original`. +| `replace` | An expression that specifies the replacement string. |=== -*Considerations:* +*Considerations:* |=== - -| If any argument is `null`, `null` will be returned. -| If `search` is not found in `original`, `original` will be returned. - +|If any argument is `null`, `null` will be returned. +|If `search` is not found in `original`, `original` will be returned. |=== -.+replace()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN replace("hello", "l", "w") ---- @@ -226,61 +184,54 @@ RETURN replace("hello", "l", "w") .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-reverse]] == reverse() `reverse()` returns a string in which the order of all characters in the original string have been reversed. -*Syntax:* - -[source, syntax, role="noheader"] ----- -reverse(original) ----- +*Syntax:* `reverse(original)` *Returns:* - |=== - -| A String. - +| +A String. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `original` -| An expression that returns a string. - +| `original` | An expression that returns a string. |=== -*Considerations:* +*Considerations:* |=== - -| `reverse(null)` returns `null`. - +|`reverse(null)` returns `null`. |=== -.+reverse+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN reverse('anagram') ---- @@ -288,69 +239,58 @@ RETURN reverse('anagram') .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-right]] == right() `right()` returns a string containing the specified number of rightmost characters of the original string. -*Syntax:* - -[source, syntax, role="noheader"] ----- -right(original, length) ----- +*Syntax:* `right(original, length)` *Returns:* - |=== - -| A String. - +| +A String. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `original` -| An expression that returns a string. - -| `length` -| An expression that returns a positive integer. - +| `original` | An expression that returns a string. +| `n` | An expression that returns a positive integer. |=== -*Considerations:* +*Considerations:* |=== - -| `right(null, length)` return `null`. -| `right(null, null)` return `null`. -| `right(original, null)` will raise an error. -// Should be: If `length` is a negative integer, an error is raised. -| If `length` is not a positive integer, an error is raised. -| If `length` exceeds the size of `original`, `original` is returned. - +|`right(null, length)` and `right(null, null)` both return `null` +|`right(original, null)` will raise an error. +|If `length` is not a positive integer, an error is raised. +|If `length` exceeds the size of `original`, `original` is returned. |=== -.+right()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN right('hello', 3) ---- @@ -358,125 +298,110 @@ RETURN right('hello', 3) .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-rtrim]] == rtrim() -`rtrim()` returns the original string with trailing whitespace removed. +`rTrim()` returns the original string with trailing whitespace removed. -*Syntax:* - -[source, syntax, role="noheader"] ----- -rtrim(original) ----- +*Syntax:* `rTrim(original)` *Returns:* - |=== - -| A String. - +| +A String. |=== + *Arguments:* [options="header"] |=== | Name | Description - -| `original` -| An expression that returns a string. - +| `original` | An expression that returns a string. |=== -*Considerations:* +*Considerations:* |=== - -| `rtrim(null)` returns `null`. - +|`rTrim(null)` returns `null` |=== -.+rtrim()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- -RETURN rtrim('hello ') +RETURN rTrim('hello ') ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-split]] == split() `split()` returns a list of strings resulting from the splitting of the original string around matches of the given delimiter. -*Syntax:* - -[source, syntax, role="noheader"] ----- -split(original, splitDelimiter) ----- +*Syntax:* `split(original, splitDelimiter)` *Returns:* - |=== - -| A list of Strings. - +| +A list of Strings. |=== + *Arguments:* [options="header"] |=== | Name | Description - -| `original` -| An expression that returns a string. - -| `splitDelimiter` -| The string with which to split `original`. - +| `original` | An expression that returns a string. +| `splitDelimiter` | The string with which to split `original`. |=== -*Considerations:* +*Considerations:* |=== - -| `split(null, splitDelimiter)` return `null`. -| `split(original, null)` return `null` - +|`split(null, splitDelimiter)` and `split(original, null)` both return `null` |=== -.+split()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN split('one,two', ',') ---- @@ -484,70 +409,61 @@ RETURN split('one,two', ',') .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-substring]] == substring() -`substring()` returns a substring of the original string, beginning with a zero-based index start and length. +`substring()` returns a substring of the original string, beginning with a 0-based index start and length. -*Syntax:* - -[source, syntax, role="noheader"] ----- -substring(original, start [, length]) ----- +*Syntax:* `substring(original, start [, length])` *Returns:* - |=== - -| A String. - +| +A String. |=== + *Arguments:* [options="header"] |=== | Name | Description - -| `original` -| An expression that returns a string. - -| `start` -| An expression that returns a positive integer, denoting the position at which the substring will begin. - -| `length` -| An expression that returns a positive integer, denoting how many characters of `original` will be returned. - +| `original` | An expression that returns a string. +| `start` | An expression that returns a positive integer, denoting the position at which the substring will begin. +| `length` | An expression that returns a positive integer, denoting how many characters of `original` will be returned. |=== + *Considerations:* |=== - -| `start` uses a zero-based index. -| If `length` is omitted, the function returns the substring starting at the position given by `start` and extending to the end of `original`. -| If `original` is `null`, `null` is returned. -| If either `start` or `length` is `null` or a negative integer, an error is raised. -| If `start` is `0`, the substring will start at the beginning of `original`. -| If `length` is `0`, the empty string will be returned. - +|`start` uses a zero-based index. +|If `length` is omitted, the function returns the substring starting at the position given by `start` and extending to the end of `original`. +|If `original` is `null`, `null` is returned. +|If either `start` or `length` is `null` or a negative integer, an error is raised. +|If `start` is `0`, the substring will start at the beginning of `original`. +|If `length` is `0`, the empty string will be returned. |=== -.+substring()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN substring('hello', 1, 3), substring('hello', 2) ---- @@ -555,60 +471,54 @@ RETURN substring('hello', 1, 3), substring('hello', 2) .Result [role="queryresult",options="header,footer",cols="2* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-tolower]] == toLower() `toLower()` returns the original string in lowercase. -*Syntax:* - -[source, syntax, role="noheader"] ----- -toLower(original) ----- +*Syntax:* `toLower(original)` *Returns:* - |=== - -| A String. - +| +A String. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `original` -| An expression that returns a string. - +| `original` | An expression that returns a string. |=== + *Considerations:* |=== - -| `toLower(null)` returns `null`. - +|`toLower(null)` returns `null` |=== -.+toLower()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN toLower('HELLO') ---- @@ -621,128 +531,123 @@ RETURN toLower('HELLO') 1+d|Rows: 1 |=== -====== - +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-tostring]] == toString() -`toString()` converts an integer, float, boolean, string, point, duration, date, time, localtime, localdatetime, or datetime value to a string. +`toString()` converts an integer, float, boolean, string, point, duration, date, time, localtime, localdatetime or datetime value to a string. -*Syntax:* - -[source, syntax, role="noheader"] ----- -toString(expression) ----- +*Syntax:* `toString(expression)` *Returns:* - |=== - -| A String. - +| +A String. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| An expression that returns a number, a boolean, string, temporal, or spatial value. - +| `expression` | An expression that returns a number, a boolean, string, temporal or spatial value. |=== -*Considerations:* +*Considerations:* |=== - -| `toString(null)` returns `null`. -| If `expression` is a string, it will be returned unchanged. -| This function will return an error if provided with an expression that is not an integer, float, string, boolean, point, duration, date, time, localtime, localdatetime or datetime value. - +|`toString(null)` returns `null` +|If `expression` is a string, it will be returned unchanged. +|This function will return an error if provided with an expression that is not an integer, float, string, boolean, point, duration, date, time, localtime, localdatetime or datetime value. |=== -.+toString()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- -RETURN - toString(11.5), - toString('already a string'), - toString(true), - toString(date({year: 1984, month: 10, day: 11})) AS dateString, - toString(datetime({year: 1984, month: 10, day: 11, hour: 12, minute: 31, second: 14, millisecond: 341, timezone: 'Europe/Stockholm'})) AS datetimeString, - toString(duration({minutes: 12, seconds: -60})) AS durationString +RETURN toString(11.5), +toString('already a string'), +toString(true), +toString(date({year:1984, month:10, day:11})) AS dateString, +toString(datetime({year:1984, month:10, day:11, hour:12, minute:31, second:14, millisecond: 341, timezone: 'Europe/Stockholm'})) AS datetimeString, +toString(duration({minutes: 12, seconds: -60})) AS durationString ---- .Result [role="queryresult",options="header,footer",cols="6* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-tostringornull]] == toStringOrNull() -The function `toStringOrNull()` converts an integer, float, boolean, string, point, duration, date, time, localtime, localdatetime, or datetime value to a string. - -*Syntax:* +The function `toStringOrNull()` converts an integer, float, boolean, string, point, duration, date, time, localtime, localdatetime or datetime value to a string. -[source, syntax, role="noheader"] ----- -toStringOrNull(expression) ----- +*Syntax:* `toStringOrNull(expression)` *Returns:* - |=== - -| A String or `null`. - +| +A String or `null`. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `expression` -| Any expression that returns a value. - +| `expression` | Any expression that returns a value. |=== + *Considerations:* |=== -| `toStringOrNull(null)` returns `null`. -| If the `expression` is not an integer, float, string, boolean, point, duration, date, time, localtime, localdatetime, or datetime value, `null` will be returned. +|`toStringOrNull(null)` returns `null`. +|If the `expression` is not an integer, float, string, boolean, point, duration, date, time, localtime, localdatetime or datetime value, `null` will be returned. |=== -.+toStringOrNull()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN toStringOrNull(11.5), toStringOrNull('already a string'), toStringOrNull(true), -toStringOrNull(date({year: 1984, month: 10, day: 11})) AS dateString, -toStringOrNull(datetime({year: 1984, month: 10, day: 11, hour: 12, minute: 31, second: 14, millisecond: 341, timezone: 'Europe/Stockholm'})) AS datetimeString, +toStringOrNull(date({year:1984, month:10, day:11})) AS dateString, +toStringOrNull(datetime({year:1984, month:10, day:11, hour:12, minute:31, second:14, millisecond: 341, timezone: 'Europe/Stockholm'})) AS datetimeString, toStringOrNull(duration({minutes: 12, seconds: -60})) AS durationString, toStringOrNull(['A', 'B', 'C']) AS list ---- @@ -750,61 +655,60 @@ toStringOrNull(['A', 'B', 'C']) AS list .Result [role="queryresult",options="header,footer",cols="7*+ 7+d|Rows: 1 - |=== -====== - +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-toupper]] == toUpper() `toUpper()` returns the original string in uppercase. -*Syntax:* - -[source, syntax, role="noheader"] ----- -toUpper(original) ----- +*Syntax:* `toUpper(original)` *Returns:* - |=== - -| A String. - +| +A String. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `original` -| An expression that returns a string. - +| `original` | An expression that returns a string. |=== -*Considerations:* +*Considerations:* |=== - -| `toUpper(null)` returns `null`. - +|`toUpper(null)` returns `null` |=== -.+toUpper()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN toUpper('hello') ---- @@ -812,60 +716,54 @@ RETURN toUpper('hello') .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-trim]] == trim() `trim()` returns the original string with leading and trailing whitespace removed. -*Syntax:* - -[source, syntax, role="noheader"] ----- -trim(original) ----- +*Syntax:* `trim(original)` *Returns:* - |=== - -| A String. - +| +A String. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `original` -| An expression that returns a string. - +| `original` | An expression that returns a string. |=== + *Considerations:* |=== - -| `trim(null)` returns `null`. - +|`trim(null)` returns `null` |=== -.+trim()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN trim(' hello ') ---- @@ -873,12 +771,21 @@ RETURN trim(' hello ') .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/functions/temporal/duration.adoc b/modules/ROOT/pages/functions/temporal/duration.adoc index 46702737b..adf4750c5 100644 --- a/modules/ROOT/pages/functions/temporal/duration.adoc +++ b/modules/ROOT/pages/functions/temporal/duration.adoc @@ -1,25 +1,23 @@ -:description: Cypher provides functions allowing for the creation and manipulation of values for a _Duration_ temporal type. - [[query-functions-temporal-duration]] = Temporal functions - duration - -[abstract] --- -Cypher provides functions allowing for the creation and manipulation of values for a _Duration_ temporal type. --- +:description: Cypher provides functions allowing for the creation and manipulation of values for a _Duration_ temporal type. [NOTE] ==== -See also xref::syntax/temporal.adoc[Temporal (Date/Time) values] and xref::syntax/operators.adoc#query-operators-temporal[Temporal operators]. +See also xref:syntax/temporal.adoc[Temporal (Date/Time) values] and xref:syntax/operators.adoc#query-operators-temporal[Temporal operators]. + + ==== + duration(): -* xref::functions/temporal/duration.adoc#functions-duration[Creating a _Duration_ from duration components] -* xref::functions/temporal/duration.adoc#functions-duration-create-string[Creating a _Duration_ from a string] -* xref::functions/temporal/duration.adoc#functions-duration-computing[Computing the _Duration_ between two temporal instants] +* xref:functions/temporal/duration.adoc#functions-duration[Creating a _Duration_ from duration components] +* xref:functions/temporal/duration.adoc#functions-duration-create-string[Creating a _Duration_ from a string] +* xref:functions/temporal/duration.adoc#functions-duration-computing[Computing the _Duration_ between two temporal instants] + -Information regarding specifying and accessing components of a _Duration_ value can be found xref::syntax/temporal.adoc#cypher-temporal-durations[here]. +Information regarding specifying and accessing components of a _Duration_ value can be found xref:syntax/temporal.adoc#cypher-temporal-durations[here]. [[functions-duration]] == Creating a _Duration_ from duration components @@ -38,91 +36,57 @@ Information regarding specifying and accessing components of a _Duration_ value * `microseconds` * `nanoseconds` -*Syntax:* + -[source, syntax, role="noheader"] ----- -duration([ {years, quarters, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds} ]) ----- +*Syntax:* `duration([ {years, quarters, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds} ])` *Returns:* - |=== - -| A Duration. - +| +A Duration. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `years` -| A numeric expression. - -| `quarters` -| A numeric expression. - -| `months` -| A numeric expression. - -| `weeks` -| A numeric expression. - -| `days` -| A numeric expression. - -| `hours` -| A numeric expression. - -| `minutes` -| A numeric expression. - -| `seconds` -| A numeric expression. - -| `milliseconds` -| A numeric expression. - -| `microseconds` -| A numeric expression. - -| `nanoseconds` -| A numeric expression. - +| `A single map consisting of the following:` | +| `years` | A numeric expression. +| `quarters` | A numeric expression. +| `months` | A numeric expression. +| `weeks` | A numeric expression. +| `days` | A numeric expression. +| `hours` | A numeric expression. +| `minutes` | A numeric expression. +| `seconds` | A numeric expression. +| `milliseconds` | A numeric expression. +| `microseconds` | A numeric expression. +| `nanoseconds` | A numeric expression. |=== -*Considerations:* +*Considerations:* |=== - -| At least one parameter must be provided (`duration()` and `+duration({})+` are invalid). -| There is no constraint on how many of the parameters are provided. -| It is possible to have a _Duration_ where the amount of a smaller unit (e.g. `seconds`) exceeds the threshold of a larger unit (e.g. `days`). -| The values of the parameters may be expressed as decimal fractions. -| The values of the parameters may be arbitrarily large. -| The values of the parameters may be negative. - +|At least one parameter must be provided (`duration()` and `duration({})` are invalid). +|There is no constraint on how many of the parameters are provided. +|It is possible to have a _Duration_ where the amount of a smaller unit (e.g. `seconds`) exceeds the threshold of a larger unit (e.g. `days`). +|The values of the parameters may be expressed as decimal fractions. +|The values of the parameters may be arbitrarily large. +|The values of the parameters may be negative. |=== -.+duration()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -duration({days: 14, hours:16, minutes: 12}), -duration({months: 5, days: 1.5}), -duration({months: 0.75}), -duration({weeks: 2.5}), -duration({minutes: 1.5, seconds: 1, milliseconds: 123, microseconds: 456, nanoseconds: 789}), -duration({minutes: 1.5, seconds: 1, nanoseconds: 123456789}) + duration({days: 14, hours:16, minutes: 12}), + duration({months: 5, days: 1.5}), + duration({months: 0.75}), + duration({weeks: 2.5}), + duration({minutes: 1.5, seconds: 1, milliseconds: 123, microseconds: 456, nanoseconds: 789}), + duration({minutes: 1.5, seconds: 1, nanoseconds: 123456789}) ] AS aDuration RETURN aDuration ---- @@ -130,7 +94,6 @@ RETURN aDuration .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-duration-create-string]] == Creating a _Duration_ from a string `duration()` returns the _Duration_ value obtained by parsing a string representation of a temporal amount. -*Syntax:* - -[source, syntax, role="noheader"] ----- -duration(temporalAmount) ----- +*Syntax:* `duration(temporalAmount)` *Returns:* - |=== - -| A Duration. - +| +A Duration. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `temporalAmount` -| A string representing a temporal amount. - +| `temporalAmount` | A string representing a temporal amount. |=== -*Considerations:* +*Considerations:* |=== - -| `temporalAmount` must comply with either the xref::syntax/temporal.adoc#cypher-temporal-specifying-durations[unit based form or date-and-time based form defined for _Durations_]. - +|`temporalAmount` must comply with either the xref:syntax/temporal.adoc#cypher-temporal-specifying-durations[unit based form or date-and-time based form defined for _Durations_]. |=== -.+duration()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -duration("P14DT16H12M"), -duration("P5M1.5D"), -duration("P0.75M"), -duration("PT0.75M"), -duration("P2012-02-02T14:37:21.545") + duration("P14DT16H12M"), + duration("P5M1.5D"), + duration("P0.75M"), + duration("PT0.75M"), + duration("P2012-02-02T14:37:21.545") ] AS aDuration RETURN aDuration ---- @@ -204,7 +169,6 @@ RETURN aDuration .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-duration-computing]] == Computing the _Duration_ between two temporal instants @@ -234,61 +214,44 @@ RETURN aDuration `duration.between()` returns the _Duration_ value equal to the difference between the two given instants. -*Syntax:* - -[source, syntax, role="noheader"] ----- -duration.between(instant1, instant2) ----- +*Syntax:* `duration.between(instant~1~, instant~2~)` *Returns:* - |=== - -| A Duration. - +| +A Duration. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `instant1` -| An expression returning any temporal instant type (_Date_ etc) that represents the starting instant. - -| `instant2` -| An expression returning any temporal instant type (_Date_ etc) that represents the ending instant. - +| `instant~1~` | An expression returning any temporal instant type (_Date_ etc) that represents the starting instant. +| `instant~2~` | An expression returning any temporal instant type (_Date_ etc) that represents the ending instant. |=== -*Considerations:* +*Considerations:* |=== - -| If `instant2` occurs earlier than `instant1`, the resulting _Duration_ will be negative. -| If `instant1` has a time component and `instant2` does not, the time component of `instant2` is assumed to be midnight, and vice versa. -| If `instant1` has a time zone component and `instant2` does not, the time zone component of `instant2` is assumed to be the same as that of `instant1`, and vice versa. -| If `instant1` has a date component and `instant2` does not, the date component of `instant2` is assumed to be the same as that of `instant1`, and vice versa. - +|If `instant~2~` occurs earlier than `instant~1~`, the resulting _Duration_ will be negative. +|If `instant~1~` has a time component and `instant~2~` does not, the time component of `instant~2~` is assumed to be midnight, and vice versa. +|If `instant~1~` has a time zone component and `instant~2~` does not, the time zone component of `instant~2~` is assumed to be the same as that of `instant~1~`, and vice versa. +|If `instant~1~` has a date component and `instant~2~` does not, the date component of `instant~2~` is assumed to be the same as that of `instant~1~`, and vice versa. |=== -.+duration.between()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -duration.between(date("1984-10-11"), date("1985-11-25")), -duration.between(date("1985-11-25"), date("1984-10-11")), -duration.between(date("1984-10-11"), datetime("1984-10-12T21:40:32.142+0100")), -duration.between(date("2015-06-24"), localtime("14:30")), -duration.between(localtime("14:30"), time("16:30+0100")), -duration.between(localdatetime("2015-07-21T21:40:32.142"), localdatetime("2016-07-21T21:45:22.142")), -duration.between(datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/Stockholm'}), datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/London'})) + duration.between(date("1984-10-11"), date("1985-11-25")), + duration.between(date("1985-11-25"), date("1984-10-11")), + duration.between(date("1984-10-11"), datetime("1984-10-12T21:40:32.142+0100")), + duration.between(date("2015-06-24"), localtime("14:30")), + duration.between(localtime("14:30"), time("16:30+0100")), + duration.between(localdatetime("2015-07-21T21:40:32.142"), localdatetime("2016-07-21T21:45:22.142")), + duration.between(datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/Stockholm'}), datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/London'})) ] AS aDuration RETURN aDuration ---- @@ -296,7 +259,6 @@ RETURN aDuration .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-duration-inmonths]] === duration.inMonths() `duration.inMonths()` returns the _Duration_ value equal to the difference in whole months, quarters or years between the two given instants. -*Syntax:* - -[source, syntax, role="noheader"] ----- -duration.inMonths(instant1, instant2) ----- +*Syntax:* `duration.inMonths(instant~1~, instant~2~)` *Returns:* - |=== - -| A Duration. - +| +A Duration. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `instant1` -| An expression returning any temporal instant type (_Date_ etc) that represents the starting instant. - -| `instant2` -| An expression returning any temporal instant type (_Date_ etc) that represents the ending instant. - +| `instant~1~` | An expression returning any temporal instant type (_Date_ etc) that represents the starting instant. +| `instant~2~` | An expression returning any temporal instant type (_Date_ etc) that represents the ending instant. |=== + *Considerations:* |=== - -| If `instant2` occurs earlier than `instant1`, the resulting _Duration_ will be negative. -| If `instant1` has a time component and `instant2` does not, the time component of `instant2` is assumed to be midnight, and vice versa. -| If `instant1` has a time zone component and `instant2` does not, the time zone component of `instant2` is assumed to be the same as that of `instant1`, and vice versa. -| If `instant1` has a date component and `instant2` does not, the date component of `instant2` is assumed to be the same as that of `instant1`, and vice versa. -| Any difference smaller than a whole month is disregarded. - +|If `instant~2~` occurs earlier than `instant~1~`, the resulting _Duration_ will be negative. +|If `instant~1~` has a time component and `instant~2~` does not, the time component of `instant~2~` is assumed to be midnight, and vice versa. +|If `instant~1~` has a time zone component and `instant~2~` does not, the time zone component of `instant~2~` is assumed to be the same as that of `instant~1~`, and vice versa. +|If `instant~1~` has a date component and `instant~2~` does not, the date component of `instant~2~` is assumed to be the same as that of `instant~1~`, and vice versa. +|Any difference smaller than a whole month is disregarded. |=== -.+duration.inMonths()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -duration.inMonths(date("1984-10-11"), date("1985-11-25")), -duration.inMonths(date("1985-11-25"), date("1984-10-11")), -duration.inMonths(date("1984-10-11"), datetime("1984-10-12T21:40:32.142+0100")), -duration.inMonths(date("2015-06-24"), localtime("14:30")), -duration.inMonths(localdatetime("2015-07-21T21:40:32.142"), localdatetime("2016-07-21T21:45:22.142")), -duration.inMonths(datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/Stockholm'}), datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/London'})) + duration.inMonths(date("1984-10-11"), date("1985-11-25")), + duration.inMonths(date("1985-11-25"), date("1984-10-11")), + duration.inMonths(date("1984-10-11"), datetime("1984-10-12T21:40:32.142+0100")), + duration.inMonths(date("2015-06-24"), localtime("14:30")), + duration.inMonths(localdatetime("2015-07-21T21:40:32.142"), localdatetime("2016-07-21T21:45:22.142")), + duration.inMonths(datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/Stockholm'}), datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/London'})) ] AS aDuration RETURN aDuration ---- @@ -378,7 +342,6 @@ RETURN aDuration .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-duration-indays]] === duration.inDays() `duration.inDays()` returns the _Duration_ value equal to the difference in whole days or weeks between the two given instants. -*Syntax:* - -[source, syntax, role="noheader"] ----- -duration.inDays(instant1, instant2) ----- +*Syntax:* `duration.inDays(instant~1~, instant~2~)` *Returns:* - |=== - -| A Duration. - +| +A Duration. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `instant1` -| An expression returning any temporal instant type (_Date_ etc) that represents the starting instant. - -| `instant2` -| An expression returning any temporal instant type (_Date_ etc) that represents the ending instant. - +| `instant~1~` | An expression returning any temporal instant type (_Date_ etc) that represents the starting instant. +| `instant~2~` | An expression returning any temporal instant type (_Date_ etc) that represents the ending instant. |=== -*Considerations:* +*Considerations:* |=== - -| If `instant2` occurs earlier than `instant1`, the resulting _Duration_ will be negative. -| If `instant1` has a time component and `instant2` does not, the time component of `instant2` is assumed to be midnight, and vice versa. -| If `instant1` has a time zone component and `instant2` does not, the time zone component of `instant2` is assumed to be the same as that of `instant1`, and vice versa. -| If `instant1` has a date component and `instant2` does not, the date component of `instant2` is assumed to be the same as that of `instant1`, and vice versa. -| Any difference smaller than a whole day is disregarded. - +|If `instant~2~` occurs earlier than `instant~1~`, the resulting _Duration_ will be negative. +|If `instant~1~` has a time component and `instant~2~` does not, the time component of `instant~2~` is assumed to be midnight, and vice versa. +|If `instant~1~` has a time zone component and `instant~2~` does not, the time zone component of `instant~2~` is assumed to be the same as that of `instant~1~`, and vice versa. +|If `instant~1~` has a date component and `instant~2~` does not, the date component of `instant~2~` is assumed to be the same as that of `instant~1~`, and vice versa. +|Any difference smaller than a whole day is disregarded. |=== -.+duration.inDays()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -duration.inDays(date("1984-10-11"), date("1985-11-25")), -duration.inDays(date("1985-11-25"), date("1984-10-11")), -duration.inDays(date("1984-10-11"), datetime("1984-10-12T21:40:32.142+0100")), -duration.inDays(date("2015-06-24"), localtime("14:30")), -duration.inDays(localdatetime("2015-07-21T21:40:32.142"), localdatetime("2016-07-21T21:45:22.142")), -duration.inDays(datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/Stockholm'}), datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/London'})) + duration.inDays(date("1984-10-11"), date("1985-11-25")), + duration.inDays(date("1985-11-25"), date("1984-10-11")), + duration.inDays(date("1984-10-11"), datetime("1984-10-12T21:40:32.142+0100")), + duration.inDays(date("2015-06-24"), localtime("14:30")), + duration.inDays(localdatetime("2015-07-21T21:40:32.142"), localdatetime("2016-07-21T21:45:22.142")), + duration.inDays(datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/Stockholm'}), datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/London'})) ] AS aDuration RETURN aDuration ---- @@ -460,7 +423,6 @@ RETURN aDuration .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-duration-inseconds]] === duration.inSeconds() `duration.inSeconds()` returns the _Duration_ value equal to the difference in seconds and fractions of seconds, or minutes or hours, between the two given instants. -*Syntax:* - -[source, syntax, role="noheader"] ----- -duration.inSeconds(instant1, instant2) ----- +*Syntax:* `duration.inSeconds(instant~1~, instant~2~)` *Returns:* - |=== - -| A Duration. - +| +A Duration. |=== + *Arguments:* [options="header"] |=== | Name | Description - -| `instant1` -| An expression returning any temporal instant type (_Date_ etc) that represents the starting instant. - -| `instant2` -| An expression returning any temporal instant type (_Date_ etc) that represents the ending instant. - +| `instant~1~` | An expression returning any temporal instant type (_Date_ etc) that represents the starting instant. +| `instant~2~` | An expression returning any temporal instant type (_Date_ etc) that represents the ending instant. |=== -*Considerations:* +*Considerations:* |=== - -| If `instant2` occurs earlier than `instant1`, the resulting _Duration_ will be negative. -| If `instant1` has a time component and `instant2` does not, the time component of `instant2` is assumed to be midnight, and vice versa. -| If `instant1` has a time zone component and `instant2` does not, the time zone component of `instant2` is assumed to be the same as that of `instant1`, and vice versa. -| If `instant1` has a date component and `instant2` does not, the date component of `instant2` is assumed to be the same as that of `instant1`, and vice versa. - +|If `instant~2~` occurs earlier than `instant~1~`, the resulting _Duration_ will be negative. +|If `instant~1~` has a time component and `instant~2~` does not, the time component of `instant~2~` is assumed to be midnight, and vice versa. +|If `instant~1~` has a time zone component and `instant~2~` does not, the time zone component of `instant~2~` is assumed to be the same as that of `instant~1~`, and vice versa. +|If `instant~1~` has a date component and `instant~2~` does not, the date component of `instant~2~` is assumed to be the same as that of `instant~1~`, and vice versa. |=== -.+duration.inSeconds()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -duration.inSeconds(date("1984-10-11"), date("1984-10-12")), -duration.inSeconds(date("1984-10-12"), date("1984-10-11")), -duration.inSeconds(date("1984-10-11"), datetime("1984-10-12T01:00:32.142+0100")), -duration.inSeconds(date("2015-06-24"), localtime("14:30")), -duration.inSeconds(datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/Stockholm'}), datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/London'})) + duration.inSeconds(date("1984-10-11"), date("1984-10-12")), + duration.inSeconds(date("1984-10-12"), date("1984-10-11")), + duration.inSeconds(date("1984-10-11"), datetime("1984-10-12T01:00:32.142+0100")), + duration.inSeconds(date("2015-06-24"), localtime("14:30")), + duration.inSeconds(datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/Stockholm'}), datetime({year: 2017, month: 10, day: 29, hour: 0, timezone: 'Europe/London'})) ] AS aDuration RETURN aDuration ---- @@ -539,7 +502,6 @@ RETURN aDuration .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/functions/temporal/index.adoc b/modules/ROOT/pages/functions/temporal/index.adoc index b4d15cd58..3369b8662 100644 --- a/modules/ROOT/pages/functions/temporal/index.adoc +++ b/modules/ROOT/pages/functions/temporal/index.adoc @@ -1,18 +1,13 @@ -:description: Cypher provides functions allowing for the creation and manipulation of values for each temporal type -- _Date_, _Time_, _LocalTime_, _DateTime_, and _LocalDateTime_. - [[query-functions-temporal-instant-types]] = Temporal functions - instant types - -[abstract] --- -Cypher provides functions allowing for the creation and manipulation of values for each temporal type -- _Date_, _Time_, _LocalTime_, _DateTime_, and _LocalDateTime_. --- +:description: Cypher provides functions allowing for the creation and manipulation of values for each temporal type -- _Date_, _Time_, _LocalTime_, _DateTime_, and _LocalDateTime_. An introduction to temporal instant types, including descriptions of creation functions, clocks, and truncation. Details for using the `date()` function. Details for using the `datetime()` function. Details for using the `localdatetime()` function. Details for using the `localtime()` function. Details for using the `time()` function. [NOTE] ==== -See also xref::syntax/temporal.adoc[Temporal (Date/Time) values] and xref::syntax/operators.adoc#query-operators-temporal[Temporal operators]. -==== +See also xref:syntax/temporal.adoc[Temporal (Date/Time) values] and xref:syntax/operators.adoc#query-operators-temporal[Temporal operators]. + +==== [[functions-temporal-instant-type]] == Temporal instant types @@ -22,9 +17,9 @@ See also xref::syntax/temporal.adoc[Temporal (Date/Time) values] and xref::synta Each function bears the same name as the type, and construct the type they correspond to in one of four ways: -* Capturing the current time. -* Composing the components of the type. -* Parsing a string representation of the temporal value. +* Capturing the current time +* Composing the components of the type +* Parsing a string representation of the temporal value * Selecting and composing components from another temporal value by ** either combining temporal values (such as combining a _Date_ with a _Time_ to create a _DateTime_), or ** selecting parts from a temporal value (such as selecting the _Date_ from a _DateTime_); the _extractors_ -- groups of components which can be selected -- are: @@ -39,71 +34,16 @@ Each function bears the same name as the type, and construct the type they corre .Temporal instant type creation functions [options="header"] |=== -| Function | Date | Time | LocalTime | DateTime | LocalDateTime - -| Getting the current value. -| xref::functions/temporal/index.adoc#functions-date-current[X] -| xref::functions/temporal/index.adoc#functions-time-current[X] -| xref::functions/temporal/index.adoc#functions-localtime-current[X] -| xref::functions/temporal/index.adoc#functions-datetime-current[X] -| xref::functions/temporal/index.adoc#functions-localdatetime-current[X] - -| Creating a calendar-based (Year-Month-Day) value. -| xref::functions/temporal/index.adoc#functions-date-calendar[X] -| -| -| xref::functions/temporal/index.adoc#functions-datetime-calendar[X] -| xref::functions/temporal/index.adoc#functions-localdatetime-calendar[X] - -| Creating a week-based (Year-Week-Day) value. -| xref::functions/temporal/index.adoc#functions-date-week[X] -| -| -| xref::functions/temporal/index.adoc#functions-datetime-week[X] -| xref::functions/temporal/index.adoc#functions-localdatetime-week[X] - -| Creating a quarter-based (Year-Quarter-Day) value. -| xref::functions/temporal/index.adoc#functions-date-quarter[X] -| -| -| xref::functions/temporal/index.adoc#functions-datetime-quarter[X] -| xref::functions/temporal/index.adoc#functions-localdatetime-quarter[X] - -| Creating an ordinal (Year-Day) value. -| xref::functions/temporal/index.adoc#functions-date-ordinal[X] -| -| -| xref::functions/temporal/index.adoc#functions-datetime-ordinal[X] -| xref::functions/temporal/index.adoc#functions-localdatetime-ordinal[X] - -| Creating a value from time components. -| -| xref::functions/temporal/index.adoc#functions-time-create[X] -| xref::functions/temporal/index.adoc#functions-localtime-create[X] -| -| - -| Creating a value from other temporal values using extractors (i.e. converting between different types). -| xref::functions/temporal/index.adoc#functions-date-temporal[X] -| xref::functions/temporal/index.adoc#functions-time-temporal[X] -| xref::functions/temporal/index.adoc#functions-localtime-temporal[X] -| xref::functions/temporal/index.adoc#functions-datetime-temporal[X] -| xref::functions/temporal/index.adoc#functions-localdatetime-temporal[X] - -| Creating a value from a string. -| xref::functions/temporal/index.adoc#functions-date-create-string[X] -| xref::functions/temporal/index.adoc#functions-time-create-string[X] -| xref::functions/temporal/index.adoc#functions-localtime-create-string[X] -| xref::functions/temporal/index.adoc#functions-datetime-create-string[X] -| xref::functions/temporal/index.adoc#functions-localdatetime-create-string[X] - -| Creating a value from a timestamp. -| -| -| -| xref::functions/temporal/index.adoc#functions-datetime-timestamp[X] -| - +| Function | Date | Time | LocalTime | DateTime | LocalDateTime +| Getting the current value | xref:functions/temporal/index.adoc#functions-date-current[X] | xref:functions/temporal/index.adoc#functions-time-current[X] | xref:functions/temporal/index.adoc#functions-localtime-current[X] | xref:functions/temporal/index.adoc#functions-datetime-current[X] | xref:functions/temporal/index.adoc#functions-localdatetime-current[X] +| Creating a calendar-based (Year-Month-Day) value | xref:functions/temporal/index.adoc#functions-date-calendar[X] | | | xref:functions/temporal/index.adoc#functions-datetime-calendar[X] | xref:functions/temporal/index.adoc#functions-localdatetime-calendar[X] +| Creating a week-based (Year-Week-Day) value | xref:functions/temporal/index.adoc#functions-date-week[X] | | | xref:functions/temporal/index.adoc#functions-datetime-week[X] | xref:functions/temporal/index.adoc#functions-localdatetime-week[X] +| Creating a quarter-based (Year-Quarter-Day) value | xref:functions/temporal/index.adoc#functions-date-quarter[X] | | | xref:functions/temporal/index.adoc#functions-datetime-quarter[X] | xref:functions/temporal/index.adoc#functions-localdatetime-quarter[X] +| Creating an ordinal (Year-Day) value | xref:functions/temporal/index.adoc#functions-date-ordinal[X] | | | xref:functions/temporal/index.adoc#functions-datetime-ordinal[X] | xref:functions/temporal/index.adoc#functions-localdatetime-ordinal[X] +| Creating a value from time components | | xref:functions/temporal/index.adoc#functions-time-create[X] | xref:functions/temporal/index.adoc#functions-localtime-create[X] | | +| Creating a value from other temporal values using extractors (i.e. converting between different types) | xref:functions/temporal/index.adoc#functions-date-temporal[X] | xref:functions/temporal/index.adoc#functions-time-temporal[X] | xref:functions/temporal/index.adoc#functions-localtime-temporal[X] | xref:functions/temporal/index.adoc#functions-datetime-temporal[X] | xref:functions/temporal/index.adoc#functions-localdatetime-temporal[X] +| Creating a value from a string | xref:functions/temporal/index.adoc#functions-date-create-string[X] | xref:functions/temporal/index.adoc#functions-time-create-string[X] | xref:functions/temporal/index.adoc#functions-localtime-create-string[X] | xref:functions/temporal/index.adoc#functions-datetime-create-string[X] | xref:functions/temporal/index.adoc#functions-localdatetime-create-string[X] +| Creating a value from a timestamp | | | | xref:functions/temporal/index.adoc#functions-datetime-timestamp[X] | |=== @@ -111,9 +51,10 @@ Each function bears the same name as the type, and construct the type they corre ==== All the temporal instant types -- including those that do not contain time zone information support such as _Date_, _LocalTime_ and _DateTime_ -- allow for a time zone to specified for the functions that retrieve the current instant. This allows for the retrieval of the current instant in the specified time zone. -==== +==== + [[functions-temporal-clock-overview]] === Controlling which clock to use @@ -126,47 +67,21 @@ A different time may be produced for different transactions. A different time may be produced for different statements within the same transaction. * `realtime`: The instant produced will be the live clock of the system. + The following table lists the different sub-functions for specifying the clock to be used when creating the current temporal instant value: [options="header"] |=== -| Type | default | transaction | statement | realtime - -| Date -| xref::functions/temporal/index.adoc#functions-date-current[date()] -| xref::functions/temporal/index.adoc#functions-date-transaction[date.transaction()] -| xref::functions/temporal/index.adoc#functions-date-statement[date.statement()] -| xref::functions/temporal/index.adoc#functions-date-realtime[date.realtime()] - -| Time -| xref::functions/temporal/index.adoc#functions-time-current[time()] -| xref::functions/temporal/index.adoc#functions-time-transaction[time.transaction()] -| xref::functions/temporal/index.adoc#functions-time-statement[time.statement()] -| xref::functions/temporal/index.adoc#functions-time-realtime[time.realtime()] - -| LocalTime -| xref::functions/temporal/index.adoc#functions-localtime-current[localtime()] -| xref::functions/temporal/index.adoc#functions-localtime-transaction[localtime.transaction()] -| xref::functions/temporal/index.adoc#functions-localtime-statement[localtime.statement()] -| xref::functions/temporal/index.adoc#functions-localtime-realtime[localtime.realtime()] - -| DateTime -| xref::functions/temporal/index.adoc#functions-datetime-current[datetime()] -| xref::functions/temporal/index.adoc#functions-datetime-transaction[datetime.transaction()] -| xref::functions/temporal/index.adoc#functions-datetime-statement[datetime.statement()] -| xref::functions/temporal/index.adoc#functions-datetime-realtime[datetime.realtime()] - -| LocalDateTime -| xref::functions/temporal/index.adoc#functions-localdatetime-current[localdatetime()] -| xref::functions/temporal/index.adoc#functions-localdatetime-transaction[localdatetime.transaction()] -| xref::functions/temporal/index.adoc#functions-localdatetime-statement[localdatetime.statement()] -| xref::functions/temporal/index.adoc#functions-localdatetime-realtime[localdatetime.realtime()] - +| Type | default | transaction | statement | realtime +| Date | xref:functions/temporal/index.adoc#functions-date-current[date()] | xref:functions/temporal/index.adoc#functions-date-transaction[date.transaction()] | xref:functions/temporal/index.adoc#functions-date-statement[date.statement()] | xref:functions/temporal/index.adoc#functions-date-realtime[date.realtime()] +| Time | xref:functions/temporal/index.adoc#functions-time-current[time()] | xref:functions/temporal/index.adoc#functions-time-transaction[time.transaction()] | xref:functions/temporal/index.adoc#functions-time-statement[time.statement()] | xref:functions/temporal/index.adoc#functions-time-realtime[time.realtime()] +| LocalTime | xref:functions/temporal/index.adoc#functions-localtime-current[localtime()] | xref:functions/temporal/index.adoc#functions-localtime-transaction[localtime.transaction()] | xref:functions/temporal/index.adoc#functions-localtime-statement[localtime.statement()] | xref:functions/temporal/index.adoc#functions-localtime-realtime[localtime.realtime()] +| DateTime | xref:functions/temporal/index.adoc#functions-datetime-current[datetime()] | xref:functions/temporal/index.adoc#functions-datetime-transaction[datetime.transaction()] | xref:functions/temporal/index.adoc#functions-datetime-statement[datetime.statement()] | xref:functions/temporal/index.adoc#functions-datetime-realtime[datetime.realtime()] +| LocalDateTime | xref:functions/temporal/index.adoc#functions-localdatetime-current[localdatetime()] | xref:functions/temporal/index.adoc#functions-localdatetime-transaction[localdatetime.transaction()] | xref:functions/temporal/index.adoc#functions-localdatetime-statement[localdatetime.statement()] | xref:functions/temporal/index.adoc#functions-localdatetime-realtime[localdatetime.realtime()] |=== [[functions-temporal-truncate-overview]] -[discrete] === Truncating temporal values A temporal instant value can be created by truncating another temporal instant value at the nearest preceding point in time at a specified component boundary (namely, a _truncation unit_). @@ -192,183 +107,77 @@ The following truncation units are supported: * `millisecond`: Select the temporal instant corresponding to the _millisecond_ of the given instant. * `microsecond`: Select the temporal instant corresponding to the _microsecond_ of the given instant. + + The following table lists the supported truncation units and the corresponding sub-functions: [options="header"] |=== -| Truncation unit | Date | Time | LocalTime | DateTime | LocalDateTime - -| `millennium` -| xref:functions-date-truncate[date.truncate('millennium', input)] -| -| -| xref:functions-datetime-truncate[datetime.truncate('millennium', input)] -| xref:functions-localdatetime-truncate[localdatetime.truncate('millennium', input)] - -| `century` -| xref:functions-date-truncate[date.truncate('century', input)] -| -| -| xref:functions-datetime-truncate[datetime.truncate('century', input)] -| xref:functions-localdatetime-truncate[localdatetime.truncate('century', input)] - -| `decade` -| xref:functions-date-truncate[date.truncate('decade', input)] -| -| -| xref:functions-datetime-truncate[datetime.truncate('decade', input)] -| xref:functions-localdatetime-truncate[localdatetime.truncate('decade', input)] - -| `year` -| xref:functions-date-truncate[date.truncate('year', input)] -| -| -| xref:functions-datetime-truncate[datetime.truncate('year', input)] -| xref:functions-localdatetime-truncate[localdatetime.truncate('year', input)] - -| `weekYear` -| xref:functions-date-truncate[date.truncate('weekYear', input)] -| -| -| xref:functions-datetime-truncate[datetime.truncate('weekYear', input)] -| xref:functions-localdatetime-truncate[localdatetime.truncate('weekYear', input)] - -| `quarter` -| xref:functions-date-truncate[date.truncate('quarter', input)] -| -| -| xref:functions-datetime-truncate[datetime.truncate('quarter', input)] -| xref:functions-localdatetime-truncate[localdatetime.truncate('quarter', input)] - -| `month` -| xref:functions-date-truncate[date.truncate('month', input)] -| -| -| xref:functions-datetime-truncate[datetime.truncate('month', input)] -| xref:functions-localdatetime-truncate[localdatetime.truncate('month', input)] - -| `week` -| xref:functions-date-truncate[date.truncate('week', input)] -| -| -| xref:functions-datetime-truncate[datetime.truncate('week', input)] -| xref:functions-localdatetime-truncate[localdatetime.truncate('week', input)] - -| `day` -| xref:functions-date-truncate[date.truncate('day', input)] -| xref:functions-time-truncate[time.truncate('day', input)] -| xref:functions-localtime-truncate[localtime.truncate('day', input)] -| xref:functions-datetime-truncate[datetime.truncate('day', input)] -| xref:functions-localdatetime-truncate[localdatetime.truncate('day', input)] - -| `hour` -| -| xref:functions-time-truncate[time.truncate('hour', input)] -| xref:functions-localtime-truncate[localtime.truncate('hour', input)] -| xref:functions-datetime-truncate[datetime.truncate('hour', input)] -| xref:functions-localdatetime-truncate[localdatetime.truncate('hour',input)] - -| `minute` -| -| xref:functions-time-truncate[time.truncate('minute', input)] -| xref:functions-localtime-truncate[localtime.truncate('minute', input)] -| xref:functions-datetime-truncate[datetime.truncate('minute', input)] -| xref:functions-localdatetime-truncate[localdatetime.truncate('minute', input)] - -| `second` -| -| xref:functions-time-truncate[time.truncate('second', input)] -| xref:functions-localtime-truncate[localtime.truncate('second', input)] -| xref:functions-datetime-truncate[datetime.truncate('second', input)] -| xref:functions-localdatetime-truncate[localdatetime.truncate('second', input)] - -| `millisecond` -| -| xref:functions-time-truncate[time.truncate('millisecond', input)] -| xref:functions-localtime-truncate[localtime.truncate('millisecond', input)] -| xref:functions-datetime-truncate[datetime.truncate('millisecond', input)] -| xref:functions-localdatetime-truncate[localdatetime.truncate('millisecond', input)] - -| `microsecond` -| -| xref:functions-time-truncate[time.truncate('microsecond', input)] -| xref:functions-localtime-truncate[localtime.truncate('microsecond', input)] -| xref:functions-datetime-truncate[datetime.truncate('microsecond', input)] -| xref:functions-localdatetime-truncate[localdatetime.truncate('microsecond', input)] - +| Truncation unit | Date | Time | LocalTime | DateTime | LocalDateTime +| `millennium` | xref:functions-date-truncate[date.truncate('millennium', input)] | | | xref:functions-datetime-truncate[datetime.truncate('millennium', input)] | xref:functions-localdatetime-truncate[localdatetime.truncate('millennium', input)] +| `century` | xref:functions-date-truncate[date.truncate('century', input)] | | | xref:functions-datetime-truncate[datetime.truncate('century', input)] | xref:functions-localdatetime-truncate[localdatetime.truncate('century', input)] +| `decade` | xref:functions-date-truncate[date.truncate('decade', input)] | | | xref:functions-datetime-truncate[datetime.truncate('decade', input)] | xref:functions-localdatetime-truncate[localdatetime.truncate('decade', input)] +| `year` | xref:functions-date-truncate[date.truncate('year', input)] | | | xref:functions-datetime-truncate[datetime.truncate('year', input)] | xref:functions-localdatetime-truncate[localdatetime.truncate('year', input)] +| `weekYear` | xref:functions-date-truncate[date.truncate('weekYear', input)] | | | xref:functions-datetime-truncate[datetime.truncate('weekYear', input)] | xref:functions-localdatetime-truncate[localdatetime.truncate('weekYear', input)] +| `quarter` | xref:functions-date-truncate[date.truncate('quarter', input)] | | | xref:functions-datetime-truncate[datetime.truncate('quarter', input)] | xref:functions-localdatetime-truncate[localdatetime.truncate('quarter', input)] +| `month` | xref:functions-date-truncate[date.truncate('month', input)] | | | xref:functions-datetime-truncate[datetime.truncate('month', input)] | xref:functions-localdatetime-truncate[localdatetime.truncate('month', input)] +| `week` | xref:functions-date-truncate[date.truncate('week', input)] | | | xref:functions-datetime-truncate[datetime.truncate('week', input)] | xref:functions-localdatetime-truncate[localdatetime.truncate('week', input)] +| `day` | xref:functions-date-truncate[date.truncate('day', input)] | xref:functions-time-truncate[time.truncate('day', input)] | xref:functions-localtime-truncate[localtime.truncate('day', input)] | xref:functions-datetime-truncate[datetime.truncate('day', input)] | xref:functions-localdatetime-truncate[localdatetime.truncate('day', input)] +| `hour` | | xref:functions-time-truncate[time.truncate('hour', input)] | xref:functions-localtime-truncate[localtime.truncate('hour', input)] | xref:functions-datetime-truncate[datetime.truncate('hour', input)] | xref:functions-localdatetime-truncate[localdatetime.truncate('hour',input)] +| `minute` | | xref:functions-time-truncate[time.truncate('minute', input)] | xref:functions-localtime-truncate[localtime.truncate('minute', input)] | xref:functions-datetime-truncate[datetime.truncate('minute', input)] | xref:functions-localdatetime-truncate[localdatetime.truncate('minute', input)] +| `second` | | xref:functions-time-truncate[time.truncate('second', input)] | xref:functions-localtime-truncate[localtime.truncate('second', input)] | xref:functions-datetime-truncate[datetime.truncate('second', input)] | xref:functions-localdatetime-truncate[localdatetime.truncate('second', input)] +| `millisecond` | | xref:functions-time-truncate[time.truncate('millisecond', input)] | xref:functions-localtime-truncate[localtime.truncate('millisecond', input)] | xref:functions-datetime-truncate[datetime.truncate('millisecond', input)] | xref:functions-localdatetime-truncate[localdatetime.truncate('millisecond', input)] +| `microsecond` | | xref:functions-time-truncate[time.truncate('microsecond', input)] | xref:functions-localtime-truncate[localtime.truncate('microsecond', input)] | xref:functions-datetime-truncate[datetime.truncate('microsecond', input)] | xref:functions-localdatetime-truncate[localdatetime.truncate('microsecond', input)] |=== [[functions-date]] -== +date()+ - -Details for using the `date()` function. +== Date: `date()` -* xref::functions/temporal/index.adoc#functions-date-current[Getting the current _Date_] -** xref::/functions/temporal/index.adoc#functions-date-transaction[+date.transaction()+] -** xref::/functions/temporal/index.adoc#functions-date-statement[+date.statement()+] -** xref::/functions/temporal/index.adoc#functions-date-realtime[+date.realtime()+] -* xref::functions/temporal/index.adoc#functions-date-calendar[Creating a calendar (Year-Month-Day) _Date_] -* xref::functions/temporal/index.adoc#functions-date-week[Creating a week (Year-Week-Day) _Date_] -* xref::functions/temporal/index.adoc#functions-date-quarter[Creating a quarter (Year-Quarter-Day) _Date_] -* xref::functions/temporal/index.adoc#functions-date-ordinal[Creating an ordinal (Year-Day) _Date_] -* xref::functions/temporal/index.adoc#functions-date-create-string[Creating a _Date_ from a string] -* xref::functions/temporal/index.adoc#functions-date-temporal[Creating a _Date_ using other temporal values as components] -* xref::functions/temporal/index.adoc#functions-date-truncate[Truncating a _Date_] +* xref:functions/temporal/index.adoc#functions-date-current[Getting the current _Date_] +* xref:functions/temporal/index.adoc#functions-date-calendar[Creating a calendar (Year-Month-Day) _Date_] +* xref:functions/temporal/index.adoc#functions-date-week[Creating a week (Year-Week-Day) _Date_] +* xref:functions/temporal/index.adoc#functions-date-quarter[Creating a quarter (Year-Quarter-Day) _Date_] +* xref:functions/temporal/index.adoc#functions-date-ordinal[Creating an ordinal (Year-Day) _Date_] +* xref:functions/temporal/index.adoc#functions-date-create-string[Creating a _Date_ from a string] +* xref:functions/temporal/index.adoc#functions-date-temporal[Creating a _Date_ using other temporal values as components] +* xref:functions/temporal/index.adoc#functions-date-truncate[Truncating a _Date_] + - -[discrete] [[functions-date-current]] === Getting the current _Date_ `date()` returns the current _Date_ value. If no time zone parameter is specified, the local time zone will be used. -*Syntax:* - -[source, syntax, role="noheader"] ----- -date([{timezone}]) ----- +*Syntax:* `+date([{timezone}])+` *Returns:* - |=== - -| A Date. - +| +A Date. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `A single map consisting of the following:` | +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -*Considerations:* +*Considerations:* |=== - -| If no parameters are provided, `date()` must be invoked (`+date({})+` is invalid). - +|If no parameters are provided, `date()` must be invoked (`date({})` is invalid). |=== -.+date()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN date() AS currentDate ---- @@ -378,23 +187,29 @@ The current date is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -.+date()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- -RETURN date({timezone: 'America/Los Angeles'}) AS currentDateInLA +RETURN date( {timezone: 'America/Los Angeles'} ) AS currentDateInLA ---- The current date in California is returned. @@ -402,55 +217,51 @@ The current date in California is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-date-transaction]] -==== +date.transaction()+ +==== date.transaction() `date.transaction()` returns the current _Date_ value using the `transaction` clock. This value will be the same for each invocation within the same transaction. However, a different value may be produced for different transactions. + -*Syntax:* - -[source, syntax, role="noheader"] ----- -date.transaction([{timezone}]) ----- +*Syntax:* `+date.transaction([{timezone}])+` *Returns:* - |=== - -| A Date. - +| +A Date. |=== + *Arguments:* [options="header"] |=== | Name | Description - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -.+date.transaction()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN date.transaction() AS currentDate ---- @@ -458,56 +269,50 @@ RETURN date.transaction() AS currentDate .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-date-statement]] -==== +date.statement()+ +==== date.statement() `date.statement()` returns the current _Date_ value using the `statement` clock. This value will be the same for each invocation within the same statement. However, a different value may be produced for different statements within the same transaction. -*Syntax:* - -[source, syntax, role="noheader"] ----- -date.statement([{timezone}]) ----- +*Syntax:* `+date.statement([{timezone}])+` *Returns:* - |=== - -| A Date. - +| +A Date. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -.+date.statement()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN date.statement() AS currentDate ---- @@ -515,55 +320,50 @@ RETURN date.statement() AS currentDate .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-date-realtime]] ==== date.realtime() `date.realtime()` returns the current _Date_ value using the `realtime` clock. This value will be the live clock of the system. + -*Syntax:* - -[source, syntax, role="noheader"] ----- -date.realtime([{timezone}]) ----- +*Syntax:* `+date.realtime([{timezone}])+` *Returns:* - |=== - -| A Date. - +| +A Date. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -.+date.realtime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN date.realtime() AS currentDate ---- @@ -571,21 +371,27 @@ RETURN date.realtime() AS currentDate .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -.+date.realtime()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN date.realtime('America/Los Angeles') AS currentDateInLA ---- @@ -593,316 +399,277 @@ RETURN date.realtime('America/Los Angeles') AS currentDateInLA .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-date-calendar]] === Creating a calendar (Year-Month-Day) _Date_ `date()` returns a _Date_ value with the specified _year_, _month_ and _day_ component values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -date({year [, month, day]}) ----- +*Syntax:* `date({year [, month, day]})` *Returns:* - |=== - -| A Date. - +| +A Date. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `year` -| An expression consisting of at xref::syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. - -| `month` -| An integer between `1` and `12` that specifies the month. - -| `day` -| An integer between `1` and `31` that specifies the day of the month. - +| `A single map consisting of the following:` | +| `year` | An expression consisting of at xref:syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. +| `month` | An integer between `1` and `12` that specifies the month. +| `day` | An integer between `1` and `31` that specifies the day of the month. |=== -*Considerations:* +*Considerations:* |=== - -| The _day of the month_ component will default to `1` if `day` is omitted. -| The _month_ component will default to `1` if `month` is omitted. -| If `month` is omitted, `day` must also be omitted. - +|The _day of the month_ component will default to `1` if `day` is omitted. +|The _month_ component will default to `1` if `month` is omitted. +|If `month` is omitted, `day` must also be omitted. |=== -.+date()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -date({year: 1984, month: 10, day: 11}), -date({year: 1984, month: 10}), -date({year: 1984}) -] AS theDate + date({year:1984, month:10, day:11}), + date({year:1984, month:10}), + date({year:1984}) +] as theDate RETURN theDate ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-date-week]] -[discrete] === Creating a week (Year-Week-Day) _Date_ `date()` returns a _Date_ value with the specified _year_, _week_ and _dayOfWeek_ component values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -date({year [, week, dayOfWeek]}) ----- +*Syntax:* `date({year [, week, dayOfWeek]})` *Returns:* - |=== - -| A Date. - +| +A Date. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `year` -| An expression consisting of at xref::syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. - -| `week` -| An integer between `1` and `53` that specifies the week. - -| `dayOfWeek` -| An integer between `1` and `7` that specifies the day of the week. - +| `A single map consisting of the following:` | +| `year` | An expression consisting of at xref:syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. +| `week` | An integer between `1` and `53` that specifies the week. +| `dayOfWeek` | An integer between `1` and `7` that specifies the day of the week. |=== -*Considerations:* +*Considerations:* |=== - -| The _day of the week_ component will default to `1` if `dayOfWeek` is omitted. -| The _week_ component will default to `1` if `week` is omitted. -| If `week` is omitted, `dayOfWeek` must also be omitted. - +|The _day of the week_ component will default to `1` if `dayOfWeek` is omitted. +|The _week_ component will default to `1` if `week` is omitted. +|If `week` is omitted, `dayOfWeek` must also be omitted. |=== -.+date()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -date({year: 1984, week: 10, dayOfWeek: 3}), -date({year: 1984, week: 10}), -date({year: 1984}) -] AS theDate + date({year:1984, week:10, dayOfWeek:3}), + date({year:1984, week:10}), + date({year:1984}) +] as theDate RETURN theDate ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-date-quarter]] === Creating a quarter (Year-Quarter-Day) _Date_ `date()` returns a _Date_ value with the specified _year_, _quarter_ and _dayOfQuarter_ component values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -date({year [, quarter, dayOfQuarter]}) ----- +*Syntax:* `date({year [, quarter, dayOfQuarter]})` *Returns:* - |=== - -| A Date. - +| +A Date. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `year` -| An expression consisting of at xref::syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. - -| `quarter` -| An integer between `1` and `4` that specifies the quarter. - -| `dayOfQuarter` -| An integer between `1` and `92` that specifies the day of the quarter. - +| `A single map consisting of the following:` | +| `year` | An expression consisting of at xref:syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. +| `quarter` | An integer between `1` and `4` that specifies the quarter. +| `dayOfQuarter` | An integer between `1` and `92` that specifies the day of the quarter. |=== -*Considerations:* +*Considerations:* |=== - -| The _day of the quarter_ component will default to `1` if `dayOfQuarter` is omitted. -| The _quarter_ component will default to `1` if `quarter` is omitted. -| If `quarter` is omitted, `dayOfQuarter` must also be omitted. - +|The _day of the quarter_ component will default to `1` if `dayOfQuarter` is omitted. +|The _quarter_ component will default to `1` if `quarter` is omitted. +|If `quarter` is omitted, `dayOfQuarter` must also be omitted. |=== -.+date()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -date({year: 1984, quarter: 3, dayOfQuarter: 45}), -date({year: 1984, quarter: 3}), -date({year: 1984}) -] AS theDate + date({year:1984, quarter:3, dayOfQuarter: 45}), + date({year:1984, quarter:3}), + date({year:1984}) +] as theDate RETURN theDate ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-date-ordinal]] === Creating an ordinal (Year-Day) _Date_ `date()` returns a _Date_ value with the specified _year_ and _ordinalDay_ component values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -date({year [, ordinalDay]}) ----- +*Syntax:* `date({year [, ordinalDay]})` *Returns:* - |=== - -| A Date. - +| +A Date. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `year` -| An expression consisting of at xref::syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. - -| `ordinalDay` -| An integer between `1` and `366` that specifies the ordinal day of the year. - +| `A single map consisting of the following:` | +| `year` | An expression consisting of at xref:syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. +| `ordinalDay` | An integer between `1` and `366` that specifies the ordinal day of the year. |=== -*Considerations:* +*Considerations:* |=== - -| The _ordinal day of the year_ component will default to `1` if `ordinalDay` is omitted. - +|The _ordinal day of the year_ component will default to `1` if `ordinalDay` is omitted. |=== -.+date()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -date({year: 1984, ordinalDay: 202}), -date({year: 1984}) -] AS theDate + date({year:1984, ordinalDay:202}), + date({year:1984}) +] as theDate RETURN theDate ---- @@ -911,81 +678,76 @@ The date corresponding to `11 February 1984` is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-date-create-string]] === Creating a _Date_ from a string `date()` returns the _Date_ value obtained by parsing a string representation of a temporal value. -*Syntax:* - -[source, syntax, role="noheader"] ----- -date(temporalValue) ----- +*Syntax:* `date(temporalValue)` *Returns:* - |=== - -|A Date. - +| +A Date. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `temporalValue` -| A string representing a temporal value. - +| `temporalValue` | A string representing a temporal value. |=== -*Considerations:* +*Considerations:* |=== - -| `temporalValue` must comply with the format defined for xref::syntax/temporal.adoc#cypher-temporal-specify-date[dates]. -| `temporalValue` must denote a valid date; i.e. a `temporalValue` denoting `30 February 2001` is invalid. -| `date(null)` returns `null`. - +|`temporalValue` must comply with the format defined for xref:syntax/temporal.adoc#cypher-temporal-specify-date[dates]. +|`temporalValue` must denote a valid date; i.e. a `temporalValue` denoting `30 February 2001` is invalid. +|`date(null)` returns null. |=== -.+date()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -date('2015-07-21'), -date('2015-07'), -date('201507'), -date('2015-W30-2'), -date('2015202'), -date('2015') -] AS theDate + date('2015-07-21'), + date('2015-07'), + date('201507'), + date('2015-W30-2'), + date('2015202'), + date('2015') +] as theDate RETURN theDate ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-date-temporal]] === Creating a _Date_ using other temporal values as components `date()` returns the _Date_ value obtained by selecting and composing components from another temporal value. In essence, this allows a _DateTime_ or _LocalDateTime_ value to be converted to a _Date_, and for "missing" components to be provided. + -*Syntax:* - -[source, syntax, role="noheader"] ----- -date({date [, year, month, day, week, dayOfWeek, quarter, dayOfQuarter, ordinalDay]}) ----- +*Syntax:* `date({date [, year, month, day, week, dayOfWeek, quarter, dayOfQuarter, ordinalDay]})` *Returns:* - |=== - -| A Date. - +| +A Date. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `date` -| A _Date_ value. - -| `year` -| An expression consisting of at xref::syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. - -| `month` -| An integer between `1` and `12` that specifies the month. - -| `day` -| An integer between `1` and `31` that specifies the day of the month. - -| `week` -| An integer between `1` and `53` that specifies the week. - -| `dayOfWeek` -| An integer between `1` and `7` that specifies the day of the week. - -| `quarter` -| An integer between `1` and `4` that specifies the quarter. - -| `dayOfQuarter` -| An integer between `1` and `92` that specifies the day of the quarter. - -| `ordinalDay` -| An integer between `1` and `366` that specifies the ordinal day of the year. - +| `A single map consisting of the following:` | +| `date` | A _Date_ value. +| `year` | An expression consisting of at xref:syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. +| `month` | An integer between `1` and `12` that specifies the month. +| `day` | An integer between `1` and `31` that specifies the day of the month. +| `week` | An integer between `1` and `53` that specifies the week. +| `dayOfWeek` | An integer between `1` and `7` that specifies the day of the week. +| `quarter` | An integer between `1` and `4` that specifies the quarter. +| `dayOfQuarter` | An integer between `1` and `92` that specifies the day of the quarter. +| `ordinalDay` | An integer between `1` and `366` that specifies the ordinal day of the year. |=== -*Considerations:* +*Considerations:* |=== - -| If any of the optional parameters are provided, these will override the corresponding components of `date`. -| `date(dd)` may be written instead of `+date({date: dd})+`. - +|If any of the optional parameters are provided, these will override the corresponding components of `date`. +|`date(dd)` may be written instead of `date({date: dd})`. |=== -.+date()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -date({year: 1984, month: 11, day: 11}), -localdatetime({year: 1984, month: 11, day: 11, hour: 12, minute: 31, second: 14}), -datetime({year: 1984, month: 11, day: 11, hour: 12, timezone: '+01:00'}) -] AS dd -RETURN date({date: dd}) AS dateOnly, date({date: dd, day: 28}) AS dateDay + date({year:1984, month:11, day:11}), + localdatetime({year:1984, month:11, day:11, hour:12, minute:31, second:14}), + datetime({year:1984, month:11, day:11, hour:12, timezone: '+01:00'}) +] as dd +RETURN date({date: dd}) AS dateOnly, + date({date: dd, day: 28}) AS dateDay ---- .Result [role="queryresult",options="header,footer",cols="2* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-date-truncate]] -[discrete] === Truncating a _Date_ `date.truncate()` returns the _Date_ value obtained by truncating a specified temporal instant value at the nearest preceding point in time at the specified component boundary (which is denoted by the truncation unit passed as a parameter to the function). @@ -1107,159 +868,130 @@ In other words, the _Date_ returned will have all components that are less signi It is possible to supplement the truncated value by providing a map containing components which are less significant than the truncation unit. This will have the effect of _overriding_ the default values which would otherwise have been set for these less significant components. -For example, `day` -- with some value `x` -- may be provided when the truncation unit string is `'year'` in order to ensure the returned value has the _day_ set to `x` instead of the default _day_ (which is `1`). - -*Syntax:* +For example, `day` -- with some value `x` -- may be provided when the truncation unit is `year` in order to ensure the returned value has the _day_ set to `x` instead of the default _day_ (which is `1`). + -[source, syntax, role="noheader"] ----- -date.truncate(unit [, temporalInstantValue [, mapOfComponents ] ]) ----- +*Syntax:* `date.truncate(unit [, temporalInstantValue [, mapOfComponents ] ])` *Returns:* - |=== - -| A Date. - +| +A Date. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `unit` -| A string expression evaluating to one of the following strings: `'millennium'`, `'century'`, `'decade'`, `'year'`, `'weekYear'`, `'quarter'`, `'month'`, `'week'`, `'day'`. - -| `temporalInstantValue` -| An expression of one of the following types: _DateTime_, _LocalDateTime_, _Date_. - -| `mapOfComponents` -| An expression evaluating to a map containing components less significant than `unit`. - +| `unit` | A string expression evaluating to one of the following: {`millennium`, `century`, `decade`, `year`, `weekYear`, `quarter`, `month`, `week`, `day`}. +| `temporalInstantValue` | An expression of one of the following types: {_DateTime_, _LocalDateTime_, _Date_}. +| `mapOfComponents` | An expression evaluating to a map containing components less significant than `unit`. |=== -*Considerations:* +*Considerations:* |=== - -| Any component that is provided in `mapOfComponents` must be less significant than `unit`; i.e. if `unit` string is `'day'`, `mapOfComponents` cannot contain information pertaining to a _month_. -| Any component that is not contained in `mapOfComponents` and which is less significant than `unit` will be set to its xref::syntax/temporal.adoc#cypher-temporal-accessing-components-temporal-instants[minimal value]. -| If `mapOfComponents` is not provided, all components of the returned value which are less significant than `unit` will be set to their default values. -| If `temporalInstantValue` is not provided, it will be set to the current date, i.e. `date.truncate(unit)` is equivalent of `date.truncate(unit, date())`. - +|Any component that is provided in `mapOfComponents` must be less significant than `unit`; i.e. if `unit` is 'day', `mapOfComponents` cannot contain information pertaining to a _month_. +|Any component that is not contained in `mapOfComponents` and which is less significant than `unit` will be set to its xref:syntax/temporal.adoc#cypher-temporal-accessing-components-temporal-instants[minimal value]. +|If `mapOfComponents` is not provided, all components of the returned value which are less significant than `unit` will be set to their default values. +|If `temporalInstantValue` is not provided, it will be set to the current date, i.e. `date.truncate(unit)` is equivalent of `date.truncate(unit, date())`. |=== -.+date.truncate()+ -====== - .Query -[source, cypher, indent=0] ----- -WITH - datetime({ - year: 2017, month: 11, day: 11, - hour: 12, minute: 31, second: 14, nanosecond: 645876123, - timezone: '+01:00' - }) AS d -RETURN - date.truncate('millennium', d) AS truncMillenium, - date.truncate('century', d) AS truncCentury, - date.truncate('decade', d) AS truncDecade, - date.truncate('year', d, {day: 5}) AS truncYear, - date.truncate('weekYear', d) AS truncWeekYear, - date.truncate('quarter', d) AS truncQuarter, - date.truncate('month', d) AS truncMonth, - date.truncate('week', d, {dayOfWeek: 2}) AS truncWeek, - date.truncate('day', d) AS truncDay +[source, cypher] +---- +WITH datetime({year:2017, month:11, day:11, hour:12, minute:31, second:14, nanosecond: 645876123, timezone: '+01:00'}) AS d +RETURN date.truncate('millennium', d) AS truncMillenium, + date.truncate('century', d) AS truncCentury, + date.truncate('decade', d) AS truncDecade, + date.truncate('year', d, {day:5}) AS truncYear, + date.truncate('weekYear', d) AS truncWeekYear, + date.truncate('quarter', d) AS truncQuarter, + date.truncate('month', d) AS truncMonth, + date.truncate('week', d, {dayOfWeek:2}) AS truncWeek, + date.truncate('day', d) AS truncDay ---- .Result [role="queryresult",options="header,footer",cols="9* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-datetime]] -== +datetime()+ - -Details for using the `datetime()` function. +== DateTime: `datetime()` -* xref::functions/temporal/index.adoc#functions-datetime-current[Getting the current _DateTime_] -** xref::functions/temporal/index.adoc#functions-datetime-transaction[+datetime.transaction()+] -** xref::functions/temporal/index.adoc#functions-datetime-statement[+datetime.statement()+] -** xref::functions/temporal/index.adoc#functions-datetime-realtime[+datetime.realtime()+] -* xref::functions/temporal/index.adoc#functions-datetime-calendar[Creating a calendar (Year-Month-Day) _DateTime_] -* xref::functions/temporal/index.adoc#functions-datetime-week[Creating a week (Year-Week-Day) _DateTime_] -* xref::functions/temporal/index.adoc#functions-datetime-quarter[Creating a quarter (Year-Quarter-Day) _DateTime_] -* xref::functions/temporal/index.adoc#functions-datetime-ordinal[Creating an ordinal (Year-Day) _DateTime_] -* xref::functions/temporal/index.adoc#functions-datetime-create-string[Creating a _DateTime_ from a string] -* xref::functions/temporal/index.adoc#functions-datetime-temporal[Creating a _DateTime_ using other temporal values as components] -* xref::functions/temporal/index.adoc#functions-datetime-timestamp[Creating a _DateTime_ from a timestamp] -* xref::functions/temporal/index.adoc#functions-datetime-truncate[Truncating a _DateTime_] +* xref:functions/temporal/index.adoc#functions-datetime-current[Getting the current _DateTime_] +* xref:functions/temporal/index.adoc#functions-datetime-calendar[Creating a calendar (Year-Month-Day) _DateTime_] +* xref:functions/temporal/index.adoc#functions-datetime-week[Creating a week (Year-Week-Day) _DateTime_] +* xref:functions/temporal/index.adoc#functions-datetime-quarter[Creating a quarter (Year-Quarter-Day) _DateTime_] +* xref:functions/temporal/index.adoc#functions-datetime-ordinal[Creating an ordinal (Year-Day) _DateTime_] +* xref:functions/temporal/index.adoc#functions-datetime-create-string[Creating a _DateTime_ from a string] +* xref:functions/temporal/index.adoc#functions-datetime-temporal[Creating a _DateTime_ using other temporal values as components] +* xref:functions/temporal/index.adoc#functions-datetime-timestamp[Creating a _DateTime_ from a timestamp] +* xref:functions/temporal/index.adoc#functions-datetime-truncate[Truncating a _DateTime_] + - -[discrete] [[functions-datetime-current]] === Getting the current _DateTime_ `datetime()` returns the current _DateTime_ value. If no time zone parameter is specified, the default time zone will be used. + -*Syntax:* - -[source, syntax, role="noheader"] ----- -datetime([{timezone}]) ----- +*Syntax:* `+datetime([{timezone}])+` *Returns:* - |=== - -| A DateTime. - +| +A DateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `A single map consisting of the following:` | +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -*Considerations:* +*Considerations:* |=== - -| If no parameters are provided, `datetime()` must be invoked (`datetime({})` is invalid). - +|If no parameters are provided, `datetime()` must be invoked (`datetime({})` is invalid). |=== -.+.datetime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN datetime() AS currentDateTime ---- @@ -1269,21 +1001,27 @@ The current date and time using the local time zone is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -.+.datetime()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN datetime({timezone: 'America/Los Angeles'}) AS currentDateTimeInLA ---- @@ -1293,17 +1031,24 @@ The current date and time of day in California is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-datetime-transaction]] ==== datetime.transaction() @@ -1311,38 +1056,25 @@ The current date and time of day in California is returned. This value will be the same for each invocation within the same transaction. However, a different value may be produced for different transactions. -*Syntax:* - -[source, syntax, role="noheader"] ----- -datetime.transaction([{timezone}]) ----- +*Syntax:* `+datetime.transaction([{timezone}])+` *Returns:* - |=== - -| A DateTime. - +| +A DateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -.+datetime.transaction()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN datetime.transaction() AS currentDateTime ---- @@ -1350,21 +1082,27 @@ RETURN datetime.transaction() AS currentDateTime .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -.+datetime.transaction()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN datetime.transaction('America/Los Angeles') AS currentDateTimeInLA ---- @@ -1372,17 +1110,24 @@ RETURN datetime.transaction('America/Los Angeles') AS currentDateTimeInLA .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-datetime-statement]] ==== datetime.statement() @@ -1390,37 +1135,25 @@ RETURN datetime.transaction('America/Los Angeles') AS currentDateTimeInLA This value will be the same for each invocation within the same statement. However, a different value may be produced for different statements within the same transaction. -*Syntax:* - -[source, syntax, role="noheader"] ----- -datetime.statement([{timezone}]) ----- +*Syntax:* `+datetime.statement([{timezone}])+` *Returns:* |=== - -| A DateTime. - +| +A DateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -.+datetime.statement()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN datetime.statement() AS currentDateTime ---- @@ -1428,55 +1161,49 @@ RETURN datetime.statement() AS currentDateTime .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-datetime-realtime]] ==== datetime.realtime() `datetime.realtime()` returns the current _DateTime_ value using the `realtime` clock. This value will be the live clock of the system. -*Syntax:* - -[source, syntax, role="noheader"] ----- -datetime.realtime([{timezone}]) ----- +*Syntax:* `+datetime.realtime([{timezone}])+` *Returns:* - |=== - -| A DateTime. - +| +A DateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -.+datetime.realtime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN datetime.realtime() AS currentDateTime ---- @@ -1484,119 +1211,90 @@ RETURN datetime.realtime() AS currentDateTime .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-datetime-calendar]] === Creating a calendar (Year-Month-Day) _DateTime_ `datetime()` returns a _DateTime_ value with the specified _year_, _month_, _day_, _hour_, _minute_, _second_, _millisecond_, _microsecond_, _nanosecond_ and _timezone_ component values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -datetime({year [, month, day, hour, minute, second, millisecond, microsecond, nanosecond, timezone]}) ----- +*Syntax:* `datetime({year [, month, day, hour, minute, second, millisecond, microsecond, nanosecond, timezone]})` *Returns:* - |=== - -| A DateTime. - +| +A DateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `year` -| An expression consisting of at xref::syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. - -| `month` -| An integer between `1` and `12` that specifies the month. - -| `day` -| An integer between `1` and `31` that specifies the day of the month. - -| `hour` -| An integer between `0` and `23` that specifies the hour of the day. - -| `minute` -| An integer between `0` and `59` that specifies the number of minutes. - -| `second` -| An integer between `0` and `59` that specifies the number of seconds. - -| `millisecond` -| An integer between `0` and `999` that specifies the number of milliseconds. - -| `microsecond` -| An integer between `0` and `999,999` that specifies the number of microseconds. - -| `nanosecond` -| An integer between `0` and `999,999,999` that specifies the number of nanoseconds. - -| `timezone` -| An expression that specifies the time zone. - +| `A single map consisting of the following:` | +| `year` | An expression consisting of at xref:syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. +| `month` | An integer between `1` and `12` that specifies the month. +| `day` | An integer between `1` and `31` that specifies the day of the month. +| `hour` | An integer between `0` and `23` that specifies the hour of the day. +| `minute` | An integer between `0` and `59` that specifies the number of minutes. +| `second` | An integer between `0` and `59` that specifies the number of seconds. +| `millisecond` | An integer between `0` and `999` that specifies the number of milliseconds. +| `microsecond` | An integer between `0` and `999,999` that specifies the number of microseconds. +| `nanosecond` | An integer between `0` and `999,999,999` that specifies the number of nanoseconds. +| `timezone` | An expression that specifies the time zone. |=== -*Considerations:* +*Considerations:* |=== - -| The _month_ component will default to `1` if `month` is omitted. -| The _day of the month_ component will default to `1` if `day` is omitted. -| The _hour_ component will default to `0` if `hour` is omitted. -| The _minute_ component will default to `0` if `minute` is omitted. -| The _second_ component will default to `0` if `second` is omitted. -| Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. -| The _timezone_ component will default to the configured default time zone if `timezone` is omitted. -| If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. -| The least significant components in the set `year`, `month`, `day`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year`, `month` and `day`, but specifying `year`, `month`, `day` and `minute` is not permitted. -| One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. - +|The _month_ component will default to `1` if `month` is omitted. +|The _day of the month_ component will default to `1` if `day` is omitted. +|The _hour_ component will default to `0` if `hour` is omitted. +|The _minute_ component will default to `0` if `minute` is omitted. +|The _second_ component will default to `0` if `second` is omitted. +|Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. +|The _timezone_ component will default to the configured default time zone if `timezone` is omitted. +|If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. +|The least significant components in the set `year`, `month`, `day`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year`, `month` and `day`, but specifying `year`, `month`, `day` and `minute` is not permitted. +|One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. |=== -.+datetime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -datetime({year: 1984, month: 10, day: 11, hour: 12, minute: 31, second: 14, millisecond: 123, microsecond: 456, nanosecond: 789}), -datetime({year: 1984, month: 10, day: 11, hour: 12, minute: 31, second: 14, millisecond: 645, timezone: '+01:00'}), -datetime({year: 1984, month: 10, day: 11, hour: 12, minute: 31, second: 14, nanosecond: 645876123, timezone: 'Europe/Stockholm'}), -datetime({year: 1984, month: 10, day: 11, hour: 12, minute: 31, second: 14, timezone: '+01:00'}), -datetime({year: 1984, month: 10, day: 11, hour: 12, minute: 31, second: 14}), -datetime({year: 1984, month: 10, day: 11, hour: 12, minute: 31, timezone: 'Europe/Stockholm'}), -datetime({year: 1984, month: 10, day: 11, hour: 12, timezone: '+01:00'}), -datetime({year: 1984, month: 10, day: 11, timezone: 'Europe/Stockholm'}) -] AS theDate + datetime({year:1984, month:10, day:11, hour:12, minute:31, second:14, millisecond: 123, microsecond: 456, nanosecond: 789}), + datetime({year:1984, month:10, day:11, hour:12, minute:31, second:14, millisecond: 645, timezone: '+01:00'}), + datetime({year:1984, month:10, day:11, hour:12, minute:31, second:14, nanosecond: 645876123, timezone: 'Europe/Stockholm'}), + datetime({year:1984, month:10, day:11, hour:12, minute:31, second:14, timezone: '+01:00'}), + datetime({year:1984, month:10, day:11, hour:12, minute:31, second:14}), + datetime({year:1984, month:10, day:11, hour:12, minute:31, timezone: 'Europe/Stockholm'}), + datetime({year:1984, month:10, day:11, hour:12, timezone: '+01:00'}), + datetime({year:1984, month:10, day:11, timezone: 'Europe/Stockholm'}) +] as theDate RETURN theDate ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-datetime-week]] === Creating a week (Year-Week-Day) _DateTime_ `datetime()` returns a _DateTime_ value with the specified _year_, _week_, _dayOfWeek_, _hour_, _minute_, _second_, _millisecond_, _microsecond_, _nanosecond_ and _timezone_ component values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -datetime({year [, week, dayOfWeek, hour, minute, second, millisecond, microsecond, nanosecond, timezone]}) ----- +*Syntax:* `datetime({year [, week, dayOfWeek, hour, minute, second, millisecond, microsecond, nanosecond, timezone]})` *Returns:* - |=== - -| A DateTime. - +| +A DateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `year` -| An expression consisting of at xref::syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. - -| `week` -| An integer between `1` and `53` that specifies the week. - -| `dayOfWeek` -| An integer between `1` and `7` that specifies the day of the week. - -| `hour` -| An integer between `0` and `23` that specifies the hour of the day. - -| `minute` -| An integer between `0` and `59` that specifies the number of minutes. - -| `second` -| An integer between `0` and `59` that specifies the number of seconds. - -| `millisecond` -| An integer between `0` and `999` that specifies the number of milliseconds. - -| `microsecond` -| An integer between `0` and `999,999` that specifies the number of microseconds. - -| `nanosecond` -| An integer between `0` and `999,999,999` that specifies the number of nanoseconds. - -| `timezone` -| An expression that specifies the time zone. - +| `A single map consisting of the following:` | +| `year` | An expression consisting of at xref:syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. +| `week` | An integer between `1` and `53` that specifies the week. +| `dayOfWeek` | An integer between `1` and `7` that specifies the day of the week. +| `hour` | An integer between `0` and `23` that specifies the hour of the day. +| `minute` | An integer between `0` and `59` that specifies the number of minutes. +| `second` | An integer between `0` and `59` that specifies the number of seconds. +| `millisecond` | An integer between `0` and `999` that specifies the number of milliseconds. +| `microsecond` | An integer between `0` and `999,999` that specifies the number of microseconds. +| `nanosecond` | An integer between `0` and `999,999,999` that specifies the number of nanoseconds. +| `timezone` | An expression that specifies the time zone. |=== -*Considerations:* +*Considerations:* |=== - -| The _week_ component will default to `1` if `week` is omitted. -| The _day of the week_ component will default to `1` if `dayOfWeek` is omitted. -| The _hour_ component will default to `0` if `hour` is omitted. -| The _minute_ component will default to `0` if `minute` is omitted. -| The _second_ component will default to `0` if `second` is omitted. -| Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. -| The _timezone_ component will default to the configured default time zone if `timezone` is omitted. -| If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. -| The least significant components in the set `year`, `week`, `dayOfWeek`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year`, `week` and `dayOfWeek`, but specifying `year`, `week`, `dayOfWeek` and `minute` is not permitted. -| One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. - +|The _week_ component will default to `1` if `week` is omitted. +|The _day of the week_ component will default to `1` if `dayOfWeek` is omitted. +|The _hour_ component will default to `0` if `hour` is omitted. +|The _minute_ component will default to `0` if `minute` is omitted. +|The _second_ component will default to `0` if `second` is omitted. +|Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. +|The _timezone_ component will default to the configured default time zone if `timezone` is omitted. +|If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. +|The least significant components in the set `year`, `week`, `dayOfWeek`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year`, `week` and `dayOfWeek`, but specifying `year`, `week`, `dayOfWeek` and `minute` is not permitted. +|One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. |=== -.+datetime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -datetime({year: 1984, week: 10, dayOfWeek: 3, hour: 12, minute: 31, second: 14, millisecond: 645}), -datetime({year: 1984, week: 10, dayOfWeek: 3, hour: 12, minute: 31, second: 14, microsecond: 645876, timezone: '+01:00'}), -datetime({year: 1984, week: 10, dayOfWeek: 3, hour: 12, minute: 31, second: 14, nanosecond: 645876123, timezone: 'Europe/Stockholm'}), -datetime({year: 1984, week: 10, dayOfWeek: 3, hour: 12, minute: 31, second: 14, timezone: 'Europe/Stockholm'}), -datetime({year: 1984, week: 10, dayOfWeek: 3, hour: 12, minute: 31, second: 14}), -datetime({year: 1984, week: 10, dayOfWeek: 3, hour: 12, timezone: '+01:00'}), -datetime({year: 1984, week: 10, dayOfWeek: 3, timezone: 'Europe/Stockholm'}) -] AS theDate + datetime({year:1984, week:10, dayOfWeek:3, hour:12, minute:31, second:14, millisecond: 645}), + datetime({year:1984, week:10, dayOfWeek:3, hour:12, minute:31, second:14, microsecond: 645876, timezone: '+01:00'}), + datetime({year:1984, week:10, dayOfWeek:3, hour:12, minute:31, second:14, nanosecond: 645876123, timezone: 'Europe/Stockholm'}), + datetime({year:1984, week:10, dayOfWeek:3, hour:12, minute:31, second:14, timezone: 'Europe/Stockholm'}), + datetime({year:1984, week:10, dayOfWeek:3, hour:12, minute:31, second:14}), + datetime({year:1984, week:10, dayOfWeek:3, hour:12, timezone: '+01:00'}), + datetime({year:1984, week:10, dayOfWeek:3, timezone: 'Europe/Stockholm'}) +] as theDate RETURN theDate ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-datetime-quarter]] === Creating a quarter (Year-Quarter-Day) _DateTime_ `datetime()` returns a _DateTime_ value with the specified _year_, _quarter_, _dayOfQuarter_, _hour_, _minute_, _second_, _millisecond_, _microsecond_, _nanosecond_ and _timezone_ component values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -datetime({year [, quarter, dayOfQuarter, hour, minute, second, millisecond, microsecond, nanosecond, timezone]}) ----- +*Syntax:* `datetime({year [, quarter, dayOfQuarter, hour, minute, second, millisecond, microsecond, nanosecond, timezone]})` *Returns:* - |=== - -| A DateTime. - +| +A DateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `year` -| An expression consisting of at xref::syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. - -| `quarter` -| An integer between `1` and `4` that specifies the quarter. - -| `dayOfQuarter` -| An integer between `1` and `92` that specifies the day of the quarter. - -| `hour` -| An integer between `0` and `23` that specifies the hour of the day. - -| `minute` -| An integer between `0` and `59` that specifies the number of minutes. - -| `second` -| An integer between `0` and `59` that specifies the number of seconds. - -| `millisecond` -| An integer between `0` and `999` that specifies the number of milliseconds. - -| `microsecond` -| An integer between `0` and `999,999` that specifies the number of microseconds. - -| `nanosecond` -| An integer between `0` and `999,999,999` that specifies the number of nanoseconds. - -| `timezone` -| An expression that specifies the time zone. - +| `A single map consisting of the following:` | +| `year` | An expression consisting of at xref:syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. +| `quarter` | An integer between `1` and `4` that specifies the quarter. +| `dayOfQuarter` | An integer between `1` and `92` that specifies the day of the quarter. +| `hour` | An integer between `0` and `23` that specifies the hour of the day. +| `minute` | An integer between `0` and `59` that specifies the number of minutes. +| `second` | An integer between `0` and `59` that specifies the number of seconds. +| `millisecond` | An integer between `0` and `999` that specifies the number of milliseconds. +| `microsecond` | An integer between `0` and `999,999` that specifies the number of microseconds. +| `nanosecond` | An integer between `0` and `999,999,999` that specifies the number of nanoseconds. +| `timezone` | An expression that specifies the time zone. |=== -*Considerations:* +*Considerations:* |=== - -| The _quarter_ component will default to `1` if `quarter` is omitted. -| The _day of the quarter_ component will default to `1` if `dayOfQuarter` is omitted. -| The _hour_ component will default to `0` if `hour` is omitted. -| The _minute_ component will default to `0` if `minute` is omitted. -| The _second_ component will default to `0` if `second` is omitted. -| Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. -| The _timezone_ component will default to the configured default time zone if `timezone` is omitted. -| If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. -| The least significant components in the set `year`, `quarter`, `dayOfQuarter`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year`, `quarter` and `dayOfQuarter`, but specifying `year`, `quarter`, `dayOfQuarter` and `minute` is not permitted. -| One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. - +|The _quarter_ component will default to `1` if `quarter` is omitted. +|The _day of the quarter_ component will default to `1` if `dayOfQuarter` is omitted. +|The _hour_ component will default to `0` if `hour` is omitted. +|The _minute_ component will default to `0` if `minute` is omitted. +|The _second_ component will default to `0` if `second` is omitted. +|Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. +|The _timezone_ component will default to the configured default time zone if `timezone` is omitted. +|If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. +|The least significant components in the set `year`, `quarter`, `dayOfQuarter`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year`, `quarter` and `dayOfQuarter`, but specifying `year`, `quarter`, `dayOfQuarter` and `minute` is not permitted. +|One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. |=== -.+datetime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -datetime({year: 1984, quarter: 3, dayOfQuarter: 45, hour: 12, minute: 31, second: 14, microsecond: 645876}), -datetime({year: 1984, quarter: 3, dayOfQuarter: 45, hour: 12, minute: 31, second: 14, timezone: '+01:00'}), -datetime({year: 1984, quarter: 3, dayOfQuarter: 45, hour: 12, timezone: 'Europe/Stockholm'}), -datetime({year: 1984, quarter: 3, dayOfQuarter: 45}) -] AS theDate + datetime({year:1984, quarter:3, dayOfQuarter: 45, hour:12, minute:31, second:14, microsecond: 645876}), + datetime({year:1984, quarter:3, dayOfQuarter: 45, hour:12, minute:31, second:14, timezone: '+01:00'}), + datetime({year:1984, quarter:3, dayOfQuarter: 45, hour:12, timezone: 'Europe/Stockholm'}), + datetime({year:1984, quarter:3, dayOfQuarter: 45}) +] as theDate RETURN theDate ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-datetime-ordinal]] === Creating an ordinal (Year-Day) _DateTime_ `datetime()` returns a _DateTime_ value with the specified _year_, _ordinalDay_, _hour_, _minute_, _second_, _millisecond_, _microsecond_, _nanosecond_ and _timezone_ component values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -datetime({year [, ordinalDay, hour, minute, second, millisecond, microsecond, nanosecond, timezone]}) ----- +*Syntax:* `datetime({year [, ordinalDay, hour, minute, second, millisecond, microsecond, nanosecond, timezone]})` *Returns:* - |=== - -| A DateTime. - +| +A DateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `year` -| An expression consisting of at xref::syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. - -| `ordinalDay` -| An integer between `1` and `366` that specifies the ordinal day of the year. - -| `hour` -| An integer between `0` and `23` that specifies the hour of the day. - -| `minute` -| An integer between `0` and `59` that specifies the number of minutes. - -| `second` -| An integer between `0` and `59` that specifies the number of seconds. - -| `millisecond` -| An integer between `0` and `999` that specifies the number of milliseconds. - -| `microsecond` -| An integer between `0` and `999,999` that specifies the number of microseconds. - -| `nanosecond` -| An integer between `0` and `999,999,999` that specifies the number of nanoseconds. - -| `timezone` -| An expression that specifies the time zone. - +| `A single map consisting of the following:` | +| `year` | An expression consisting of at xref:syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. +| `ordinalDay` | An integer between `1` and `366` that specifies the ordinal day of the year. +| `hour` | An integer between `0` and `23` that specifies the hour of the day. +| `minute` | An integer between `0` and `59` that specifies the number of minutes. +| `second` | An integer between `0` and `59` that specifies the number of seconds. +| `millisecond` | An integer between `0` and `999` that specifies the number of milliseconds. +| `microsecond` | An integer between `0` and `999,999` that specifies the number of microseconds. +| `nanosecond` | An integer between `0` and `999,999,999` that specifies the number of nanoseconds. +| `timezone` | An expression that specifies the time zone. |=== -*Considerations:* +*Considerations:* |=== - -| The _ordinal day of the year_ component will default to `1` if `ordinalDay` is omitted. -| The _hour_ component will default to `0` if `hour` is omitted. -| The _minute_ component will default to `0` if `minute` is omitted. -| The _second_ component will default to `0` if `second` is omitted. -| Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. -| The _timezone_ component will default to the configured default time zone if `timezone` is omitted. -| If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. -| The least significant components in the set `year`, `ordinalDay`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year` and `ordinalDay`, but specifying `year`, `ordinalDay` and `minute` is not permitted. -| One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. - +|The _ordinal day of the year_ component will default to `1` if `ordinalDay` is omitted. +|The _hour_ component will default to `0` if `hour` is omitted. +|The _minute_ component will default to `0` if `minute` is omitted. +|The _second_ component will default to `0` if `second` is omitted. +|Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. +|The _timezone_ component will default to the configured default time zone if `timezone` is omitted. +|If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. +|The least significant components in the set `year`, `ordinalDay`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year` and `ordinalDay`, but specifying `year`, `ordinalDay` and `minute` is not permitted. +|One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. |=== -.+datetime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -datetime({year: 1984, ordinalDay: 202, hour: 12, minute: 31, second: 14, millisecond: 645}), -datetime({year: 1984, ordinalDay: 202, hour: 12, minute: 31, second: 14, timezone: '+01:00'}), -datetime({year: 1984, ordinalDay: 202, timezone: 'Europe/Stockholm'}), -datetime({year: 1984, ordinalDay: 202}) -] AS theDate + datetime({year:1984, ordinalDay:202, hour:12, minute:31, second:14, millisecond: 645}), + datetime({year:1984, ordinalDay:202, hour:12, minute:31, second:14, timezone: '+01:00'}), + datetime({year:1984, ordinalDay:202, timezone: 'Europe/Stockholm'}), + datetime({year:1984, ordinalDay:202}) +] as theDate RETURN theDate ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-datetime-create-string]] === Creating a _DateTime_ from a string `datetime()` returns the _DateTime_ value obtained by parsing a string representation of a temporal value. -*Syntax:* - -[source, syntax, role="noheader"] ----- -datetime(temporalValue) ----- +*Syntax:* `datetime(temporalValue)` *Returns:* - |=== - -| A DateTime. - +| +A DateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `temporalValue` -| A string representing a temporal value. - +| `temporalValue` | A string representing a temporal value. |=== -*Considerations:* +*Considerations:* |=== - -| `temporalValue` must comply with the format defined for xref::syntax/temporal.adoc#cypher-temporal-specify-date[dates], xref::syntax/temporal.adoc#cypher-temporal-specify-time[times] and xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zones]. -| The _timezone_ component will default to the configured default time zone if it is omitted. -| `temporalValue` must denote a valid date and time; i.e. a `temporalValue` denoting `30 February 2001` is invalid. -| `datetime(null)` returns null. - +|`temporalValue` must comply with the format defined for xref:syntax/temporal.adoc#cypher-temporal-specify-date[dates], xref:syntax/temporal.adoc#cypher-temporal-specify-time[times] and xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zones]. +|The _timezone_ component will default to the configured default time zone if it is omitted. +|`temporalValue` must denote a valid date and time; i.e. a `temporalValue` denoting `30 February 2001` is invalid. +|`datetime(null)` returns null. |=== -.+datetime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -datetime('2015-07-21T21:40:32.142+0100'), -datetime('2015-W30-2T214032.142Z'), -datetime('2015T214032-0100'), -datetime('20150721T21:40-01:30'), -datetime('2015-W30T2140-02'), -datetime('2015202T21+18:00'), -datetime('2015-07-21T21:40:32.142[Europe/London]'), -datetime('2015-07-21T21:40:32.142-04[America/New_York]') + datetime('2015-07-21T21:40:32.142+0100'), + datetime('2015-W30-2T214032.142Z'), + datetime('2015T214032-0100'), + datetime('20150721T21:40-01:30'), + datetime('2015-W30T2140-02'), + datetime('2015202T21+18:00'), + datetime('2015-07-21T21:40:32.142[Europe/London]'), + datetime('2015-07-21T21:40:32.142-04[America/New_York]') ] AS theDate RETURN theDate ---- @@ -2014,7 +1654,6 @@ RETURN theDate .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-datetime-temporal]] === Creating a _DateTime_ using other temporal values as components `datetime()` returns the _DateTime_ value obtained by selecting and composing components from another temporal value. In essence, this allows a _Date_, _LocalDateTime_, _Time_ or _LocalTime_ value to be converted to a _DateTime_, and for "missing" components to be provided. + -*Syntax:* - -[source, syntax, role="noheader"] ----- -datetime({datetime [, year, ..., timezone]}) | datetime({date [, year, ..., timezone]}) | datetime({time [, year, ..., timezone]}) | datetime({date, time [, year, ..., timezone]}) ----- +*Syntax:* `datetime({datetime [, year, ..., timezone]}) | datetime({date [, year, ..., timezone]}) | datetime({time [, year, ..., timezone]}) | datetime({date, time [, year, ..., timezone]})` *Returns:* - |=== - -|A DateTime. - +| +A DateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `datetime` -| A _DateTime_ value. - -| `date` -| A _Date_ value. - -| `time` -| A _Time_ value. - -| `year` -| An expression consisting of at xref::syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. - -| `month` -| An integer between `1` and `12` that specifies the month. - -| `day` -| An integer between `1` and `31` that specifies the day of the month. - -| `week` -| An integer between `1` and `53` that specifies the week. - -| `dayOfWeek` -| An integer between `1` and `7` that specifies the day of the week. - -| `quarter` -| An integer between `1` and `4` that specifies the quarter. - -| `dayOfQuarter` -| An integer between `1` and `92` that specifies the day of the quarter. - -| `ordinalDay` -| An integer between `1` and `366` that specifies the ordinal day of the year. - -| `hour` -| An integer between `0` and `23` that specifies the hour of the day. - -| `minute` -| An integer between `0` and `59` that specifies the number of minutes. - -| `second` -| An integer between `0` and `59` that specifies the number of seconds. - -| `millisecond` -| An integer between `0` and `999` that specifies the number of milliseconds. - -| `microsecond` -| An integer between `0` and `999,999` that specifies the number of microseconds. - -| `nanosecond` -| An integer between `0` and `999,999,999` that specifies the number of nanoseconds. - -| `timezone` -| An expression that specifies the time zone. - +| `A single map consisting of the following:` | +| `datetime` | A _DateTime_ value. +| `date` | A _Date_ value. +| `time` | A _Time_ value. +| `year` | An expression consisting of at xref:syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. +| `month` | An integer between `1` and `12` that specifies the month. +| `day` | An integer between `1` and `31` that specifies the day of the month. +| `week` | An integer between `1` and `53` that specifies the week. +| `dayOfWeek` | An integer between `1` and `7` that specifies the day of the week. +| `quarter` | An integer between `1` and `4` that specifies the quarter. +| `dayOfQuarter` | An integer between `1` and `92` that specifies the day of the quarter. +| `ordinalDay` | An integer between `1` and `366` that specifies the ordinal day of the year. +| `hour` | An integer between `0` and `23` that specifies the hour of the day. +| `minute` | An integer between `0` and `59` that specifies the number of minutes. +| `second` | An integer between `0` and `59` that specifies the number of seconds. +| `millisecond` | An integer between `0` and `999` that specifies the number of milliseconds. +| `microsecond` | An integer between `0` and `999,999` that specifies the number of microseconds. +| `nanosecond` | An integer between `0` and `999,999,999` that specifies the number of nanoseconds. +| `timezone` | An expression that specifies the time zone. |=== -*Considerations:* +*Considerations:* |=== - -| If any of the optional parameters are provided, these will override the corresponding components of `datetime`, `date` and/or `time`. -| `datetime(dd)` may be written instead of `+datetime({datetime: dd})+`. -| Selecting a _Time_ or _DateTime_ value as the `time` component also selects its time zone. If a _LocalTime_ or _LocalDateTime_ is selected instead, the default time zone is used. In any case, the time zone can be overridden explicitly. -| Selecting a _DateTime_ as the `datetime` component and overwriting the time zone will adjust the local time to keep the same point in time. -| Selecting a _DateTime_ or _Time_ as the `time` component and overwriting the time zone will adjust the local time to keep the same point in time. - +|If any of the optional parameters are provided, these will override the corresponding components of `datetime`, `date` and/or `time`. +|`datetime(dd)` may be written instead of `datetime({datetime: dd})`. +|Selecting a _Time_ or _DateTime_ value as the `time` component also selects its time zone. If a _LocalTime_ or _LocalDateTime_ is selected instead, the default time zone is used. In any case, the time zone can be overridden explicitly. +|Selecting a _DateTime_ as the `datetime` component and overwriting the time zone will adjust the local time to keep the same point in time. +|Selecting a _DateTime_ or _Time_ as the `time` component and overwriting the time zone will adjust the local time to keep the same point in time. |=== +The following query shows the various usages of `datetime({date [, year, ..., timezone]})` -.+datetime()+ -====== - -The following query shows the various usages of `+datetime({date [, year, ..., timezone]})+`. .Query -[source, cypher, indent=0] +[source, cypher] ---- -WITH date({year: 1984, month: 10, day: 11}) AS dd -RETURN - datetime({date: dd, hour: 10, minute: 10, second: 10}) AS dateHHMMSS, - datetime({date: dd, hour: 10, minute: 10, second: 10, timezone:'+05:00'}) AS dateHHMMSSTimezone, - datetime({date: dd, day: 28, hour: 10, minute: 10, second: 10}) AS dateDDHHMMSS, - datetime({date: dd, day: 28, hour: 10, minute: 10, second: 10, timezone:'Pacific/Honolulu'}) AS dateDDHHMMSSTimezone +WITH date({year:1984, month:10, day:11}) AS dd +RETURN datetime({date:dd, hour: 10, minute: 10, second: 10}) AS dateHHMMSS, + datetime({date:dd, hour: 10, minute: 10, second: 10, timezone:'+05:00'}) AS dateHHMMSSTimezone, + datetime({date:dd, day: 28, hour: 10, minute: 10, second: 10}) AS dateDDHHMMSS, + datetime({date:dd, day: 28, hour: 10, minute: 10, second: 10, timezone:'Pacific/Honolulu'}) AS dateDDHHMMSSTimezone ---- .Result [role="queryresult",options="header,footer",cols="4* +Try this query live + +++++ +endif::nonhtmloutput[] -.+datetime()+ -====== +The following query shows the various usages of `datetime({time [, year, ..., timezone]})` -The following query shows the various usages of `datetime({time [, year, ..., timezone]})`. .Query -[source, cypher, indent=0] +[source, cypher] ---- -WITH time({hour: 12, minute: 31, second: 14, microsecond: 645876, timezone: '+01:00'}) AS tt -RETURN - datetime({year: 1984, month: 10, day: 11, time: tt}) AS YYYYMMDDTime, - datetime({year: 1984, month: 10, day: 11, time: tt, timezone:'+05:00'}) AS YYYYMMDDTimeTimezone, - datetime({year: 1984, month: 10, day: 11, time: tt, second: 42}) AS YYYYMMDDTimeSS, - datetime({year: 1984, month: 10, day: 11, time: tt, second: 42, timezone: 'Pacific/Honolulu'}) AS YYYYMMDDTimeSSTimezone +WITH time({hour:12, minute:31, second:14, microsecond: 645876, timezone: '+01:00'}) AS tt +RETURN datetime({year:1984, month:10, day:11, time:tt}) AS YYYYMMDDTime, + datetime({year:1984, month:10, day:11, time:tt, timezone:'+05:00'}) AS YYYYMMDDTimeTimezone, + datetime({year:1984, month:10, day:11, time:tt, second: 42}) AS YYYYMMDDTimeSS, + datetime({year:1984, month:10, day:11, time:tt, second: 42, timezone:'Pacific/Honolulu'}) AS YYYYMMDDTimeSSTimezone ---- .Result [role="queryresult",options="header,footer",cols="4* +Try this query live + +++++ +endif::nonhtmloutput[] +The following query shows the various usages of `datetime({date, time [, year, ..., timezone]})`; i.e. combining a _Date_ and a _Time_ value to create a single _DateTime_ value: -.+datetime()+ -====== - -The following query shows the various usages of `+datetime({date, time [, year, ..., timezone]})+`; i.e. combining a _Date_ and a _Time_ value to create a single _DateTime_ value. .Query -[source, cypher, indent=0] +[source, cypher] ---- -WITH - date({year: 1984, month: 10, day: 11}) AS dd, - localtime({hour: 12, minute: 31, second: 14, millisecond: 645}) AS tt -RETURN - datetime({date: dd, time: tt}) AS dateTime, - datetime({date: dd, time: tt, timezone: '+05:00'}) AS dateTimeTimezone, - datetime({date: dd, time: tt, day: 28, second: 42}) AS dateTimeDDSS, - datetime({date: dd, time: tt, day: 28, second: 42, timezone: 'Pacific/Honolulu'}) AS dateTimeDDSSTimezone +WITH date({year:1984, month:10, day:11}) AS dd, + localtime({hour:12, minute:31, second:14, millisecond: 645}) AS tt +RETURN datetime({date:dd, time:tt}) as dateTime, + datetime({date:dd, time:tt, timezone:'+05:00'}) AS dateTimeTimezone, + datetime({date:dd, time:tt, day: 28, second: 42}) AS dateTimeDDSS, + datetime({date:dd, time:tt, day: 28, second: 42, timezone:'Pacific/Honolulu'}) AS dateTimeDDSSTimezone ---- .Result [role="queryresult",options="header,footer",cols="4* +Try this query live + +++++ +endif::nonhtmloutput[] -.+datetime()+ -====== +The following query shows the various usages of `datetime({datetime [, year, ..., timezone]})` -The following query shows the various usages of `+datetime({datetime [, year, ..., timezone]})+`. .Query -[source, cypher, indent=0] +[source, cypher] ---- -WITH - datetime({ - year: 1984, month: 10, day: 11, - hour: 12, - timezone: 'Europe/Stockholm' - }) AS dd -RETURN - datetime({datetime: dd}) AS dateTime, - datetime({datetime: dd, timezone: '+05:00'}) AS dateTimeTimezone, - datetime({datetime: dd, day: 28, second: 42}) AS dateTimeDDSS, - datetime({datetime: dd, day: 28, second: 42, timezone: 'Pacific/Honolulu'}) AS dateTimeDDSSTimezone +WITH datetime({year:1984, month:10, day:11, hour:12, timezone: 'Europe/Stockholm'}) AS dd +RETURN datetime({datetime:dd}) AS dateTime, + datetime({datetime:dd, timezone:'+05:00'}) AS dateTimeTimezone, + datetime({datetime:dd, day: 28, second: 42}) AS dateTimeDDSS, + datetime({datetime:dd, day: 28, second: 42, timezone:'Pacific/Honolulu'}) AS dateTimeDDSSTimezone ---- .Result [role="queryresult",options="header,footer",cols="4* +Try this query live + +++++ +endif::nonhtmloutput[] - -[discrete] [[functions-datetime-timestamp]] === Creating a _DateTime_ from a timestamp @@ -2262,74 +1901,61 @@ RETURN Conversions to other temporal instant types from UNIX epoch representations can be achieved by transforming a _DateTime_ value to one of these types. -*Syntax:* - -[source, syntax, role="noheader"] ----- -datetime({ epochSeconds | epochMillis }) ----- +*Syntax:* `datetime({ epochSeconds | epochMillis })` *Returns:* - |=== - -| A DateTime. - +| +A DateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `epochSeconds` -| A numeric value representing the number of seconds from the UNIX epoch in the UTC time zone. - -| `epochMillis` -| A numeric value representing the number of milliseconds from the UNIX epoch in the UTC time zone. - +| `A single map consisting of the following:` | +| `epochSeconds` | A numeric value representing the number of seconds from the UNIX epoch in the UTC time zone. +| `epochMillis` | A numeric value representing the number of milliseconds from the UNIX epoch in the UTC time zone. |=== -*Considerations:* +*Considerations:* |=== - -| `epochSeconds`/`epochMillis` may be used in conjunction with `nanosecond`. - +|`epochSeconds`/`epochMillis` may be used in conjunction with `nanosecond` |=== -.+datetime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- -RETURN datetime({epochSeconds: timestamp() / 1000, nanosecond: 23}) AS theDate +RETURN datetime({epochSeconds:timestamp() / 1000, nanosecond: 23}) AS theDate ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -.+datetime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN datetime({epochMillis: 424797300000}) AS theDate ---- @@ -2337,17 +1963,24 @@ RETURN datetime({epochMillis: 424797300000}) AS theDate .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-datetime-truncate]] === Truncating a _DateTime_ @@ -2356,161 +1989,126 @@ In other words, the _DateTime_ returned will have all components that are less s It is possible to supplement the truncated value by providing a map containing components which are less significant than the truncation unit. This will have the effect of _overriding_ the default values which would otherwise have been set for these less significant components. -For example, `day` -- with some value `x` -- may be provided when the truncation unit string is `'year'` in order to ensure the returned value has the _day_ set to `x` instead of the default _day_ (which is `1`). - -*Syntax:* +For example, `day` -- with some value `x` -- may be provided when the truncation unit is `year` in order to ensure the returned value has the _day_ set to `x` instead of the default _day_ (which is `1`). + -[source, syntax, role="noheader"] ----- -datetime.truncate(unit [, temporalInstantValue [, mapOfComponents ] ]) ----- +*Syntax:* `datetime.truncate(unit [, temporalInstantValue [, mapOfComponents ] ])` *Returns:* - |=== - -| A DateTime. - +| +A DateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `unit` -| A string expression evaluating to one of the following strings: `'millennium'`, `'century'`, `'decade'`, `'year'`, `'weekYear'`, `'quarter'`, `'month'`, `'week'`, `'day'`, `'hour'`, `'minute'`, `'second'`, `'millisecond'`, `'microsecond'`. - -| `temporalInstantValue` -| An expression of one of the following types: _DateTime_, _LocalDateTime_, _Date_. - -| `mapOfComponents` -a| -An expression evaluating to a map containing components less significant than `unit`. -During truncation, a time zone can be attached or overridden using the key `timezone`. - +| `unit` | A string expression evaluating to one of the following: {`millennium`, `century`, `decade`, `year`, `weekYear`, `quarter`, `month`, `week`, `day`, `hour`, `minute`, `second`, `millisecond`, `microsecond`}. +| `temporalInstantValue` | An expression of one of the following types: {_DateTime_, _LocalDateTime_, _Date_}. +| `mapOfComponents` | An expression evaluating to a map containing components less significant than `unit`. During truncation, a time zone can be attached or overridden using the key `timezone`. |=== -*Considerations:* +*Considerations:* |=== - -| `temporalInstantValue` cannot be a _Date_ value if `unit` is one of: `'hour'`, `'minute'`, `'second'`, `'millisecond'`, `'microsecond'`. -| The time zone of `temporalInstantValue` may be overridden; for example, `+datetime.truncate('minute', input, {timezone: '+0200'})+`. -| If `temporalInstantValue` is one of _Time_, _DateTime_ -- a value with a time zone -- and the time zone is overridden, no time conversion occurs. -| If `temporalInstantValue` is one of _LocalDateTime_, _Date_ -- a value without a time zone -- and the time zone is not overridden, the configured default time zone will be used. -| Any component that is provided in `mapOfComponents` must be less significant than `unit`; i.e. if `unit` is `'day'`, `mapOfComponents` cannot contain information pertaining to a _month_. -| Any component that is not contained in `mapOfComponents` and which is less significant than `unit` will be set to its xref::syntax/temporal.adoc#cypher-temporal-accessing-components-temporal-instants[minimal value]. -| If `mapOfComponents` is not provided, all components of the returned value which are less significant than `unit` will be set to their default values. -| If `temporalInstantValue` is not provided, it will be set to the current date, time and timezone, i.e. `datetime.truncate(unit)` is equivalent of `datetime.truncate(unit, datetime())`. - +|`temporalInstantValue` cannot be a _Date_ value if `unit` is one of {`hour`, `minute`, `second`, `millisecond`, `microsecond`}. +|The time zone of `temporalInstantValue` may be overridden; for example, `datetime.truncate('minute', input, {timezone:'+0200'})`. +|If `temporalInstantValue` is one of {_Time_, _DateTime_} -- a value with a time zone -- and the time zone is overridden, no time conversion occurs. +|If `temporalInstantValue` is one of {_LocalDateTime_, _Date_} -- a value without a time zone -- and the time zone is not overridden, the configured default time zone will be used. +|Any component that is provided in `mapOfComponents` must be less significant than `unit`; i.e. if `unit` is 'day', `mapOfComponents` cannot contain information pertaining to a _month_. +|Any component that is not contained in `mapOfComponents` and which is less significant than `unit` will be set to its xref:syntax/temporal.adoc#cypher-temporal-accessing-components-temporal-instants[minimal value]. +|If `mapOfComponents` is not provided, all components of the returned value which are less significant than `unit` will be set to their default values. +|If `temporalInstantValue` is not provided, it will be set to the current date, time and timezone, i.e. `datetime.truncate(unit)` is equivalent of `datetime.truncate(unit, datetime())`. |=== -.+datetime()+ -====== - .Query -[source, cypher, indent=0] ----- -WITH - datetime({ - year:2017, month:11, day:11, - hour:12, minute:31, second:14, nanosecond: 645876123, - timezone: '+03:00' - }) AS d -RETURN - datetime.truncate('millennium', d, {timezone: 'Europe/Stockholm'}) AS truncMillenium, - datetime.truncate('year', d, {day: 5}) AS truncYear, - datetime.truncate('month', d) AS truncMonth, - datetime.truncate('day', d, {millisecond: 2}) AS truncDay, - datetime.truncate('hour', d) AS truncHour, - datetime.truncate('second', d) AS truncSecond +[source, cypher] +---- +WITH datetime({year:2017, month:11, day:11, hour:12, minute:31, second:14, nanosecond: 645876123, timezone: '+03:00'}) AS d +RETURN datetime.truncate('millennium', d, {timezone:'Europe/Stockholm'}) AS truncMillenium, + datetime.truncate('year', d, {day:5}) AS truncYear, + datetime.truncate('month', d) AS truncMonth, + datetime.truncate('day', d, {millisecond:2}) AS truncDay, + datetime.truncate('hour', d) AS truncHour, + datetime.truncate('second', d) AS truncSecond ---- .Result [role="queryresult",options="header,footer",cols="6* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-localdatetime]] -== +localdatetime()+ - -Details for using the `localdatetime()` function. +== LocalDateTime: `localdatetime()` -* xref::functions/temporal/index.adoc#functions-localdatetime-current[Getting the current _LocalDateTime_] -** xref::functions/temporal/index.adoc#functions-localdatetime-transaction[+localdatetime.transaction()+] -** xref::functions/temporal/index.adoc#functions-localdatetime-statement[+localdatetime.statement()+] -** xref::functions/temporal/index.adoc#functions-localdatetime-realtime[+localdatetime.realtime()+] -* xref::functions/temporal/index.adoc#functions-localdatetime-calendar[Creating a calendar (Year-Month-Day) _LocalDateTime_] -* xref::functions/temporal/index.adoc#functions-localdatetime-week[Creating a week (Year-Week-Day) _LocalDateTime_] -* xref::functions/temporal/index.adoc#functions-localdatetime-quarter[Creating a quarter (Year-Quarter-Day) _LocalDateTime_] -* xref::functions/temporal/index.adoc#functions-localdatetime-ordinal[Creating an ordinal (Year-Day) _LocalDateTime_] -* xref::functions/temporal/index.adoc#functions-localdatetime-create-string[Creating a _LocalDateTime_ from a string] -* xref::functions/temporal/index.adoc#functions-localdatetime-temporal[Creating a _LocalDateTime_ using other temporal values as components] -* xref::functions/temporal/index.adoc#functions-localdatetime-truncate[Truncating a _LocalDateTime_] +* xref:functions/temporal/index.adoc#functions-localdatetime-current[Getting the current _LocalDateTime_] +* xref:functions/temporal/index.adoc#functions-localdatetime-calendar[Creating a calendar (Year-Month-Day) _LocalDateTime_] +* xref:functions/temporal/index.adoc#functions-localdatetime-week[Creating a week (Year-Week-Day) _LocalDateTime_] +* xref:functions/temporal/index.adoc#functions-localdatetime-quarter[Creating a quarter (Year-Quarter-Day) _LocalDateTime_] +* xref:functions/temporal/index.adoc#functions-localdatetime-ordinal[Creating an ordinal (Year-Day) _LocalDateTime_] +* xref:functions/temporal/index.adoc#functions-localdatetime-create-string[Creating a _LocalDateTime_ from a string] +* xref:functions/temporal/index.adoc#functions-localdatetime-temporal[Creating a _LocalDateTime_ using other temporal values as components] +* xref:functions/temporal/index.adoc#functions-localdatetime-truncate[Truncating a _LocalDateTime_] + - -[discrete] [[functions-localdatetime-current]] === Getting the current _LocalDateTime_ `localdatetime()` returns the current _LocalDateTime_ value. If no time zone parameter is specified, the local time zone will be used. -*Syntax:* - -[source, syntax, role="noheader"] ----- -localdatetime([{timezone}]) ----- +*Syntax:* `+localdatetime([{timezone}])+` *Returns:* - |=== - -| A LocalDateTime. - +| +A LocalDateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `A single map consisting of the following:` | +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -*Considerations:* +*Considerations:* |=== - -| If no parameters are provided, `localdatetime()` must be invoked (+localdatetime({})+ is invalid). - +|If no parameters are provided, `localdatetime()` must be invoked (`localdatetime({})` is invalid). |=== -.+localdatetime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN localdatetime() AS now ---- @@ -2520,21 +2118,27 @@ The current local date and time (i.e. in the local time zone) is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -.+localdatetime()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN localdatetime({timezone: 'America/Los Angeles'}) AS now ---- @@ -2544,17 +2148,24 @@ The current local date and time in California is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-localdatetime-transaction]] ==== localdatetime.transaction() @@ -2562,38 +2173,25 @@ The current local date and time in California is returned. This value will be the same for each invocation within the same transaction. However, a different value may be produced for different transactions. -*Syntax:* - -[source, syntax, role="noheader"] ----- -localdatetime.transaction([{timezone}]) ----- +*Syntax:* `+localdatetime.transaction([{timezone}])+` *Returns:* - |=== - -| A LocalDateTime. - +| +A LocalDateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -.+localdatetime.transaction()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN localdatetime.transaction() AS now ---- @@ -2601,17 +2199,24 @@ RETURN localdatetime.transaction() AS now .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-localdatetime-statement]] ==== localdatetime.statement() @@ -2619,38 +2224,25 @@ RETURN localdatetime.transaction() AS now This value will be the same for each invocation within the same statement. However, a different value may be produced for different statements within the same transaction. -*Syntax:* - -[source, syntax, role="noheader"] ----- -localdatetime.statement([{timezone}]) ----- +*Syntax:* `+localdatetime.statement([{timezone}])+` *Returns:* - |=== - -| A LocalDateTime. - +| +A LocalDateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -.+localdatetime.statement()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN localdatetime.statement() AS now ---- @@ -2658,55 +2250,49 @@ RETURN localdatetime.statement() AS now .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-localdatetime-realtime]] ==== localdatetime.realtime() `localdatetime.realtime()` returns the current _LocalDateTime_ value using the `realtime` clock. This value will be the live clock of the system. -*Syntax:* - -[source, syntax, role="noheader"] ----- -localdatetime.realtime([{timezone}]) ----- +*Syntax:* `+localdatetime.realtime([{timezone}])+` *Returns:* - |=== - -| A LocalDateTime. - +| +A LocalDateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -.+localdatetime.realtime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN localdatetime.realtime() AS now ---- @@ -2714,21 +2300,27 @@ RETURN localdatetime.realtime() AS now .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -.+localdatetime.realtime()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN localdatetime.realtime('America/Los Angeles') AS nowInLA ---- @@ -2736,474 +2328,348 @@ RETURN localdatetime.realtime('America/Los Angeles') AS nowInLA .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-localdatetime-calendar]] === Creating a calendar (Year-Month-Day) _LocalDateTime_ `localdatetime()` returns a _LocalDateTime_ value with the specified _year_, _month_, _day_, _hour_, _minute_, _second_, _millisecond_, _microsecond_ and _nanosecond_ component values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -localdatetime({year [, month, day, hour, minute, second, millisecond, microsecond, nanosecond]}) ----- +*Syntax:* `localdatetime({year [, month, day, hour, minute, second, millisecond, microsecond, nanosecond]})` *Returns:* - |=== - -| A LocalDateTime. - +| +A LocalDateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `year` -| An expression consisting of at xref::syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. - -| `month` -| An integer between `1` and `12` that specifies the month. - -| `day` -| An integer between `1` and `31` that specifies the day of the month. - -| `hour` -| An integer between `0` and `23` that specifies the hour of the day. - -| `minute` -| An integer between `0` and `59` that specifies the number of minutes. - -| `second` -| An integer between `0` and `59` that specifies the number of seconds. - -| `millisecond` -| An integer between `0` and `999` that specifies the number of milliseconds. - -| `microsecond` -| An integer between `0` and `999,999` that specifies the number of microseconds. - -| `nanosecond` -| An integer between `0` and `999,999,999` that specifies the number of nanoseconds. - +| `A single map consisting of the following:` | +| `year` | An expression consisting of at xref:syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. +| `month` | An integer between `1` and `12` that specifies the month. +| `day` | An integer between `1` and `31` that specifies the day of the month. +| `hour` | An integer between `0` and `23` that specifies the hour of the day. +| `minute` | An integer between `0` and `59` that specifies the number of minutes. +| `second` | An integer between `0` and `59` that specifies the number of seconds. +| `millisecond` | An integer between `0` and `999` that specifies the number of milliseconds. +| `microsecond` | An integer between `0` and `999,999` that specifies the number of microseconds. +| `nanosecond` | An integer between `0` and `999,999,999` that specifies the number of nanoseconds. |=== -*Considerations:* +*Considerations:* |=== - -| The _month_ component will default to `1` if `month` is omitted. -| The _day of the month_ component will default to `1` if `day` is omitted. -| The _hour_ component will default to `0` if `hour` is omitted. -| The _minute_ component will default to `0` if `minute` is omitted. -| The _second_ component will default to `0` if `second` is omitted. -| Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. -| If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. -| The least significant components in the set `year`, `month`, `day`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year`, `month` and `day`, but specifying `year`, `month`, `day` and `minute` is not permitted. -| One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. - +|The _month_ component will default to `1` if `month` is omitted. +|The _day of the month_ component will default to `1` if `day` is omitted. +|The _hour_ component will default to `0` if `hour` is omitted. +|The _minute_ component will default to `0` if `minute` is omitted. +|The _second_ component will default to `0` if `second` is omitted. +|Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. +|If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. +|The least significant components in the set `year`, `month`, `day`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year`, `month` and `day`, but specifying `year`, `month`, `day` and `minute` is not permitted. +|One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. |=== -.+localdatetime.realtime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- -RETURN - localdatetime({ - year: 1984, month: 10, day: 11, - hour: 12, minute: 31, second: 14, millisecond: 123, microsecond: 456, nanosecond: 789 - }) AS theDate +RETURN localdatetime({year:1984, month:10, day:11, hour:12, minute:31, second:14, millisecond: 123, microsecond: 456, nanosecond: 789}) AS theDate ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-localdatetime-week]] === Creating a week (Year-Week-Day) _LocalDateTime_ `localdatetime()` returns a _LocalDateTime_ value with the specified _year_, _week_, _dayOfWeek_, _hour_, _minute_, _second_, _millisecond_, _microsecond_ and _nanosecond_ component values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -localdatetime({year [, week, dayOfWeek, hour, minute, second, millisecond, microsecond, nanosecond]}) ----- +*Syntax:* `localdatetime({year [, week, dayOfWeek, hour, minute, second, millisecond, microsecond, nanosecond]})` *Returns:* - |=== - -| A LocalDateTime. - +| +A LocalDateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== -| Name | Description - -| `A single map consisting of the following:` -| - -| `year` -| An expression consisting of at xref::syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. - -| `week` -| An integer between `1` and `53` that specifies the week. - -| `dayOfWeek` -| An integer between `1` and `7` that specifies the day of the week. - -| `hour` -| An integer between `0` and `23` that specifies the hour of the day. - -| `minute` -| An integer between `0` and `59` that specifies the number of minutes. - -| `second` -| An integer between `0` and `59` that specifies the number of seconds. - -| `millisecond` -| An integer between `0` and `999` that specifies the number of milliseconds. - -| `microsecond` -| An integer between `0` and `999,999` that specifies the number of microseconds. - -| `nanosecond` -| An integer between `0` and `999,999,999` that specifies the number of nanoseconds. - +| Name | Description +| `A single map consisting of the following:` | +| `year` | An expression consisting of at xref:syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. +| `week` | An integer between `1` and `53` that specifies the week. +| `dayOfWeek` | An integer between `1` and `7` that specifies the day of the week. +| `hour` | An integer between `0` and `23` that specifies the hour of the day. +| `minute` | An integer between `0` and `59` that specifies the number of minutes. +| `second` | An integer between `0` and `59` that specifies the number of seconds. +| `millisecond` | An integer between `0` and `999` that specifies the number of milliseconds. +| `microsecond` | An integer between `0` and `999,999` that specifies the number of microseconds. +| `nanosecond` | An integer between `0` and `999,999,999` that specifies the number of nanoseconds. |=== -*Considerations:* +*Considerations:* |=== - -| The _week_ component will default to `1` if `week` is omitted. -| The _day of the week_ component will default to `1` if `dayOfWeek` is omitted. -| The _hour_ component will default to `0` if `hour` is omitted. -| The _minute_ component will default to `0` if `minute` is omitted. -| The _second_ component will default to `0` if `second` is omitted. -| Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. -| If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. -| The least significant components in the set `year`, `week`, `dayOfWeek`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year`, `week` and `dayOfWeek`, but specifying `year`, `week`, `dayOfWeek` and `minute` is not permitted. -| One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. - +|The _week_ component will default to `1` if `week` is omitted. +|The _day of the week_ component will default to `1` if `dayOfWeek` is omitted. +|The _hour_ component will default to `0` if `hour` is omitted. +|The _minute_ component will default to `0` if `minute` is omitted. +|The _second_ component will default to `0` if `second` is omitted. +|Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. +|If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. +|The least significant components in the set `year`, `week`, `dayOfWeek`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year`, `week` and `dayOfWeek`, but specifying `year`, `week`, `dayOfWeek` and `minute` is not permitted. +|One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. |=== -.+localdatetime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- -RETURN - localdatetime({ - year: 1984, week: 10, dayOfWeek: 3, - hour: 12, minute: 31, second: 14, millisecond: 645 - }) AS theDate +RETURN localdatetime({year:1984, week:10, dayOfWeek:3, hour:12, minute:31, second:14, millisecond: 645}) AS theDate ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] - -[discrete] [[functions-localdatetime-quarter]] === Creating a quarter (Year-Quarter-Day) _DateTime_ `localdatetime()` returns a _LocalDateTime_ value with the specified _year_, _quarter_, _dayOfQuarter_, _hour_, _minute_, _second_, _millisecond_, _microsecond_ and _nanosecond_ component values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -localdatetime({year [, quarter, dayOfQuarter, hour, minute, second, millisecond, microsecond, nanosecond]}) ----- +*Syntax:* `localdatetime({year [, quarter, dayOfQuarter, hour, minute, second, millisecond, microsecond, nanosecond]})` *Returns:* - |=== - -| A LocalDateTime. - +| +A LocalDateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `year` -| An expression consisting of at xref::syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. - -| `quarter` -| An integer between `1` and `4` that specifies the quarter. - -| `dayOfQuarter` -| An integer between `1` and `92` that specifies the day of the quarter. - -| `hour` -| An integer between `0` and `23` that specifies the hour of the day. - -| `minute` -| An integer between `0` and `59` that specifies the number of minutes. - -| `second` -| An integer between `0` and `59` that specifies the number of seconds. - -| `millisecond` -| An integer between `0` and `999` that specifies the number of milliseconds. - -| `microsecond` -| An integer between `0` and `999,999` that specifies the number of microseconds. - -| `nanosecond` -| An integer between `0` and `999,999,999` that specifies the number of nanoseconds. - +| `A single map consisting of the following:` | +| `year` | An expression consisting of at xref:syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. +| `quarter` | An integer between `1` and `4` that specifies the quarter. +| `dayOfQuarter` | An integer between `1` and `92` that specifies the day of the quarter. +| `hour` | An integer between `0` and `23` that specifies the hour of the day. +| `minute` | An integer between `0` and `59` that specifies the number of minutes. +| `second` | An integer between `0` and `59` that specifies the number of seconds. +| `millisecond` | An integer between `0` and `999` that specifies the number of milliseconds. +| `microsecond` | An integer between `0` and `999,999` that specifies the number of microseconds. +| `nanosecond` | An integer between `0` and `999,999,999` that specifies the number of nanoseconds. |=== -*Considerations:* +*Considerations:* |=== - -| The _quarter_ component will default to `1` if `quarter` is omitted. -| The _day of the quarter_ component will default to `1` if `dayOfQuarter` is omitted. -| The _hour_ component will default to `0` if `hour` is omitted. -| The _minute_ component will default to `0` if `minute` is omitted. -| The _second_ component will default to `0` if `second` is omitted. -| Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. -| If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. -| The least significant components in the set `year`, `quarter`, `dayOfQuarter`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year`, `quarter` and `dayOfQuarter`, but specifying `year`, `quarter`, `dayOfQuarter` and `minute` is not permitted. -| One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. - +|The _quarter_ component will default to `1` if `quarter` is omitted. +|The _day of the quarter_ component will default to `1` if `dayOfQuarter` is omitted. +|The _hour_ component will default to `0` if `hour` is omitted. +|The _minute_ component will default to `0` if `minute` is omitted. +|The _second_ component will default to `0` if `second` is omitted. +|Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. +|If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. +|The least significant components in the set `year`, `quarter`, `dayOfQuarter`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year`, `quarter` and `dayOfQuarter`, but specifying `year`, `quarter`, `dayOfQuarter` and `minute` is not permitted. +|One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. |=== -.+localdatetime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- -RETURN - localdatetime({ - year: 1984, quarter: 3, dayOfQuarter: 45, - hour: 12, minute: 31, second: 14, nanosecond: 645876123 - }) AS theDate +RETURN localdatetime({year:1984, quarter:3, dayOfQuarter: 45, hour:12, minute:31, second:14, nanosecond: 645876123}) AS theDate ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-localdatetime-ordinal]] === Creating an ordinal (Year-Day) _LocalDateTime_ `localdatetime()` returns a _LocalDateTime_ value with the specified _year_, _ordinalDay_, _hour_, _minute_, _second_, _millisecond_, _microsecond_ and _nanosecond_ component values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -localdatetime({year [, ordinalDay, hour, minute, second, millisecond, microsecond, nanosecond]}) ----- +*Syntax:* `localdatetime({year [, ordinalDay, hour, minute, second, millisecond, microsecond, nanosecond]})` *Returns:* - |=== - -| A LocalDateTime. - +| +A LocalDateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `year` -| An expression consisting of at xref::syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. - -| `ordinalDay` -| An integer between `1` and `366` that specifies the ordinal day of the year. - -| `hour` -| An integer between `0` and `23` that specifies the hour of the day. - -| `minute` -| An integer between `0` and `59` that specifies the number of minutes. - -| `second` -| An integer between `0` and `59` that specifies the number of seconds. - -| `millisecond` -| An integer between `0` and `999` that specifies the number of milliseconds. - -| `microsecond` -| An integer between `0` and `999,999` that specifies the number of microseconds. - -| `nanosecond` -| An integer between `0` and `999,999,999` that specifies the number of nanoseconds. - +| `A single map consisting of the following:` | +| `year` | An expression consisting of at xref:syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. +| `ordinalDay` | An integer between `1` and `366` that specifies the ordinal day of the year. +| `hour` | An integer between `0` and `23` that specifies the hour of the day. +| `minute` | An integer between `0` and `59` that specifies the number of minutes. +| `second` | An integer between `0` and `59` that specifies the number of seconds. +| `millisecond` | An integer between `0` and `999` that specifies the number of milliseconds. +| `microsecond` | An integer between `0` and `999,999` that specifies the number of microseconds. +| `nanosecond` | An integer between `0` and `999,999,999` that specifies the number of nanoseconds. |=== -*Considerations:* +*Considerations:* |=== - -| The _ordinal day of the year_ component will default to `1` if `ordinalDay` is omitted. -| The _hour_ component will default to `0` if `hour` is omitted. -| The _minute_ component will default to `0` if `minute` is omitted. -| The _second_ component will default to `0` if `second` is omitted. -| Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. -| If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. -| The least significant components in the set `year`, `ordinalDay`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year` and `ordinalDay`, but specifying `year`, `ordinalDay` and `minute` is not permitted. -| One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. - +|The _ordinal day of the year_ component will default to `1` if `ordinalDay` is omitted. +|The _hour_ component will default to `0` if `hour` is omitted. +|The _minute_ component will default to `0` if `minute` is omitted. +|The _second_ component will default to `0` if `second` is omitted. +|Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. +|If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. +|The least significant components in the set `year`, `ordinalDay`, `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `year` and `ordinalDay`, but specifying `year`, `ordinalDay` and `minute` is not permitted. +|One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. |=== -.+localdatetime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- -RETURN - localdatetime({ - year: 1984, ordinalDay: 202, - hour: 12, minute: 31, second: 14, microsecond: 645876 - }) AS theDate +RETURN localdatetime({year:1984, ordinalDay:202, hour:12, minute:31, second:14, microsecond: 645876}) AS theDate ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-localdatetime-create-string]] === Creating a _LocalDateTime_ from a string `localdatetime()` returns the _LocalDateTime_ value obtained by parsing a string representation of a temporal value. -*Syntax:* - -[source, syntax, role="noheader"] ----- -localdatetime(temporalValue) ----- +*Syntax:* `localdatetime(temporalValue)` *Returns:* - |=== - -| A LocalDateTime. - +| +A LocalDateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `temporalValue` -| A string representing a temporal value. - +| `temporalValue` | A string representing a temporal value. |=== -*Considerations:* +*Considerations:* |=== - -| `temporalValue` must comply with the format defined for xref::syntax/temporal.adoc#cypher-temporal-specify-date[dates] and xref::syntax/temporal.adoc#cypher-temporal-specify-time[times]. -| `temporalValue` must denote a valid date and time; i.e. a `temporalValue` denoting `30 February 2001` is invalid. -| `localdatetime(null)` returns null. - +|`temporalValue` must comply with the format defined for xref:syntax/temporal.adoc#cypher-temporal-specify-date[dates] and xref:syntax/temporal.adoc#cypher-temporal-specify-time[times]. +|`temporalValue` must denote a valid date and time; i.e. a `temporalValue` denoting `30 February 2001` is invalid. +|`localdatetime(null)` returns null. |=== -.+localdatetime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -localdatetime('2015-07-21T21:40:32.142'), -localdatetime('2015-W30-2T214032.142'), -localdatetime('2015-202T21:40:32'), -localdatetime('2015202T21') + localdatetime('2015-07-21T21:40:32.142'), + localdatetime('2015-W30-2T214032.142'), + localdatetime('2015-202T21:40:32'), + localdatetime('2015202T21') ] AS theDate RETURN theDate ---- @@ -3211,228 +2677,218 @@ RETURN theDate .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-localdatetime-temporal]] === Creating a _LocalDateTime_ using other temporal values as components `localdatetime()` returns the _LocalDateTime_ value obtained by selecting and composing components from another temporal value. In essence, this allows a _Date_, _DateTime_, _Time_ or _LocalTime_ value to be converted to a _LocalDateTime_, and for "missing" components to be provided. + -*Syntax:* - -[source, syntax, role="noheader"] ----- -localdatetime({datetime [, year, ..., nanosecond]}) | localdatetime({date [, year, ..., nanosecond]}) | localdatetime({time [, year, ..., nanosecond]}) | localdatetime({date, time [, year, ..., nanosecond]}) ----- +*Syntax:* `localdatetime({datetime [, year, ..., nanosecond]}) | localdatetime({date [, year, ..., nanosecond]}) | localdatetime({time [, year, ..., nanosecond]}) | localdatetime({date, time [, year, ..., nanosecond]})` *Returns:* - |=== - -| A LocalDateTime. - +| +A LocalDateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - +| `A single map consisting of the following:` | | `datetime` | A _DateTime_ value. - -| `date` -| A _Date_ value. - -| `time` -| A _Time_ value. - -| `year` -| An expression consisting of at xref::syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. - -| `month` -| An integer between `1` and `12` that specifies the month. - -| `day` -| An integer between `1` and `31` that specifies the day of the month. - -| `week` -| An integer between `1` and `53` that specifies the week. - -| `dayOfWeek` -| An integer between `1` and `7` that specifies the day of the week. - -| `quarter` -| An integer between `1` and `4` that specifies the quarter. - -| `dayOfQuarter` -| An integer between `1` and `92` that specifies the day of the quarter. - -| `ordinalDay` -| An integer between `1` and `366` that specifies the ordinal day of the year. - -| `hour` -| An integer between `0` and `23` that specifies the hour of the day. - -| `minute` -| An integer between `0` and `59` that specifies the number of minutes. - -| `second` -| An integer between `0` and `59` that specifies the number of seconds. - -| `millisecond` -| An integer between `0` and `999` that specifies the number of milliseconds. - -| `microsecond` -| An integer between `0` and `999,999` that specifies the number of microseconds. - -| `nanosecond` -| An integer between `0` and `999,999,999` that specifies the number of nanoseconds. - +| `date` | A _Date_ value. +| `time` | A _Time_ value. +| `year` | An expression consisting of at xref:syntax/temporal.adoc#cypher-temporal-year[least four digits] that specifies the year. +| `month` | An integer between `1` and `12` that specifies the month. +| `day` | An integer between `1` and `31` that specifies the day of the month. +| `week` | An integer between `1` and `53` that specifies the week. +| `dayOfWeek` | An integer between `1` and `7` that specifies the day of the week. +| `quarter` | An integer between `1` and `4` that specifies the quarter. +| `dayOfQuarter` | An integer between `1` and `92` that specifies the day of the quarter. +| `ordinalDay` | An integer between `1` and `366` that specifies the ordinal day of the year. +| `hour` | An integer between `0` and `23` that specifies the hour of the day. +| `minute` | An integer between `0` and `59` that specifies the number of minutes. +| `second` | An integer between `0` and `59` that specifies the number of seconds. +| `millisecond` | An integer between `0` and `999` that specifies the number of milliseconds. +| `microsecond` | An integer between `0` and `999,999` that specifies the number of microseconds. +| `nanosecond` | An integer between `0` and `999,999,999` that specifies the number of nanoseconds. |=== -*Considerations:* +*Considerations:* |=== - -| If any of the optional parameters are provided, these will override the corresponding components of `datetime`, `date` and/or `time`. -| `localdatetime(dd)` may be written instead of `+localdatetime({datetime: dd})+`. - +|If any of the optional parameters are provided, these will override the corresponding components of `datetime`, `date` and/or `time`. +|`localdatetime(dd)` may be written instead of `localdatetime({datetime: dd})`. |=== +The following query shows the various usages of `localdatetime({date [, year, ..., nanosecond]})` -.+localdatetime()+ -====== - -The following query shows the various usages of `+localdatetime({date [, year, ..., nanosecond]})+`. .Query -[source, cypher, indent=0] +[source, cypher] ---- -WITH date({year: 1984, month: 10, day: 11}) AS dd -RETURN - localdatetime({date: dd, hour: 10, minute: 10, second: 10}) AS dateHHMMSS, - localdatetime({date: dd, day: 28, hour: 10, minute: 10, second: 10}) AS dateDDHHMMSS +WITH date({year:1984, month:10, day:11}) AS dd +RETURN localdatetime({date:dd, hour: 10, minute: 10, second: 10}) AS dateHHMMSS, + localdatetime({date:dd, day: 28, hour: 10, minute: 10, second: 10}) AS dateDDHHMMSS ---- .Result [role="queryresult",options="header,footer",cols="2* +Try this query live + +++++ +endif::nonhtmloutput[] +The following query shows the various usages of `localdatetime({time [, year, ..., nanosecond]})` -.+localdatetime()+ -====== - -The following query shows the various usages of `+localdatetime({time [, year, ..., nanosecond]})+`. .Query -[source, cypher, indent=0] +[source, cypher] ---- -WITH time({hour: 12, minute: 31, second: 14, microsecond: 645876, timezone: '+01:00'}) AS tt -RETURN - localdatetime({year: 1984, month: 10, day: 11, time: tt}) AS YYYYMMDDTime, - localdatetime({year: 1984, month: 10, day: 11, time: tt, second: 42}) AS YYYYMMDDTimeSS +WITH time({hour:12, minute:31, second:14, microsecond: 645876, timezone: '+01:00'}) AS tt +RETURN localdatetime({year:1984, month:10, day:11, time:tt}) AS YYYYMMDDTime, + localdatetime({year:1984, month:10, day:11, time:tt, second: 42}) AS YYYYMMDDTimeSS ---- .Result [role="queryresult",options="header,footer",cols="2* +Try this query live + +++++ +endif::nonhtmloutput[] -.+localdatetime()+ -====== +The following query shows the various usages of `localdatetime({date, time [, year, ..., nanosecond]})`; i.e. combining a _Date_ and a _Time_ value to create a single _LocalDateTime_ value: -The following query shows the various usages of `+localdatetime({date, time [, year, ..., nanosecond]})+`; i.e. combining a _Date_ and a _Time_ value to create a single _LocalDateTime_ value. .Query -[source, cypher, indent=0] +[source, cypher] ---- -WITH - date({year: 1984, month: 10, day: 11}) AS dd, - time({hour: 12, minute: 31, second: 14, microsecond: 645876, timezone: '+01:00'}) AS tt -RETURN - localdatetime({date: dd, time: tt}) AS dateTime, - localdatetime({date: dd, time: tt, day: 28, second: 42}) AS dateTimeDDSS +WITH date({year:1984, month:10, day:11}) AS dd, + time({hour:12, minute:31, second:14, microsecond: 645876, timezone: '+01:00'}) AS tt +RETURN localdatetime({date:dd, time:tt}) AS dateTime, + localdatetime({date:dd, time:tt, day: 28, second: 42}) AS dateTimeDDSS ---- .Result [role="queryresult",options="header,footer",cols="2* +Try this query live + +++++ +endif::nonhtmloutput[] +The following query shows the various usages of `localdatetime({datetime [, year, ..., nanosecond]})` -.+localdatetime()+ -====== - -The following query shows the various usages of `+localdatetime({datetime [, year, ..., nanosecond]})+`. .Query -[source, cypher, indent=0] +[source, cypher] ---- -WITH - datetime({ - year: 1984, month: 10, day: 11, - hour: 12, - timezone: '+01:00' - }) AS dd -RETURN - localdatetime({datetime: dd}) AS dateTime, - localdatetime({datetime: dd, day: 28, second: 42}) AS dateTimeDDSS +WITH datetime({year:1984, month:10, day:11, hour:12, timezone: '+01:00'}) as dd +RETURN localdatetime({datetime:dd}) as dateTime, + localdatetime({datetime:dd, day: 28, second: 42}) as dateTimeDDSS ---- .Result [role="queryresult",options="header,footer",cols="2* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-localdatetime-truncate]] === Truncating a _LocalDateTime_ @@ -3441,152 +2897,120 @@ In other words, the _LocalDateTime_ returned will have all components that are l It is possible to supplement the truncated value by providing a map containing components which are less significant than the truncation unit. This will have the effect of _overriding_ the default values which would otherwise have been set for these less significant components. -For example, `day` -- with some value `x` -- may be provided when the truncation unit string is `'year'` in order to ensure the returned value has the _day_ set to `x` instead of the default _day_ (which is `1`). +For example, `day` -- with some value `x` -- may be provided when the truncation unit is `year` in order to ensure the returned value has the _day_ set to `x` instead of the default _day_ (which is `1`). + -*Syntax:* - -[source, syntax, role="noheader"] ----- -localdatetime.truncate(unit [, temporalInstantValue [, mapOfComponents ] ]) ----- +*Syntax:* `localdatetime.truncate(unit [, temporalInstantValue [, mapOfComponents ] ])` *Returns:* - |=== - -| A LocalDateTime. - +| +A LocalDateTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `unit` -| A string expression evaluating to one of the following strings: `'millennium'`, `'century'`, `'decade'`, `'year'`, `'weekYear'`, `'quarter'`, `'month'`, `'week'`, `'day'`, `'hour'`, `'minute'`, `'second'`, `'millisecond'`, `'microsecond'`. - -| `temporalInstantValue` -| An expression of one of the following types: _DateTime_, _LocalDateTime_, _Date_. - -| `mapOfComponents` -| An expression evaluating to a map containing components less significant than `unit`. - +| `unit` | A string expression evaluating to one of the following: {`millennium`, `century`, `decade`, `year`, `weekYear`, `quarter`, `month`, `week`, `day`, `hour`, `minute`, `second`, `millisecond`, `microsecond`}. +| `temporalInstantValue` | An expression of one of the following types: {_DateTime_, _LocalDateTime_, _Date_}. +| `mapOfComponents` | An expression evaluating to a map containing components less significant than `unit`. |=== -*Considerations:* +*Considerations:* |=== - -| `temporalInstantValue` cannot be a _Date_ value if `unit` is one of: `'hour'`, `'minute'`, `'second'`, `'millisecond'`, `'microsecond'`. -| Any component that is provided in `mapOfComponents` must be less significant than `unit`; i.e. if `unit` is `'day'`, `mapOfComponents` cannot contain information pertaining to a _month_. -| Any component that is not contained in `mapOfComponents` and which is less significant than `unit` will be set to its xref::syntax/temporal.adoc#cypher-temporal-accessing-components-temporal-instants[minimal value]. -| If `mapOfComponents` is not provided, all components of the returned value which are less significant than `unit` will be set to their default values. -| If `temporalInstantValue` is not provided, it will be set to the current date and time, i.e. `localdatetime.truncate(unit)` is equivalent of `localdatetime.truncate(unit, localdatetime())`. - +|`temporalInstantValue` cannot be a _Date_ value if `unit` is one of {`hour`, `minute`, `second`, `millisecond`, `microsecond`}. +|Any component that is provided in `mapOfComponents` must be less significant than `unit`; i.e. if `unit` is 'day', `mapOfComponents` cannot contain information pertaining to a _month_. +|Any component that is not contained in `mapOfComponents` and which is less significant than `unit` will be set to its xref:syntax/temporal.adoc#cypher-temporal-accessing-components-temporal-instants[minimal value]. +|If `mapOfComponents` is not provided, all components of the returned value which are less significant than `unit` will be set to their default values. +|If `temporalInstantValue` is not provided, it will be set to the current date and time, i.e. `localdatetime.truncate(unit)` is equivalent of `localdatetime.truncate(unit, localdatetime())`. |=== -.+localdatetime.truncate()+ -====== - .Query -[source, cypher, indent=0] ----- -WITH - localdatetime({ - year: 2017, month: 11, day: 11, - hour: 12, minute: 31, second: 14, nanosecond: 645876123 - }) AS d -RETURN - localdatetime.truncate('millennium', d) AS truncMillenium, - localdatetime.truncate('year', d, {day: 2}) AS truncYear, - localdatetime.truncate('month', d) AS truncMonth, - localdatetime.truncate('day', d) AS truncDay, - localdatetime.truncate('hour', d, {nanosecond: 2}) AS truncHour, - localdatetime.truncate('second', d) AS truncSecond +[source, cypher] +---- +WITH localdatetime({year:2017, month:11, day:11, hour:12, minute:31, second:14, nanosecond: 645876123}) AS d +RETURN localdatetime.truncate('millennium', d) AS truncMillenium, + localdatetime.truncate('year', d, {day:2}) AS truncYear, + localdatetime.truncate('month', d) AS truncMonth, + localdatetime.truncate('day', d) AS truncDay, + localdatetime.truncate('hour', d, {nanosecond:2}) AS truncHour, + localdatetime.truncate('second', d) AS truncSecond ---- .Result [role="queryresult",options="header,footer",cols="6* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-localtime]] -== +localtime()+ +== LocalTime: `localtime()` -Details for using the `localtime()` function. -* xref::functions/temporal/index.adoc#functions-localtime-current[Getting the current _LocalTime_] -** xref::functions/temporal/index.adoc#functions-localtime-transaction[+localtime.transaction()+] -** xref::functions/temporal/index.adoc#functions-localtime-statement[+localtime.statement()+] -** xref::functions/temporal/index.adoc#functions-localtime-realtime[+localtime.realtime()+] +* xref:functions/temporal/index.adoc#functions-localtime-current[Getting the current _LocalTime_] +* xref:functions/temporal/index.adoc#functions-localtime-create[Creating a _LocalTime_] +* xref:functions/temporal/index.adoc#functions-localtime-create-string[Creating a _LocalTime_ from a string] +* xref:functions/temporal/index.adoc#functions-localtime-temporal[Creating a _LocalTime_ using other temporal values as components] +* xref:functions/temporal/index.adoc#functions-localtime-truncate[Truncating a _LocalTime_] + -* xref::functions/temporal/index.adoc#functions-localtime-create[Creating a _LocalTime_] -* xref::functions/temporal/index.adoc#functions-localtime-create-string[Creating a _LocalTime_ from a string] -* xref::functions/temporal/index.adoc#functions-localtime-temporal[Creating a _LocalTime_ using other temporal values as components] -* xref::functions/temporal/index.adoc#functions-localtime-truncate[Truncating a _LocalTime_] - - -[discrete] [[functions-localtime-current]] === Getting the current _LocalTime_ `localtime()` returns the current _LocalTime_ value. If no time zone parameter is specified, the local time zone will be used. -*Syntax:* - -[source, syntax, role="noheader"] ----- -localtime([{timezone}]) ----- +*Syntax:* `+localtime([{timezone}])+` *Returns:* - |=== - -| A LocalTime. - +| +A LocalTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `A single map consisting of the following:` | +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -*Considerations:* +*Considerations:* |=== - -| If no parameters are provided, `localtime()` must be invoked (`+localtime({})+` is invalid). - +|If no parameters are provided, `localtime()` must be invoked (`localtime({})` is invalid). |=== -.+localtime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN localtime() AS now ---- @@ -3596,21 +3020,27 @@ The current local time (i.e. in the local time zone) is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -.+localtime()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN localtime({timezone: 'America/Los Angeles'}) AS nowInLA ---- @@ -3620,17 +3050,24 @@ The current local time in California is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-localtime-transaction]] ==== localtime.transaction() @@ -3638,38 +3075,25 @@ The current local time in California is returned. This value will be the same for each invocation within the same transaction. However, a different value may be produced for different transactions. -*Syntax:* - -[source, syntax, role="noheader"] ----- -localtime.transaction([{timezone}]) ----- +*Syntax:* `+localtime.transaction([{timezone}])+` *Returns:* - |=== - -| A LocalTime. - +| +A LocalTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -.+localtime.transaction()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN localtime.transaction() AS now ---- @@ -3677,17 +3101,24 @@ RETURN localtime.transaction() AS now .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-localtime-statement]] ==== localtime.statement() @@ -3695,38 +3126,25 @@ RETURN localtime.transaction() AS now This value will be the same for each invocation within the same statement. However, a different value may be produced for different statements within the same transaction. -*Syntax:* - -[source, syntax, role="noheader"] ----- -localtime.statement([{timezone}]) ----- +*Syntax:* `+localtime.statement([{timezone}])+` *Returns:* - |=== - -| A LocalTime. - +| +A LocalTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -.+localtime.statement()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN localtime.statement() AS now ---- @@ -3734,21 +3152,27 @@ RETURN localtime.statement() AS now .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -.+localtime.statement()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN localtime.statement('America/Los Angeles') AS nowInLA ---- @@ -3756,55 +3180,49 @@ RETURN localtime.statement('America/Los Angeles') AS nowInLA .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-localtime-realtime]] ==== localtime.realtime() `localtime.realtime()` returns the current _LocalTime_ value using the `realtime` clock. This value will be the live clock of the system. -*Syntax:* - -[source, syntax, role="noheader"] ----- -localtime.realtime([{timezone}]) ----- +*Syntax:* `+localtime.realtime([{timezone}])+` *Returns:* - |=== - -| A LocalTime. - +| +A LocalTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -.+localtime.realtime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN localtime.realtime() AS now ---- @@ -3812,163 +3230,141 @@ RETURN localtime.realtime() AS now .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-localtime-create]] === Creating a _LocalTime_ `localtime()` returns a _LocalTime_ value with the specified _hour_, _minute_, _second_, _millisecond_, _microsecond_ and _nanosecond_ component values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -localtime({hour [, minute, second, millisecond, microsecond, nanosecond]}) ----- +*Syntax:* `localtime({hour [, minute, second, millisecond, microsecond, nanosecond]})` *Returns:* - |=== - -| A LocalTime. - +| +A LocalTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== -| Name | Description - -| `A single map consisting of the following:` -| - -| `hour` -| An integer between `0` and `23` that specifies the hour of the day. - -| `minute` -| An integer between `0` and `59` that specifies the number of minutes. - -| `second` -| An integer between `0` and `59` that specifies the number of seconds. - -| `millisecond` -| An integer between `0` and `999` that specifies the number of milliseconds. - -| `microsecond` -| An integer between `0` and `999,999` that specifies the number of microseconds. - -| `nanosecond` -| An integer between `0` and `999,999,999` that specifies the number of nanoseconds. - -|=== - -*Considerations:* +| Name | Description +| `A single map consisting of the following:` | +| `hour` | An integer between `0` and `23` that specifies the hour of the day. +| `minute` | An integer between `0` and `59` that specifies the number of minutes. +| `second` | An integer between `0` and `59` that specifies the number of seconds. +| `millisecond` | An integer between `0` and `999` that specifies the number of milliseconds. +| `microsecond` | An integer between `0` and `999,999` that specifies the number of microseconds. +| `nanosecond` | An integer between `0` and `999,999,999` that specifies the number of nanoseconds. |=== -| The _hour_ component will default to `0` if `hour` is omitted. -| The _minute_ component will default to `0` if `minute` is omitted. -| The _second_ component will default to `0` if `second` is omitted. -| Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. -| If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. -| The least significant components in the set `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `hour` and `minute`, but specifying `hour` and `second` is not permitted. -| One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. +*Considerations:* +|=== +|The _hour_ component will default to `0` if `hour` is omitted. +|The _minute_ component will default to `0` if `minute` is omitted. +|The _second_ component will default to `0` if `second` is omitted. +|Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. +|If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. +|The least significant components in the set `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `hour` and `minute`, but specifying `hour` and `second` is not permitted. +|One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. |=== -.+localtime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -localtime({hour: 12, minute: 31, second: 14, nanosecond: 789, millisecond: 123, microsecond: 456}), -localtime({hour: 12, minute: 31, second: 14}), -localtime({hour: 12}) -] AS theTime + localtime({hour:12, minute:31, second:14, nanosecond: 789, millisecond: 123, microsecond: 456}), + localtime({hour:12, minute:31, second:14}), + localtime({hour:12}) +] as theTime RETURN theTime ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-localtime-create-string]] === Creating a _LocalTime_ from a string `localtime()` returns the _LocalTime_ value obtained by parsing a string representation of a temporal value. -*Syntax:* - -[source, syntax, role="noheader"] ----- -localtime(temporalValue) ----- +*Syntax:* `localtime(temporalValue)` *Returns:* - |=== - -| A LocalTime. - +| +A LocalTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `temporalValue` -| A string representing a temporal value. - +| `temporalValue` | A string representing a temporal value. |=== -*Considerations:* +*Considerations:* |=== - -| `temporalValue` must comply with the format defined for xref::syntax/temporal.adoc#cypher-temporal-specify-time[times]. -| `temporalValue` must denote a valid time; i.e. a `temporalValue` denoting `13:46:64` is invalid. -| `localtime(null)` returns null. - +|`temporalValue` must comply with the format defined for xref:syntax/temporal.adoc#cypher-temporal-specify-time[times]. +|`temporalValue` must denote a valid time; i.e. a `temporalValue` denoting `13:46:64` is invalid. +|`localtime(null)` returns null. |=== -.+localtime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -localtime('21:40:32.142'), -localtime('214032.142'), -localtime('21:40'), -localtime('21') + localtime('21:40:32.142'), + localtime('214032.142'), + localtime('21:40'), + localtime('21') ] AS theTime RETURN theTime ---- @@ -3976,109 +3372,102 @@ RETURN theTime .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-localtime-temporal]] === Creating a _LocalTime_ using other temporal values as components `localtime()` returns the _LocalTime_ value obtained by selecting and composing components from another temporal value. In essence, this allows a _DateTime_, _LocalDateTime_ or _Time_ value to be converted to a _LocalTime_, and for "missing" components to be provided. + -*Syntax:* - -[source, syntax, role="noheader"] ----- -localtime({time [, hour, ..., nanosecond]}) ----- +*Syntax:* `localtime({time [, hour, ..., nanosecond]})` *Returns:* - |=== - -| A LocalTime. - +| +A LocalTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `time` -| A _Time_ value. - -| `hour` -| An integer between `0` and `23` that specifies the hour of the day. - -| `minute` -| An integer between `0` and `59` that specifies the number of minutes. - -| `second` -| An integer between `0` and `59` that specifies the number of seconds. - -| `millisecond` -| An integer between `0` and `999` that specifies the number of milliseconds. - -| `microsecond` -| An integer between `0` and `999,999` that specifies the number of microseconds. - -| `nanosecond` -| An integer between `0` and `999,999,999` that specifies the number of nanoseconds. - +| `A single map consisting of the following:` | +| `time` | A _Time_ value. +| `hour` | An integer between `0` and `23` that specifies the hour of the day. +| `minute` | An integer between `0` and `59` that specifies the number of minutes. +| `second` | An integer between `0` and `59` that specifies the number of seconds. +| `millisecond` | An integer between `0` and `999` that specifies the number of milliseconds. +| `microsecond` | An integer between `0` and `999,999` that specifies the number of microseconds. +| `nanosecond` | An integer between `0` and `999,999,999` that specifies the number of nanoseconds. |=== -*Considerations:* +*Considerations:* |=== - -| If any of the optional parameters are provided, these will override the corresponding components of `time`. -| `localtime(tt)` may be written instead of `+localtime({time: tt})+`. - +|If any of the optional parameters are provided, these will override the corresponding components of `time`. +|`localtime(tt)` may be written instead of `localtime({time: tt})`. |=== -.+localtime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- -WITH time({hour: 12, minute: 31, second: 14, microsecond: 645876, timezone: '+01:00'}) AS tt -RETURN - localtime({time: tt}) AS timeOnly, - localtime({time: tt, second: 42}) AS timeSS +WITH time({hour:12, minute:31, second:14, microsecond: 645876, timezone: '+01:00'}) AS tt +RETURN localtime({time:tt}) AS timeOnly, + localtime({time:tt, second: 42}) AS timeSS ---- .Result [role="queryresult",options="header,footer",cols="2* +Try this query live + +++++ +endif::nonhtmloutput[] - -[discrete] [[functions-localtime-truncate]] === Truncating a _LocalTime_ @@ -4087,150 +3476,120 @@ In other words, the _LocalTime_ returned will have all components that are less It is possible to supplement the truncated value by providing a map containing components which are less significant than the truncation unit. This will have the effect of _overriding_ the default values which would otherwise have been set for these less significant components. -For example, `minute` -- with some value `x` -- may be provided when the truncation unit string is `'hour'` in order to ensure the returned value has the _minute_ set to `x` instead of the default _minute_ (which is `1`). - -*Syntax:* +For example, `minute` -- with some value `x` -- may be provided when the truncation unit is `hour` in order to ensure the returned value has the _minute_ set to `x` instead of the default _minute_ (which is `1`). + -[source, syntax, role="noheader"] ----- -localtime.truncate(unit [, temporalInstantValue [, mapOfComponents ] ]) ----- +*Syntax:* `localtime.truncate(unit [, temporalInstantValue [, mapOfComponents ] ])` *Returns:* - |=== - -| A LocalTime. - +| +A LocalTime. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `unit` -| A string expression evaluating to one of the following strings: `'day'`, `'hour'`, `'minute'`, `'second'`, `'millisecond'`, `'microsecond'`. - -| `temporalInstantValue` -| An expression of one of the following types: _DateTime_, _LocalDateTime_, _Time_, _LocalTime_. - -| `mapOfComponents` -| An expression evaluating to a map containing components less significant than `unit`. - +| `unit` | A string expression evaluating to one of the following: {`day`, `hour`, `minute`, `second`, `millisecond`, `microsecond`}. +| `temporalInstantValue` | An expression of one of the following types: {_DateTime_, _LocalDateTime_, _Time_, _LocalTime_}. +| `mapOfComponents` | An expression evaluating to a map containing components less significant than `unit`. |=== -*Considerations:* +*Considerations:* |=== - -a| -Truncating time to day -- i.e. `unit` is `'day'` -- is supported, and yields midnight at the start of the day (`00:00`), regardless of the value of `temporalInstantValue`. -However, the time zone of `temporalInstantValue` is retained. -| Any component that is provided in `mapOfComponents` must be less significant than `unit`; i.e. if `unit` is `'second'`, `mapOfComponents` cannot contain information pertaining to a _minute_. -| Any component that is not contained in `mapOfComponents` and which is less significant than `unit` will be set to its xref::syntax/temporal.adoc#cypher-temporal-accessing-components-temporal-instants[minimal value]. -| If `mapOfComponents` is not provided, all components of the returned value which are less significant than `unit` will be set to their default values. -| If `temporalInstantValue` is not provided, it will be set to the current time, i.e. `localtime.truncate(unit)` is equivalent of `localtime.truncate(unit, localtime())`. - +|Truncating time to day -- i.e. `unit` is 'day' -- is supported, and yields midnight at the start of the day (`00:00`), regardless of the value of `temporalInstantValue`. However, the time zone of `temporalInstantValue` is retained. +|Any component that is provided in `mapOfComponents` must be less significant than `unit`; i.e. if `unit` is 'second', `mapOfComponents` cannot contain information pertaining to a _minute_. +|Any component that is not contained in `mapOfComponents` and which is less significant than `unit` will be set to its xref:syntax/temporal.adoc#cypher-temporal-accessing-components-temporal-instants[minimal value]. +|If `mapOfComponents` is not provided, all components of the returned value which are less significant than `unit` will be set to their default values. +|If `temporalInstantValue` is not provided, it will be set to the current time, i.e. `localtime.truncate(unit)` is equivalent of `localtime.truncate(unit, localtime())`. |=== -.+localtime.truncate()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- -WITH time({hour: 12, minute: 31, second: 14, nanosecond: 645876123, timezone: '-01:00'}) AS t -RETURN - localtime.truncate('day', t) AS truncDay, - localtime.truncate('hour', t) AS truncHour, - localtime.truncate('minute', t, {millisecond: 2}) AS truncMinute, - localtime.truncate('second', t) AS truncSecond, - localtime.truncate('millisecond', t) AS truncMillisecond, - localtime.truncate('microsecond', t) AS truncMicrosecond +WITH time({hour:12, minute:31, second:14, nanosecond: 645876123, timezone: '-01:00'}) AS t +RETURN localtime.truncate('day', t) AS truncDay, + localtime.truncate('hour', t) AS truncHour, + localtime.truncate('minute', t, {millisecond:2}) AS truncMinute, + localtime.truncate('second', t) AS truncSecond, + localtime.truncate('millisecond', t) AS truncMillisecond, + localtime.truncate('microsecond', t) AS truncMicrosecond ---- .Result [role="queryresult",options="header,footer",cols="6* +Try this query live + +++++ +endif::nonhtmloutput[] [[functions-time]] -== +time()+ - -Details for using the `time()` function. - -* xref::functions/temporal/index.adoc#functions-time-current[Getting the current _Time_] -** xref::functions/temporal/index.adoc#functions-time-transaction[+time.transaction()+] -** xref::functions/temporal/index.adoc#functions-time-statement[+time.statement()+] -** xref::functions/temporal/index.adoc#functions-time-realtime[+time.realtime()+] +== Time: `time()` -* xref::functions/temporal/index.adoc#functions-time-create[Creating a _Time_] -* xref::functions/temporal/index.adoc#functions-time-create-string[Creating a _Time_ from a string] -* xref::functions/temporal/index.adoc#functions-time-temporal[Creating a _Time_ using other temporal values as components] -* xref::functions/temporal/index.adoc#functions-time-truncate[Truncating a _Time_] +* xref:functions/temporal/index.adoc#functions-time-current[Getting the current _Time_] +* xref:functions/temporal/index.adoc#functions-time-create[Creating a _Time_] +* xref:functions/temporal/index.adoc#functions-time-create-string[Creating a _Time_ from a string] +* xref:functions/temporal/index.adoc#functions-time-temporal[Creating a _Time_ using other temporal values as components] +* xref:functions/temporal/index.adoc#functions-time-truncate[Truncating a _Time_] + -[discrete] [[functions-time-current]] === Getting the current _Time_ `time()` returns the current _Time_ value. If no time zone parameter is specified, the local time zone will be used. -*Syntax:* - -[source, syntax, role="noheader"] ----- -time([{timezone}]) ----- +*Syntax:* `+time([{timezone}])+` *Returns:* - |=== - -| A Time. - +| +A Time. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `A single map consisting of the following:` | +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -*Considerations:* +*Considerations:* |=== - -| If no parameters are provided, `time()` must be invoked (`+time({})+` is invalid). - +|If no parameters are provided, `time()` must be invoked (`time({})` is invalid). |=== -.+time()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN time() AS currentTime ---- @@ -4240,21 +3599,27 @@ The current time of day using the local time zone is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -.+time()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN time({timezone: 'America/Los Angeles'}) AS currentTimeInLA ---- @@ -4264,17 +3629,24 @@ The current time of day in California is returned. .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-time-transaction]] ==== time.transaction() @@ -4282,37 +3654,25 @@ The current time of day in California is returned. This value will be the same for each invocation within the same transaction. However, a different value may be produced for different transactions. -*Syntax:* - -[source, syntax, role="noheader"] ----- -time.transaction([{timezone}]) ----- +*Syntax:* `+time.transaction([{timezone}])+` *Returns:* - |=== - -| A Time. - +| +A Time. |=== + *Arguments:* [options="header"] |=== | Name | Description - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -.+time.transaction()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN time.transaction() AS currentTime ---- @@ -4321,14 +3681,23 @@ RETURN time.transaction() AS currentTime [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-time-statement]] ==== time.statement() @@ -4336,19 +3705,12 @@ RETURN time.transaction() AS currentTime This value will be the same for each invocation within the same statement. However, a different value may be produced for different statements within the same transaction. -*Syntax:* - -[source, syntax, role="noheader"] ----- -time.statement([{timezone}]) ----- +*Syntax:* `+time.statement([{timezone}])+` *Returns:* - |=== - -| A Time. - +| +A Time. |=== @@ -4356,17 +3718,12 @@ time.statement([{timezone}]) [options="header"] |=== | Name | Description - -| `timezone` | A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -.+time.statement()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN time.statement() AS currentTime ---- @@ -4374,21 +3731,27 @@ RETURN time.statement() AS currentTime .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -.+time.statement()+ -====== .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN time.statement('America/Los Angeles') AS currentTimeInLA ---- @@ -4396,55 +3759,49 @@ RETURN time.statement('America/Los Angeles') AS currentTimeInLA .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-time-realtime]] ==== time.realtime() `time.realtime()` returns the current _Time_ value using the `realtime` clock. This value will be the live clock of the system. -*Syntax:* - -[source, syntax, role="noheader"] ----- -time.realtime([{timezone}]) ----- +*Syntax:* `+time.realtime([{timezone}])+` *Returns:* - |=== - -| A Time. - +| +A Time. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `timezone` -| A string expression that represents the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone]. - +| `timezone` | A string expression that represents the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone] |=== -.+time.realtime()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN time.realtime() AS currentTime ---- @@ -4452,97 +3809,75 @@ RETURN time.realtime() AS currentTime .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-time-create]] === Creating a _Time_ `time()` returns a _Time_ value with the specified _hour_, _minute_, _second_, _millisecond_, _microsecond_, _nanosecond_ and _timezone_ component values. -*Syntax:* - -[source, syntax, role="noheader"] ----- -time({hour [, minute, second, millisecond, microsecond, nanosecond, timezone]}) ----- +*Syntax:* `time({hour [, minute, second, millisecond, microsecond, nanosecond, timezone]})` *Returns:* - |=== - -| A Time. - +| +A Time. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `hour` -| An integer between `0` and `23` that specifies the hour of the day. - -| `minute` -| An integer between `0` and `59` that specifies the number of minutes. - -| `second` -| An integer between `0` and `59` that specifies the number of seconds. - -| `millisecond` -| An integer between `0` and `999` that specifies the number of milliseconds. - -| `microsecond` -| An integer between `0` and `999,999` that specifies the number of microseconds. - -| `nanosecond` -| An integer between `0` and `999,999,999` that specifies the number of nanoseconds. - -| `timezone` -| An expression that specifies the time zone. - +| `A single map consisting of the following:` | +| `hour` | An integer between `0` and `23` that specifies the hour of the day. +| `minute` | An integer between `0` and `59` that specifies the number of minutes. +| `second` | An integer between `0` and `59` that specifies the number of seconds. +| `millisecond` | An integer between `0` and `999` that specifies the number of milliseconds. +| `microsecond` | An integer between `0` and `999,999` that specifies the number of microseconds. +| `nanosecond` | An integer between `0` and `999,999,999` that specifies the number of nanoseconds. +| `timezone` | An expression that specifies the time zone. |=== -*Considerations:* +*Considerations:* |=== - -| The _hour_ component will default to `0` if `hour` is omitted. -| The _minute_ component will default to `0` if `minute` is omitted. -| The _second_ component will default to `0` if `second` is omitted. -| Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. -| The _timezone_ component will default to the configured default time zone if `timezone` is omitted. -| If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. -| The least significant components in the set `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `hour` and `minute`, but specifying `hour` and `second` is not permitted. -| One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. - +|The _hour_ component will default to `0` if `hour` is omitted. +|The _minute_ component will default to `0` if `minute` is omitted. +|The _second_ component will default to `0` if `second` is omitted. +|Any missing `millisecond`, `microsecond` or `nanosecond` values will default to `0`. +|The _timezone_ component will default to the configured default time zone if `timezone` is omitted. +|If `millisecond`, `microsecond` and `nanosecond` are given in combination (as part of the same set of parameters), the individual values must be in the range `0` to `999`. +|The least significant components in the set `hour`, `minute`, and `second` may be omitted; i.e. it is possible to specify only `hour` and `minute`, but specifying `hour` and `second` is not permitted. +|One or more of `millisecond`, `microsecond` and `nanosecond` can only be specified as long as `second` is also specified. |=== -.+time()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -time({hour: 12, minute: 31, second: 14, millisecond: 123, microsecond: 456, nanosecond: 789}), -time({hour: 12, minute: 31, second: 14, nanosecond: 645876123}), -time({hour: 12, minute: 31, second: 14, microsecond: 645876, timezone: '+01:00'}), -time({hour: 12, minute: 31, timezone: '+01:00'}), -time({hour: 12, timezone: '+01:00'}) + time({hour:12, minute:31, second:14, millisecond: 123, microsecond: 456, nanosecond: 789}), + time({hour:12, minute:31, second:14, nanosecond: 645876123}), + time({hour:12, minute:31, second:14, microsecond: 645876, timezone: '+01:00'}), + time({hour:12, minute:31, timezone: '+01:00'}), + time({hour:12, timezone: '+01:00'}) ] AS theTime RETURN theTime ---- @@ -4550,7 +3885,6 @@ RETURN theTime .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-time-create-string]] === Creating a _Time_ from a string `time()` returns the _Time_ value obtained by parsing a string representation of a temporal value. -*Syntax:* - -[source, syntax, role="noheader"] ----- -time(temporalValue) ----- +*Syntax:* `time(temporalValue)` *Returns:* - |=== - -| A Time. - +| +A Time. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `temporalValue` -| A string representing a temporal value. - +| `temporalValue` | A string representing a temporal value. |=== -*Considerations:* +*Considerations:* |=== - -| `temporalValue` must comply with the format defined for xref::syntax/temporal.adoc#cypher-temporal-specify-time[times] and xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zones]. -| The _timezone_ component will default to the configured default time zone if it is omitted. -| `temporalValue` must denote a valid time; i.e. a `temporalValue` denoting `15:67` is invalid. -| `time(null)` returns `null`. - +|`temporalValue` must comply with the format defined for xref:syntax/temporal.adoc#cypher-temporal-specify-time[times] and xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zones]. +|The _timezone_ component will default to the configured default time zone if it is omitted. +|`temporalValue` must denote a valid time; i.e. a `temporalValue` denoting `15:67` is invalid. +|`time(null)` returns null. |=== -.+time()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- UNWIND [ -time('21:40:32.142+0100'), -time('214032.142Z'), -time('21:40:32+01:00'), -time('214032-0100'), -time('21:40-01:30'), -time('2140-00:00'), -time('2140-02'), -time('22+18:00') + time('21:40:32.142+0100'), + time('214032.142Z'), + time('21:40:32+01:00'), + time('214032-0100'), + time('21:40-01:30'), + time('2140-00:00'), + time('2140-02'), + time('22+18:00') ] AS theTime RETURN theTime ---- @@ -4630,7 +3964,6 @@ RETURN theTime .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] -[discrete] [[functions-time-temporal]] === Creating a _Time_ using other temporal values as components `time()` returns the _Time_ value obtained by selecting and composing components from another temporal value. In essence, this allows a _DateTime_, _LocalDateTime_ or _LocalTime_ value to be converted to a _Time_, and for "missing" components to be provided. + -*Syntax:* - -[source, syntax, role="noheader"] ----- -time({time [, hour, ..., timezone]}) ----- +*Syntax:* `time({time [, hour, ..., timezone]})` *Returns:* - |=== - -| A Time. - +| +A Time. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `A single map consisting of the following:` -| - -| `time` -| A _Time_ value. - -| `hour` -| An integer between `0` and `23` that specifies the hour of the day. - -| `minute` -| An integer between `0` and `59` that specifies the number of minutes. - -| `second` -| An integer between `0` and `59` that specifies the number of seconds. - -| `millisecond` -| An integer between `0` and `999` that specifies the number of milliseconds. - -| `microsecond` -| An integer between `0` and `999,999` that specifies the number of microseconds. - -| `nanosecond` -| An integer between `0` and `999,999,999` that specifies the number of nanoseconds. - -| `timezone` -| An expression that specifies the time zone. - +| `A single map consisting of the following:` | +| `time` | A _Time_ value. +| `hour` | An integer between `0` and `23` that specifies the hour of the day. +| `minute` | An integer between `0` and `59` that specifies the number of minutes. +| `second` | An integer between `0` and `59` that specifies the number of seconds. +| `millisecond` | An integer between `0` and `999` that specifies the number of milliseconds. +| `microsecond` | An integer between `0` and `999,999` that specifies the number of microseconds. +| `nanosecond` | An integer between `0` and `999,999,999` that specifies the number of nanoseconds. +| `timezone` | An expression that specifies the time zone. |=== -*Considerations:* +*Considerations:* |=== - -| If any of the optional parameters are provided, these will override the corresponding components of `time`. -| `time(tt)` may be written instead of `+time({time: tt})+`. -| Selecting a _Time_ or _DateTime_ value as the `time` component also selects its time zone. If a _LocalTime_ or _LocalDateTime_ is selected instead, the default time zone is used. In any case, the time zone can be overridden explicitly. -| Selecting a _DateTime_ or _Time_ as the `time` component and overwriting the time zone will adjust the local time to keep the same point in time. - +|If any of the optional parameters are provided, these will override the corresponding components of `time`. +|`time(tt)` may be written instead of `time({time: tt})`. +|Selecting a _Time_ or _DateTime_ value as the `time` component also selects its time zone. If a _LocalTime_ or _LocalDateTime_ is selected instead, the default time zone is used. In any case, the time zone can be overridden explicitly. +|Selecting a _DateTime_ or _Time_ as the `time` component and overwriting the time zone will adjust the local time to keep the same point in time. |=== -.+time()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- -WITH localtime({hour: 12, minute: 31, second: 14, microsecond: 645876}) AS tt -RETURN - time({time: tt}) AS timeOnly, - time({time: tt, timezone: '+05:00'}) AS timeTimezone, - time({time: tt, second: 42}) AS timeSS, - time({time: tt, second: 42, timezone: '+05:00'}) AS timeSSTimezone +WITH localtime({hour:12, minute:31, second:14, microsecond: 645876}) AS tt +RETURN time({time:tt}) AS timeOnly, + time({time:tt, timezone:'+05:00'}) AS timeTimezone, + time({time:tt, second: 42}) AS timeSS, + time({time:tt, second: 42, timezone:'+05:00'}) AS timeSSTimezone ---- .Result [role="queryresult",options="header,footer",cols="4* +Try this query live + +++++ +endif::nonhtmloutput[] - -[discrete] [[functions-time-truncate]] === Truncating a _Time_ @@ -4752,82 +4083,77 @@ In other words, the _Time_ returned will have all components that are less signi It is possible to supplement the truncated value by providing a map containing components which are less significant than the truncation unit. This will have the effect of _overriding_ the default values which would otherwise have been set for these less significant components. -For example, `minute` -- with some value `x` -- may be provided when the truncation unit string is `'hour'` in order to ensure the returned value has the _minute_ set to `x` instead of the default _minute_ (which is `1`). - -*Syntax:* +For example, `minute` -- with some value `x` -- may be provided when the truncation unit is `hour` in order to ensure the returned value has the _minute_ set to `x` instead of the default _minute_ (which is `1`). + -[source, syntax, role="noheader"] ----- -time.truncate(unit [, temporalInstantValue [, mapOfComponents ] ]) ----- +*Syntax:* `time.truncate(unit [, temporalInstantValue [, mapOfComponents ] ])` *Returns:* - |=== - -| A Time. - +| +A Time. |=== -*Arguments:* +*Arguments:* [options="header"] |=== | Name | Description - -| `unit` -| A string expression evaluating to one of the following strings: `'day'`, `'hour'`, `'minute'`, `'second'`, `'millisecond'`, `'microsecond'`. - -| `temporalInstantValue` -| An expression of one of the following types: _DateTime_, _LocalDateTime_, _Time_, _LocalTime_. - -| `mapOfComponents` -a| -An expression evaluating to a map containing components less significant than `unit`. -During truncation, a time zone can be attached or overridden using the key `timezone`. - +| `unit` | A string expression evaluating to one of the following: {`day`, `hour`, `minute`, `second`, `millisecond`, `microsecond`}. +| `temporalInstantValue` | An expression of one of the following types: {_DateTime_, _LocalDateTime_, _Time_, _LocalTime_}. +| `mapOfComponents` | An expression evaluating to a map containing components less significant than `unit`. During truncation, a time zone can be attached or overridden using the key `timezone`. |=== -*Considerations:* +*Considerations:* |=== - -| Truncating time to day -- i.e. `unit` is `'day'` -- is supported, and yields midnight at the start of the day (`00:00`), regardless of the value of `temporalInstantValue`. However, the time zone of `temporalInstantValue` is retained. -| The time zone of `temporalInstantValue` may be overridden; for example, `+time.truncate('minute', input, {timezone: '+0200'})+`. -| If `temporalInstantValue` is one of _Time_, _DateTime_ -- a value with a time zone -- and the time zone is overridden, no time conversion occurs. -| If `temporalInstantValue` is one of _LocalTime_, _LocalDateTime_, _Date_ -- a value without a time zone -- and the time zone is not overridden, the configured default time zone will be used. -| Any component that is provided in `mapOfComponents` must be less significant than `unit`; i.e. if `unit` is `'second'`, `mapOfComponents` cannot contain information pertaining to a _minute_. -| Any component that is not contained in `mapOfComponents` and which is less significant than `unit` will be set to its xref::syntax/temporal.adoc#cypher-temporal-accessing-components-temporal-instants[minimal value]. -| If `mapOfComponents` is not provided, all components of the returned value which are less significant than `unit` will be set to their default values. -| If `temporalInstantValue` is not provided, it will be set to the current time and timezone, i.e. `time.truncate(unit)` is equivalent of `time.truncate(unit, time())`. +|Truncating time to day -- i.e. `unit` is 'day' -- is supported, and yields midnight at the start of the day (`00:00`), regardless of the value of `temporalInstantValue`. However, the time zone of `temporalInstantValue` is retained. +|The time zone of `temporalInstantValue` may be overridden; for example, `time.truncate('minute', input, {timezone:'+0200'})`. +|If `temporalInstantValue` is one of {_Time_, _DateTime_} -- a value with a time zone -- and the time zone is overridden, no time conversion occurs. +|If `temporalInstantValue` is one of {_LocalTime_, _LocalDateTime_, _Date_} -- a value without a time zone -- and the time zone is not overridden, the configured default time zone will be used. +|Any component that is provided in `mapOfComponents` must be less significant than `unit`; i.e. if `unit` is 'second', `mapOfComponents` cannot contain information pertaining to a _minute_. +|Any component that is not contained in `mapOfComponents` and which is less significant than `unit` will be set to its xref:syntax/temporal.adoc#cypher-temporal-accessing-components-temporal-instants[minimal value]. +|If `mapOfComponents` is not provided, all components of the returned value which are less significant than `unit` will be set to their default values. +|If `temporalInstantValue` is not provided, it will be set to the current time and timezone, i.e. `time.truncate(unit)` is equivalent of `time.truncate(unit, time())`. |=== -.+time()+ -====== - .Query -[source, cypher, indent=0] +[source, cypher] ---- -WITH time({hour: 12, minute: 31, second: 14, nanosecond: 645876123, timezone: '-01:00'}) AS t -RETURN - time.truncate('day', t) AS truncDay, - time.truncate('hour', t) AS truncHour, - time.truncate('minute', t) AS truncMinute, - time.truncate('second', t) AS truncSecond, - time.truncate('millisecond', t, {nanosecond: 2}) AS truncMillisecond, - time.truncate('microsecond', t) AS truncMicrosecond +WITH time({hour:12, minute:31, second:14, nanosecond: 645876123, timezone: '-01:00'}) AS t +RETURN time.truncate('day', t) AS truncDay, + time.truncate('hour', t) AS truncHour, + time.truncate('minute', t) AS truncMinute, + time.truncate('second', t) AS truncSecond, + time.truncate('millisecond', t, {nanosecond:2}) AS truncMillisecond, + time.truncate('microsecond', t) AS truncMicrosecond ---- .Result [role="queryresult",options="header,footer",cols="6* +Try this query live + +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/functions/user-defined.adoc b/modules/ROOT/pages/functions/user-defined.adoc index f21f155c9..92703836c 100644 --- a/modules/ROOT/pages/functions/user-defined.adoc +++ b/modules/ROOT/pages/functions/user-defined.adoc @@ -1,92 +1,81 @@ -:description: User-defined functions are written in Java, deployed into the database and are called in the same way as any other Cypher function. - [[query-functions-user-defined]] = User-defined functions - -[abstract] --- -User-defined functions are written in Java, deployed into the database and are called in the same way as any other Cypher function. --- +:description: User-defined functions are written in Java, deployed into the database and are called in the same way as any other Cypher function. There are two main types of functions that can be developed and used: [options="header"] |=== -| Type | Description | Usage | Developing - -| Scalar -| For each row the function takes parameters and returns a result. -| xref::functions/user-defined.adoc#query-functions-udf[Using UDF] -| link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/functions#extending-neo4j-functions[Extending Neo4j (UDF)] - -| Aggregating -| Consumes many rows and produces an aggregated result. -| xref::functions/user-defined.adoc#query-functions-user-defined-aggregation[Using aggregating UDF] -| link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/aggregation-functions#extending-neo4j-aggregation-functions[Extending Neo4j (Aggregating UDF)] - +|Type | Description | Usage | Developing +|Scalar | For each row the function takes parameters and returns a result | xref:functions/user-defined.adoc#query-functions-udf[Using UDF] | link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/functions#extending-neo4j-functions[Extending Neo4j (UDF)] +|Aggregating | Consumes many rows and produces an aggregated result | xref:functions/user-defined.adoc#query-functions-user-defined-aggregation[Using aggregating UDF] | link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/aggregation-functions#extending-neo4j-aggregation-functions[Extending Neo4j (Aggregating UDF)] |=== +// User-defined functions [[query-functions-udf]] == User-defined scalar functions -For each incoming row the function takes parameters and returns a single result. - -For developing and deploying user-defined functions in Neo4j, see link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/functions#extending-neo4j-functions[Extending Neo4j -> User-defined functions]. +For each incoming row the function takes parameters and returns a single result. -.Call a user-defined function -====== This example shows how you invoke a user-defined function called `join` from Cypher. +== Call a user-defined function + This calls the user-defined function `org.neo4j.procedure.example.join()`. -//// -UNWIND ['John', 'Paul', 'George', 'Ringo'] AS name -CREATE (:Member {name: name}) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- -MATCH (n:Member) -RETURN org.neo4j.function.example.join(collect(n.name)) AS members +MATCH (n:Member) RETURN org.neo4j.function.example.join(collect(n.name)) AS members ---- .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] + + +For developing and deploying user-defined functions in Neo4j, see link:{neo4j-docs-base-uri}/java-reference/{page-version}/extending-neo4j/functions#extending-neo4j-functions[Extending Neo4j -> User-defined functions]. +// User-defined aggregating functions + [[query-functions-user-defined-aggregation]] == User-defined aggregation functions Aggregating functions consume many rows and produces a single aggregated result. - -.Call a user-defined aggregation function -====== - This example shows how you invoke a user-defined aggregation function called `longestString` from Cypher. +[[functions-call-a-user-defined-aggregation-function]] +== Call a user-defined aggregation function + This calls the user-defined function `org.neo4j.function.example.longestString()`. -//// -UNWIND ['John', 'Paul', 'George', 'Ringe'] AS beatle -CREATE (:Member {name: beatle}) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n:Member) RETURN org.neo4j.function.example.longestString(n.name) AS member @@ -95,12 +84,24 @@ RETURN org.neo4j.function.example.longestString(n.name) AS member .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/index.adoc b/modules/ROOT/pages/index.adoc index e9525199e..7306294cf 100644 --- a/modules/ROOT/pages/index.adoc +++ b/modules/ROOT/pages/index.adoc @@ -1,49 +1,42 @@ -:description: This is the Cypher Query Language documentation for Neo4j version {neo4j-version}, authored by the Neo4j Team. +[[cypher-manual]] += The Neo4j Cypher Manual v{neo4j-version} +:description: This is the Cypher manual for Neo4j version {neo4j-version}, authored by the Neo4j Team. +:sectnums: -[[cypher-main]] -= Cypher +:neo4j-buildnumber: {neo4j-version} -[discrete] -== Neo4j v{neo4j-version} +ifdef::backend-html5[(C) {copyright}] +ifndef::backend-pdf[] +Documentation license: link:{common-license-page-uri}[Creative Commons 4.0] +endif::[] +ifdef::backend-pdf[] (C) {copyright} -ifndef::backend-pdf[] -License: link:{common-license-page-uri}[Creative Commons 4.0] +Documentation license: <> endif::[] -//License page should be added at the end when generating pdf. (neo4j-manual-modeling-antora) -ifdef::backend-pdf[] -License: Creative Commons 4.0 -endif::[] +This manual covers the following areas: + +* xref:introduction/index.adoc[] -- Introducing the Cypher query language. +* xref:syntax/index.adoc[] -- Learn Cypher query syntax. +* xref:clauses/index.adoc[] -- Reference of Cypher query clauses. +* xref:functions/index.adoc[] -- Reference of Cypher query functions. +* xref:indexes-for-search-performance.adoc[] -- How to manage indexes used for search performance. +* xref:indexes-for-full-text-search.adoc[Full-text search index] -- How to use full-text indexes, to enable full-text search. +* xref:constraints/index.adoc[Constraints] -- How to manage constraints used for ensuring data integrity. +* xref:databases.adoc[Database management] -- How to use Cypher to manage Neo4j databases. +* xref:access-control/index.adoc[Access control] -- How to manage Neo4j role-based access control and fine-grained security. +* xref:query-tuning/index.adoc[] -- Learn to analyze queries and tune them for performance. +* xref:execution-plans/index.adoc[] -- Cypher execution plans and operators. +* xref:deprecations-additions-removals-compatibility.adoc[] -- An overview of language developments across versions. +* xref:keyword-glossary.adoc[] -- A glossary of Cypher keywords, with links to other parts of the Cypher manual. +* xref:styleguide.adoc[Cypher styleguide] -- A guide to the recommended style for writing Cypher queries. -[.lead] -*Cypher -- Neo4j’s graph database query language* - -Cypher is Neo4j’s graph database query language that allows users to store and retrieve data from the graph database. -It is a declarative, SQL-inspired language for describing visual patterns in graphs. -The syntax provides a visual and logical way to match patterns of nodes and relationships in the graph. - -[.lead] -*Contents* - -* xref::introduction/index.adoc[] -- Introduction to Cypher; Neo4js graph query language. -* xref::syntax/index.adoc[] -- Learn Cypher query syntax. -* xref::clauses/index.adoc[] -- Reference of Cypher query clauses. -* xref::functions/index.adoc[] -- Reference of Cypher query functions. -* xref::indexes-for-search-performance.adoc[] -- How to manage indexes used for search performance. -* xref::indexes-for-full-text-search.adoc[] -- How to use full-text indexes, to enable full-text search. -* xref::constraints/index.adoc[] -- How to manage constraints used for ensuring data integrity. -* xref::databases.adoc[] -- How to use Cypher to manage Neo4j databases. -* xref::aliases.adoc[] -- How to manage local database aliases and remote database aliases. -* xref::access-control/index.adoc[] -- How to manage Neo4j role-based access control and fine-grained security. -* xref::query-tuning/index.adoc[] -- Learn to analyze queries and tune them for performance. -* xref::execution-plans/index.adoc[] -- Cypher execution plans and operators. -* xref::deprecations-additions-removals-compatibility.adoc[] -- An overview of language developments across versions. -* xref::keyword-glossary.adoc[] -- A glossary of Cypher keywords, with links to other parts of the Cypher manual. -* xref::styleguide.adoc[] -- A guide to the recommended style for writing Cypher queries. _Who should read this?_ -This content is written for the developer of a Neo4j client application. +This manual is written for the developer of a Neo4j client application. +ifdef::backend-pdf[] +endif::[] diff --git a/modules/ROOT/pages/indexes-for-full-text-search.adoc b/modules/ROOT/pages/indexes-for-full-text-search.adoc index 8940d2609..599734dc6 100644 --- a/modules/ROOT/pages/indexes-for-full-text-search.adoc +++ b/modules/ROOT/pages/indexes-for-full-text-search.adoc @@ -1,16 +1,10 @@ -:description: This chapter describes how to use full-text indexes, to enable full-text search. - [[administration-indexes-fulltext-search]] = Full-text search index - -[abstract] --- -This chapter describes how to use full-text indexes, to enable full-text search. --- +:description: This chapter describes how to use full-text indexes, to enable full-text search. Full-text indexes are powered by the link:https://lucene.apache.org/[Apache Lucene] indexing and search library, and can be used to index nodes and relationships by string properties. A full-text index allows you to write queries that match within the _contents_ of indexed string properties. -For instance, the B-tree indexes described in previous sections can only do exact matching or prefix matches on strings. +For instance, the b-tree indexes described in previous sections can only do exact matching or prefix matches on strings. A full-text index will instead tokenize the indexed string values, so it can match _terms_ anywhere within the strings. How the indexed strings are tokenized and broken into terms, is determined by what analyzer the full-text index is configured with. For instance, the _swedish_ analyzer knows how to tokenize and stem Swedish words, and will avoid indexing Swedish stop words. @@ -35,15 +29,14 @@ Using this feature, it is possible to work around the slow Lucene writes from th At first sight, the construction of full-text indexes can seem similar to regular indexes. However there are some things that are interesting to note: -In contrast to xref::indexes-for-search-performance.adoc[B-tree indexes], a full-text index can be: +In contrast to xref:indexes-for-search-performance.adoc[b-tree indexes], a full-text index can be: * applied to more than one label. * applied to more than one relationship type. -* applied to more than one property at a time (similar to a xref::indexes-for-search-performance.adoc#administration-indexes-create-a-composite-b-tree-index-for-nodes[_composite index_]) but with an important difference: +* applied to more than one property at a time (similar to a xref:indexes-for-search-performance.adoc#administration-indexes-create-a-composite-index-for-nodes[_composite index_]) but with an important difference: While a composite index applies only to entities that match the indexed label and _all_ of the indexed properties, full-text index will index entities that have at least one of the indexed labels or relationship types, and at least one of the indexed properties. -For information on how to configure full-text indexes, refer to link:{neo4j-docs-base-uri}/operations-manual/{page-version}/performance/index-configuration#index-configuration-fulltext[Operations Manual -> Indexes to support full-text search]. - +For information on how to configure full-text indexes, refer to link:{neo4j-docs-base-uri}/operations-manual/{page-version}/performance-configuration#index-configuration-fulltext[Operations Manual -> Indexes to support full-text search]. [[administration-indexes-fulltext-search-manage]] == Full-text search procedures @@ -55,49 +48,15 @@ The commands and procedures for full-text indexes are listed in the table below: [options="header"] |=== -| Usage | Procedure/Command | Description - -| Create full-text node index. -| `+CREATE FULLTEXT INDEX ...+` -| Create a node fulltext index for the given labels and properties. -The optional 'options' map can be used to supply provider and settings to the index. -Supported settings are 'fulltext.analyzer', for specifying what analyzer to use when indexing and querying. -Use the `db.index.fulltext.listAvailableAnalyzers` procedure to see what options are available. -And 'fulltext.eventually_consistent' which can be set to 'true' to make this index eventually consistent, such that updates from committing transactions are applied in a background thread. - -| Create full-text relationship index. -| `+CREATE FULLTEXT INDEX ...+` -a| -Create a relationship fulltext index for the given relationship types and properties. -The optional 'options' map can be used to supply provider and settings to the index. -Supported settings are 'fulltext.analyzer', for specifying what analyzer to use when indexing and querying. -Use the `db.index.fulltext.listAvailableAnalyzers` procedure to see what options are available. -And 'fulltext.eventually_consistent' which can be set to 'true' to make this index eventually consistent, such that updates from committing transactions are applied in a background thread. - -| List available analyzers. -| `db.index.fulltext.listAvailableAnalyzers` -| List the available analyzers that the full-text indexes can be configured with. - -| Use full-text node index. -| `db.index.fulltext.queryNodes` -| Query the given full-text index. Returns the matching nodes and their Lucene query score, ordered by score. - -| Use full-text relationship index. -| `db.index.fulltext.queryRelationships` -| Query the given full-text index. Returns the matching relationships and their Lucene query score, ordered by score. - -| Drop full-text index. -| `+DROP INDEX ...+` -| Drop the specified index. - -| Eventually consistent indexes. -| `db.index.fulltext.awaitEventuallyConsistentIndexRefresh` -| Wait for the updates from recently committed transactions to be applied to any eventually-consistent full-text indexes. - -| Listing all fulltext indexes. -| `SHOW FULLTEXT INDEXES` -| Lists all fulltext indexes, see xref::indexes-for-search-performance.adoc#administration-indexes-list-indexes[the `SHOW INDEXES` command] for details. - +| Usage | Procedure/Command | Description +| Create full-text node index | `CREATE FULLTEXT INDEX ...` | Create a node fulltext index for the given labels and properties. The optional 'options' map can be used to supply provider and settings to the index. Supported settings are 'fulltext.analyzer', for specifying what analyzer to use when indexing and querying. Use the `db.index.fulltext.listAvailableAnalyzers` procedure to see what options are available. And 'fulltext.eventually_consistent' which can be set to 'true' to make this index eventually consistent, such that updates from committing transactions are applied in a background thread. +| Create full-text relationship index | `CREATE FULLTEXT INDEX ...` | Create a relationship fulltext index for the given relationship types and properties. The optional 'options' map can be used to supply provider and settings to the index. Supported settings are 'fulltext.analyzer', for specifying what analyzer to use when indexing and querying. Use the `db.index.fulltext.listAvailableAnalyzers` procedure to see what options are available. And 'fulltext.eventually_consistent' which can be set to 'true' to make this index eventually consistent, such that updates from committing transactions are applied in a background thread. +| List available analyzers | `db.index.fulltext.listAvailableAnalyzers` | List the available analyzers that the full-text indexes can be configured with. +| Use full-text node index | `db.index.fulltext.queryNodes` | Query the given full-text index. Returns the matching nodes and their Lucene query score, ordered by score. +| Use full-text relationship index | `db.index.fulltext.queryRelationships` | Query the given full-text index. Returns the matching relationships and their Lucene query score, ordered by score. +| Drop full-text index | `DROP INDEX ...` | Drop the specified index. +| Eventually consistent indexes | `db.index.fulltext.awaitEventuallyConsistentIndexRefresh` | Wait for the updates from recently committed transactions to be applied to any eventually-consistent full-text indexes. +| Listing all fulltext indexes | `SHOW FULLTEXT INDEXES` | Lists all fulltext indexes, see xref:indexes-for-search-performance.adoc#administration-indexes-list-indexes[the `SHOW INDEXES` command] for details. |=== @@ -109,11 +68,11 @@ An index can be given a unique name when created (or get a generated one), which A full-text index applies to a list of labels or a list of relationship types, for node and relationship indexes respectively, and then a list of property names. .Syntax for creating fulltext indexes -[options="header", width="100%", cols="5a, 3, 3a"] +[options="header", width="100%", cols="5a,3, 3a"] |=== | Command | Description | Comment -| [source, cypher, role=noplay, indent=0] +| [source, cypher, role=noplay] ---- CREATE FULLTEXT INDEX [index_name] [IF NOT EXISTS] FOR (n:LabelName["\|" ...]) @@ -132,7 +91,7 @@ The command is optionally idempotent, with the default behavior to throw an erro With `IF NOT EXISTS`, no error is thrown and nothing happens should an index with the same name, schema or both already exist. It may still throw an error should a constraint with the same name exist. -| [source, cypher, role=noplay, indent=0] +| [source, cypher, role=noplay] ---- CREATE FULLTEXT INDEX [index_name] [IF NOT EXISTS] FOR ()-"["r:TYPE_NAME["\|" ...]"]"-() @@ -140,24 +99,13 @@ ON EACH "[" r.propertyName[, ...] "]" [OPTIONS "{" option: value[, ...] "}"] ---- | Create a fulltext index on relationships. - |=== - -.+CREATE FULLTEXT INDEX+ -====== - For instance, if we have a movie with a title. -//// -CREATE (m:Movie {title: "The Matrix"}) RETURN m.title -CREATE (:Movie {title: "Full Metal Jacket"}), (:Movie {title: "The Jacket"}), (:Movie {title: "Yellow Jacket"}), (:Movie {title: "Full Moon High"}), (:Movie {title: "Metallica Through The Never", description: "The movie follows the young roadie Trip through his surreal adventure with the band."}) -CREATE FULLTEXT INDEX titlesAndDescriptions FOR (n:Movie|Book) ON EACH [n.title, n.description] -CALL db.awaitIndexes(1000) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (m:Movie {title: "The Matrix"}) RETURN m.title ---- @@ -165,42 +113,62 @@ CREATE (m:Movie {title: "The Matrix"}) RETURN m.title .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] + +And we have a full-text index on the `title` and `description` properties of movies and books. + .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE FULLTEXT INDEX titlesAndDescriptions FOR (n:Movie|Book) ON EACH [n.title, n.description] ---- -Then our movie node from above will be included in the index, even though it only has one of the indexed labels, and only one of the indexed properties: - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + +Then our movie node from above will be included in the index, even though it only has one of the indexed labels, and only one of the indexed properties: + .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL db.index.fulltext.queryNodes("titlesAndDescriptions", "matrix") YIELD node, score RETURN node.title, node.description, score @@ -209,18 +177,31 @@ RETURN node.title, node.description, score .Result [role="queryresult",options="header,footer",cols="3*+ | +0.7799721956253052+ 3+d|Rows: 1 - |=== -The same is true for full-text indexes on relationships. -Though a relationship can only have one type, a relationship full-text index can index multiple types, and all relationships will be included that match one of the relationship types, and at least one of the indexed properties. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] +The same is true for full-text indexes on relationships. +Though a relationship can only have one type, a relationship full-text index can index multiple types, and all relationships will be included that match one of the relationship types, and at least one of the indexed properties. The `CREATE FULLTEXT INDEX` command take an optional clause, called `options`. This have two parts, the `indexProvider` and `indexConfig`. The provider can only have the default value, `'fulltext-1.0'`. @@ -231,26 +212,10 @@ The `fulltext.eventually_consistent` setting, if set to `true`, will put the ind This means that updates will be applied in a background thread "as soon as possible", instead of during transaction commit like other indexes. -.+CREATE FULLTEXT INDEX+ -====== - -//// -CREATE (m:Movie {title: "The Matrix"}) RETURN m.title -CREATE (:Movie {title: "Full Metal Jacket"}), (:Movie {title: "The Jacket"}), (:Movie {title: "Yellow Jacket"}), (:Movie {title: "Full Moon High"}), (:Movie {title: "Metallica Through The Never", description: "The movie follows the young roadie Trip through his surreal adventure with the band."}) -CREATE FULLTEXT INDEX titlesAndDescriptions FOR (n:Movie|Book) ON EACH [n.title, n.description] -CALL db.awaitIndexes(1000) -//// - .Query -[source, cypher, indent=0] +[source, cypher] ---- -CREATE FULLTEXT INDEX taggedByRelationshipIndex FOR ()-[r:TAGGED_AS]-() ON EACH [r.taggedByUser] -OPTIONS { - indexConfig: { - `fulltext.analyzer`: 'url_or_email', - `fulltext.eventually_consistent`: true - } -} +CREATE FULLTEXT INDEX taggedByRelationshipIndex FOR ()-[r:TAGGED_AS]-() ON EACH [r.taggedByUser] OPTIONS {indexConfig: {`fulltext.analyzer`: 'url_or_email', `fulltext.eventually_consistent`: true}} ---- In this example, an eventually consistent relationship full-text index is created for the `TAGGED_AS` relationship type, and the `taggedByUser` property, and the index uses the `url_or_email` analyzer. @@ -260,15 +225,27 @@ Had it not been for the relationship index, one would have had to add artificial .Result [role="queryresult",options="footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] [[administration-indexes-fulltext-search-query]] == Query full-text indexes @@ -277,22 +254,11 @@ Full-text indexes will, in addition to any exact matches, also return _approxima Both the property values that are indexed, and the queries to the index, are processed through the analyzer such that the index can find that don't _exactly_ matches. The `score` that is returned alongside each result entry, represents how well the index thinks that entry matches the given query. The results are always returned in _descending score order_, where the best matching result entry is put first. - - -.Query full-text -====== - To illustrate, in the example below, we search our movie database for `"Full Metal Jacket"`, and even though there is an exact match as the first result, we also get three other less interesting results: -//// -CREATE (m:Movie {title: "The Matrix"}) RETURN m.title -CREATE (:Movie {title: "Full Metal Jacket"}), (:Movie {title: "The Jacket"}), (:Movie {title: "Yellow Jacket"}), (:Movie {title: "Full Moon High"}), (:Movie {title: "Metallica Through The Never", description: "The movie follows the young roadie Trip through his surreal adventure with the band."}) -CREATE FULLTEXT INDEX titlesAndDescriptions FOR (n:Movie|Book) ON EACH [n.title, n.description] -CALL db.awaitIndexes(1000) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL db.index.fulltext.queryNodes("titlesAndDescriptions", "Full Metal Jacket") YIELD node, score RETURN node.title, score @@ -301,17 +267,31 @@ RETURN node.title, score .Result [role="queryresult",options="header,footer",cols="2* +Try this query live + +++++ +endif::nonhtmloutput[] Full-text indexes are powered by the link:https://lucene.apache.org/[Apache Lucene] indexing and search library. @@ -319,18 +299,8 @@ This means that we can use Lucene's full-text query language to express what we For instance, if we are only interested in exact matches, then we can quote the string we are searching for. -.Query full-text -====== - -//// -CREATE (m:Movie {title: "The Matrix"}) RETURN m.title -CREATE (:Movie {title: "Full Metal Jacket"}), (:Movie {title: "The Jacket"}), (:Movie {title: "Yellow Jacket"}), (:Movie {title: "Full Moon High"}), (:Movie {title: "Metallica Through The Never", description: "The movie follows the young roadie Trip through his surreal adventure with the band."}) -CREATE FULLTEXT INDEX titlesAndDescriptions FOR (n:Movie|Book) ON EACH [n.title, n.description] -CALL db.awaitIndexes(1000) -//// - .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL db.index.fulltext.queryNodes("titlesAndDescriptions", '"Full Metal Jacket"') YIELD node, score RETURN node.title, score @@ -346,24 +316,29 @@ When we put "Full Metal Jacket" in quotes, Lucene only gives us exact matches. 2+d|Rows: 1 |=== -====== - - -Lucene also allows us to use logical operators, such as `AND` and `OR`, to search for terms. - - -.Query full-text -====== - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + +Lucene also allows us to use logical operators, such as `AND` and `OR`, to search for terms: + .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL db.index.fulltext.queryNodes("titlesAndDescriptions", 'full AND metal') YIELD node, score RETURN node.title, score @@ -374,31 +349,34 @@ Only the `Full Metal Jacket` movie in our database has both the words `full` and .Result [role="queryresult",options="header,footer",cols="2* +Try this query live + +++++ +endif::nonhtmloutput[] It is also possible to search for only specific properties, by putting the property name and a colon in front of the text being searched for. -.Query full-text -====== - -//// -CREATE (m:Movie {title: "The Matrix"}) RETURN m.title -CREATE (:Movie {title: "Full Metal Jacket"}), (:Movie {title: "The Jacket"}), (:Movie {title: "Yellow Jacket"}), (:Movie {title: "Full Moon High"}), (:Movie {title: "Metallica Through The Never", description: "The movie follows the young roadie Trip through his surreal adventure with the band."}) -CREATE FULLTEXT INDEX titlesAndDescriptions FOR (n:Movie|Book) ON EACH [n.title, n.description] -CALL db.awaitIndexes(1000) -//// - .Query -[source, cypher, indent=0] +[source, cypher] ---- CALL db.index.fulltext.queryNodes("titlesAndDescriptions", 'description:"surreal adventure"') YIELD node, score RETURN node.title, node.description, score @@ -407,43 +385,41 @@ RETURN node.title, node.description, score .Result [role="queryresult",options="header,footer",cols="3* +Try this query live + +++++ +endif::nonhtmloutput[] +A complete description of the Lucene query syntax can be found in the link:https://lucene.apache.org/core/8_2_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package.description[Lucene documentation]. [[administration-indexes-fulltext-search-drop]] == Drop full-text indexes -A full-text node index is dropped by using the xref::indexes-for-search-performance.adoc#administration-indexes-drop-an-index[same command as for other indexes], `DROP INDEX`. - - -.+DROP INDEX+ -====== +A full-text node index is dropped by using the xref:indexes-for-search-performance.adoc#administration-indexes-drop-an-index[same command as for other indexes], `DROP INDEX`. In the following example, we will drop the `taggedByRelationshipIndex` that we created previously: -//// -CREATE (m:Movie {title: "The Matrix"}) RETURN m.title -CREATE -(:Movie {title: "Full Metal Jacket"}), -(:Movie {title: "The Jacket"}), -(:Movie {title: "Yellow Jacket"}), -(:Movie {title: "Full Moon High"}), -(:Movie {title: "Metallica Through The Never", description: "The movie follows the young roadie Trip through his surreal adventure with the band."}) -CREATE FULLTEXT INDEX titlesAndDescriptions FOR (n:Movie|Book) ON EACH [n.title, n.description] -CALL db.awaitIndexes(1000) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- DROP INDEX taggedByRelationshipIndex ---- @@ -451,12 +427,25 @@ DROP INDEX taggedByRelationshipIndex .Result [role="queryresult",options="footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/indexes-for-search-performance.adoc b/modules/ROOT/pages/indexes-for-search-performance.adoc index e45fdbc47..d3bd66311 100644 --- a/modules/ROOT/pages/indexes-for-search-performance.adoc +++ b/modules/ROOT/pages/indexes-for-search-performance.adoc @@ -1,19 +1,13 @@ -:description: This section explains how to manage indexes used for search performance. - [[administration-indexes-search-performance]] = Indexes for search performance +:description: This section explains how to manage indexes used for search performance. -[abstract] --- -This section explains how to manage indexes used for search performance. --- - +This section describes how to manage indexes. For query performance purposes, it is important to also understand how the indexes are used by the Cypher planner. -Refer to xref::query-tuning/index.adoc[] for examples and in-depth discussions on how query plans result from different index and query scenarios. -See specifically xref::query-tuning/indexes.adoc[The use of indexes] for examples of how various index scenarios result in different query plans. - -For information on index configuration and limitations, refer to link:{neo4j-docs-base-uri}/operations-manual/{page-version}/performance/index-configuration[Operations Manual -> Index configuration]. +Refer to xref:query-tuning/index.adoc[] for examples and in-depth discussions on how query plans result from different index and query scenarios. +See specifically xref:query-tuning/indexes.adoc[The use of indexes] for examples of how various index scenarios result in different query plans. +For information on index configuration and limitations, refer to link:{neo4j-docs-base-uri}/operations-manual/{page-version}/performance-configuration[Operations Manual -> Index configuration]. [[administration-indexes-types]] == Indexes (types and limitations) @@ -24,355 +18,154 @@ This comes at the cost of additional storage space and slower writes, so decidin Once an index has been created, it will be managed and kept up to date by the DBMS. Neo4j will automatically pick up and start using the index once it has been created and brought online. -There are multiple index types available: B-tree (deprecated), fulltext, lookup, and text index types. -See xref::indexes-for-full-text-search.adoc[Full-text search index] for more information about fulltext indexes. -Token lookup indexes contain nodes with one or more labels or relationship types, without regard for any properties. - -Cypher enables the creation of B-tree indexes on one or more properties for all nodes or relationships with a given label or relationship type: +Cypher enables the creation of indexes on one or more properties for all nodes or relationships that have a given label or relationship type: -* An index created on a single property for any given label or relationship type is called a _single-property index_. -* An index created on more than one property for any given label or relationship type is called a _composite index_. +* An index that is created on a single property for any given label or relationship type is called a _single-property index_. +* An index that is created on more than one property for any given label or relationship type is called a _composite index_. -Differences in the usage patterns between composite and single-property indexes are described in xref::indexes-for-search-performance.adoc#administration-indexes-single-vs-composite-index[]. - -Additionally, a text index is a kind of single-property index, with the limitation that it only recognizes properties with string values. -Nodes or relationships with the indexed label or relationship type where the indexed property is of another value type are not included in the index. +Differences in the usage patterns between composite and single-property indexes are described in xref:indexes-for-search-performance.adoc#administration-indexes-single-vs-composite-index[]. The following is true for indexes: * Best practice is to give the index a name when it is created. -If the index is not explicitly named, it gets an auto-generated name. +If the index is not explicitly named, it will get an auto-generated name. * The index name must be unique among both indexes and constraints. * Index creation is by default not idempotent, and an error will be thrown if you attempt to create the same index twice. Using the keyword `IF NOT EXISTS` makes the command idempotent, and no error will be thrown if you attempt to create the same index twice. [[administration-indexes-syntax]] -== Syntax - -[IMPORTANT] -==== -The index name must be unique among both indexes and constraints. -==== - -[NOTE] -==== -Best practice is to give the index a name when it is created. -If the index is not explicitly named, it gets an auto-generated name. -==== - -[NOTE] -==== -The `+CREATE ... INDEX ...+` command is optionally idempotent, with the default behavior to throw an error if you attempt to create the same index twice. -With `IF NOT EXISTS`, no error is thrown and nothing happens should an index with the same name or same schema and index type already exist. -It may still throw an error if conflicting constraints exist, such as constraints with the same name or schema and backing index type. -==== - +=== Syntax -.+Create a single-property index on nodes+ label:deprecated[] -[options="noheader", width="100%", cols="2, 8a"] +.Syntax for managing indexes +[options="header", width="100%", cols="5a,3, 3a"] |=== +| Command | Description | Comment -| Syntax -| -[source, syntax, role="noheader"] +| [source, cypher, role=noplay] ---- CREATE [BTREE] INDEX [index_name] [IF NOT EXISTS] FOR (n:LabelName) ON (n.propertyName) [OPTIONS "{" option: value[, ...] "}"] ---- - -| Description -| -Create a single-property index on nodes. +| Create a single-property index on nodes. Index provider and configuration can be specified using the `OPTIONS` clause. +.6+.^| Best practice is to give the index a name when it is created. +If the index is not explicitly named, it will get an auto-generated name. -| Note -| Explicit use of `BTREE` keyword or B-tree options are deprecated in 4.4 and will be replaced in 5.0. - -|=== - +The index name must be unique among both indexes and constraints. -.+Create a single-property index on relationships+ label:deprecated[] -[options="noheader", width="100%", cols="2, 8a"] -|=== +The command is optionally idempotent, with the default behavior to throw an error if you attempt to create the same index twice. +With `IF NOT EXISTS`, no error is thrown and nothing happens should an index with the same name, schema or both already exist. +It may still throw an error if conflicting constraints exist, such as constraints with the same name or schema. -| Syntax -| -[source, syntax, role="noheader"] +| [source, cypher, role=noplay] ---- CREATE [BTREE] INDEX [index_name] [IF NOT EXISTS] FOR ()-"["r:TYPE_NAME"]"-() ON (r.propertyName) [OPTIONS "{" option: value[, ...] "}"] ---- - -| Description -| -Create a single-property index on relationships. +| Create a single-property index on relationships. Index provider and configuration can be specified using the `OPTIONS` clause. -| Note -| Explicit use of `BTREE` keyword or B-tree options are deprecated in 4.4 and will be replaced in 5.0. - -|=== - - -.+Create a composite index on nodes+ label:deprecated[] -[options="noheader", width="100%", cols="2, 8a"] -|=== - -| Syntax -| -[source, syntax, role="noheader"] +| [source, cypher, role=noplay] ---- CREATE [BTREE] INDEX [index_name] [IF NOT EXISTS] FOR (n:LabelName) ON (n.propertyName_1, n.propertyName_2, - ... + … n.propertyName_n) [OPTIONS "{" option: value[, ...] "}"] ---- - -| Description -| -Create a composite index on nodes. +| Create a composite index on nodes. Index provider and configuration can be specified using the `OPTIONS` clause. -| Note -| Explicit use of `BTREE` keyword or B-tree options are deprecated in 4.4 and will be replaced in 5.0. - -|=== - - -.+Create a composite index on relationships+ label:deprecated[] -[options="noheader", width="100%", cols="2, 8a"] -|=== - -| Syntax -| -[source, syntax, role="noheader"] +| [source, cypher, role=noplay] ---- CREATE [BTREE] INDEX [index_name] [IF NOT EXISTS] FOR ()-"["r:TYPE_NAME"]"-() ON (r.propertyName_1, r.propertyName_2, - ... + … r.propertyName_n) [OPTIONS "{" option: value[, ...] "}"] ---- - -| Description -| -Create a composite index on relationships. +| Create a composite index on relationships. Index provider and configuration can be specified using the `OPTIONS` clause. -| Note -| Explicit use of `BTREE` keyword or B-tree options are deprecated in 4.4 and will be replaced in 5.0. - -|=== - - -.+Create a node label lookup index+ -[options="noheader", width="100%", cols="2, 8a"] -|=== - -| Syntax -| -[source, syntax, role="noheader"] +| [source, cypher, role=noplay] ---- CREATE LOOKUP INDEX [index_name] [IF NOT EXISTS] FOR (n) ON EACH labels(n) -[OPTIONS "{" option: value[, ...] "}"] ---- +| Create a node label lookup index. -| Description -| -Create a node label lookup index. - -Index provider can be specified using the `OPTIONS` clause. - -|=== - - - -.+Create a relationship type lookup index+ -[options="noheader", width="100%", cols="2, 8a"] -|=== +Token lookup indexes do not support any `OPTIONS` values. -| Syntax -| -[source, syntax, role="noheader"] +| [source, cypher, role=noplay] ---- CREATE LOOKUP INDEX [index_name] [IF NOT EXISTS] FOR ()-"["r"]"-() ON [EACH] type(r) -[OPTIONS "{" option: value[, ...] "}"] ----- - -| Description -| -Create a relationship type lookup index. - -Index provider can be specified using the `OPTIONS` clause. - -|=== - - -.+Create a text index on nodes+ -[options="noheader", width="100%", cols="2, 8a"] -|=== - -| Syntax -| -[source, syntax, role="noheader"] ----- -CREATE TEXT INDEX [index_name] [IF NOT EXISTS] -FOR (n:LabelName) -ON (n.propertyName) -[OPTIONS "{" option: value[, ...] "}"] ----- - -| Description -| -Create a text index on nodes where the property has a string value. - -Index provider can be specified using the `OPTIONS` clause. - -|=== - - -.+Create a text index on relationships+ -[options="noheader", width="100%", cols="2, 8a"] -|=== - -| Syntax -| -[source, syntax, role="noheader"] ----- -CREATE TEXT INDEX [index_name] [IF NOT EXISTS] -FOR ()-"["r:TYPE_NAME"]"-() -ON (r.propertyName) -[OPTIONS "{" option: value[, ...] "}"] ---- +| Create a relationship type lookup index. -| Description -| -Create a text index on relationships where the property has a string value. - -Index provider can be specified using the `OPTIONS` clause. - -|=== - - -.+Drop an index+ -[options="noheader", width="100%", cols="2, 8a"] -|=== +Token lookup indexes do not support any `OPTIONS` values. -| Syntax -| -[source, syntax, role="noheader"] +| [source, cypher, role=noplay] ---- DROP INDEX index_name [IF EXISTS] ---- - -| Description -| Drop an index of any index type. - -| Note -| -The command is optionally idempotent, with the default behavior to throw an error if you attempt to drop the same index twice. +| Drop an index, either a b-tree, fulltext, or token lookup index. +| The command is optionally idempotent, with the default behavior to throw an error if you attempt to drop the same index twice. With `IF EXISTS`, no error is thrown and nothing happens should the index not exist. -|=== - - -.+Drop a single-property index+ label:deprecated[] -[options="noheader", width="100%", cols="2, 8a"] -|=== +| [source, cypher, role=noplay] +---- +SHOW [ALL\|BTREE\|FULLTEXT\|LOOKUP] INDEX[ES] + [YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] + [WHERE expression] + [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] +---- +| List indexes in the database, either all or filtered on b-tree, fulltext, or token lookup indexes. +| When using the `RETURN` clause, the `YIELD` clause is mandatory and may not be omitted. -| Syntax -| -[source, syntax, role="noheader"] +| [source, cypher, role=noplay] ---- DROP INDEX ON :LabelName(propertyName) ---- - -| Description | Drop a single-property index on nodes without specifying a name. +.2+.^| [deprecated]#This syntax is deprecated.# -| Note -| This syntax is deprecated. - -|=== - - -.+Drop a composite index+ label:deprecated[] -[options="noheader", width="100%", cols="2, 8a"] -|=== - -| Syntax -| -[source, syntax, role="noheader"] +| [source, cypher, role=noplay] ---- DROP INDEX ON :LabelName (n.propertyName_1, n.propertyName_2, -... +… n.propertyName_n) ---- - -| Description | Drop a composite index on nodes without specifying a name. - -| Note -| This syntax is deprecated. - -|=== - - -.List indexes -[options="noheader", width="100%", cols="2, 8a"] -|=== - -| Syntax -| -[source, syntax, role="noheader"] ----- -SHOW [ALL \| BTREE \| FULLTEXT \| LOOKUP \| TEXT] INDEX[ES] - [YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] - [WHERE expression] - [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ----- - -| Description -| List indexes in the database, either all or filtered on index type. - -| Note -| When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. - |=== +Creating an index requires xref:access-control/database-administration.adoc#access-control-database-administration-index[the `CREATE INDEX` privilege], +while dropping an index requires xref:access-control/database-administration.adoc#access-control-database-administration-index[the `DROP INDEX` privilege] and +listing indexes require xref:access-control/database-administration.adoc#access-control-database-administration-index[the `SHOW INDEX` privilege]. - - -Creating an index requires xref::access-control/database-administration.adoc#access-control-database-administration-index[the `CREATE INDEX` privilege], -while dropping an index requires xref::access-control/database-administration.adoc#access-control-database-administration-index[the `DROP INDEX` privilege] and -listing indexes require xref::access-control/database-administration.adoc#access-control-database-administration-index[the `SHOW INDEX` privilege]. - -xref::query-tuning/using.adoc[Planner hints and the USING keyword] describes how to make the Cypher planner use specific indexes (especially in cases where the planner would not necessarily have used them). +xref:query-tuning/using.adoc[Planner hints and the USING keyword] describes how to make the Cypher planner use specific indexes (especially in cases where the planner would not necessarily have used them). [[administration-indexes-single-vs-composite-index]] -== Composite index limitations +=== Composite index limitations -Like single-property B-tree indexes, composite B-tree indexes support all predicates: +Like single-property indexes, composite indexes support all predicates: * equality check: `n.prop = value` * list membership check: `n.prop IN list` @@ -384,7 +177,7 @@ Like single-property B-tree indexes, composite B-tree indexes support all predic [NOTE] ==== -For details about each operator, see xref::syntax/operators.adoc[Operators]. +For details about each operator, see xref:syntax/operators.adoc[Operators]. ==== However, predicates might be planned as existence check and a filter. @@ -401,14 +194,14 @@ any predicates following after will therefore also be planned as such. For example, an index on nodes with `:Label(prop1,prop2,prop3,prop4,prop5,prop6)` and predicates: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- WHERE n.prop1 = 'x' AND n.prop2 = 1 AND n.prop3 > 5 AND n.prop4 < 'e' AND n.prop5 = true AND n.prop6 IS NOT NULL ---- will be planned as: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- WHERE n.prop1 = 'x' AND n.prop2 = 1 AND n.prop3 > 5 AND n.prop4 IS NOT NULL AND n.prop5 IS NOT NULL AND n.prop6 IS NOT NULL ---- @@ -417,14 +210,14 @@ with filters on `n.prop4 < 'e'` and `n.prop5 = true`, since `n.prop3` has a `ran And an index on nodes with `:Label(prop1,prop2)` with predicates: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- WHERE n.prop1 ENDS WITH 'x' AND n.prop2 = false ---- will be planned as: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- WHERE n.prop1 IS NOT NULL AND n.prop2 IS NOT NULL ---- @@ -437,61 +230,20 @@ To get this kind of fallback behavior, it is necessary to create additional inde [[administration-indexes-examples]] -== +CREATE INDEX+ examples - -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-single-property-b-tree-index-for-nodes[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-single-property-b-tree-index-for-relationships[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-single-property-b-tree-index-only-if-it-does-not-already-exist[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-single-property-b-tree-index-with-specified-index-provider[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-single-property-b-tree-index-with-specified-index-configuration[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-composite-b-tree-index-for-nodes[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-composite-b-tree-index-for-relationships[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-composite-b-tree-index-with-specified-index-provider-and-configuration[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-node-label-lookup-index[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-relationship-type-lookup-index[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-token-lookup-index-specifying-the-index-provider[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-node-text-index[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-relationship-text-index[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-text-index-only-if-it-does-not-already-exist[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-text-index-specifying-the-index-provider[] -* xref::indexes-for-search-performance.adoc#administration-indexes-failure-to-create-an-already-existing-index[] -* xref::indexes-for-search-performance.adoc#administration-indexes-failure-to-create-an-index-with-the-same-name-as-an-already-existing-index[] -* xref::indexes-for-search-performance.adoc#administration-indexes-failure-to-create-an-index-when-a-constraint-already-exists[] -* xref::indexes-for-search-performance.adoc#administration-indexes-failure-to-create-an-index-with-the-same-name-as-an-already-existing-constraint[] - - -[discrete] -[[administration-indexes-create-a-single-property-b-tree-index-for-nodes]] -=== Create a single-property B-tree index for nodes - -A named B-tree index on a single property for all nodes with a particular label can be created with: - -[source, syntax, role="noheader"] ----- -CREATE [BTREE] INDEX index_name FOR (n:Label) ON (n.property) ----- - -[NOTE] -==== -The index is not immediately available, but is created in the background. -==== - - -.+CREATE BTREE INDEX+ -====== +== Creating indexes -//// -CREATE (p0:Person {age: 35, name: 'Smith'}) -CREATE (p1:Person {age: 40, name: 'Jones'}) -//// +[[administration-indexes-create-a-single-property-index-for-nodes]] +== Create a single-property index for nodes == +A named index on a single property for all nodes that have a particular label can be created with `CREATE INDEX index_name FOR (n:Label) ON (n.property)`. Note that the index is not immediately available, but is created in the background. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE INDEX node_index_name FOR (n:Person) ON (n.name) +CREATE INDEX node_index_name FOR (n:Person) ON (n.surname) ---- -Note that the index name must be unique. + +Note that the index name needs to be unique. .Result [queryresult] @@ -502,41 +254,98 @@ Note that the index name must be unique. Indexes added: 1 ---- -====== - - -[discrete] -[[administration-indexes-create-a-single-property-b-tree-index-for-relationships]] -=== Create a single-property B-tree index for relationships - -A named B-tree index on a single property for all relationships with a particular relationship type can be created with: - -[source, syntax, role="noheader"] ----- -CREATE [BTREE] INDEX index_name FOR ()-[r:TYPE]-() ON (r.property) ----- - -[NOTE] -==== -The index is not immediately available, but is created in the background. -==== - -.+CREATE BTREE INDEX+ -====== -//// -CREATE (p0:Person {age: 35, name: 'Smith'}) -CREATE (p1:Person {age: 40, name: 'Jones'}) -CREATE (p0)-[:KNOWS {location: 'Earth', since: 1992}]->(p1) -//// +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; + + +CREATE INDEX node_index_name FOR (n:Person) ON (n.surname) +---- + + +[[administration-indexes-create-a-single-property-index-for-relationships]] +== Create a single-property index for relationships == +A named index on a single property for all relationships that have a particular relationship type can be created with `CREATE INDEX index_name FOR ()-[r:TYPE]-() ON (r.property)`. Note that the index is not immediately available, but is created in the background. .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE INDEX rel_index_name FOR ()-[r:KNOWS]-() ON (r.since) ---- -Note that the index name must be unique. + +Note that the index name needs to be unique. .Result [queryresult] @@ -547,34 +356,99 @@ Note that the index name must be unique. Indexes added: 1 ---- -====== - -[discrete] -[[administration-indexes-create-a-single-property-b-tree-index-only-if-it-does-not-already-exist]] -=== Create a single-property B-tree index only if it does not already exist +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create index `node_index_name` for (n:`Person`) ON (n.`surname`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; -If it is not known whether an index exists or not, add `IF NOT EXISTS` to ensure it does. +CREATE INDEX rel_index_name FOR ()-[r:KNOWS]-() ON (r.since) +---- -.+CREATE BTREE INDEX+ -====== -//// -CREATE (p0:Person {age: 35, name: 'Smith'}) -CREATE (p1:Person {age: 40, name: 'Jones'}) -CREATE (p0)-[:KNOWS {location: 'Earth', since: 1992}]->(p1) -CREATE INDEX node_index_name FOR (n:Person) ON (n.name) -//CREATE BTREE INDEX rel_index_name for ()-[r:KNOWS]-() ON (r.since) -//// +[[administration-indexes-create-a-single-property-index-only-if-it-does-not-already-exist]] +== Create a single-property index only if it does not already exist == +If it is unknown if an index exists or not but we want to make sure it does, we add `IF NOT EXISTS`. .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE INDEX node_index_name IF NOT EXISTS FOR (n:Person) ON (n.surname) ---- -Note that the index will not be created if there already exists an index with the same schema and type, same name or both. + +Note that the index will not be created if there already exists an index with the same name, same schema or both. .Result [queryresult] @@ -584,33 +458,101 @@ Note that the index will not be created if there already exists an index with th +--------------------------------------------+ ---- -====== - -[discrete] -[[administration-indexes-create-a-single-property-b-tree-index-with-specified-index-provider]] -=== Create a single-property B-tree index with specified index provider label:deprecated[] +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create index `node_index_name` for (n:`Person`) ON (n.`surname`); +create index `rel_index_name` for ()-[r:`KNOWS`]-() ON (r.`since`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; -To create a single property B-tree index with a specific index provider, the `OPTIONS` clause is used. -Valid values for the index provider are `native-btree-1.0` and `lucene+native-3.0`, default is `native-btree-1.0`. +CREATE INDEX node_index_name IF NOT EXISTS FOR (n:Person) ON (n.surname) +---- -.+CREATE BTREE INDEX+ -====== -//// -CREATE (p0:Person {age: 35, name: 'Smith'}) -CREATE (p1:Person {age: 40, name: 'Jones'}) -CREATE (p0)-[:TYPE {name: 'Example', since: 1992}]->(p1) -//// +[[administration-indexes-create-a-single-property-index-with-specified-index-provider]] +== Create a single-property index with specified index provider == +To create a single property index with a specific index provider, the `OPTIONS` clause is used. +Valid values for the index provider is `native-btree-1.0` and `lucene+native-3.0`, default if nothing is specified is `native-btree-1.0`. .Query -[source, cypher, indent=0] +[source,cypher] ---- -CREATE BTREE INDEX index_with_provider FOR ()-[r:TYPE]-() ON (r.prop1) -OPTIONS {indexProvider: 'native-btree-1.0'} +CREATE BTREE INDEX index_with_provider FOR ()-[r:TYPE]-() ON (r.prop1) OPTIONS {indexProvider: + 'native-btree-1.0'} ---- + Can be combined with specifying index configuration. .Result @@ -622,49 +564,104 @@ Can be combined with specifying index configuration. Indexes added: 1 ---- -====== - - -[discrete] -[[administration-indexes-create-a-single-property-b-tree-index-with-specified-index-configuration]] -=== Create a single-property B-tree index with specified index configuration label:deprecated[] - -To create a single property B-tree index with a specific index configuration, the `OPTIONS` clause is used. - -The valid configuration settings are: - -* `spatial.cartesian.min` -* `spatial.cartesian.max` -* `spatial.cartesian-3d.min` -* `spatial.cartesian-3d.max` -* `spatial.wgs-84.min` -* `spatial.wgs-84.max` -* `spatial.wgs-84-3d.min` -* `spatial.wgs-84-3d.max` - -Non-specified settings have their respective default values. - -.+CREATE BTREE INDEX+ -====== - -//// -CREATE (n0:Label {prop2: 20, age: 35, name: 'Smith'}) -CREATE (n1:Label {prop2: -30, age: 40, name: 'Jones'}) -//// +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create index `node_index_name` for (n:`Person`) ON (n.`surname`); +create index `rel_index_name` for ()-[r:`KNOWS`]-() ON (r.`since`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; + + +CREATE BTREE INDEX index_with_provider FOR ()-[r:TYPE]-() ON (r.prop1) OPTIONS {indexProvider: 'native-btree-1.0'} +---- + + +[[administration-indexes-create-a-single-property-index-with-specified-index-configuration]] +== Create a single-property index with specified index configuration == +To create a single property index with a specific index configuration, the `OPTIONS` clause is used. +Valid configuration settings are `spatial.cartesian.min`, `spatial.cartesian.max`, `spatial.cartesian-3d.min`, `spatial.cartesian-3d.max`, +`spatial.wgs-84.min`, `spatial.wgs-84.max`, `spatial.wgs-84-3d.min`, and `spatial.wgs-84-3d.max`. +Non-specified settings get their respective default values. .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE BTREE INDEX index_with_config FOR (n:Label) ON (n.prop2) -OPTIONS { - indexConfig: { - `spatial.cartesian.min`: [-100.0, -100.0], - `spatial.cartesian.max`: [100.0, 100.0] - } -} +OPTIONS {indexConfig: {`spatial.cartesian.min`: [-100.0, -100.0], `spatial.cartesian.max`: [100.0, + 100.0]}} ---- + Can be combined with specifying index provider. .Result @@ -676,45 +673,102 @@ Can be combined with specifying index provider. Indexes added: 1 ---- -====== +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create index `index_with_provider` for ()-[r:`TYPE`]-() ON (r.`prop1`); +create index `node_index_name` for (n:`Person`) ON (n.`surname`); +create index `rel_index_name` for ()-[r:`KNOWS`]-() ON (r.`since`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; -[discrete] -[[administration-indexes-create-a-composite-b-tree-index-for-nodes]] -=== Create a composite B-tree index for nodes -A named B-tree index on multiple properties for all nodes with a particular label -- i.e. a composite index -- can be created with: - -[source, syntax, role="noheader"] ----- -CREATE INDEX index_name FOR (n:Label) ON (n.prop1, ..., n.propN) +CREATE BTREE INDEX index_with_config FOR (n:Label) ON (n.prop2) +OPTIONS {indexConfig: {`spatial.cartesian.min`: [-100.0, -100.0], `spatial.cartesian.max`: [100.0, 100.0]}} ---- -Only nodes with the specified label and that contain all the properties in the index definition will be added to the index. - -[NOTE] -==== -The composite index is not immediately available, but is created in the background. -==== - -.+CREATE BTREE INDEX+ -====== - -The following statement will create a named composite index on all nodes labeled with `Person` and which have both an `age` and `country` property: - -//// -CREATE (n0:Person {age: 35, name: 'Smith', country: 'Sweden'}) -CREATE (n1:Person {age: 40, name: 'Jones', country: 'Example'}) -//// +[[administration-indexes-create-a-composite-index-for-nodes]] +== Create a composite index for nodes == +A named index on multiple properties for all nodes that have a particular label -- i.e. a composite index -- can be created with `CREATE INDEX index_name FOR (n:Label) ON (n.prop1, ..., n.propN)`. Only nodes labeled with the specified label and which contain all the properties in the index definition will be added to the index. Note that the composite index is not immediately available, but is created in the background. The following statement will create a named composite index on all nodes labeled with `Person` and which have both an `age` and `country` property: .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE INDEX node_index_name FOR (n:Person) ON (n.age, n.country) ---- -Note that the index name must be unique. + +Note that the index name needs to be unique. .Result [queryresult] @@ -725,42 +779,98 @@ Note that the index name must be unique. Indexes added: 1 ---- -====== - -[discrete] -[[administration-indexes-create-a-composite-b-tree-index-for-relationships]] -=== Create a composite B-tree index for relationships +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; -A named B-tree index on multiple properties for all relationships with a particular relationship type -- i.e. a composite index -- can be created with `+CREATE INDEX index_name FOR ()-[r:TYPE]-() ON (r.prop1, ..., r.propN)+`. -Only relationships with the specified type and that contain all the properties in the index definition will be added to the index. -Note that the composite index is not immediately available, but is created in the background. +CREATE INDEX node_index_name FOR (n:Person) ON (n.age, n.country) +---- -.+CREATE BTREE INDEX+ -====== - -The following statement will create a named composite index on all relationships labeled with `PURCHASED` and which have both a `date` and `amount` property: -//// -create BTREE index `index_44d2128f` for (n:`Person`) ON (n.`middlename`); -create BTREE index `index_58a1c03e` for (n:`Person`) ON (n.`location`); -create BTREE index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); -create BTREE index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); -create TEXT index `index_763f72db` for (n:`Person`) ON (n.`middlename`); -create TEXT index `index_eadb868e` for (n:`Person`) ON (n.`surname`); -create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `middlename`:"Ron", `name`:"john", `surname`:"Smith"}) -create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `middlename`:"Mark", `name`:"andy", `surname`:"Jones"}) -create (_0)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_1) -//// +[[administration-indexes-create-a-composite-index-for-relationships]] +== Create a composite index for relationships == +A named index on multiple properties for all relationships that have a particular relationship type -- i.e. a composite index -- can be created with `CREATE INDEX index_name FOR ()-[r:TYPE]-() ON (r.prop1, ..., r.propN)`. Only relationships labeled with the specified type and which contain all the properties in the index definition will be added to the index. Note that the composite index is not immediately available, but is created in the background. The following statement will create a named composite index on all relationships labeled with `PURCHASED` and which have both a `date` and `amount` property: .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE INDEX rel_index_name FOR ()-[r:PURCHASED]-() ON (r.date, r.amount) ---- -Note that the index name must be unique. + +Note that the index name needs to be unique. .Result [queryresult] @@ -771,50 +881,105 @@ Note that the index name must be unique. Indexes added: 1 ---- -====== +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; -[discrete] -[[administration-indexes-create-a-composite-b-tree-index-with-specified-index-provider-and-configuration]] -=== Create a composite B-tree index with specified index provider and configuration label:deprecated[] - -To create a composite B-tree index with a specific index provider and configuration, the `OPTIONS` clause is used. -Valid values for the index provider are `native-btree-1.0` and `lucene+native-3.0`, default is `native-btree-1.0`. -The valid configuration settings are: - -* `spatial.cartesian.min` -* `spatial.cartesian.max` -* `spatial.cartesian-3d.min` -* `spatial.cartesian-3d.max` -* `spatial.wgs-84.min` -* `spatial.wgs-84.max` -* `spatial.wgs-84-3d.min` -* `spatial.wgs-84-3d.max` - -Non-specified settings have their respective default values. +CREATE INDEX rel_index_name FOR ()-[r:PURCHASED]-() ON (r.date, r.amount) +---- -.+CREATE BTREE INDEX+ -====== -//// -CREATE (n0:Label1 {prop1: 3, prop2: 'Green') -CREATE (n1:Label1 {prop1: 5, prop2: 'Pink') -//// +[[administration-indexes-create-a-composite-index-with-specified-index-provider-and-configuration]] +== Create a composite index with specified index provider and configuration == +To create a composite index with a specific index provider and configuration, the `OPTIONS` clause is used. +Valid values for the index provider is `native-btree-1.0` and `lucene+native-3.0`, default if nothing is specified is `native-btree-1.0`. +Valid configuration settings are `spatial.cartesian.min`, `spatial.cartesian.max`, `spatial.cartesian-3d.min`, `spatial.cartesian-3d.max`, +`spatial.wgs-84.min`, `spatial.wgs-84.max`, `spatial.wgs-84-3d.min`, and `spatial.wgs-84-3d.max`. +Non-specified settings get their respective default values. .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE INDEX index_with_options FOR (n:Label) ON (n.prop1, n.prop2) OPTIONS { indexProvider: 'lucene+native-3.0', - indexConfig: { - `spatial.wgs-84.min`: [-100.0, -80.0], - `spatial.wgs-84.max`: [100.0, 80.0] - } + indexConfig: {`spatial.wgs-84.min`: [-100.0, -80.0], `spatial.wgs-84.max`: [100.0, 80.0]} } ---- + Specifying index provider and configuration can be done individually. .Result @@ -826,41 +991,101 @@ Specifying index provider and configuration can be done individually. Indexes added: 1 ---- -====== +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; -[discrete] -[[administration-indexes-create-a-node-label-lookup-index]] -=== Create a node label lookup index - -A named token lookup index for all nodes with one or more labels can be created with: -[source, syntax, role="noheader"] ----- -CREATE LOOKUP INDEX index_name FOR (n) ON EACH labels(n) +CREATE INDEX index_with_options FOR (n:Label) ON (n.prop1, n.prop2) +OPTIONS { + indexProvider: 'lucene+native-3.0', + indexConfig: {`spatial.wgs-84.min`: [-100.0, -80.0], `spatial.wgs-84.max`: [100.0, 80.0]} +} ---- -[NOTE] -==== -The index is not immediately available, but is created in the background. -==== - - -.+CREATE LOOKUP INDEX+ -====== -//// -CREATE (n0:Label1:Label2 {prop1: 3, prop2: 'Green') -CREATE (n1:Label1:Label3 {prop1: 5, prop2: 'Pink') -CREATE (n2:Label1 {prop1: 7, prop2: 'Blue') -//// +[[administration-indexes-create-a-node-label-lookup-index]] +== Create a node label lookup index == +A named token lookup index for all nodes with one or more labels can be created with `CREATE LOOKUP INDEX index_name FOR (n) ON EACH labels(n)`. Note that the index is not immediately available, but is created in the background. .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE LOOKUP INDEX node_label_lookup_index FOR (n) ON EACH labels(n) ---- + Note that it can only be created once and that the index name must be unique. .Result @@ -872,42 +1097,97 @@ Note that it can only be created once and that the index name must be unique. Indexes added: 1 ---- -====== +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; -[discrete] -[[administration-indexes-create-a-relationship-type-lookup-index]] -=== Create a relationship type lookup index - -A named token lookup index for all relationships with any relationship type can be created with: -[source, syntax, role="noheader"] ----- -CREATE LOOKUP INDEX index_name FOR ()-[r]-() ON EACH type(r) +CREATE LOOKUP INDEX node_label_lookup_index FOR (n) ON EACH labels(n) ---- -[NOTE] -==== -The index is not immediately available, but is created in the background. -==== - -.+CREATE LOOKUP INDEX+ -====== -//// -CREATE (n0:Label1:Label2 {prop1: 3, prop2: 'Green') -CREATE (n1:Label1:Label3 {prop1: 5, prop2: 'Pink') -CREATE (n2:Label1 {prop1: 7, prop2: 'Blue') -CREATE (n0)-[TYPE1]->(n1) -CREATE (n0)-[TYPE2]->(n2) -//// +[[administration-indexes-create-a-relationship-type-lookup-index]] +== Create a relationship type lookup index == +A named token lookup index for all relationships with any relationship type can be created with `CREATE LOOKUP INDEX index_name FOR ()-[r]-() ON EACH type(r)`. Note that the index is not immediately available, but is created in the background. .Query -[source, cypher, indent=0] +[source,cypher] ---- CREATE LOOKUP INDEX rel_type_lookup_index FOR ()-[r]-() ON EACH type(r) ---- + Note that it can only be created once and that the index name must be unique. .Result @@ -919,547 +1199,417 @@ Note that it can only be created once and that the index name must be unique. Indexes added: 1 ---- -====== +.Try this query live +[console] +---- +CREATE LOOKUP INDEX `node_label_lookup_index` FOR (n) ON EACH labels(n); +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; -[discrete] -[[administration-indexes-create-a-token-lookup-index-specifying-the-index-provider]] -=== Create a token lookup index specifying the index provider -Token lookup indexes allow setting the index provider using the `OPTIONS` clause. -Only one valid value exists for the index provider, `token-lookup-1.0`, which is the default value. +CREATE LOOKUP INDEX rel_type_lookup_index FOR ()-[r]-() ON EACH type(r) +---- -//// -CREATE (n0:Label1:Label2 {prop1: 3, prop2: 'Green') -CREATE (n1:Label1:Label3 {prop1: 5, prop2: 'Pink') -CREATE (n2:Label1 {prop1: 7, prop2: 'Blue') -//// -.+CREATE LOOKUP INDEX+ -====== +[[administration-indexes-list-indexes]] +== Listing indexes -.Query -[source, cypher, indent=0] ----- -CREATE LOOKUP INDEX node_label_lookup_index_2 FOR (n) ON EACH labels(n) -OPTIONS {indexProvider: 'token-lookup-1.0'} ----- +Listing indexes can be done with `SHOW INDEXES`, which will produce a table with the following columns: -There is no supported index configuration for token lookup indexes. - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Indexes added: 1 ----- - -====== - - -[discrete] -[[administration-indexes-create-a-node-text-index]] -=== Create a node text index - -A named text index on a single property for all nodes with a particular label can be created with: -Text indexes only recognize string values, do not support multiple properties, and that the index name must be unique. - -[source, syntax, role="noheader"] ----- -CREATE TEXT INDEX index_name FOR (n:Label) ON (n.property) ----- - -[NOTE] -==== -The index is not immediately available, but is created in the background. -==== - -.+CREATE TEXT INDEX+ -====== - -//// -CREATE (n0:Label1:Label2 {prop1: 3, prop2: 'Green') -CREATE (n1:Label1:Label3 {prop1: 5, prop2: 'Pink') -CREATE (n2:Label1 {prop1: 7, prop2: 'Blue') -//// - -.Query -[source, cypher, indent=0] ----- -CREATE TEXT INDEX node_index_name FOR (n:Person) ON (n.nickname) ----- - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Indexes added: 1 ----- - -====== - - -[discrete] -[[administration-indexes-create-a-relationship-text-index]] -=== Create a relationship text index - -A named text index on a single property for all relationships with a particular relationship type can be created with: - -[source, syntax, role="noheader"] ----- -CREATE TEXT INDEX index_name FOR ()-[r:TYPE]-() ON (r.property) ----- - -[NOTE] -==== -The index is not immediately available, but is created in the background. -==== - -.+CREATE TEXT INDEX+ -====== - -//// -CREATE (n0:Label1:Label2 {prop1: 3, prop2: 'Green') -CREATE (n1:Label1:Label3 {prop1: 5, prop2: 'Pink') -CREATE (n2:Label1 {prop1: 7, prop2: 'Blue') -CREATE (n0)-[:KNOWS {interest: 'tennis'}]->(n1) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE TEXT INDEX rel_index_name FOR ()-[r:KNOWS]-() ON (r.interest) ----- - -Note that text indexes only recognize string values, do not support multiple properties, and that the index name must be unique. - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Indexes added: 1 ----- - -====== - - -[discrete] -[[administration-indexes-create-a-text-index-only-if-it-does-not-already-exist]] -=== Create a text index only if it does not already exist - -If it is not known whether an index exists or not, add `IF NOT EXISTS` to ensure it does. - - -.+CREATE TEXT INDEX+ -====== - -//// -CREATE (n0:Label1:Label2 {prop1: 3, prop2: 'Green') -CREATE (n1:Label1:Label3 {prop1: 5, prop2: 'Pink') -CREATE (n2:Label1 {prop1: 7, prop2: 'Blue') -//// - -.Query -[source, cypher, indent=0] ----- -CREATE TEXT INDEX node_index_name IF NOT EXISTS FOR (n:Person) ON (n.nickname) ----- - -Note that the index will not be created if there already exists an index with the same schema and type, same name or both. - -.Result -[queryresult] ----- -+--------------------------------------------+ -| No data returned, and nothing was changed. | -+--------------------------------------------+ ----- - -====== - - -[discrete] -[[administration-indexes-create-a-text-index-specifying-the-index-provider]] -=== Create a text index specifying the index provider - -To create a text index with a specific index provider, the `OPTIONS` clause is used. -Only one valid value exists for the index provider, `text-1.0`, which is the default value. - -.+CREATE TEXT INDEX+ -====== - -//// -CREATE (n0:Label1:Label2 {prop1: 3, prop2: 'Green') -CREATE (n1:Label1:Label3 {prop1: 5, prop2: 'Pink') -CREATE (n2:Label1 {prop1: 7, prop2: 'Blue') -CREATE (n0)-[:TYPE1 {prop1: 'tennis'}]->(n1) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE TEXT INDEX index_with_provider FOR ()-[r:TYPE]-() ON (r.prop1) -OPTIONS {indexProvider: 'text-1.0'} ----- - -There is no supported index configuration for text indexes. - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Indexes added: 1 ----- - -====== - - -[discrete] -[[administration-indexes-failure-to-create-an-already-existing-index]] -=== Failure to create an already existing index - -Create an index on the property `title` on nodes with the `Book` label, when that index already exists. - -.+CREATE BTREE INDEX+ -====== - -//// -CREATE (n0:Label1:Label2 {prop1: 3, prop2: 'Green') -CREATE (n1:Label1:Label3 {prop1: 5, prop2: 'Pink') -CREATE (n2:Label1 {prop1: 7, prop2: 'Blue') -CREATE INDEX example_index FOR (n:Book) ON (n.title) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE INDEX bookTitleIndex FOR (book:Book) ON (book.title) ----- - -In this case the index can not be created because it already exists. - -.Error message -[source, role=nocopy, indent=0] ----- -There already exists an index (:Book {title}). ----- - -====== - - -[discrete] -[[administration-indexes-failure-to-create-an-index-with-the-same-name-as-an-already-existing-index]] -=== Failure to create an index with the same name as an already existing index - -Create a named index on the property `numberOfPages` on nodes with the `Book` label, when an index with that name already exists. - - -.+CREATE BTREE INDEX+ -====== - -//// -CREATE (n0:Label1:Label2 {prop1: 3, prop2: 'Green') -CREATE (n1:Label1:Label3 {prop1: 5, prop2: 'Pink') -CREATE (n2:Label1 {prop1: 7, prop2: 'Blue') -CREATE INDEX bookTitleIndex FOR (n:Label1) ON (b.prop1) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE INDEX indexOnBooks FOR (book:Book) ON (book.numberOfPages) ----- - -In this case the index can't be created because there already exists an index with that name. - -.Error message -[source, role=nocopy, indent=0] ----- -There already exists an index called 'indexOnBooks'. ----- - -====== - - -[discrete] -[[administration-indexes-failure-to-create-an-index-when-a-constraint-already-exists]] -=== Failure to create an index when a constraint already exists - -Create an index on the property `isbn` on nodes with the `Book` label, when an index-backed constraint already exists on that schema. - - -.+CREATE BTREE INDEX+ -====== - -//// -CREATE CONSTRAINT .... -//// - -.Query -[source, cypher, indent=0] ----- -CREATE INDEX bookIsbnIndex FOR (book:Book) ON (book.isbn) ----- - -In this case the index can not be created because a index-backed constraint already exists on that label and property combination. - -.Error message -[source, role=nocopy, indent=0] ----- -There is a uniqueness constraint on (:Book {isbn}), so an index is already created that matches this. ----- - -====== - - -[discrete] -[[administration-indexes-failure-to-create-an-index-with-the-same-name-as-an-already-existing-constraint]] -=== Failure to create an index with the same name as an already existing constraint - -Create a named index on the property `numberOfPages` on nodes with the `Book` label, when a constraint with that name already exists. - - -.+CREATE BTREE INDEX+ -====== - -//// -CREATE CONSTRAINT .... -//// - -.Query -[source, cypher, indent=0] ----- -CREATE INDEX bookRecommendations FOR (book:Book) ON (book.recommendations) ----- - -In this case the index can not be created because there already exists a constraint with that name. - -.Error message -[source, role=nocopy, indent=0] ----- -There already exists a constraint called 'bookRecommendations'. ----- - -====== - - -[[administration-indexes-list-indexes]] -== +SHOW INDEXES+ - -Listing indexes can be done with `SHOW INDEXES`, which will produce a table with the following columns: - -[NOTE] -==== -The command `SHOW INDEXES` returns only the default output. -For a full output use the optional `YIELD` command. -Full output: `+SHOW INDEXES YIELD *+`. -==== - -.List indexes output -[options="header", cols="4,6"] -|=== -| Column | Description +.List indexes output +[options="header", width="100%", cols="1a,4,^.^,^"] +|=== +| Column +| Description +| Default output +| Full output | `id` -| The id of the index. label:default-output[] +| The id of the index. +| `+` +| `+` | `name` -| Name of the index (explicitly set by the user or automatically assigned). label:default-output[] +| Name of the index (explicitly set by the user or automatically assigned). +| `+` +| `+` | `state` -| Current state of the index. label:default-output[] +| Current state of the index. +| `+` +| `+` | `populationPercent` -| % of index population. label:default-output[] +| % of index population. +| `+` +| `+` | `uniqueness` -| Tells if the index is only meant to allow one value per key. label:default-output[] +| Tells if the index is only meant to allow one value per key. +| `+` +| `+` | `type` -| The IndexType of this index (`BTREE`, `FULLTEXT`, `LOOKUP`, or `TEXT`). label:default-output[] +| The IndexType of this index (`BTREE`, `FULLTEXT`, or `LOOKUP`). +| `+` +| `+` | `entityType` -| Type of entities this index represents (nodes or relationship). label:default-output[] +| Type of entities this index represents (nodes or relationship). +| `+` +| `+` | `labelsOrTypes` -| The labels or relationship types of this index. label:default-output[] +| The labels or relationship types of this index. +| `+` +| `+` | `properties` -| The properties of this index. label:default-output[] +| The properties of this index. +| `+` +| `+` | `indexProvider` -| The index provider for this index. label:default-output[] +| The index provider for this index. +| `+` +| `+` | `options` | The options passed to `CREATE` command. +| +| `+` | `failureMessage` | The failure description of a failed index. +| +| `+` | `createStatement` | Statement used to create the index. - +| +| `+` |=== Listing indexes also allows for `WHERE` and `YIELD` clauses to filter the returned rows and columns. [NOTE] ==== -While the command for listing indexes require the xref::access-control/database-administration.adoc#access-control-database-administration-index[`SHOW INDEX` privilege], the deprecated built-in procedures for listing indexes, such as `db.indexes`, work as before and are not affected by the privilege. +The deprecated built-in procedures for listing indexes, such as `db.indexes`, work as before and are not affected by the xref:access-control/database-administration.adoc#access-control-database-administration-index[`SHOW INDEXES` privilege]. ==== -== +SHOW INDEXES+ examples - -* xref::indexes-for-search-performance.adoc#administration-indexes-listing-all-indexes[] -* xref::indexes-for-search-performance.adoc#administration-indexes-listing-indexes-with-filtering[] - +=== Listing indexes examples -[discrete] +//cypher/cypher-docs/target/docs/dev/ql/administration/indexes/listing-all-indexes.asciidoc [[administration-indexes-listing-all-indexes]] -=== Listing all indexes +== Listing all indexes == To list all indexes with the default output columns, the `SHOW INDEXES` command can be used. If all columns are required, use `SHOW INDEXES YIELD *`. - -.+SHOW INDEXES+ -====== - -//// -create BTREE index `index_44d2128f` for (n:`Person`) ON (n.`middlename`); -create BTREE index `index_58a1c03e` for (n:`Person`) ON (n.`location`); -create BTREE index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); -create BTREE index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); -create TEXT index `index_763f72db` for (n:`Person`) ON (n.`middlename`); -create TEXT index `index_eadb868e` for (n:`Person`) ON (n.`surname`); -//// - .Query -[source, cypher, indent=0] +[source,cypher] ---- SHOW INDEXES ---- + One of the output columns from `SHOW INDEXES` is the name of the index. -This can be used to drop the index with the xref::indexes-for-search-performance.adoc#administration-indexes-drop-an-index[`DROP INDEX` command]. +This can be used to drop the index with the xref:indexes-for-search-performance.adoc#administration-indexes-drop-an-index[`DROP INDEX` command]. .Result [queryresult] ---- -+-------------------------------------------------------------------------------------------------------------------------------------------------+ -| id | name | state | populationPercent | uniqueness | type | entityType | labelsOrTypes | properties | indexProvider | -+-------------------------------------------------------------------------------------------------------------------------------------------------+ -| 4 | "index_44d2128f" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "NODE" | ["Person"] | ["middlename"] | "native-btree-1.0" | -| 7 | "index_58a1c03e" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "NODE" | ["Person"] | ["location"] | "native-btree-1.0" | -| 5 | "index_763f72db" | "ONLINE" | 100.0 | "NONUNIQUE" | "TEXT" | "NODE" | ["Person"] | ["middlename"] | "text-1.0" | -| 8 | "index_d7c12ba3" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "NODE" | ["Person"] | ["highScore"] | "native-btree-1.0" | -| 3 | "index_deeafdb2" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "NODE" | ["Person"] | ["firstname"] | "native-btree-1.0" | -| 6 | "index_eadb868e" | "ONLINE" | 100.0 | "NONUNIQUE" | "TEXT" | "NODE" | ["Person"] | ["surname"] | "text-1.0" | -+-------------------------------------------------------------------------------------------------------------------------------------------------+ -6 rows ----- ++------------------------------------------------------------------------------------------------------------------------------------------------+ +| id | name | state | populationPercent | uniqueness | type | entityType | labelsOrTypes | properties | indexProvider | ++------------------------------------------------------------------------------------------------------------------------------------------------+ +| 4 | "index_58a1c03e" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "NODE" | ["Person"] | ["location"] | "native-btree-1.0" | +| 5 | "index_d7c12ba3" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "NODE" | ["Person"] | ["highScore"] | "native-btree-1.0" | +| 3 | "index_deeafdb2" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "NODE" | ["Person"] | ["firstname"] | "native-btree-1.0" | ++------------------------------------------------------------------------------------------------------------------------------------------------+ +3 rows +---- + + +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; + -====== +SHOW INDEXES +---- -[discrete] +//cypher/cypher-docs/target/docs/dev/ql/administration/indexes/listing-indexes-with-filtering.asciidoc [[administration-indexes-listing-indexes-with-filtering]] -=== Listing indexes with filtering +== Listing indexes with filtering == One way of filtering the output from `SHOW INDEXES` by index type is the use of type keywords, -listed in the xref::indexes-for-search-performance.adoc#administration-indexes-syntax[syntax table]. -For example, to show only B-tree indexes, use `SHOW BTREE INDEXES`. +listed in the xref:indexes-for-search-performance.adoc#administration-indexes-syntax[syntax table]. +For example, to show only b-tree indexes, use `SHOW BTREE INDEXES`. Another more flexible way of filtering the output is to use the `WHERE` clause. An example is to only show indexes not belonging to constraints. - -.+SHOW BTREE INDEXES+ -====== - -//// -create BTREE index `index_44d2128f` for (n:`Person`) ON (n.`middlename`); -create BTREE index `index_58a1c03e` for (n:`Person`) ON (n.`location`); -create BTREE index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); -create BTREE index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); -create TEXT index `index_763f72db` for (n:`Person`) ON (n.`middlename`); -create TEXT index `index_eadb868e` for (n:`Person`) ON (n.`surname`); -//// - .Query -[source, cypher, indent=0] +[source,cypher] ---- SHOW BTREE INDEXES WHERE uniqueness = 'NONUNIQUE' ---- + This will only return the default output columns. -To get all columns, use `+SHOW INDEXES YIELD * WHERE ...+`. +To get all columns, use `SHOW INDEXES YIELD * WHERE ...`. .Result [queryresult] ---- -+-----------------------------------------------------------------------------------------------------------------------------------------------------+ -| id | name | state | populationPercent | uniqueness | type | entityType | labelsOrTypes | properties | indexProvider | -+-----------------------------------------------------------------------------------------------------------------------------------------------------+ -| 4 | "index_44d2128f" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "NODE" | ["Person"] | ["middlename"] | "native-btree-1.0" | -| 7 | "index_58a1c03e" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "NODE" | ["Person"] | ["location"] | "native-btree-1.0" | -| 9 | "index_c207e3e6" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "RELATIONSHIP" | ["KNOWS"] | ["since"] | "native-btree-1.0" | -| 8 | "index_d7c12ba3" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "NODE" | ["Person"] | ["highScore"] | "native-btree-1.0" | -| 3 | "index_deeafdb2" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "NODE" | ["Person"] | ["firstname"] | "native-btree-1.0" | -+-----------------------------------------------------------------------------------------------------------------------------------------------------+ -5 rows ----- - -====== - - -[[administration-indexes-drop-indexes]] -== +DROP INDEX+ ++----------------------------------------------------------------------------------------------------------------------------------------------------+ +| id | name | state | populationPercent | uniqueness | type | entityType | labelsOrTypes | properties | indexProvider | ++----------------------------------------------------------------------------------------------------------------------------------------------------+ +| 4 | "index_58a1c03e" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "NODE" | ["Person"] | ["location"] | "native-btree-1.0" | +| 6 | "index_c207e3e6" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "RELATIONSHIP" | ["KNOWS"] | ["since"] | "native-btree-1.0" | +| 5 | "index_d7c12ba3" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "NODE" | ["Person"] | ["highScore"] | "native-btree-1.0" | +| 3 | "index_deeafdb2" | "ONLINE" | 100.0 | "NONUNIQUE" | "BTREE" | "NODE" | ["Person"] | ["firstname"] | "native-btree-1.0" | ++----------------------------------------------------------------------------------------------------------------------------------------------------+ +4 rows +---- + + +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; -An index can be dropped (removed) using the name with the `DROP INDEX index_name` command. -This command can drop indexes of any type, except those backing constraints. -The name of the index can be found using the xref::indexes-for-search-performance.adoc#administration-indexes-list-indexes[`SHOW INDEXES` command], given in the output column `name`. +SHOW BTREE INDEXES WHERE uniqueness = 'NONUNIQUE' +---- -[[drop-indexes-examples]] -== +DROP INDEX+ examples -* xref::indexes-for-search-performance.adoc#administration-indexes-drop-an-index[] -* xref::indexes-for-search-performance.adoc#administration-indexes-drop-a-non-existing-index[] +[[administration-indexes-drop-indexes]] +== Deleting indexes -[discrete] [[administration-indexes-drop-an-index]] -=== Drop an index - - -.+DROP INDEX+ -====== - -//// -create BTREE index `index_44d2128f` for (n:`Person`) ON (n.`middlename`); -create BTREE index `index_58a1c03e` for (n:`Person`) ON (n.`location`); -create BTREE index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); -create BTREE index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); -create TEXT index `index_763f72db` for (n:`Person`) ON (n.`middlename`); -create TEXT index `index_eadb868e` for (n:`Person`) ON (n.`surname`); -//// +== Drop an index == +An index can be dropped using the name with the `DROP INDEX index_name` command. This command can drop b-tree, fulltext, or token lookup indexes. +The name of the index can be found using the xref:indexes-for-search-performance.adoc#administration-indexes-list-indexes[`SHOW INDEXES` command], given in the output column `name`. .Query -[source, cypher, indent=0] +[source,cypher] ---- DROP INDEX index_name ---- + .Result [queryresult] ---- @@ -1469,25 +1619,97 @@ DROP INDEX index_name Indexes removed: 1 ---- -====== +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; -[discrete] -[[administration-indexes-drop-a-non-existing-index]] -=== Drop a non-existing index -If it is uncertain if an index exists and you want to drop it if it does but not get an error should it not, use `IF EXISTS`. +DROP INDEX index_name +---- -.+DROP INDEX+ -====== +[[administration-indexes-drop-a-non-existing-index]] +== Drop a non-existing index == +If it is uncertain if an index exists and you want to drop it if it does but not get an error should it not, use: .Query -[source, cypher, indent=0] +[source,cypher] ---- DROP INDEX missing_index_name IF EXISTS ---- + .Result [queryresult] ---- @@ -1496,777 +1718,207 @@ DROP INDEX missing_index_name IF EXISTS +--------------------------------------------+ ---- -====== - - -[[indexes-future-indexes]] -== Future indexes - -Two new types of indexes, point and range indexes, will be introduced in a future release. -They cannot be used in queries yet, but they can be created and dropped for migration purposes. -These new index types together with text indexes will replace the current B-tree indexes. -For more details on these new types, see the link:{neo4j-docs-base-uri}/operations-manual/{page-version}/performance/index-configuration#future-indexes[Operations Manual -> Future indexes]. -Like B-tree indexes, range indexes are created on one or more properties for all nodes or relationships with a given label or relationship type: +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create index `index_name` for (n:`Person`) ON (n.`surname`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; -* An index created on a single property for any given label or relationship type is called a _single-property index_. -* An index created on more than one property for any given label or relationship type is called a _composite index_. -The differences in the usage patterns between composite and single-property indexes described in xref::indexes-for-search-performance.adoc#administration-indexes-single-vs-composite-index[] also applies to range indexes. - -Similar to B-tree indexes, range indexes may also back constraints by giving the range index provider when creating an index-backed constraint. - -Point indexes, like text indexes, are a kind of single-property indexes, with the limitation that they only recognize properties with point values. -Nodes or relationships with the indexed label or relationship type where the indexed property is of another value type are not included in the index. +DROP INDEX missing_index_name IF EXISTS +---- -=== Syntax -[IMPORTANT] -==== -The index name must be unique among both indexes and constraints. -==== +[role=deprecated] +[[administration-indexes-examples-deprecated-syntax]] +== Deprecated syntax [NOTE] ==== -Best practice is to give the index a name when it is created. -If the index is not explicitly named, it gets an auto-generated name. +This syntax does not support dropping relationship property indexes, these can only be dropped by name. ==== -[NOTE] -==== -The `+CREATE ... INDEX ...+` command is optionally idempotent, with the default behavior to throw an error if you attempt to create the same index twice. -With `IF NOT EXISTS`, no error is thrown and nothing happens should an index with the same name or same schema and index type already exist. -It may still throw an error if conflicting constraints exist, such as constraints with the same name or schema and backing index type. -==== +[[administration-indexes-drop-a-single-property-index]] +== Drop a single-property index == +An index on all nodes that have a label and single property combination can be dropped with `DROP INDEX ON :Label(property)`. +.Query +[source,cypher] +---- +DROP INDEX ON :Person(firstname) +---- -.+Create a range index on nodes+ -[options="noheader", width="100%", cols="2, 8a"] -|=== -| Syntax -| -[source, syntax, role="noheader"] +.Result +[queryresult] ---- -CREATE RANGE INDEX [index_name] [IF NOT EXISTS] -FOR (n:LabelName) -ON (n.propertyName_1[, - n.propertyName_2, - ... - n.propertyName_n]) -[OPTIONS "{" option: value[, ...] "}"] ++-------------------+ +| No data returned. | ++-------------------+ +Indexes removed: 1 ---- -| Description -| -Create a range index on nodes, either on a single property or composite. -Index provider can be specified using the `OPTIONS` clause. +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; -|=== +DROP INDEX ON :Person(firstname) +---- -.+Create a range index on relationships+ -[options="noheader", width="100%", cols="2, 8a"] -|=== -| Syntax -| -[source, syntax, role="noheader"] +[[administration-indexes-drop-a-composite-index]] +== Drop a composite index == +A composite index on all nodes that have a label and multiple property combination can be dropped with `DROP INDEX ON :Label(prop1, ..., propN)`. The following statement will drop a composite index on all nodes labeled with `Person` and which have both an `age` and `country` property: + +.Query +[source,cypher] ---- -CREATE RANGE INDEX [index_name] [IF NOT EXISTS] -FOR ()-"["r:TYPE_NAME"]"-() -ON (r.propertyName_1[, - r.propertyName_2, - ... - r.propertyName_n]) -[OPTIONS "{" option: value[, ...] "}"] +DROP INDEX ON :Person(age, country) ---- -| Description -| -Create a range index on relationships, either on a single property or composite. - -Index provider can be specified using the `OPTIONS` clause. - -|=== - - -.+Create a point index on nodes+ -[options="noheader", width="100%", cols="2, 8a"] -|=== - -| Syntax -| -[source, syntax, role="noheader"] ----- -CREATE POINT INDEX [index_name] [IF NOT EXISTS] -FOR (n:LabelName) -ON (n.propertyName) -[OPTIONS "{" option: value[, ...] "}"] ----- - -| Description -| -Create a point index on nodes where the property has a point value. - -Index provider and configuration can be specified using the `OPTIONS` clause. - -|=== - - -.+Create a point index on relationships+ -[options="noheader", width="100%", cols="2, 8a"] -|=== - -| Syntax -| -[source, syntax, role="noheader"] ----- -CREATE POINT INDEX [index_name] [IF NOT EXISTS] -FOR ()-"["r:TYPE_NAME"]"-() -ON (r.propertyName) -[OPTIONS "{" option: value[, ...] "}"] ----- - -| Description -| -Create a point index on relationships where the property has a point value. - -Index provider and configuration can be specified using the `OPTIONS` clause. - -|=== - - -.+Drop an index+ -[options="noheader", width="100%", cols="2, 8a"] -|=== - -| Syntax -| -[source, syntax, role="noheader"] ----- -DROP INDEX index_name [IF EXISTS] ----- - -| Description -| -Drop an index of any index type. - -This is the xref::indexes-for-search-performance.adoc#administration-indexes-drop-indexes[same command] as for the existing indexes. - -| Note -| -The command is optionally idempotent, with the default behavior to throw an error if you attempt to drop the same index twice. -With `IF EXISTS`, no error is thrown and nothing happens should the index not exist. - -|=== - - -.+List indexes+ -[options="noheader", width="100%", cols="2, 8a"] -|=== - -| Syntax -| -[source, syntax, role="noheader"] ----- -SHOW [ALL \| BTREE \| FULLTEXT \| LOOKUP \| POINT \| RANGE \| TEXT] INDEX[ES] - [YIELD { * \| field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] - [WHERE expression] - [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]] ----- - -| Description -| Extending the existing xref::indexes-for-search-performance.adoc#administration-indexes-list-indexes[list index command] with filters for the new types. - -| Note -| When using the `RETURN` clause, the `YIELD` clause is mandatory and must not be omitted. - -|=== - - -=== Examples - -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-single-property-range-index-for-nodes[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-single-property-range-index-for-relationships[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-range-index-only-if-it-does-not-already-exist[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-range-index-specifying-the-index-provider[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-composite-range-index-for-nodes[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-composite-range-index-for-relationships[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-node-point-index[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-relationship-point-index[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-point-index-only-if-it-does-not-already-exist[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-point-index-specifying-the-index-provider[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-point-index-specifying-the-index-configuration[] -* xref::indexes-for-search-performance.adoc#administration-indexes-create-a-point-index-specifying-both-the-index-provider-and-configuration[] - - -[discrete] -[[administration-indexes-create-a-single-property-range-index-for-nodes]] -==== Create a single-property range index for nodes - -A named range index on a single property for all nodes with a particular label can be created with: - -[source, syntax, role="noheader"] ----- -CREATE RANGE INDEX index_name FOR (n:Label) ON (n.property) ----- - -[NOTE] -==== -The index is not immediately available, but is created in the background. -==== - -.+CREATE RANGE INDEX+ -====== - -//// -create (p0:Person {surname: 'Smith'}) -create (p1:Person {surename: 'John'}) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE RANGE INDEX node_range_index_name FOR (n:Person) ON (n.surname) ----- - -Note that the index name must be unique. - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Indexes added: 1 ----- - -====== - - -[discrete] -[[administration-indexes-create-a-single-property-range-index-for-relationships]] -==== Create a single-property range index for relationships - -A named range index on a single property for all relationships with a particular relationship type can be created with: - -[source, syntax, role="noheader"] ----- -CREATE RANGE INDEX index_name FOR ()-[r:TYPE]-() ON (r.property) ----- - -[NOTE] -==== -The index is not immediately available, but is created in the background. -==== - - -.+CREATE RANGE INDEX+ -====== - -//// -CREATE (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `middlename`:"Ron", `name`:"john", `surname`:"Smith"}) -CREATE (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `middlename`:"Mark", `name`:"andy", `surname`:"Jones"}) -CREATE (_0)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_1) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE RANGE INDEX rel_range_index_name FOR ()-[r:KNOWS]-() ON (r.since) ----- - -Note that the index name must be unique. - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Indexes added: 1 ----- - -====== - - -[discrete] -[[administration-indexes-create-a-range-index-only-if-it-does-not-already-exist]] -==== Create a range index only if it does not already exist - -If it is not known whether an index exists or not, add `IF NOT EXISTS` to ensure it does. - - -.+CREATE RANGE INDEX+ -====== - -//// -CREATE (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `middlename`:"Ron", `name`:"john", `surname`:"Smith"}) -CREATE (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `middlename`:"Mark", `name`:"andy", `surname`:"Jones"}) -CREATE (_0)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_1) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE RANGE INDEX node_range_index_name IF NOT EXISTS FOR (n:Person) ON (n.surname) ----- - -Note that the index will not be created if there already exists an index with the same schema and type, same name or both. - -.Result -[queryresult] ----- -+--------------------------------------------+ -| No data returned, and nothing was changed. | -+--------------------------------------------+ ----- - -====== - - -[discrete] -[[administration-indexes-create-a-range-index-specifying-the-index-provider]] -==== Create a range index specifying the index provider - -To create a range index with a specific index provider, the `OPTIONS` clause is used. -Only one valid value exists for the index provider, `range-1.0`, which is the default value. - - -.+CREATE RANGE INDEX+ -====== - -//// -CREATE (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `middlename`:"Ron", `name`:"john", `surname`:"Smith"}) -CREATE (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `middlename`:"Mark", `name`:"andy", `surname`:"Jones"}) -CREATE (_0)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_1) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE RANGE INDEX range_index_with_provider FOR ()-[r:TYPE]-() ON (r.prop1) -OPTIONS {indexProvider: 'range-1.0'} ----- - -There is no supported index configuration for range indexes. - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Indexes added: 1 ----- - -====== - - -[discrete] -[[administration-indexes-create-a-composite-range-index-for-nodes]] -==== Create a composite range index for nodes - -A named range index on multiple properties for all nodes with a particular label -- i.e. a composite index -- can be created with: - -[source, syntax, role="noheader"] ----- -CREATE RANGE INDEX index_name FOR (n:Label) ON (n.prop1, ..., n.propN) ----- - -Only nodes with the specified label and that contain all the properties in the index definition will be added to the index. - -[NOTE] -==== -The composite index is not immediately available, but is created in the background. -==== - - -.+CREATE RANGE INDEX+ -====== - -The following statement will create a named composite range index on all nodes labeled with `Person` and which have both an `age` and `country` property: - -//// -CREATE (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `middlename`:"Ron", `name`:"john", `surname`:"Smith"}) -CREATE (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `middlename`:"Mark", `name`:"andy", `surname`:"Jones"}) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE RANGE INDEX composite_range_node_index_name FOR (n:Person) ON (n.age, n.country) ----- - -Note that the index name must be unique. - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Indexes added: 1 ----- - -====== - - -[discrete] -[[administration-indexes-create-a-composite-range-index-for-relationships]] -==== Create a composite range index for relationships - -A named range index on multiple properties for all relationships with a particular relationship type -- i.e. a composite index -- can be created with: - -[source, syntax, role="noheader"] ----- -CREATE RANGE INDEX index_name FOR ()-[r:TYPE]-() ON (r.prop1, ..., r.propN) ----- - -Only relationships with the specified type and that contain all the properties in the index definition will be added to the index. - -[NOTE] -==== -The composite index is not immediately available, but is created in the background. -==== - - -.+CREATE RANGE INDEX+ -====== - -The following statement will create a named composite range index on all relationships labeled with `PURCHASED` and which have both a `date` and `amount` property: - -//// -CREATE (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `middlename`:"Ron", `name`:"john", `surname`:"Smith"}) -CREATE (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `middlename`:"Mark", `name`:"andy", `surname`:"Jones"}) -CREATE (_0)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_1) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE RANGE INDEX composite_range_rel_index_name FOR ()-[r:PURCHASED]-() ON (r.date, r.amount) ----- - -Note that the index name must be unique. - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Indexes added: 1 ----- - -====== - - -[discrete] -[[administration-indexes-create-a-node-point-index]] -==== Create a node point index - -A named point index on a single property for all nodes with a particular label can be created with: - -[source, syntax, role="noheader"] ----- -CREATE POINT INDEX index_name FOR (n:Label) ON (n.property) ----- - -[NOTE] -==== -The index is not immediately available, but is created in the background. -==== - - -.+CREATE POINT INDEX+ -====== - -//// -CREATE (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `middlename`:"Ron", `name`:"john", `surname`:"Smith"}) -CREATE (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `middlename`:"Mark", `name`:"andy", `surname`:"Jones"}) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE POINT INDEX node_index_name FOR (n:Person) ON (n.location) ----- - -Note that point indexes only recognize point values, do not support multiple properties, and that the index name must be unique. - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Indexes added: 1 ----- - -====== - - -[discrete] -[[administration-indexes-create-a-relationship-point-index]] -==== Create a relationship point index - -A named point index on a single property for all relationships with a particular relationship type can be created with: - -[source, syntax, role="noheader"] ----- -CREATE POINT INDEX index_name FOR ()-[r:TYPE]-() ON (r.property) ----- - -[NOTE] -==== -The index is not immediately available, but is created in the background. -==== - - -.+CREATE POINT INDEX+ -====== - -//// -CREATE (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `middlename`:"Ron", `name`:"john", `surname`:"Smith"}) -CREATE (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `middlename`:"Mark", `name`:"andy", `surname`:"Jones"}) -CREATE (_0)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_1) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE POINT INDEX rel_index_name FOR ()-[r:STREET]-() ON (r.intersection) ----- - -Note that point indexes only recognize point values, do not support multiple properties, and that the index name must be unique. - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Indexes added: 1 ----- - -====== - - -[discrete] -[[administration-indexes-create-a-point-index-only-if-it-does-not-already-exist]] -==== Create a point index only if it does not already exist - -If it is not known whether an index exists or not, add `IF NOT EXISTS` to ensure it does. - - -.+CREATE POINT INDEX+ -====== - -//// -CREATE (p0:Person {location: point({x: 5, y: 10})}) -CREATE (p1:Person {location: point({x: 7, y: 11})}) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE POINT INDEX node_index_name IF NOT EXISTS FOR (n:Person) ON (n.location) ----- - -Note that the index will not be created if there already exists an index with the same schema and type, same name or both. - -.Result -[queryresult] ----- -+--------------------------------------------+ -| No data returned, and nothing was changed. | -+--------------------------------------------+ ----- - -====== - - -[discrete] -[[administration-indexes-create-a-point-index-specifying-the-index-provider]] -==== Create a point index specifying the index provider - -To create a point index with a specific index provider, the `OPTIONS` clause is used. -Only one valid value exists for the index provider, `point-1.0`, which is the default value. - -.+CREATE POINT INDEX+ -====== - -//// -CREATE (p0:Person {location: point({x: 5, y: 10})}) -CREATE (p1:Person {location: point({x: 7, y: 11})}) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE POINT INDEX index_with_provider FOR (n:Label) ON (n.prop1) -OPTIONS {indexProvider: 'point-1.0'} ----- - -Can be combined with specifying index configuration. - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Indexes added: 1 ----- - -====== - - -[discrete] -[[administration-indexes-create-a-point-index-specifying-the-index-configuration]] -==== Create a point index specifying the index configuration - -To create a point index with a specific index configuration, the `OPTIONS` clause is used. -The valid configuration settings are: - -* `spatial.cartesian.min` -* `spatial.cartesian.max` -* `spatial.cartesian-3d.min` -* `spatial.cartesian-3d.max` -* `spatial.wgs-84.min` -* `spatial.wgs-84.max` -* `spatial.wgs-84-3d.min` -* `spatial.wgs-84-3d.max` - -Non-specified settings have their respective default values. - - -.+CREATE POINT INDEX+ -====== - -//// -CREATE (p0:Person {location: point({x: 5, y: 10})}) -CREATE (p1:Person {location: point({x: 7, y: 11})}) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE POINT INDEX index_with_config FOR (n:Label) ON (n.prop2) -OPTIONS { - indexConfig: { - `spatial.cartesian.min`: [-100.0, -100.0], - `spatial.cartesian.max`: [100.0, 100.0] - } -} ----- - -Can be combined with specifying index provider. - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Indexes added: 1 ----- - -====== - - -[discrete] -[[administration-indexes-create-a-point-index-specifying-both-the-index-provider-and-configuration]] -==== Create a point index specifying both the index provider and configuration - -To create a point index with a specific index provider and configuration, the `OPTIONS` clause is used. -Only one valid value exists for the index provider, `point-1.0`, which is the default value. -The valid configuration settings are: - -* `spatial.cartesian.min` -* `spatial.cartesian.max` -* `spatial.cartesian-3d.min` -* `spatial.cartesian-3d.max` -* `spatial.wgs-84.min` -* `spatial.wgs-84.max` -* `spatial.wgs-84-3d.min` -* `spatial.wgs-84-3d.max` - -Non-specified settings have their respective default values. - - -.+CREATE POINT INDEX+ -====== - -//// -CREATE (p0:Person {location: point({x: 5, y: 10})}) -CREATE (p1:Person {location: point({x: 7, y: 11})}) -CREATE (p0)-[:TYPE {prop1: point({x: 5, y: 10})}]->(p1) -//// - -.Query -[source, cypher, indent=0] ----- -CREATE POINT INDEX index_with_options FOR ()-[r:TYPE]-() ON (r.prop1) -OPTIONS { - indexProvider: 'point-1.0', - indexConfig: { - `spatial.wgs-84.min`: [-100.0, -80.0], - `spatial.wgs-84.max`: [100.0, 80.0] - } -} ----- - -Specifying index provider and configuration can be done individually. - -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Indexes added: 1 ----- - -====== - - -[role=deprecated] -[[administration-indexes-examples-deprecated-syntax]] -== Deprecated syntax - -[NOTE] -==== -This syntax only supports dropping B-tree node property indexes, all others can only be dropped by name. -==== - -[[administration-indexes-drop-a-single-property-index]] -=== Drop a single-property index - -A B-tree index on all nodes with a label and single property combination can be dropped with: - -[source, syntax, role="noheader"] ----- -DROP INDEX ON :Label(property) ----- - - -.+DROP INDEX+ -====== - -//// -CREATE BTREE index `index_deeafdb2` for (n:Person) ON (n.firstname) -//// - -.Query -[source, cypher, indent=0] ----- -DROP INDEX ON :Person(firstname) ----- .Result [queryresult] @@ -2277,42 +1929,82 @@ DROP INDEX ON :Person(firstname) Indexes removed: 1 ---- -====== - - -[[administration-indexes-drop-a-composite-index]] -=== Drop a composite index - -A composite B-tree index on all nodes with a label and multiple property combination can be dropped with: - -[source, syntax, role="noheader"] ----- -DROP INDEX ON :Label(prop1, ..., propN) ----- - -.+DROP INDEX+ -====== +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; -The following statement will drop a composite index on all nodes labeled with `Person` and which have both an `age` and `country` property: -//// -CREATE BTREE index `index_44d2128f` for (n:Person) ON (n.middlename, n.country) -//// - -.Query -[source, cypher, indent=0] ----- DROP INDEX ON :Person(age, country) ---- -.Result -[queryresult] ----- -+-------------------+ -| No data returned. | -+-------------------+ -Indexes removed: 1 ----- -====== - diff --git a/modules/ROOT/pages/introduction/index.adoc b/modules/ROOT/pages/introduction/index.adoc index 928be4699..07f9e951c 100644 --- a/modules/ROOT/pages/introduction/index.adoc +++ b/modules/ROOT/pages/introduction/index.adoc @@ -1,30 +1,20 @@ -:description: This section provides an introduction to the Cypher query language. - [[cypher-intro]] = Introduction +:description: This section provides an introduction to the Cypher query language. [[cypher-introduction]] == What is Cypher? -Cypher is a declarative graph query language that allows for expressive and efficient xref::introduction/quering-updating-administering.adoc[querying, updating and administering] of the graph. +Cypher is a declarative graph query language that allows for expressive and efficient xref:introduction/quering-updating-administering.adoc[querying, updating and administering] of the graph. It is designed to be suitable for both developers and operations professionals. Cypher is designed to be simple, yet powerful; highly complicated database queries can be easily expressed, enabling you to focus on your domain, instead of getting lost in database access. Cypher is inspired by a number of different approaches and builds on established practices for expressive querying. -Many of the keywords, such as `WHERE` and `ORDER BY`, are inspired by link:https://en.wikipedia.org/wiki/SQL[SQL]. -Pattern matching borrows expression approaches from link:https://en.wikipedia.org/wiki/SPARQL[SPARQL]. +Many of the keywords, such as `WHERE` and `ORDER BY`, are inspired by http://en.wikipedia.org/wiki/SQL[SQL]. +Pattern matching borrows expression approaches from http://en.wikipedia.org/wiki/SPARQL[SPARQL]. Some of the list semantics are borrowed from languages such as Haskell and Python. Cypher's constructs, based on English prose and neat iconography, make queries easy, both to write and to read. -[NOTE] -==== -* Cypher keywords are not case-sensitive. -* Cypher is case-sensitive for variables. -* There are special naming rules for database names. -* There are special naming rules for database aliases. -==== - - **Structure** Cypher borrows its structure from SQL -- queries are built up using various clauses. @@ -33,8 +23,7 @@ Clauses are chained together, and they feed intermediate result sets between eac For example, the matching variables from one `MATCH` clause will be the context that the next clause exists in. The query language is comprised of several distinct clauses. -These are discussed in more detail in the chapter on xref::clauses/index.adoc[Clauses]. - +These are discussed in more detail in the chapter on xref:clauses/index.adoc[Clauses]. The following are a few examples of clauses used to read from the graph: @@ -46,24 +35,12 @@ The following are a few examples of clauses used to read from the graph: * `RETURN`: What to return. - -And these are examples of clauses that are used to update the graph: - -* `CREATE` (and `DELETE`): Create (and delete) nodes and relationships. - -* `SET` (and `REMOVE`): Set values to properties and add labels on nodes using `SET` and use `REMOVE` to remove them. - -* `MERGE`: Match existing or create new nodes and patterns. This is especially useful together with unique constraints. - - -.Cypher Query -====== - Let's see `MATCH` and `RETURN` in action. + Let's create a simple example graph with the following query: -[source,cypher, indent=0] +[source,cypher] ---- CREATE (john:Person {name: 'John'}) CREATE (joe:Person {name: 'Joe'}) @@ -74,28 +51,67 @@ CREATE (john)-[:FRIEND]->(joe)-[:FRIEND]->(steve) CREATE (john)-[:FRIEND]->(sara)-[:FRIEND]->(maria) ---- -image::graph1.svg[] +.Example Graph +["dot", "Example-Graph-cypher-intro.svg", "neoviz", ""] +---- + N0 [ + label = "{Person|name = \'John\'\l}" + ] + N0 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "FRIEND\n" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "FRIEND\n" + ] + N1 [ + label = "{Person|name = \'Joe\'\l}" + ] + N1 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "FRIEND\n" + ] + N2 [ + label = "{Person|name = \'Steve\'\l}" + ] + N3 [ + label = "{Person|name = \'Sara\'\l}" + ] + N3 -> N4 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "FRIEND\n" + ] + N4 [ + label = "{Person|name = \'Maria\'\l}" + ] +---- + For example, here is a query which finds a user called *'John'* and *'John's'* friends (though not his direct friends) before returning both *'John'* and any friends-of-friends that are found. -[source,cypher, indent=0] +[source,cypher] ---- MATCH (john {name: 'John'})-[:FRIEND]->()-[:FRIEND]->(fof) RETURN john.name, fof.name ---- + Resulting in: [queryresult] ---- -Rows: 2 - +----------------------+ | john.name | fof.name | +----------------------+ -| 'John' | 'Maria' | -| 'John' | 'Steve' | +| "John" | "Maria" | +| "John" | "Steve" | +----------------------+ +2 rows ---- @@ -103,27 +119,34 @@ Next up we will add filtering to set more parts in motion: We take a list of user names and find all nodes with names from this list, match their friends and return only those followed users who have a *'name'* property starting with *'S'*. - -[source,cypher, indent=0] +[source,cypher] ---- MATCH (user)-[:FRIEND]->(follower) WHERE user.name IN ['Joe', 'John', 'Sara', 'Maria', 'Steve'] AND follower.name =~ 'S.*' RETURN user.name, follower.name ---- + Resulting in: [queryresult] ---- -Rows: 2 - +---------------------------+ | user.name | follower.name | +---------------------------+ -| 'John' | 'Sara' | -| 'Joe' | 'Steve' | +| "John" | "Sara" | +| "Joe" | "Steve" | +---------------------------+ +2 rows ---- -====== + +And these are examples of clauses that are used to update the graph: + +* `CREATE` (and `DELETE`): Create (and delete) nodes and relationships. + +* `SET` (and `REMOVE`): Set values to properties and add labels on nodes using `SET` and use `REMOVE` to remove them. + +* `MERGE`: Match existing or create new nodes and patterns. This is especially useful together with unique constraints. + diff --git a/modules/ROOT/pages/introduction/neo4j-databases-graphs.adoc b/modules/ROOT/pages/introduction/neo4j-databases-graphs.adoc index 1fea90687..f8452878f 100644 --- a/modules/ROOT/pages/introduction/neo4j-databases-graphs.adoc +++ b/modules/ROOT/pages/introduction/neo4j-databases-graphs.adoc @@ -1,12 +1,6 @@ -:description: This section describes databases and graphs in Neo4j. - [[neo4j-databases-graphs]] = Neo4j databases and graphs - -[abstract] --- -This section describes databases and graphs in Neo4j. --- +:description: This section describes databases and graphs in Neo4j. Cypher queries are executed against a Neo4j database, but normally apply to specific graphs. It is important to understand the meaning of these terms and exactly when a graph is not a database. @@ -18,7 +12,7 @@ A client session provides access to any graph in the DBMS. Graph:: This is a data model within a database. -Normally there is only one graph within each database, and many xref::introduction/quering-updating-administering.adoc[administrative] commands that refer to a specific graph do so using the database name. +Normally there is only one graph within each database, and many xref:introduction/quering-updating-administering.adoc[administrative] commands that refer to a specific graph do so using the database name. + Cypher queries executed in a session may declare which graph they apply to, or use a default, given by the session. + @@ -27,7 +21,7 @@ In Neo4j Fabric it is possible to refer to multiple graphs within the same query Database:: A database is a storage and retrieval mechanism for collecting data in a defined space on disk and in memory. -Most of the time Cypher queries are xref::introduction/quering-updating-administering.adoc[reading or updating queries], which are run against a graph. +Most of the time Cypher queries are xref:introduction/quering-updating-administering.adoc[reading or updating queries], which are run against a graph. There are also administrative commands that apply to a database, or to the entire DBMS. Administrative commands cannot be run in a session connected to a normal user database, but instead need to be run within a session connected to the _system_ database. @@ -41,61 +35,37 @@ A fresh installation of Neo4j includes two databases: * `system` - the system database described above, containing meta-data on the DBMS and security configuration. * `neo4j` - the default database, named using the config option `dbms.default_database=neo4j`. -For more information about the _system_ database, see the sections on xref::databases.adoc[Database management] and xref::access-control/index.adoc[Access control]. +For more information about the _system_ database, see the sections on xref:databases.adoc[Database management] and xref:access-control/index.adoc[Access control]. == Different editions of Neo4j Neo4j has two editions, a commercial Enterprise Edition with additional performance and administrative features, and an open-source Community Edition. Cypher works almost identically between the two editions, and as such most of this manual will not differentiate between them. -In the few cases where there is a difference in Cypher language support or behaviour between editions, these are highlighted as described below in xref::introduction/neo4j-databases-graphs.adoc#cypher-limited-support[]. +In the few cases where there is a difference in Cypher language support or behaviour between editions, these are highlighted as described below in xref:introduction/neo4j-databases-graphs.adoc#cypher-limited-support[]. However it is worth listing up-front the key areas that are not supported in the open-source edition: [options="header"] |=== | Feature | Enterprise | Community - -| xref::databases.adoc[Multi-database] -a| -Any number of user databases. -a| -Only `system` and one user database. - -| Role-based security -a| -User, role, and privilege management for flexible xref::access-control/index.adoc[access control] and xref::access-control/manage-privileges.adoc[sub-graph access control]. -a| -xref::access-control/manage-users.adoc[Multi-user management]. -All users have full access rights. - -| Constraints -a| -xref::constraints/examples.adoc#administration-constraints-prop-exist-nodes[Existence constraints], xref::constraints/examples.adoc#administration-constraints-unique-nodes[uniqueness constraints], and xref::constraints/examples.adoc#administration-constraints-node-key[`NODE KEY` constraints]. -a| -Only xref::constraints/examples.adoc#administration-constraints-unique-nodes[uniqueness constraints]. - +| xref:databases.adoc[Multi-database] | Any number of user databases | Only `system` and one user database +| Role-based security | User, role, and privilege management for flexible xref:access-control/index.adoc[access control] and xref:access-control/manage-privileges.adoc[sub-graph access control]. | xref:access-control/manage-users.adoc[Multi-user management]. All users have full access rights. +| Constraints | xref:constraints/examples.adoc#administration-constraints-prop-exist-nodes[Existence constraints] and xref:constraints/examples.adoc#administration-constraints-node-key[multi-property `NODE KEY` constraints]. | Only xref:constraints/examples.adoc#administration-constraints-unique-nodes[single property uniqueness constraints] |=== [[cypher-limited-support]] == Limited Support Features -Some elements of Cypher do not work in all deployments of Neo4j. - -Specific labels are added to the documentation to highlight these cases. +Some elements of Cypher do not work in all deployments of Neo4j, and we use specific markers to highlight these cases: [options="header"] |=== -| Description | Label - -| This feature has been deprecated and will be removed or replaced in the future. -| label:deprecated[] - -| This feature only works in the enterprise edition of Neo4j. -| label:enterprise-edition[] - -| This feature only works in a fabric deployment of Neo4j. -| label:fabric[] - +| Marker | Description | Example +| `deprecated` | This feature is deprecated and will be removed in a future version +| [deprecated]#`DROP INDEX ON :Label(property)`# +| `enterprise-only` | This feature only works in the enterprise edition of Neo4j +| [enterprise-edition]#`CREATE DATABASE foo`# +| `fabric` | This feature only works in a fabric deployment of Neo4j. +| [fabric]#`USE fabric.graph(0)`# |=== - diff --git a/modules/ROOT/pages/introduction/quering-updating-administering.adoc b/modules/ROOT/pages/introduction/quering-updating-administering.adoc index bb2439dad..cfaf2bd96 100644 --- a/modules/ROOT/pages/introduction/quering-updating-administering.adoc +++ b/modules/ROOT/pages/introduction/quering-updating-administering.adoc @@ -1,13 +1,8 @@ -:description: This section describes using Cypher for both querying and updating your graph, as well as administering graphs and databases. [[cypher-querying-updating-administering]] = Querying, updating and administering +:description: This section describes using Cypher for both querying and updating your graph, as well as administering graphs and databases. -[abstract] --- -This section describes using Cypher for both querying and updating your graph, as well as administering graphs and databases. --- - -In the xref::introduction/index.adoc#cypher-introduction[introduction] we described the common case of using Cypher to perform read-only queries of the graph. +In the xref:introduction/index.adoc#cypher-introduction[introduction] we described the common case of using Cypher to perform read-only queries of the graph. However, it is also possible to use Cypher to perform updates to the graph, import data into the graph, and perform administrative actions on graphs, databases and the entire DBMS. All these various options are described in more detail in later sections, but it is worth summarizing a few key points first. @@ -19,7 +14,7 @@ Cypher administrative queries cannot be combined with normal reading and writing Each administrative query will perform either an update action to the `system` or a read of status information from the `system`. Some administrative commands make changes to a specific database, and will therefore be possible to run only when connected to the database of interest. Others make changes to the state of the entire DBMS and can only be run against the special `system` database. - +// All administrative queries are described in more detail in the section on <>. [[cypher-updating-queries]] == The structure of update queries @@ -28,9 +23,8 @@ Others make changes to the state of the entire DBMS and can only be run against If you read from the graph and then update the graph, your query implicitly has two parts -- the reading is the first part, and the writing is the second part. [NOTE] -==== A Cypher query part can either read and match on the graph, or make updates on it, not both simultaneously. -==== + If your query only performs reads, Cypher will not actually match the pattern until you ask for the results. In an updating query, the semantics are that _all_ the reading will be done before any writing is performed. @@ -41,7 +35,7 @@ The parts are separated using the `WITH` statement. When you want to filter using aggregated data, you have to chain together two reading query parts -- the first one does the aggregating, and the second filters on the results coming from the first one. -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'John'})-[:FRIEND]-(friend) WITH n, count(friend) AS friendsCount @@ -53,7 +47,7 @@ Using `WITH`, you specify how you want the aggregation to happen, and that the a Here's an example of updating the graph, writing the aggregated data to the graph: -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n {name: 'John'})-[:FRIEND]-(friend) WITH n, count(friend) AS friendsCount diff --git a/modules/ROOT/pages/introduction/transactions.adoc b/modules/ROOT/pages/introduction/transactions.adoc index 44862f0c9..034c607f4 100644 --- a/modules/ROOT/pages/introduction/transactions.adoc +++ b/modules/ROOT/pages/introduction/transactions.adoc @@ -1,38 +1,23 @@ -:description: This section describes how Cypher queries work with database transactions. - [[query-transactions]] = Transactions +:description: This section describes how Cypher queries work with database transactions. -[abstract] --- -This section describes how Cypher queries work with database transactions. --- - -All Cypher queries run within transactions. -Modifications done by updating queries are held in memory by the transaction until it is committed, at which point the changes are persisted to disk and become visible to other transactions. -If an error occurs - either during query evaluation, such as division by zero, or during commit, such as constraint violations - the transaction is automatically rolled back, and no changes are persisted in the graph. +All Cypher statements are explicitly run within a transaction. +For read-only queries, the transaction will always succeed. +For updating queries it is possible that a failure can occur for some reason, for example if the query attempts to violate a constraint, in which case the entire transaction is rolled back, and no changes are made to the graph. +Every statement is executed within the context of the transaction, and nothing will be persisted to disk until that transaction is successfully committed. -In short, an updating query always either fully succeeds, or does not succeed at all. - -[NOTE] -==== -A query that makes a large number of updates consequently uses large amounts of memory since the transaction holds changes in memory. -For memory configuration in Neo4j, see the link:{neo4j-docs-base-uri}/operations-manual/{page-version}/performance/memory-configuration[Neo4j Operations Manual -> Memory configuration]. -==== +In short, an updating query will always either fully succeed, or not succeed at all. -Transactions can be either _explicit_ or _implicit_. +While it is not possible to run a Cypher query outside a transaction, it is possible to run multiple queries within a single transaction using the following sequence of operations: -- _Explicit_ transactions: - * Are opened by the user. - * Can execute multiple Cypher queries in sequence. - * Are committed, or rolled back, by the user. +. Open a transaction, +. Run multiple updating Cypher queries. +. Commit all of them in one go. -- _Implicit_ transactions, sometimes called auto-commit transactions or `:auto` transactions: - * Are opened automatically. - * Can execute a single Cypher query. - * Are committed automatically when the query finishes successfully. - -Queries that start separate transactions themselves, such as queries using xref::clauses/call-subquery.adoc#subquery-call-in-transactions[`CALL { ... } IN TRANSACTIONS`] or xref::query-tuning/using.adoc#query-using-periodic-commit-hint[`PERIODIC COMMIT`] are only allowed in _implicit_ mode. +Note that the transaction will hold the changes in memory until the whole query, or whole set of queries, has finished executing. +A query that makes a large number of updates will consequently use large amounts of memory. +For memory configuration in Neo4j, see the link:{neo4j-docs-base-uri}/operations-manual/{page-version}/performance/memory-configuration[Neo4j Operations Manual -> Memory configuration]. For examples of the API's used to start and commit transactions, refer to the API specific documentation: @@ -66,4 +51,3 @@ DBMS transactions have the following limitations: + It is not possible to combine any of these workloads in a single DBMS transaction. - diff --git a/modules/ROOT/pages/introduction/uniqueness.adoc b/modules/ROOT/pages/introduction/uniqueness.adoc index 895b9b1bb..af66cec23 100644 --- a/modules/ROOT/pages/introduction/uniqueness.adoc +++ b/modules/ROOT/pages/introduction/uniqueness.adoc @@ -1,12 +1,6 @@ -:description: Cypher path matching uses relationship isomorphism, the same relationship cannot be returned more than once in the same result record. - [[cypher-result-uniqueness]] = Cypher path matching - -[abstract] --- -Cypher path matching uses relationship isomorphism, the same relationship cannot be returned more than once in the same result record. --- +:description: Cypher path matching uses relationship isomorphism, the same relationship cannot be returned more than once in the same result record. **Neo4j Cypher** makes use of **relationship isomorphism** for path matching and is a very effective way of reducing the result set size and preventing infinite traversals. @@ -44,7 +38,7 @@ If the query is looking for paths of length `n` and do not care about the direct For example, find all paths with 5 relationships and do not care about the relationship direction: -[source, role=noheader, indent=0] +[source, role=noheader] ---- MATCH p = ()-[*5]-() RETURN nodes(p) @@ -65,7 +59,7 @@ In another two-node example, such as `+(a:Node)-[r:R]->(b:Node)+`; only paths of ==== The graph is composed of only two nodes `(a)` and `(b)`, connected by one relationship, `+(a:Node)-[r:R]->(b:Node)+`. -[source, role=noheader, indent=0] +[source, role=noheader] ---- MATCH p = ()-[*1]-() RETURN nodes(p) @@ -86,7 +80,7 @@ In another two-node example, such as `+(a:Node)-[r:R]->(b:Node)+`; only paths of ==== The graph is composed of only two nodes `(a)` and `(b)`, connected by one relationship, `+(a:Node)-[r:R]->(b:Node)+`. -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- MATCH p = ()-[*1]-() RETURN nodes(p) @@ -109,7 +103,7 @@ To demonstrate this, let's create a few nodes and relationships: **Query 1, create data.** -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- CREATE (adam:User {name: 'Adam'}), @@ -119,7 +113,7 @@ CREATE (pernilla)-[:FRIEND]->(david) ---- -[source, role=noheader, indent=0] +[source, role=noheader] ---- Nodes created: 3 Relationships created: 2 @@ -128,27 +122,50 @@ Properties set: 3 Which gives us the following graph: -image::graph2.svg[] + +["dot", "cypherdoc--13303421.svg", "neoviz"] +---- +N0 [ + label = "{User|name = \'Adam\'\l}" +] +N1 [ + label = "{User|name = \'Pernilla\'\l}" +] +N2 [ + label = "{User|name = \'David\'\l}" +] +N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "FRIEND\n" +] +N1 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "FRIEND\n" +] +---- + Now let's look for friends of friends of Adam: **Query 2, friend of friends of Adam.** -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- MATCH (user:User {name: 'Adam'})-[r1:FRIEND]-()-[r2:FRIEND]-(friend_of_a_friend) RETURN friend_of_a_friend.name AS fofName ---- -[source, role=noheader, indent=0] +[source, role=noheader] ---- -Rows: 1 - +---------+ | fofName | +---------+ | "David" | +---------+ + +Rows: 1 ---- In this query, Cypher makes sure to not return matches where the pattern relationships `r1` and `r2` point to the same graph relationship. @@ -158,30 +175,31 @@ If the query should return the user, it is possible to spread the matching over **Query 3, multiple MATCH clauses.** -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- MATCH (user:User {name: 'Adam'})-[r1:FRIEND]-(friend) MATCH (friend)-[r2:FRIEND]-(friend_of_a_friend) RETURN friend_of_a_friend.name AS fofName ---- -[source, role=noheader, indent=0] +[source, role=noheader] ---- -Rows: 2 - +---------+ | fofName | +---------+ | "David" | | "Adam" | +---------+ + +Rows: 2 ---- + Note that while the following **Query 4** looks similar to **Query 3**, it is actually equivalent to **Query 2**. **Query 4, equivalent to query 2.** -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- MATCH (user:User {name: 'Adam'})-[r1:FRIEND]-(friend), @@ -191,15 +209,15 @@ RETURN friend_of_a_friend.name AS fofName Here, the `MATCH` clause has a single pattern with two paths, while the previous query has two distinct patterns. -[source, role=noheader, indent=0] +[source, role=noheader] ---- -Rows: 1 - +---------+ | fofName | +---------+ | "David" | +---------+ + +Rows: 1 ---- ==== diff --git a/modules/ROOT/pages/keyword-glossary.adoc b/modules/ROOT/pages/keyword-glossary.adoc index fa67c878c..a28f997c6 100644 --- a/modules/ROOT/pages/keyword-glossary.adoc +++ b/modules/ROOT/pages/keyword-glossary.adoc @@ -1,994 +1,264 @@ -:description: Glossary of all the keywords -- grouped by category and thence ordered lexicographically -- in the Cypher query language. - [[cypher-glossary]] = Glossary of keywords +:description: This section comprises a glossary of all the keywords -- grouped by category and thence ordered lexicographically -- in the Cypher query language. -[abstract] --- -This section comprises a glossary of all the keywords -- grouped by category and thence ordered lexicographically -- in the Cypher query language. --- - -* xref::keyword-glossary.adoc#glossary-clauses[Clauses] -* xref::keyword-glossary.adoc#glossary-operators[Operators] -* xref::keyword-glossary.adoc#glossary-functions[Functions] -* xref::keyword-glossary.adoc#glossary-expressions[Expressions] -* xref::keyword-glossary.adoc#glossary-cypher-query-options[Cypher query options] -* xref::keyword-glossary.adoc#glossary-admin-commands[Administrative commands] -* xref::keyword-glossary.adoc#glossary-privileges[Privilege Actions] +* xref:keyword-glossary.adoc#glossary-clauses[Clauses] +* xref:keyword-glossary.adoc#glossary-operators[Operators] +* xref:keyword-glossary.adoc#glossary-functions[Functions] +* xref:keyword-glossary.adoc#glossary-expressions[Expressions] +* xref:keyword-glossary.adoc#glossary-cypher-query-options[Cypher query options] +* xref:keyword-glossary.adoc#glossary-admin-commands[Administrative commands] +* xref:keyword-glossary.adoc#glossary-privileges[Privilege Actions] [[glossary-clauses]] -== xref::clauses/index.adoc[Clauses] +== xref:clauses/index.adoc[Clauses] [options="header"] |=== -| Clause | Category | Description - -| xref::clauses/call.adoc[CALL [...YIELD\]] -| Reading/Writing -| Invoke a procedure deployed in the database. - -| xref::clauses/call-subquery.adoc[CALL {...}] -| Reading/Writing -| Evaluates a subquery, typically used for post-union processing or aggregations. - -| xref::clauses/create.adoc[CREATE] -| Writing -| Create nodes and relationships. - -| xref::constraints/syntax.adoc#administration-constraints-syntax-create-node-exists[CREATE CONSTRAINT [existence\] [IF NOT EXISTS\] FOR (n:Label) REQUIRE n.property IS NOT NULL [OPTIONS {}\]] -| Schema -| Create a constraint ensuring that all nodes with a particular label have a certain property. - -| xref::constraints/syntax.adoc#administration-constraints-syntax-create-node-key[CREATE CONSTRAINT [node_key\] [IF NOT EXISTS\] FOR (n:Label) REQUIRE (n.prop1[, ..., n.propN\]) IS NODE KEY [OPTIONS {optionKey: optionValue[, ...\]}\]] -| Schema -| Create a constraint that ensures all nodes with a particular label have all the specified properties and that the combination of property values is unique; i.e. ensures existence and uniqueness. - -| xref::constraints/syntax.adoc#administration-constraints-syntax-create-rel-exists[CREATE CONSTRAINT [existence\] [IF NOT EXISTS\] FOR ()-"["r:REL_TYPE"\]"-() REQUIRE r.property IS NOT NULL [OPTIONS {}\]] -| Schema -| Create a constraint that ensures all relationships with a particular type have a certain property. - -| xref::constraints/syntax.adoc#administration-constraints-syntax-create-unique[CREATE CONSTRAINT [uniqueness\] [IF NOT EXISTS\] FOR (n:Label) REQUIRE (n.prop1[, ..., n.propN\]) IS UNIQUE [OPTIONS {optionKey: optionValue[, ...\]}\]] -| Schema -| Create a constraint that ensures the uniqueness of the combination of node label and property values for a particular property key combination across all nodes. - -| xref::indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE [BTREE\] INDEX [single\] [IF NOT EXISTS\] FOR (n:Label) ON (n.property) [OPTIONS {optionKey: optionValue[, ...\]}\]] -| Schema -| Create an index on all nodes with a particular label and a single property; i.e. create a single-property index. - -| xref::indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE [BTREE\] INDEX [single\] [IF NOT EXISTS\] FOR ()-"["r:TYPE"\]"-() ON (r.property) [OPTIONS {optionKey: optionValue[, ...\]}\]] -| Schema -| Create an index on all relationships with a particular relationship type and a single property; i.e. create a single-property index. - -| xref::indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE [BTREE\] INDEX [composite\] [IF NOT EXISTS\] FOR (n:Label) ON (n.prop1, ..., n.propN) [OPTIONS {optionKey: optionValue[, ...\]}\]] -| Schema -| Create an index on all nodes with a particular label and multiple properties; i.e. create a composite index. - -| xref::indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE [BTREE\] INDEX [composite\] [IF NOT EXISTS\] FOR ()-"["r:TYPE"\]"-() ON (r.prop1, ..., r.propN) [OPTIONS {optionKey: optionValue[, ...\]}\]] -| Schema -| Create an index on all relationships with a particular relationship type and multiple properties; i.e. create a composite index. - -| xref::indexes-for-full-text-search.adoc[CREATE FULLTEXT INDEX [name\] [IF NOT EXISTS\] FOR (n:Label["\|" ... "\|" LabelN\]) ON EACH "[" n.property[, ..., n.propertyN\] "\]" [OPTIONS {optionKey: optionValue[, ...\]}\]] -| Schema -| Create a fulltext index on nodes. - -| xref::indexes-for-full-text-search.adoc[CREATE FULLTEXT INDEX [name\] [IF NOT EXISTS\] FOR ()-"["r:TYPE["\|" ... "\|" TYPE_N\]"\]"-() ON EACH "[" r.property[, ..., r.propertyN\] "\]" [OPTIONS {optionKey: optionValue[, ...\]}\]] -| Schema -| Create a fulltext index on relationships. - -| xref::indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE LOOKUP INDEX [name\] [IF NOT EXISTS\] FOR (n) ON EACH labels(n) [OPTIONS {optionKey: optionValue[, ...\]}\]] -| Schema -| Create an index on all nodes with any label. - -| xref::indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE LOOKUP INDEX [name\] [IF NOT EXISTS\] FOR ()-"["r"\]"-() ON [EACH\] type(r) [OPTIONS {optionKey: optionValue[, ...\]}\]] -| Schema -| Create an index on all relationships with any relationship type. - -| xref::indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE TEXT INDEX [name\] [IF NOT EXISTS\] FOR (n:Label) ON (n.property) [OPTIONS {optionKey: optionValue[, ...\]}\]] -| Schema -| Create a text index on nodes. - -| xref::indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE TEXT INDEX [name\] [IF NOT EXISTS\] FOR ()-"["r:TYPE"\]"-() ON (r.property) [OPTIONS {optionKey: optionValue[, ...\]}\]] -| Schema -| Create a text index on relationships. - -| xref::clauses/delete.adoc[DELETE] -| Writing -| -Delete nodes, relationships or paths. -Any node to be deleted must also have all associated relationships explicitly deleted. - -| xref::clauses/delete.adoc[DETACH DELETE] -| Writing -| -Delete a node or set of nodes. -All associated relationships will automatically be deleted. - -| xref::constraints/syntax.adoc[DROP CONSTRAINT name [IF EXISTS\]] -| Schema -| Drop a constraint using the name. - -| xref::indexes-for-search-performance.adoc#administration-indexes-syntax[DROP INDEX name [IF EXISTS\]] -| Schema -| Drop an index using the name. - -| xref::clauses/foreach.adoc[FOREACH] -| Writing -| Update data within a list, whether components of a path, or the result of aggregation. - -| xref::clauses/limit.adoc[LIMIT] -| Reading sub-clause -| A sub-clause used to constrain the number of rows in the output. - -| xref::clauses/load-csv.adoc[LOAD CSV] -| Importing data -| Use when importing data from CSV files. - -| xref::clauses/match.adoc[MATCH] -| Reading -| Specify the patterns to search for in the database. - -| xref::clauses/merge.adoc[MERGE] -| Reading/Writing -| -Ensures that a pattern exists in the graph. -Either the pattern already exists, or it needs to be created. - -| xref::clauses/merge.adoc#query-merge-on-create-on-match[ON CREATE] -| Reading/Writing -| Used in conjunction with `MERGE`, specifying the actions to take if the pattern needs to be created. - -| xref::clauses/merge.adoc#query-merge-on-create-on-match[ON MATCH] -| Reading/Writing -| Used in conjunction with `MERGE`, specifying the actions to take if the pattern already exists. - -| xref::clauses/optional-match.adoc[OPTIONAL MATCH] -| Reading -| Specify the patterns to search for in the database while using `nulls` for missing parts of the pattern. - -| xref::clauses/order-by.adoc[ORDER BY [ASC[ENDING\] \| DESC[ENDING\]\]] -| Reading sub-clause -| A sub-clause following `RETURN` or `WITH`, specifying that the output should be sorted in either ascending (the default) or descending order. - -| xref::clauses/remove.adoc[REMOVE] -| Writing -| Remove properties and labels from nodes and relationships. - -| xref::clauses/return.adoc[RETURN ... [AS\]] -| Projecting -| Defines what to include in the query result set. - -| xref::clauses/set.adoc[SET] -| Writing -| Update labels on nodes and properties on nodes and relationships. - -| xref::constraints/syntax.adoc#administration-constraints-syntax-list[SHOW [ALL\|UNIQUE\|NODE [PROPERTY\] EXIST[ENCE\]\|REL[ATIONSHIP\] [PROPERTY\] EXIST[ENCE\]\|[PROPERTY\] EXIST[ENCE\]\|NODE KEY\] CONSTRAINT[S\]] -| Schema -| -List constraints in the database, either all or filtered on type. -Also allows `WHERE` and `YIELD` clauses. - -| xref::indexes-for-search-performance.adoc#administration-indexes-list-indexes[SHOW [ALL\|BTREE\|FULLTEXT\|LOOKUP\] INDEX[ES\]] -| Schema -| -List indexes in the database, either all or filtered on B-tree, fulltext or token lookup indexes. -Also allows `WHERE` and `YIELD` clauses. - -| xref::clauses/listing-functions.adoc[SHOW [ALL\|BUILT IN\|USER DEFINED\] FUNCTION[S\] [EXECUTABLE [BY {CURRENT USER\|username}\]\]] -| DBMS -| -List functions, either all or filtered. -Available filters are executable by a user or function type (built-in or user-defined). -Also allows `WHERE` and `YIELD` clauses. - -| xref::clauses/listing-procedures.adoc[SHOW PROCEDURE[S\] [EXECUTABLE [BY {CURRENT USER\|username}\]\]] -| DBMS -| -List procedures, either all or filtered on executable by a user. -Also allows `WHERE` and `YIELD` clauses. - -|xref:clauses/transaction-clauses.adoc#query-listing-transactions[SHOW TRANSACTION[S\] [transaction-id[, ...\]\]] -| DBMS -| -List transactions, either all or filtered on ID. -Also allows `WHERE` and `YIELD` clauses. - -| xref::clauses/skip.adoc[SKIP] -| Reading/Writing -| A sub-clause defining from which row to start including the rows in the output. - -| xref:clauses/transaction-clauses.adoc#query-terminate-transactions[TERMINATE TRANSACTION[S\] transaction-id[, ...\]] -| DBMS -| Terminate transactions with the given IDs. - -| xref::clauses/union.adoc[UNION] -| Set operations -| -Combines the result of multiple queries. -Duplicates are removed. - -| xref::clauses/union.adoc[UNION ALL] -| Set operations -| -Combines the result of multiple queries. -Duplicates are retained. - -| xref::clauses/unwind.adoc[UNWIND ... [AS\]] -| Projecting -| Expands a list into a sequence of rows. - -| xref::clauses/use.adoc[USE] -| Multiple graphs -| Determines which graph a query, or query part, is executed against. label:fabric[] - -| xref::query-tuning/using.adoc#query-using-index-hint[USING INDEX variable:Label(property)] -| Hint -| Index hints are used to specify which index, if any, the planner should use as a starting point. - -| xref::query-tuning/using.adoc#query-using-index-hint[USING INDEX SEEK variable:Label(property)] -| Hint -| Index seek hint instructs the planner to use an index seek for this clause. - -| xref::query-tuning/using.adoc#query-using-join-hint[USING JOIN ON variable] -| Hint -| Join hints are used to enforce a join operation at specified points. - -| xref::query-tuning/using.adoc#query-using-periodic-commit-hint[USING PERIODIC COMMIT] -| Hint -| This query hint may be used to prevent an out-of-memory error from occurring when importing large amounts of data using `LOAD CSV`. - -| xref::query-tuning/using.adoc#query-using-scan-hint[USING SCAN variable:Label] -| Hint -| Scan hints are used to force the planner to do a label scan (followed by a filtering operation) instead of using an index. - -| xref::clauses/with.adoc[WITH ... [AS\]] -| Projecting -| Allows query parts to be chained together, piping the results from one to be used as starting points or criteria in the next. - -| xref::clauses/where.adoc[WHERE] -| Reading sub-clause -| A sub-clause used to add constraints to the patterns in a `MATCH` or `OPTIONAL MATCH` clause, or to filter the results of a `WITH` clause. - -| xref::clauses/where.adoc#existential-subqueries[WHERE EXISTS {...}] -| Reading sub-clause -| An existential sub-query used to filter the results of a `MATCH`, `OPTIONAL MATCH`, or `WITH` clause. - +|Clause | Category | Description +|xref:clauses/call.adoc[CALL [...YIELD\]] | Reading/Writing | Invoke a procedure deployed in the database. +|xref:clauses/call-subquery.adoc[CALL {...}] | Reading/Writing | Evaluates a subquery, typically used for post-union processing or aggregations. +|xref:clauses/create.adoc[CREATE] | Writing | Create nodes and relationships. +|xref:constraints/syntax.adoc[CREATE CONSTRAINT [existence\] [IF NOT EXISTS\] ON (n:Label) ASSERT n.property IS NOT NULL] | Schema | Create a constraint ensuring that all nodes with a particular label have a certain property. +|xref:constraints/syntax.adoc[CREATE CONSTRAINT [node_key\] [IF NOT EXISTS\] ON (n:Label) ASSERT (n.prop1, ..., n.propN) IS NODE KEY [OPTIONS {optionKey: optionValue[, ...\]}\]] | Schema | Create a constraint ensuring all nodes with a particular label have all the specified properties and that the combination of property values is unique; i.e. ensures existence and uniqueness. +|xref:constraints/syntax.adoc[CREATE CONSTRAINT [existence\] [IF NOT EXISTS\] ON ()-"["r:REL_TYPE"\]"-() ASSERT r.property IS NOT NULL] | Schema | Create a constraint ensuring that all relationships with a particular type have a certain property. +|xref:constraints/syntax.adoc[CREATE CONSTRAINT [uniqueness\] [IF NOT EXISTS\] ON (n:Label) ASSERT n.property IS UNIQUE [OPTIONS {optionKey: optionValue[, ...\]}\]] | Schema | Create a constraint ensuring the uniqueness of the combination of node label and property value for a particular property key across all nodes. +|xref:indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE [BTREE\] INDEX [single\] [IF NOT EXISTS\] FOR (n:Label) ON (n.property) [OPTIONS {optionKey: optionValue[, ...\]}\]] | Schema | Create an index on all nodes with a particular label and a single property; i.e. create a single-property index. +|xref:indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE [BTREE\] INDEX [single\] [IF NOT EXISTS\] FOR ()-"["r:TYPE"\]"-() ON (r.property) [OPTIONS {optionKey: optionValue[, ...\]}\]] | Schema | Create an index on all relationships with a particular relationship type and a single property; i.e. create a single-property index. +|xref:indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE [BTREE\] INDEX [composite\] [IF NOT EXISTS\] FOR (n:Label) ON (n.prop1, ..., n.propN) [OPTIONS {optionKey: optionValue[, ...\]}\]] | Schema | Create an index on all nodes with a particular label and multiple properties; i.e. create a composite index. +|xref:indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE [BTREE\] INDEX [composite\] [IF NOT EXISTS\] FOR ()-"["r:TYPE"\]"-() ON (r.prop1, ..., r.propN) [OPTIONS {optionKey: optionValue[, ...\]}\]] | Schema | Create an index on all relationships with a particular relationship type and multiple properties; i.e. create a composite index. +|xref:indexes-for-full-text-search.adoc[CREATE FULLTEXT INDEX [name\] [IF NOT EXISTS\] FOR (n:Label["\|" ... "\|" LabelN\]) ON EACH "[" n.property[, ..., n.propertyN\] "\]" [OPTIONS {optionKey: optionValue[, ...\]}\]] | Schema | Create a fulltext index on nodes. +|xref:indexes-for-full-text-search.adoc[CREATE FULLTEXT INDEX [name\] [IF NOT EXISTS\] FOR ()-"["r:TYPE["\|" ... "\|" TYPE_N\]"\]"-() ON EACH "[" r.property[, ..., r.propertyN\] "\]" [OPTIONS {optionKey: optionValue[, ...\]}\]] | Schema | Create a fulltext index on relationships. +|xref:indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE LOOKUP INDEX [name\] [IF NOT EXISTS\] FOR (n) ON EACH labels(n) [OPTIONS {}\]] | Schema | Create an index on all nodes with any label. +|xref:indexes-for-search-performance.adoc#administration-indexes-syntax[CREATE LOOKUP INDEX [name\] [IF NOT EXISTS\] FOR ()-"["r"\]"-() ON [EACH\] type(r) [OPTIONS {}\]] | Schema | Create an index on all relationships with any relationship type. +|xref:clauses/delete.adoc[DELETE] | Writing | Delete nodes, relationships or paths. Any node to be deleted must also have all associated relationships explicitly deleted. +|xref:clauses/delete.adoc[DETACH DELETE] | Writing | Delete a node or set of nodes. All associated relationships will automatically be deleted. +|xref:constraints/syntax.adoc[DROP CONSTRAINT name [IF EXISTS\]] | Schema | Drop a constraint using the name. +|xref:indexes-for-search-performance.adoc#administration-indexes-syntax[DROP INDEX name [IF EXISTS\]] | Schema | Drop an index using the name. +|xref:clauses/foreach.adoc[FOREACH] | Writing | Update data within a list, whether components of a path, or the result of aggregation. +|xref:clauses/limit.adoc[LIMIT] | Reading sub-clause | A sub-clause used to constrain the number of rows in the output. +|xref:clauses/load-csv.adoc[LOAD CSV] | Importing data | Use when importing data from CSV files. +|xref:clauses/match.adoc[MATCH] | Reading | Specify the patterns to search for in the database. +|xref:clauses/merge.adoc[MERGE] | Reading/Writing | Ensures that a pattern exists in the graph. Either the pattern already exists, or it needs to be created. +|xref:clauses/merge.adoc#query-merge-on-create-on-match[ON CREATE] | Reading/Writing | Used in conjunction with `MERGE`, specifying the actions to take if the pattern needs to be created. +|xref:clauses/merge.adoc#query-merge-on-create-on-match[ON MATCH] | Reading/Writing | Used in conjunction with `MERGE`, specifying the actions to take if the pattern already exists. +|xref:clauses/optional-match.adoc[OPTIONAL MATCH] | Reading | Specify the patterns to search for in the database while using `nulls` for missing parts of the pattern. +|xref:clauses/order-by.adoc[ORDER BY [ASC[ENDING\] \| DESC[ENDING\]\]] | Reading sub-clause | A sub-clause following `RETURN` or `WITH`, specifying that the output should be sorted in either ascending (the default) or descending order. +|xref:clauses/remove.adoc[REMOVE] | Writing | Remove properties and labels from nodes and relationships. +|xref:clauses/return.adoc[RETURN ... [AS\]] | Projecting | Defines what to include in the query result set. +|xref:clauses/set.adoc[SET] | Writing | Update labels on nodes and properties on nodes and relationships. +|xref:constraints/syntax.adoc#administration-constraints-syntax-list[SHOW [ALL\|UNIQUE\|NODE [PROPERTY\] EXIST[ENCE\]\|REL[ATIONSHIP\] [PROPERTY\] EXIST[ENCE\]\|[PROPERTY\] EXIST[ENCE\]\|NODE KEY\] CONSTRAINT[S\]] | Schema | List constraints in the database, either all or filtered on type. Also allows `WHERE` and `YIELD` clauses. +|xref:indexes-for-search-performance.adoc#administration-indexes-list-indexes[SHOW [ALL\|BTREE\|FULLTEXT\|LOOKUP\] INDEX[ES\]] | Schema | List indexes in the database, either all or filtered on b-tree, fulltext or token lookup indexes. Also allows `WHERE` and `YIELD` clauses. +|xref:clauses/listing-functions.adoc[SHOW [ALL\|BUILT IN\|USER DEFINED\] FUNCTION[S\] [EXECUTABLE [BY {CURRENT USER\|username}\]\]] | Listing | List functions, either all or filtered. Available filters are executable by a user or function type (built-in or user defined). Also allows `WHERE` and `YIELD` clauses. +|xref:clauses/listing-procedures.adoc[SHOW PROCEDURE[S\] [EXECUTABLE [BY {CURRENT USER\|username}\]\]] | Listing | List procedures, either all or filtered on executable by a user. Also allows `WHERE` and `YIELD` clauses. +|xref:clauses/skip.adoc[SKIP] | Reading/Writing | A sub-clause defining from which row to start including the rows in the output. +|xref:clauses/union.adoc[UNION] | Set operations | Combines the result of multiple queries. Duplicates are removed. +|xref:clauses/union.adoc[UNION ALL] | Set operations | Combines the result of multiple queries. Duplicates are retained. +|xref:clauses/unwind.adoc[UNWIND ... [AS\]] | Projecting | Expands a list into a sequence of rows. +|xref:clauses/use.adoc[USE] | Multiple graphs | [fabric]#Determines which graph a query, or query part, is executed against.# +|xref:query-tuning/using.adoc#query-using-index-hint[USING INDEX variable:Label(property)] | Hint | Index hints are used to specify which index, if any, the planner should use as a starting point. +|xref:query-tuning/using.adoc#query-using-index-hint[USING INDEX SEEK variable:Label(property)] | Hint | Index seek hint instructs the planner to use an index seek for this clause. +|xref:query-tuning/using.adoc#query-using-join-hint[USING JOIN ON variable] | Hint | Join hints are used to enforce a join operation at specified points. +|xref:query-tuning/using.adoc#query-using-periodic-commit-hint[USING PERIODIC COMMIT] | Hint | This query hint may be used to prevent an out-of-memory error from occurring when importing large amounts of data using `LOAD CSV`. +|xref:query-tuning/using.adoc#query-using-scan-hint[USING SCAN variable:Label] | Hint | Scan hints are used to force the planner to do a label scan (followed by a filtering operation) instead of using an index. +|xref:clauses/with.adoc[WITH ... [AS\]] | Projecting | Allows query parts to be chained together, piping the results from one to be used as starting points or criteria in the next. +|xref:clauses/where.adoc[WHERE] | Reading sub-clause | A sub-clause used to add constraints to the patterns in a `MATCH` or `OPTIONAL MATCH` clause, or to filter the results of a `WITH` clause. +|xref:clauses/where.adoc#existential-subqueries[WHERE EXISTS {...}] | Reading sub-clause | An existential sub-query used to filter the results of a `MATCH`, `OPTIONAL MATCH` or `WITH` clause. |=== [[glossary-operators]] -== xref::syntax/operators.adoc[Operators] +== xref:syntax/operators.adoc[Operators] [options="header"] |=== -|Operator | Category | Description - -| xref::syntax/operators.adoc#query-operators-mathematical[%] -| Mathematical -| Modulo division. - -| xref::syntax/operators.adoc#query-operators-mathematical[*] -| Mathematical -| Multiplication. - -| xref::syntax/operators.adoc#query-operators-temporal[*] -| Temporal -| Multiplying a duration with a number. - -| xref::syntax/operators.adoc#query-operators-mathematical[+] -| Mathematical -| Addition. - -| xref::syntax/operators.adoc#query-operators-string[+] -| String -| Concatenation. - -| xref::syntax/operators.adoc#query-operators-property[+=] -| Property -| Property mutation. - -| xref::syntax/operators.adoc#query-operators-list[+] -| List -| Concatenation - -| xref::syntax/operators.adoc#query-operators-temporal[+] -| Temporal -| Adding two durations, or a duration and a temporal instant. - -| xref::syntax/operators.adoc#query-operators-mathematical[-] -| Mathematical -| Subtraction or unary minus. - -| xref::syntax/operators.adoc#query-operators-temporal[-] -| Temporal -| Subtracting a duration from a temporal instant or from another duration. - -| xref::syntax/operators.adoc#query-operators-map[.] -| Map -| Static value access by key. - -| xref::syntax/operators.adoc#query-operators-property[.] -| Property -| Static property access. - -| xref::syntax/operators.adoc#query-operators-mathematical[/] -| Mathematical -| Division. - -| xref::syntax/operators.adoc#query-operators-temporal[/] -| Temporal -| Dividing a duration by a number. - -| xref::syntax/operators.adoc#query-operators-comparison[<] -| Comparison -| Less than. - -| xref::syntax/operators.adoc#query-operators-comparison[<=] -| Comparison -| Less than or equal to. - -| xref::syntax/operators.adoc#query-operators-comparison[<>] -| Comparison -| Inequality. - -| xref::syntax/operators.adoc#query-operators-comparison[=] -| Comparison -| Equality. - -| xref::syntax/operators.adoc#query-operators-property[=] -| Property -| Property replacement. - -| xref::syntax/operators.adoc#query-operators-string[=~] -| String -| Regular expression match. - -| xref::syntax/operators.adoc#query-operators-comparison[>] -| Comparison -| Greater than. - -| xref::syntax/operators.adoc#query-operators-comparison[>=] -| Comparison -| Greater than or equal to. - -| xref::syntax/operators.adoc#query-operators-boolean[AND] -| Boolean -| Conjunction. - -| xref::syntax/operators.adoc#query-operator-comparison-string-specific[CONTAINS] -| String comparison -| Case-sensitive inclusion search. - -| xref::syntax/operators.adoc#query-operators-aggregation[DISTINCT] -| Aggregation -| Duplicate removal. - -| xref::syntax/operators.adoc#query-operator-comparison-string-specific[ENDS WITH] -| String comparison -| Case-sensitive suffix search. - -| xref::syntax/operators.adoc#query-operators-list[IN] -| List -| List element existence check. - -| xref::syntax/operators.adoc#query-operators-comparison[IS NOT NULL] -| Comparison -| Non-`null` check. - -| xref::syntax/operators.adoc#query-operators-comparison[IS NULL] -| Comparison -| `null` check. - -| xref::syntax/operators.adoc#query-operators-boolean[NOT] -| Boolean -| Negation. - -| xref::syntax/operators.adoc#query-operators-boolean[OR] -| Boolean -| Disjunction. - -| xref::syntax/operators.adoc#query-operator-comparison-string-specific[STARTS WITH] -| String comparison -| Case-sensitive prefix search. - -| xref::syntax/operators.adoc#query-operators-boolean[XOR] -| Boolean -| Exclusive disjunction. - -| xref::syntax/operators.adoc#query-operators-map[[\]] -| Map -| Subscript (dynamic value access by key). - -| xref::syntax/operators.adoc#query-operators-property[[\]] -| Property -| Subscript (dynamic property access). - -| xref::syntax/operators.adoc#query-operators-list[[\]] -| List -| Subscript (accessing element(s) in a list). - -| xref::syntax/operators.adoc#query-operators-mathematical[^] -| Mathematical -| Exponentiation. - +|Operator | Category | Description +| xref:syntax/operators.adoc#query-operators-mathematical[%] | Mathematical | Modulo division +| xref:syntax/operators.adoc#query-operators-mathematical[*] | Mathematical | Multiplication +| xref:syntax/operators.adoc#query-operators-temporal[*] | Temporal | Multiplying a duration with a number +| xref:syntax/operators.adoc#query-operators-mathematical[+] | Mathematical | Addition +| xref:syntax/operators.adoc#query-operators-string[+] | String | Concatenation +| xref:syntax/operators.adoc#query-operators-property[+=] | Property | Property mutation +| xref:syntax/operators.adoc#query-operators-list[+] | List | Concatenation +| xref:syntax/operators.adoc#query-operators-temporal[+] | Temporal | Adding two durations, or a duration and a temporal instant +| xref:syntax/operators.adoc#query-operators-mathematical[-] | Mathematical | Subtraction or unary minus +| xref:syntax/operators.adoc#query-operators-temporal[-] | Temporal | Subtracting a duration from a temporal instant or from another duration +| xref:syntax/operators.adoc#query-operators-map[.] | Map | Static value access by key +| xref:syntax/operators.adoc#query-operators-property[.] | Property | Static property access +| xref:syntax/operators.adoc#query-operators-mathematical[/] | Mathematical | Division +| xref:syntax/operators.adoc#query-operators-temporal[/] | Temporal | Dividing a duration by a number +| xref:syntax/operators.adoc#query-operators-comparison[<] | Comparison | Less than +| xref:syntax/operators.adoc#query-operators-comparison[<=] | Comparison | Less than or equal to +| xref:syntax/operators.adoc#query-operators-comparison[<>] | Comparison | Inequality +| xref:syntax/operators.adoc#query-operators-comparison[=] | Comparison | Equality +| xref:syntax/operators.adoc#query-operators-property[=] | Property | Property replacement +| xref:syntax/operators.adoc#query-operators-string[=~] | String | Regular expression match +| xref:syntax/operators.adoc#query-operators-comparison[>] | Comparison | Greater than +| xref:syntax/operators.adoc#query-operators-comparison[>=] | Comparison | Greater than or equal to +| xref:syntax/operators.adoc#query-operators-boolean[AND] | Boolean | Conjunction +| xref:syntax/operators.adoc#query-operator-comparison-string-specific[CONTAINS] | String comparison | Case-sensitive inclusion search +| xref:syntax/operators.adoc#query-operators-aggregation[DISTINCT] | Aggregation | Duplicate removal +| xref:syntax/operators.adoc#query-operator-comparison-string-specific[ENDS WITH] | String comparison | Case-sensitive suffix search +| xref:syntax/operators.adoc#query-operators-list[IN] | List | List element existence check +| xref:syntax/operators.adoc#query-operators-comparison[IS NOT NULL] | Comparison | Non-`null` check +| xref:syntax/operators.adoc#query-operators-comparison[IS NULL] | Comparison | `null` check +| xref:syntax/operators.adoc#query-operators-boolean[NOT] | Boolean | Negation +| xref:syntax/operators.adoc#query-operators-boolean[OR] | Boolean | Disjunction +| xref:syntax/operators.adoc#query-operator-comparison-string-specific[STARTS WITH] | String comparison | Case-sensitive prefix search +| xref:syntax/operators.adoc#query-operators-boolean[XOR] | Boolean | Exclusive disjunction +| xref:syntax/operators.adoc#query-operators-map[[\]] | Map | Subscript (dynamic value access by key) +| xref:syntax/operators.adoc#query-operators-property[[\]] | Property | Subscript (dynamic property access) +| xref:syntax/operators.adoc#query-operators-list[[\]] | List | Subscript (accessing element(s) in a list) +| xref:syntax/operators.adoc#query-operators-mathematical[^] | Mathematical | Exponentiation |=== [[glossary-functions]] -== xref::functions/index.adoc[Functions] +== xref:functions/index.adoc[Functions] [options="header"] |=== -| Function | Category | Description - -| xref::functions/mathematical-numeric.adoc#functions-abs[abs()] -| Numeric -| Returns the absolute value of a number. - -| xref::functions/mathematical-trigonometric.adoc#functions-acos[acos()] -| Trigonometric -| Returns the arccosine of a number in radians. - -| xref::functions/predicate.adoc#functions-all[all()] -| Predicate -| Tests whether the predicate holds for all elements in a list. - -| xref::functions/predicate.adoc#functions-any[any()] -| Predicate -| Tests whether the predicate holds for at least one element in a list. - -| xref::functions/mathematical-trigonometric.adoc#functions-asin[asin()] -| Trigonometric -| Returns the arcsine of a number in radians. - -| xref::functions/mathematical-trigonometric.adoc#functions-atan[atan()] -| Trigonometric -| Returns the arctangent of a number in radians. - -| xref::functions/mathematical-trigonometric.adoc#functions-atan2[atan2()] -| Trigonometric -| Returns the arctangent2 of a set of coordinates in radians. - -| xref::functions/aggregating.adoc#functions-avg[avg()] -| Aggregating -| Returns the average of a set of values. - -| xref::functions/mathematical-numeric.adoc#functions-ceil[ceil()] -| Numeric -| Returns the smallest floating point number that is greater than or equal to a number and equal to a mathematical integer. - -| xref::functions/scalar.adoc#functions-coalesce[coalesce()] -| Scalar -| Returns the first non-`null` value in a list of expressions. - -| xref::functions/aggregating.adoc#functions-collect[collect()] -| Aggregating -| Returns a list containing the values returned by an expression. - -| xref::functions/mathematical-trigonometric.adoc#functions-cos[cos()] -| Trigonometric -| Returns the cosine of a number. - -| xref::functions/mathematical-trigonometric.adoc#functions-cot[cot()] -| Trigonometric -| Returns the cotangent of a number. - -| xref::functions/aggregating.adoc#functions-count[count()] -| Aggregating -| Returns the number of values or rows. - -| xref::functions/temporal/index.adoc#functions-date-current[date()] -| Temporal -| Returns the current _Date_. - -| xref::functions/temporal/index.adoc#functions-date-calendar[date({year [, month, day\]})] -| Temporal -| Returns a calendar (Year-Month-Day) _Date_. - -| xref::functions/temporal/index.adoc#functions-date-week[date({year [, week, dayOfWeek\]})] -| Temporal -| Returns a week (Year-Week-Day) _Date_. - -| xref::functions/temporal/index.adoc#functions-date-quarter[date({year [, quarter, dayOfQuarter\]})] -| Temporal -| Returns a quarter (Year-Quarter-Day) _Date_. - -| xref::functions/temporal/index.adoc#functions-date-ordinal[date({year [, ordinalDay\]})] -| Temporal -| Returns an ordinal (Year-Day) _Date_. - -| xref::functions/temporal/index.adoc#functions-date-create-string[date(string)] -| Temporal -| Returns a _Date_ by parsing a string. - -| xref::functions/temporal/index.adoc#functions-date-temporal[+date({map})+] -| Temporal -| Returns a _Date_ from a map of another temporal value's components. - -| xref::functions/temporal/index.adoc#functions-date-realtime[date.realtime()] -| Temporal -| Returns the current _Date_ using the `realtime` clock. - -| xref::functions/temporal/index.adoc#functions-date-statement[date.statement()] -| Temporal -| Returns the current _Date_ using the `statement` clock. - -| xref::functions/temporal/index.adoc#functions-date-transaction[date.transaction()] -| Temporal -| Returns the current _Date_ using the `transaction` clock. - -| xref::functions/temporal/index.adoc#functions-date-truncate[date.truncate()] -| Temporal -| Returns a _Date_ obtained by truncating a value at a specific component boundary. xref::functions/temporal/index.adoc#functions-temporal-truncate-overview[Truncation summary]. - -| xref::functions/temporal/index.adoc#functions-datetime-current[datetime()] -| Temporal -| Returns the current _DateTime_. - -| xref::functions/temporal/index.adoc#functions-datetime-calendar[datetime({year [, month, day, ...\]})] -| Temporal -| Returns a calendar (Year-Month-Day) _DateTime_. - -| xref::functions/temporal/index.adoc#functions-datetime-week[datetime({year [, week, dayOfWeek, ...\]})] -| Temporal -| Returns a week (Year-Week-Day) _DateTime_. - -| xref::functions/temporal/index.adoc#functions-datetime-quarter[datetime({year [, quarter, dayOfQuarter, ...\]})] -| Temporal -| Returns a quarter (Year-Quarter-Day) _DateTime_. - -| xref::functions/temporal/index.adoc#functions-datetime-ordinal[datetime({year [, ordinalDay, ...\]})] -| Temporal -| Returns an ordinal (Year-Day) _DateTime_. - -| xref::functions/temporal/index.adoc#functions-datetime-create-string[datetime(string)] -| Temporal -| Returns a _DateTime_ by parsing a string. - -| xref::functions/temporal/index.adoc#functions-datetime-temporal[+datetime({map})+] -| Temporal -| Returns a _DateTime_ from a map of another temporal value's components. - -| xref::functions/temporal/index.adoc#functions-datetime-timestamp[+datetime({epochSeconds})+] -| Temporal -| Returns a _DateTime_ from a timestamp. - -| xref::functions/temporal/index.adoc#functions-datetime-realtime[datetime.realtime()] -| Temporal -| Returns the current _DateTime_ using the `realtime` clock. - -| xref::functions/temporal/index.adoc#functions-datetime-statement[datetime.statement()] -| Temporal -| Returns the current _DateTime_ using the `statement` clock. - -| xref::functions/temporal/index.adoc#functions-datetime-transaction[datetime.transaction()] -| Temporal -| Returns the current _DateTime_ using the `transaction` clock. - -| xref::functions/temporal/index.adoc#functions-datetime-truncate[datetime.truncate()] -| Temporal -| -Returns a _DateTime_ obtained by truncating a value at a specific component boundary. -xref::functions/temporal/index.adoc#functions-temporal-truncate-overview[Truncation summary]. - -| xref::functions/mathematical-trigonometric.adoc#functions-degrees[degrees()] -| Trigonometric -| Converts radians to degrees. - -| xref::functions/temporal/duration.adoc#functions-duration[+duration({map})+] -| Temporal -| Returns a _Duration_ from a map of its components. - -| xref::functions/temporal/duration.adoc#functions-duration-create-string[duration(string)] -| Temporal -| Returns a _Duration_ by parsing a string. - -| xref::functions/temporal/duration.adoc#functions-duration-between[duration.between()] -| Temporal -| Returns a _Duration_ equal to the difference between two given instants. - -| xref::functions/temporal/duration.adoc#functions-duration-indays[duration.inDays()] -| Temporal -| Returns a _Duration_ equal to the difference in whole days or weeks between two given instants. - -| xref::functions/temporal/duration.adoc#functions-duration-inmonths[duration.inMonths()] -| Temporal -| Returns a _Duration_ equal to the difference in whole months, quarters or years between two given instants. - -| xref::functions/temporal/duration.adoc#functions-duration-inseconds[duration.inSeconds()] -| Temporal -| Returns a _Duration_ equal to the difference in seconds and fractions of seconds, or minutes or hours, between two given instants. - -| xref::functions/mathematical-logarithmic.adoc#functions-e[e()] -| Logarithmic -| Returns the base of the natural logarithm, `e`. - -| xref::functions/scalar.adoc#functions-endnode[endNode()] -| Scalar -| Returns the end node of a relationship. - -| xref::functions/predicate.adoc#functions-exists[exists()] -| Predicate -| Returns true if a match for the pattern exists in the graph, or if the specified property exists in the node, relationship or map. - -| xref::functions/mathematical-logarithmic.adoc#functions-exp[exp()] -| Logarithmic -| Returns `e^n`, where `e` is the base of the natural logarithm, and `n` is the value of the argument expression. - -| xref::functions/mathematical-numeric.adoc#functions-floor[floor()] -| Numeric -| Returns the largest floating point number that is less than or equal to a number and equal to a mathematical integer. - -| xref::functions/mathematical-trigonometric.adoc#functions-haversin[haversin()] -| Trigonometric -| Returns half the versine of a number. - -| xref::functions/scalar.adoc#functions-head[head()] -| Scalar -| Returns the first element in a list. - -| xref::functions/scalar.adoc#functions-id[id()] -| Scalar -| Returns the id of a relationship or node. - -| xref::functions/predicate.adoc#functions-isempty[isEmpty()] -| Predicate -| Returns true if the given list or map contains no elements or if the given string contains no characters. - -| xref::functions/list.adoc#functions-keys[keys()] -| List -| Returns a list containing the string representations for all the property names of a node, relationship, or map. - -| xref::functions/list.adoc#functions-labels[labels()] -| List -| Returns a list containing the string representations for all the labels of a node. - -| xref::functions/scalar.adoc#functions-last[last()] -| Scalar -| Returns the last element in a list. - -| xref::functions/string.adoc#functions-left[left()] -| String -| Returns a string containing the specified number of leftmost characters of the original string. - -| xref::functions/scalar.adoc#functions-length[length()] -| Scalar -| Returns the length of a path. - -| xref::functions/temporal/index.adoc#functions-localdatetime-current[localdatetime()] -| Temporal -| Returns the current _LocalDateTime_. - -| xref::functions/temporal/index.adoc#functions-localdatetime-calendar[localdatetime({year [, month, day, ...\]})] -| Temporal -| Returns a calendar (Year-Month-Day) _LocalDateTime_. - -| xref::functions/temporal/index.adoc#functions-localdatetime-week[localdatetime({year [, week, dayOfWeek, ...\]})] -| Temporal -| Returns a week (Year-Week-Day) _LocalDateTime_. - -| xref::functions/temporal/index.adoc#functions-localdatetime-quarter[localdatetime({year [, quarter, dayOfQuarter, ...\]})] -| Temporal -| Returns a quarter (Year-Quarter-Day) _DateTime_. - -| xref::functions/temporal/index.adoc#functions-localdatetime-ordinal[localdatetime({year [, ordinalDay, ...\]})] -| Temporal -| Returns an ordinal (Year-Day) _LocalDateTime_. - -| xref::functions/temporal/index.adoc#functions-localdatetime-create-string[localdatetime(string)] -| Temporal -| Returns a _LocalDateTime_ by parsing a string. - -| xref::functions/temporal/index.adoc#functions-localdatetime-temporal[localdatetime(+{map}+)] -| Temporal -| Returns a _LocalDateTime_ from a map of another temporal value's components. - -| xref::functions/temporal/index.adoc#functions-localdatetime-realtime[localdatetime.realtime()] -| Temporal -| Returns the current _LocalDateTime_ using the `realtime` clock. - -| xref::functions/temporal/index.adoc#functions-localdatetime-statement[localdatetime.statement()] -| Temporal -| Returns the current _LocalDateTime_ using the `statement` clock. - -| xref::functions/temporal/index.adoc#functions-localdatetime-transaction[localdatetime.transaction()] -| Temporal -| Returns the current _LocalDateTime_ using the `transaction` clock. - -| xref::functions/temporal/index.adoc#functions-localdatetime-truncate[localdatetime.truncate()] -| Temporal -| -Returns a _LocalDateTime_ obtained by truncating a value at a specific component boundary. -xref::functions/temporal/index.adoc#functions-temporal-truncate-overview[Truncation summary]. - -| xref::functions/temporal/index.adoc#functions-localtime-current[localtime()] -| Temporal -| Returns the current _LocalTime_. - -| xref::functions/temporal/index.adoc#functions-localtime-create[localtime({hour [, minute, second, ...\]})] -| Temporal -| Returns a _LocalTime_ with the specified component values. - -| xref::functions/temporal/index.adoc#functions-localtime-create-string[localtime(string)] -| Temporal -| Returns a _LocalTime_ by parsing a string. - -| xref::functions/temporal/index.adoc#functions-localtime-temporal[localtime({time [, hour, ...\]})] -| Temporal -| Returns a _LocalTime_ from a map of another temporal value's components. - -| xref::functions/temporal/index.adoc#functions-localtime-realtime[localtime.realtime()] -| Temporal -| Returns the current _LocalTime_ using the `realtime` clock. - -| xref::functions/temporal/index.adoc#functions-localtime-statement[localtime.statement()] -| Temporal -| Returns the current _LocalTime_ using the `statement` clock. - -| xref::functions/temporal/index.adoc#functions-localtime-transaction[localtime.transaction()] -| Temporal -| Returns the current _LocalTime_ using the `transaction` clock. - -| xref::functions/temporal/index.adoc#functions-localtime-truncate[localtime.truncate()] -| Temporal -| -Returns a _LocalTime_ obtained by truncating a value at a specific component boundary. -xref::functions/temporal/index.adoc#functions-temporal-truncate-overview[Truncation summary]. - -| xref::functions/mathematical-logarithmic.adoc#functions-log[log()] -| Logarithmic -| Returns the natural logarithm of a number. - -| xref::functions/mathematical-logarithmic.adoc#functions-log10[log10()] -| Logarithmic -| Returns the common logarithm (base 10) of a number. - -| xref::functions/string.adoc#functions-ltrim[lTrim()] -| String -| Returns the original string with leading whitespace removed. - -| xref::functions/aggregating.adoc#functions-max[max()] -| Aggregating -| Returns the maximum value in a set of values. - -| xref::functions/aggregating.adoc#functions-min[min()] -| Aggregating -| Returns the minimum value in a set of values. - -| xref::functions/list.adoc#functions-nodes[nodes()] -| List -| Returns a list containing all the nodes in a path. - -| xref::functions/predicate.adoc#functions-none[none()] -| Predicate -| Returns true if the predicate holds for no element in a list. - -| xref::functions/aggregating.adoc#functions-percentilecont[percentileCont()] -| Aggregating -| Returns the percentile of the given value over a group using linear interpolation. - -| xref::functions/aggregating.adoc#functions-percentiledisc[percentileDisc()] -| Aggregating -| Returns the nearest value to the given percentile over a group using a rounding method. - -| xref::functions/mathematical-trigonometric.adoc#functions-pi[pi()] -| Trigonometric -| Returns the mathematical constant _pi_. - -| xref::functions/spatial.adoc#functions-point-cartesian-2d[point() - Cartesian 2D] -| Spatial -| Returns a 2D point object, given two coordinate values in the Cartesian coordinate system. - -| xref::functions/spatial.adoc#functions-point-cartesian-3d[point() - Cartesian 3D] -| Spatial -| Returns a 3D point object, given three coordinate values in the Cartesian coordinate system. - -| xref::functions/spatial.adoc#functions-point-wgs84-2d[point() - WGS 84 2D] -| Spatial -| Returns a 2D point object, given two coordinate values in the WGS 84 coordinate system. - -| xref::functions/spatial.adoc#functions-point-wgs84-3d[point() - WGS 84 3D] -| Spatial -| Returns a 3D point object, given three coordinate values in the WGS 84 coordinate system. - -| xref::functions/spatial.adoc#functions-distance[point.distance()] -| Spatial -| Returns true if the provided point is within the bounding box defined by the two provided points. - -| xref::functions/spatial.adoc#functions-distance[point.withinBBox()] -| Spatial -| Returns a floating point number representing the geodesic distance between any two points in the same CRS. - -| xref::functions/scalar.adoc#functions-properties[properties()] -| Scalar -| Returns a map containing all the properties of a node or relationship. - -| xref::functions/mathematical-trigonometric.adoc#functions-radians[radians()] -| Trigonometric -| Converts degrees to radians. - -| xref::functions/mathematical-numeric.adoc#functions-rand[rand()] -| Numeric -| Returns a random floating point number in the range from 0 (inclusive) to 1 (exclusive); i.e. `[0, 1)`. - -| xref::functions/scalar.adoc#functions-randomuuid[randomUUID()] -| Scalar -| Returns a string value corresponding to a randomly-generated UUID. - -| xref::functions/list.adoc#functions-range[range()] -| List -| Returns a list comprising all integer values within a specified range. - -| xref::functions/list.adoc#functions-reduce[reduce()] -| List -| Runs an expression against individual elements of a list, storing the result of the expression in an accumulator. - -| xref::functions/list.adoc#functions-relationships[relationships()] -| List -| Returns a list containing all the relationships in a path. - -| xref::functions/string.adoc#functions-replace[replace()] -| String -| Returns a string in which all occurrences of a specified string in the original string have been replaced by another (specified) string. - -| xref::functions/list.adoc#functions-reverse-list[reverse()] -| List -| Returns a list in which the order of all elements in the original list have been reversed. - -| xref::functions/string.adoc#functions-reverse[reverse()] -| String -| Returns a string in which the order of all characters in the original string have been reversed. - -| xref::functions/string.adoc#functions-right[right()] -| String -| Returns a string containing the specified number of rightmost characters of the original string. - -| xref::functions/mathematical-numeric.adoc#functions-round[round()] -| Numeric -| Returns the value of the given number rounded to the nearest integer, with half-way values always rounded up. - -| xref::functions/mathematical-numeric.adoc#functions-round2[round(), with precision] -| Numeric -| Returns the value of the given number rounded with the specified precision, with half-values always being rounded up. - -| xref::functions/mathematical-numeric.adoc#functions-round3[round(), with precision and rounding mode] -| Numeric -| Returns the value of the given number rounded with the specified precision and the specified rounding mode. - -| xref::functions/string.adoc#functions-rtrim[rTrim()] -| String -| Returns the original string with trailing whitespace removed. - -| xref::functions/mathematical-numeric.adoc#functions-sign[sign()] -| Numeric -| Returns the signum of a number: `0` if the number is `0`, `-1` for any negative number, and `1` for any positive number. - -| xref::functions/mathematical-trigonometric.adoc#functions-sin[sin()] -| Trigonometric -| Returns the sine of a number. - -| xref::functions/predicate.adoc#functions-single[single()] -| Predicate -| Returns true if the predicate holds for exactly one of the elements in a list. - -| xref::functions/scalar.adoc#functions-size[size()] -| Scalar -| Returns the number of items in a list. - -| xref::functions/scalar.adoc#functions-size-of-pattern-comprehension[size() applied to pattern comprehension] -| Scalar -| Returns the number of paths matching the pattern comprehension. - -| xref::functions/scalar.adoc#functions-size-of-string[size() applied to string] -| Scalar -| Returns the number of Unicode characters in a string. - -| xref::functions/string.adoc#functions-split[split()] -| String -| Returns a list of strings resulting from the splitting of the original string around matches of the given delimiter. - -| xref::functions/mathematical-logarithmic.adoc#functions-sqrt[sqrt()] -| Logarithmic -| Returns the square root of a number. - -| xref::functions/scalar.adoc#functions-startnode[startNode()] -| Scalar -| Returns the start node of a relationship. - -| xref::functions/aggregating.adoc#functions-stdev[stDev()] -| Aggregating -| Returns the standard deviation for the given value over a group for a sample of a population. - -| xref::functions/aggregating.adoc#functions-stdevp[stDevP()] -| Aggregating -| Returns the standard deviation for the given value over a group for an entire population. - -| xref::functions/string.adoc#functions-substring[substring()] -| String -| Returns a substring of the original string, beginning with a 0-based index start and length. - -| xref::functions/aggregating.adoc#functions-sum[sum()] -| Aggregating -| Returns the sum of a set of numeric values. - -| xref::functions/list.adoc#functions-tail[tail()] -| List -| Returns all but the first element in a list. - -| xref::functions/mathematical-trigonometric.adoc#functions-tan[tan()] -| Trigonometric -| Returns the tangent of a number. - -| xref::functions/temporal/index.adoc#functions-time-current[time()] -| Temporal -| Returns the current _Time_. - -| xref::functions/temporal/index.adoc#functions-time-create[time({hour [, minute, ...\]})] -| Temporal -| Returns a _Time_ with the specified component values. - -| xref::functions/temporal/index.adoc#functions-time-create-string[time(string)] -| Temporal -| Returns a _Time_ by parsing a string. - -| xref::functions/temporal/index.adoc#functions-time-temporal[time({time [, hour, ..., timezone\]})] -| Temporal -| Returns a _Time_ from a map of another temporal value's components. - -| xref::functions/temporal/index.adoc#functions-time-realtime[time.realtime()] -| Temporal -| Returns the current _Time_ using the `realtime` clock. - -| xref::functions/temporal/index.adoc#functions-time-statement[time.statement()] -| Temporal -| Returns the current _Time_ using the `statement` clock. - -| xref::functions/temporal/index.adoc#functions-time-transaction[time.transaction()] -| Temporal -| Returns the current _Time_ using the `transaction` clock. - -| xref::functions/temporal/index.adoc#functions-time-truncate[time.truncate()] -| Temporal -| -Returns a _Time_ obtained by truncating a value at a specific component boundary. -xref::functions/temporal/index.adoc#functions-temporal-truncate-overview[Truncation summary]. - -| xref::functions/scalar.adoc#functions-timestamp[timestamp()] -| Scalar -| Returns the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC. - -| xref::functions/scalar.adoc#functions-toboolean[toBoolean()] -| Scalar -| Converts a string value to a boolean value. - -| xref::functions/scalar.adoc#functions-tofloat[toFloat()] -| Scalar -| Converts an integer or string value to a floating point number. - -| xref::functions/scalar.adoc#functions-tointeger[toInteger()] -| Scalar -| Converts a floating point or string value to an integer value. - -| xref::functions/string.adoc#functions-tolower[toLower()] -| String -| Returns the original string in lowercase. - -| xref::functions/string.adoc#functions-tostring[toString()] -| String -| Converts an integer, float, boolean or temporal (i.e. Date, Time, LocalTime, DateTime, LocalDateTime or Duration) value to a string. - -| xref::functions/string.adoc#functions-toupper[toUpper()] -| String -| Returns the original string in uppercase. - -| xref::functions/string.adoc#functions-trim[trim()] -| String -| Returns the original string with leading and trailing whitespace removed. - -| xref::functions/scalar.adoc#functions-type[type()] -| Scalar -| Returns the string representation of the relationship type. - +|Function | Category | Description +|xref:functions/mathematical-numeric.adoc#functions-abs[abs()] | Numeric | Returns the absolute value of a number. +|xref:functions/mathematical-trigonometric.adoc#functions-acos[acos()] | Trigonometric | Returns the arccosine of a number in radians. +|xref:functions/predicate.adoc#functions-all[all()] | Predicate | Tests whether the predicate holds for all elements in a list. +|xref:functions/predicate.adoc#functions-any[any()] | Predicate | Tests whether the predicate holds for at least one element in a list. +|xref:functions/mathematical-trigonometric.adoc#functions-asin[asin()] | Trigonometric | Returns the arcsine of a number in radians. +|xref:functions/mathematical-trigonometric.adoc#functions-atan[atan()] | Trigonometric | Returns the arctangent of a number in radians. +|xref:functions/mathematical-trigonometric.adoc#functions-atan2[atan2()] | Trigonometric | Returns the arctangent2 of a set of coordinates in radians. +|xref:functions/aggregating.adoc#functions-avg[avg()] | Aggregating | Returns the average of a set of values. +|xref:functions/mathematical-numeric.adoc#functions-ceil[ceil()] | Numeric | Returns the smallest floating point number that is greater than or equal to a number and equal to a mathematical integer. +|xref:functions/scalar.adoc#functions-coalesce[coalesce()] | Scalar | Returns the first non-`null` value in a list of expressions. +|xref:functions/aggregating.adoc#functions-collect[collect()] | Aggregating | Returns a list containing the values returned by an expression. +|xref:functions/mathematical-trigonometric.adoc#functions-cos[cos()] | Trigonometric | Returns the cosine of a number. +|xref:functions/mathematical-trigonometric.adoc#functions-cot[cot()] | Trigonometric | Returns the cotangent of a number. +|xref:functions/aggregating.adoc#functions-count[count()] | Aggregating | Returns the number of values or rows. +| xref:functions/temporal/index.adoc#functions-date-current[date()] | Temporal | Returns the current _Date_. +| xref:functions/temporal/index.adoc#functions-date-calendar[date({year [, month, day\]})] | Temporal | Returns a calendar (Year-Month-Day) _Date_. +| xref:functions/temporal/index.adoc#functions-date-week[date({year [, week, dayOfWeek\]})] | Temporal | Returns a week (Year-Week-Day) _Date_. +| xref:functions/temporal/index.adoc#functions-date-quarter[date({year [, quarter, dayOfQuarter\]})] | Temporal | Returns a quarter (Year-Quarter-Day) _Date_. +| xref:functions/temporal/index.adoc#functions-date-ordinal[date({year [, ordinalDay\]})] | Temporal | Returns an ordinal (Year-Day) _Date_. +| xref:functions/temporal/index.adoc#functions-date-create-string[date(string)] | Temporal | Returns a _Date_ by parsing a string. +| xref:functions/temporal/index.adoc#functions-date-temporal[date(+{map}+)] | Temporal | Returns a _Date_ from a map of another temporal value's components. +| xref:functions/temporal/index.adoc#functions-date-realtime[date.realtime()] | Temporal | Returns the current _Date_ using the `realtime` clock. +| xref:functions/temporal/index.adoc#functions-date-statement[date.statement()] | Temporal | Returns the current _Date_ using the `statement` clock. +| xref:functions/temporal/index.adoc#functions-date-transaction[date.transaction()] | Temporal | Returns the current _Date_ using the `transaction` clock. +| xref:functions/temporal/index.adoc#functions-date-truncate[date.truncate()] | Temporal | Returns a _Date_ obtained by truncating a value at a specific component boundary. xref:functions/temporal/index.adoc#functions-temporal-truncate-overview[Truncation summary]. +| xref:functions/temporal/index.adoc#functions-datetime-current[datetime()] | Temporal | Returns the current _DateTime_. +| xref:functions/temporal/index.adoc#functions-datetime-calendar[datetime({year [, month, day, ...\]})] | Temporal | Returns a calendar (Year-Month-Day) _DateTime_. +| xref:functions/temporal/index.adoc#functions-datetime-week[datetime({year [, week, dayOfWeek, ...\]})] | Temporal | Returns a week (Year-Week-Day) _DateTime_. +| xref:functions/temporal/index.adoc#functions-datetime-quarter[datetime({year [, quarter, dayOfQuarter, ...\]})] | Temporal | Returns a quarter (Year-Quarter-Day) _DateTime_. +| xref:functions/temporal/index.adoc#functions-datetime-ordinal[datetime({year [, ordinalDay, ...\]})] | Temporal | Returns an ordinal (Year-Day) _DateTime_. +| xref:functions/temporal/index.adoc#functions-datetime-create-string[datetime(string)] | Temporal | Returns a _DateTime_ by parsing a string. +| xref:functions/temporal/index.adoc#functions-datetime-temporal[datetime(+{map}+)] | Temporal | Returns a _DateTime_ from a map of another temporal value's components. +| xref:functions/temporal/index.adoc#functions-datetime-timestamp[datetime(+{epochSeconds}+)] | Temporal | Returns a _DateTime_ from a timestamp. +| xref:functions/temporal/index.adoc#functions-datetime-realtime[datetime.realtime()] | Temporal | Returns the current _DateTime_ using the `realtime` clock. +| xref:functions/temporal/index.adoc#functions-datetime-statement[datetime.statement()] | Temporal | Returns the current _DateTime_ using the `statement` clock. +| xref:functions/temporal/index.adoc#functions-datetime-transaction[datetime.transaction()] | Temporal | Returns the current _DateTime_ using the `transaction` clock. +| xref:functions/temporal/index.adoc#functions-datetime-truncate[datetime.truncate()] | Temporal | Returns a _DateTime_ obtained by truncating a value at a specific component boundary. xref:functions/temporal/index.adoc#functions-temporal-truncate-overview[Truncation summary]. +|xref:functions/mathematical-trigonometric.adoc#functions-degrees[degrees()] | Trigonometric | Converts radians to degrees. +|xref:functions/spatial.adoc#functions-distance[distance()] | Spatial | Returns a floating point number representing the geodesic distance between any two points in the same CRS. +| xref:functions/temporal/duration.adoc#functions-duration[duration(+{map}+)] | Temporal | Returns a _Duration_ from a map of its components. +| xref:functions/temporal/duration.adoc#functions-duration-create-string[duration(string)] | Temporal | Returns a _Duration_ by parsing a string. +| xref:functions/temporal/duration.adoc#functions-duration-between[duration.between()] | Temporal | Returns a _Duration_ equal to the difference between two given instants. +| xref:functions/temporal/duration.adoc#functions-duration-indays[duration.inDays()] | Temporal | Returns a _Duration_ equal to the difference in whole days or weeks between two given instants. +| xref:functions/temporal/duration.adoc#functions-duration-inmonths[duration.inMonths()] | Temporal | Returns a _Duration_ equal to the difference in whole months, quarters or years between two given instants. +| xref:functions/temporal/duration.adoc#functions-duration-inseconds[duration.inSeconds()] | Temporal | Returns a _Duration_ equal to the difference in seconds and fractions of seconds, or minutes or hours, between two given instants. +|xref:functions/mathematical-logarithmic.adoc#functions-e[e()] | Logarithmic | Returns the base of the natural logarithm, `e`. +|xref:functions/scalar.adoc#functions-endnode[endNode()] | Scalar | Returns the end node of a relationship. +|xref:functions/predicate.adoc#functions-exists[exists()] | Predicate | Returns true if a match for the pattern exists in the graph, or if the specified property exists in the node, relationship or map. +|xref:functions/mathematical-logarithmic.adoc#functions-exp[exp()] | Logarithmic | Returns `e^n`, where `e` is the base of the natural logarithm, and `n` is the value of the argument expression. +|xref:functions/mathematical-numeric.adoc#functions-floor[floor()] | Numeric | Returns the largest floating point number that is less than or equal to a number and equal to a mathematical integer. +|xref:functions/mathematical-trigonometric.adoc#functions-haversin[haversin()] | Trigonometric | Returns half the versine of a number. +|xref:functions/scalar.adoc#functions-head[head()] | Scalar | Returns the first element in a list. +|xref:functions/scalar.adoc#functions-id[id()] | Scalar | Returns the id of a relationship or node. +|xref:functions/predicate.adoc#functions-isempty[isEmpty()] | Predicate | Returns true if the given list or map contains no elements or if the given string contains no characters. +|xref:functions/list.adoc#functions-keys[keys()] | List | Returns a list containing the string representations for all the property names of a node, relationship, or map. +|xref:functions/list.adoc#functions-labels[labels()] | List | Returns a list containing the string representations for all the labels of a node. +|xref:functions/scalar.adoc#functions-last[last()] | Scalar | Returns the last element in a list. +|xref:functions/string.adoc#functions-left[left()] | String | Returns a string containing the specified number of leftmost characters of the original string. +|xref:functions/scalar.adoc#functions-length[length()] | Scalar | Returns the length of a path. +| xref:functions/temporal/index.adoc#functions-localdatetime-current[localdatetime()] | Temporal | Returns the current _LocalDateTime_. +| xref:functions/temporal/index.adoc#functions-localdatetime-calendar[localdatetime({year [, month, day, ...\]})] | Temporal | Returns a calendar (Year-Month-Day) _LocalDateTime_. +| xref:functions/temporal/index.adoc#functions-localdatetime-week[localdatetime({year [, week, dayOfWeek, ...\]})] | Temporal | Returns a week (Year-Week-Day) _LocalDateTime_. +| xref:functions/temporal/index.adoc#functions-localdatetime-quarter[localdatetime({year [, quarter, dayOfQuarter, ...\]})] | Temporal | Returns a quarter (Year-Quarter-Day) _DateTime_. +| xref:functions/temporal/index.adoc#functions-localdatetime-ordinal[localdatetime({year [, ordinalDay, ...\]})] | Temporal | Returns an ordinal (Year-Day) _LocalDateTime_. +| xref:functions/temporal/index.adoc#functions-localdatetime-create-string[localdatetime(string)] | Temporal | Returns a _LocalDateTime_ by parsing a string. +| xref:functions/temporal/index.adoc#functions-localdatetime-temporal[localdatetime(+{map}+)] | Temporal | Returns a _LocalDateTime_ from a map of another temporal value's components. +| xref:functions/temporal/index.adoc#functions-localdatetime-realtime[localdatetime.realtime()] | Temporal | Returns the current _LocalDateTime_ using the `realtime` clock. +| xref:functions/temporal/index.adoc#functions-localdatetime-statement[localdatetime.statement()] | Temporal | Returns the current _LocalDateTime_ using the `statement` clock. +| xref:functions/temporal/index.adoc#functions-localdatetime-transaction[localdatetime.transaction()] | Temporal | Returns the current _LocalDateTime_ using the `transaction` clock. +| xref:functions/temporal/index.adoc#functions-localdatetime-truncate[localdatetime.truncate()] | Temporal | Returns a _LocalDateTime_ obtained by truncating a value at a specific component boundary. xref:functions/temporal/index.adoc#functions-temporal-truncate-overview[Truncation summary]. +| xref:functions/temporal/index.adoc#functions-localtime-current[localtime()] | Temporal | Returns the current _LocalTime_. +| xref:functions/temporal/index.adoc#functions-localtime-create[localtime({hour [, minute, second, ...\]})] | Temporal | Returns a _LocalTime_ with the specified component values. +| xref:functions/temporal/index.adoc#functions-localtime-create-string[localtime(string)] | Temporal | Returns a _LocalTime_ by parsing a string. +| xref:functions/temporal/index.adoc#functions-localtime-temporal[localtime({time [, hour, ...\]})] | Temporal | Returns a _LocalTime_ from a map of another temporal value's components. +| xref:functions/temporal/index.adoc#functions-localtime-realtime[localtime.realtime()] | Temporal | Returns the current _LocalTime_ using the `realtime` clock. +| xref:functions/temporal/index.adoc#functions-localtime-statement[localtime.statement()] | Temporal | Returns the current _LocalTime_ using the `statement` clock. +| xref:functions/temporal/index.adoc#functions-localtime-transaction[localtime.transaction()] | Temporal | Returns the current _LocalTime_ using the `transaction` clock. +| xref:functions/temporal/index.adoc#functions-localtime-truncate[localtime.truncate()] | Temporal | Returns a _LocalTime_ obtained by truncating a value at a specific component boundary. xref:functions/temporal/index.adoc#functions-temporal-truncate-overview[Truncation summary]. +|xref:functions/mathematical-logarithmic.adoc#functions-log[log()] | Logarithmic | Returns the natural logarithm of a number. +|xref:functions/mathematical-logarithmic.adoc#functions-log10[log10()] | Logarithmic | Returns the common logarithm (base 10) of a number. +|xref:functions/string.adoc#functions-ltrim[lTrim()] | String | Returns the original string with leading whitespace removed. +|xref:functions/aggregating.adoc#functions-max[max()] | Aggregating | Returns the maximum value in a set of values. +|xref:functions/aggregating.adoc#functions-min[min()] | Aggregating | Returns the minimum value in a set of values. +|xref:functions/list.adoc#functions-nodes[nodes()] | List | Returns a list containing all the nodes in a path. +|xref:functions/predicate.adoc#functions-none[none()] | Predicate | Returns true if the predicate holds for no element in a list. +|xref:functions/aggregating.adoc#functions-percentilecont[percentileCont()] | Aggregating | Returns the percentile of the given value over a group using linear interpolation. +|xref:functions/aggregating.adoc#functions-percentiledisc[percentileDisc()] | Aggregating | Returns the nearest value to the given percentile over a group using a rounding method. +|xref:functions/mathematical-trigonometric.adoc#functions-pi[pi()] | Trigonometric | Returns the mathematical constant _pi_. +|xref:functions/spatial.adoc#functions-point-cartesian-2d[point() - Cartesian 2D] | Spatial | Returns a 2D point object, given two coordinate values in the Cartesian coordinate system. +|xref:functions/spatial.adoc#functions-point-cartesian-3d[point() - Cartesian 3D] | Spatial | Returns a 3D point object, given three coordinate values in the Cartesian coordinate system. +|xref:functions/spatial.adoc#functions-point-wgs84-2d[point() - WGS 84 2D] | Spatial | Returns a 2D point object, given two coordinate values in the WGS 84 coordinate system. +|xref:functions/spatial.adoc#functions-point-wgs84-3d[point() - WGS 84 3D] | Spatial | Returns a 3D point object, given three coordinate values in the WGS 84 coordinate system. +|xref:functions/scalar.adoc#functions-properties[properties()] | Scalar | Returns a map containing all the properties of a node or relationship. +|xref:functions/mathematical-trigonometric.adoc#functions-radians[radians()] | Trigonometric | Converts degrees to radians. +|xref:functions/mathematical-numeric.adoc#functions-rand[rand()] | Numeric | Returns a random floating point number in the range from 0 (inclusive) to 1 (exclusive); i.e. `[0, 1)`. +|xref:functions/scalar.adoc#functions-randomuuid[randomUUID()] | Scalar | Returns a string value corresponding to a randomly-generated UUID. +|xref:functions/list.adoc#functions-range[range()] | List | Returns a list comprising all integer values within a specified range. +|xref:functions/list.adoc#functions-reduce[reduce()] | List | Runs an expression against individual elements of a list, storing the result of the expression in an accumulator. +|xref:functions/list.adoc#functions-relationships[relationships()] | List | Returns a list containing all the relationships in a path. +|xref:functions/string.adoc#functions-replace[replace()] | String | Returns a string in which all occurrences of a specified string in the original string have been replaced by another (specified) string. +|xref:functions/list.adoc#functions-reverse-list[reverse()] | List | Returns a list in which the order of all elements in the original list have been reversed. +|xref:functions/string.adoc#functions-reverse[reverse()] | String | Returns a string in which the order of all characters in the original string have been reversed. +|xref:functions/string.adoc#functions-right[right()] | String | Returns a string containing the specified number of rightmost characters of the original string. +|xref:functions/mathematical-numeric.adoc#functions-round[round()] | Numeric | Returns the value of the given number rounded to the nearest integer, with half-way values always rounded up. +|xref:functions/mathematical-numeric.adoc#functions-round2[round(), with precision] | Numeric | Returns the value of the given number rounded with the specified precision, with half-values always being rounded up. +|xref:functions/mathematical-numeric.adoc#functions-round3[round(), with precision and rounding mode] | Numeric | Returns the value of the given number rounded with the specified precision and the specified rounding mode. +|xref:functions/string.adoc#functions-rtrim[rTrim()] | String | Returns the original string with trailing whitespace removed. +|xref:functions/mathematical-numeric.adoc#functions-sign[sign()] | Numeric | Returns the signum of a number: `0` if the number is `0`, `-1` for any negative number, and `1` for any positive number. +|xref:functions/mathematical-trigonometric.adoc#functions-sin[sin()] | Trigonometric | Returns the sine of a number. +|xref:functions/predicate.adoc#functions-single[single()] | Predicate | Returns true if the predicate holds for exactly one of the elements in a list. +|xref:functions/scalar.adoc#functions-size[size()] | Scalar | Returns the number of items in a list. +|xref:functions/scalar.adoc#functions-size-of-pattern-expression[size() applied to pattern expression] | Scalar | Returns the number of paths matching the pattern expression. +|xref:functions/scalar.adoc#functions-size-of-string[size() applied to string] | Scalar | Returns the number of Unicode characters in a string. +|xref:functions/string.adoc#functions-split[split()] | String | Returns a list of strings resulting from the splitting of the original string around matches of the given delimiter. +|xref:functions/mathematical-logarithmic.adoc#functions-sqrt[sqrt()] | Logarithmic | Returns the square root of a number. +|xref:functions/scalar.adoc#functions-startnode[startNode()] | Scalar | Returns the start node of a relationship. +|xref:functions/aggregating.adoc#functions-stdev[stDev()] | Aggregating | Returns the standard deviation for the given value over a group for a sample of a population. +|xref:functions/aggregating.adoc#functions-stdevp[stDevP()] | Aggregating | Returns the standard deviation for the given value over a group for an entire population. +|xref:functions/string.adoc#functions-substring[substring()] | String | Returns a substring of the original string, beginning with a 0-based index start and length. +|xref:functions/aggregating.adoc#functions-sum[sum()] | Aggregating | Returns the sum of a set of numeric values. +|xref:functions/list.adoc#functions-tail[tail()] | List | Returns all but the first element in a list. +|xref:functions/mathematical-trigonometric.adoc#functions-tan[tan()] | Trigonometric | Returns the tangent of a number. +| xref:functions/temporal/index.adoc#functions-time-current[time()] | Temporal | Returns the current _Time_. +| xref:functions/temporal/index.adoc#functions-time-create[time({hour [, minute, ...\]})] | Temporal | Returns a _Time_ with the specified component values. +| xref:functions/temporal/index.adoc#functions-time-create-string[time(string)] | Temporal | Returns a _Time_ by parsing a string. +| xref:functions/temporal/index.adoc#functions-time-temporal[time({time [, hour, ..., timezone\]})] | Temporal | Returns a _Time_ from a map of another temporal value's components. +| xref:functions/temporal/index.adoc#functions-time-realtime[time.realtime()] | Temporal | Returns the current _Time_ using the `realtime` clock. +| xref:functions/temporal/index.adoc#functions-time-statement[time.statement()] | Temporal | Returns the current _Time_ using the `statement` clock. +| xref:functions/temporal/index.adoc#functions-time-transaction[time.transaction()] | Temporal | Returns the current _Time_ using the `transaction` clock. +| xref:functions/temporal/index.adoc#functions-time-truncate[time.truncate()] | Temporal | Returns a _Time_ obtained by truncating a value at a specific component boundary. xref:functions/temporal/index.adoc#functions-temporal-truncate-overview[Truncation summary]. +|xref:functions/scalar.adoc#functions-timestamp[timestamp()] | Scalar | Returns the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC. +|xref:functions/scalar.adoc#functions-toboolean[toBoolean()] | Scalar | Converts a string value to a boolean value. +|xref:functions/scalar.adoc#functions-tofloat[toFloat()] | Scalar | Converts an integer or string value to a floating point number. +|xref:functions/scalar.adoc#functions-tointeger[toInteger()] | Scalar | Converts a floating point or string value to an integer value. +|xref:functions/string.adoc#functions-tolower[toLower()] | String | Returns the original string in lowercase. +|xref:functions/string.adoc#functions-tostring[toString()] | String | Converts an integer, float, boolean or temporal (i.e. Date, Time, LocalTime, DateTime, LocalDateTime or Duration) value to a string. +|xref:functions/string.adoc#functions-toupper[toUpper()] | String | Returns the original string in uppercase. +|xref:functions/string.adoc#functions-trim[trim()] | String | Returns the original string with leading and trailing whitespace removed. +|xref:functions/scalar.adoc#functions-type[type()] | Scalar | Returns the string representation of the relationship type. |=== @@ -997,11 +267,8 @@ xref::functions/temporal/index.adoc#functions-temporal-truncate-overview[Truncat [options="header"] |=== -| Name | Description - -| xref::syntax/expressions.adoc#query-syntax-case[CASE Expression] -| A generic conditional expression, similar to if/else statements available in other languages. - +|Name | Description +| xref:syntax/expressions.adoc#query-syntax-case[CASE Expression] | A generic conditional expression, similar to if/else statements available in other languages. |=== @@ -1010,465 +277,115 @@ xref::functions/temporal/index.adoc#functions-temporal-truncate-overview[Truncat [options="header"] |=== -| Name | Type | Description - -| xref::query-tuning/query-options.adoc#cypher-version[CYPHER $version query] -| Version -| -This will force `'query'` to use Neo4j Cypher `$version`. -//The default is `4.0`. - -| xref::query-tuning/query-options.adoc#cypher-runtime[CYPHER runtime=interpreted query] -| Runtime -| -This will force the query planner to use the interpreted runtime. -This is the only option in Neo4j Community Edition. - -| xref::query-tuning/query-options.adoc#cypher-runtime[CYPHER runtime=slotted query] -| Runtime -| -This will cause the query planner to use the slotted runtime. -This is only available in Neo4j Enterprise Edition. - -| xref::query-tuning/query-options.adoc#cypher-runtime[CYPHER runtime=pipelined query] -| Runtime -| -This will cause the query planner to use the pipelined runtime if it supports `'query'`. -This is only available in Neo4j Enterprise Edition. - +|Name | Type | Description +| xref:query-tuning/index.adoc#cypher-version[CYPHER $version query] | Version | This will force `'query'` to use Neo4j Cypher `$version`. The default is `4.0`. +| xref:query-tuning/index.adoc#cypher-runtime[CYPHER runtime=interpreted query] | Runtime | This will force the query planner to use the interpreted runtime. This is the only option in Neo4j Community Edition. +| xref:query-tuning/index.adoc#cypher-runtime[CYPHER runtime=slotted query] | Runtime | This will cause the query planner to use the slotted runtime. This is only available in Neo4j Enterprise Edition. +| xref:query-tuning/index.adoc#cypher-runtime[CYPHER runtime=pipelined query] | Runtime | This will cause the query planner to use the pipelined runtime if it supports `'query'`. This is only available in Neo4j Enterprise Edition. |=== - [[glossary-admin-commands]] == Administrative commands - The following commands are only executable against the `system` database: [options="header"] |=== -| Command | Admin category | Description - -| xref::aliases.adoc#alias-management-alter-database-alias[ALTER ALIAS ... [IF EXISTS\] SET DATABASE ...] -| Database alias -| Modifies a database alias. - -| xref::access-control/manage-users.adoc#access-control-alter-password[ALTER CURRENT USER SET PASSWORD FROM ... TO] -| User and role -| Change the password of the user that is currently logged in. - -| xref::databases.adoc#administration-databases-alter-database[ALTER DATABASE ... [IF EXISTS\] SET ACCESS {READ ONLY \| READ WRITE}] -| Database -| Modifies the database access mode. - -| xref::access-control/manage-users.adoc#access-control-alter-users[ALTER USER ... [IF EXISTS\] [SET [PLAINTEXT \| ENCRYPTED\] PASSWORD {password [CHANGE [NOT\] REQUIRED\] \| CHANGE [NOT\] REQUIRED}\] [SET STATUS {ACTIVE \| SUSPENDED}\] [SET HOME DATABASE name\] [REMOVE HOME DATABASE\]] -| User and role -| -Changes a user account. -Changes can include setting a new password, setting the account status, setting or removing home database and enabling that the user should change the password upon next login. - -| xref::aliases.adoc#alias-management-create-database-alias[CREATE [OR REPLACE\] ALIAS ... [IF NOT EXISTS\] FOR DATABASE ...] -| Database alias -| Creates a new database alias. - -| xref::databases.adoc#administration-databases-create-database[CREATE [OR REPLACE\] DATABASE ... [IF NOT EXISTS\] [OPTIONS {optionKey: optionValue[, ...\]}\] [WAIT [n [SEC[OND[S\]\]\]\]\|NOWAIT\]] -| Database -| Creates a new database. - -| xref::access-control/manage-roles.adoc#access-control-create-roles[CREATE [OR REPLACE\] ROLE ... [IF NOT EXISTS\] [AS COPY OF\]] -| User and role -| Creates new roles. - -| xref::access-control/manage-users.adoc#access-control-create-users[CREATE [OR REPLACE\] USER ... [IF NOT EXISTS\] SET [PLAINTEXT \| ENCRYPTED\] PASSWORD ... [[SET PASSWORD\] CHANGE [NOT\] REQUIRED\] [SET STATUS {ACTIVE \| SUSPENDED}\] [SET HOME DATABASE name\]] -| User and role -| -Creates a new user and sets the password for the new account. -Optionally the account status and home database can also be set and if the user should change the password upon first login. - -| xref::access-control/database-administration.adoc[DENY ... ON DATABASE ... TO] -| Privilege -| Denies a database or schema privilege to one or multiple roles. - -| xref::access-control/dbms-administration.adoc[DENY ... ON DBMS TO] -| Privilege -| Denies a DBMS privilege to one or multiple roles. - -| xref::access-control/manage-privileges.adoc#access-control-graph-privileges[DENY ... ON GRAPH ... [NODES \| RELATIONSHIPS \| ELEMENTS\] ... TO] -| Privilege -| Denies a graph privilege for one or multiple specified elements to one or multiple roles. - -| xref::aliases.adoc#alias-management-drop-database-alias[DROP ALIAS ... [IF EXISTS\] FOR DATABASE] -| Database alias -| Deletes a specified database alias. - -| xref::databases.adoc#administration-databases-drop-database[DROP DATABASE ... [IF EXISTS\] [DUMP DATA \| DESTROY DATA\]] -| Database -| Deletes a specified database. - -| xref::access-control/manage-roles.adoc#access-control-drop-roles[DROP ROLE ... [IF EXISTS\]] -| User and role -| Deletes a specified role. - -| xref::access-control/manage-users.adoc#access-control-drop-users[DROP USER ... [IF EXISTS\]] -| User and role -| Deletes a specified user. - -| xref::access-control/database-administration.adoc[GRANT ... ON DATABASE ... TO] -| Privilege -| Assigns a database or schema privilege to one or multiple roles. - -| xref::access-control/dbms-administration.adoc[GRANT ... ON DBMS TO] -| Privilege -| Assigns a DBMS privilege to one or multiple roles. - -| xref::access-control/manage-privileges.adoc#access-control-graph-privileges[GRANT ... ON GRAPH ... [NODES \| RELATIONSHIPS \| ELEMENTS\] ... TO] -| Privilege -| Assigns a graph privilege for one or multiple specified elements to one or multiple roles. - -| xref::access-control/manage-roles.adoc#access-control-assign-roles[GRANT ROLE[S\] ... TO] -| User and role -| Assigns one or multiple roles to one or multiple users. - -| xref::access-control/manage-roles.adoc#access-control-rename-roles[RENAME ROLE ... [IF EXISTS\] TO ...] -| User and role -| Changes the name of a role. - -| xref::access-control/manage-users.adoc#access-control-rename-users[RENAME USER ... [IF EXISTS\] TO ...] -| User and role -| Changes the name of a user. - -| xref::access-control/database-administration.adoc[REVOKE [GRANT \| DENY\] ... ON DATABASE ... FROM] -| Privilege -| Removes a database or schema privilege from one or multiple roles. - -| xref::access-control/dbms-administration.adoc[REVOKE [GRANT \| DENY\] ... ON DBMS FROM] -| Privilege -| Removes a DBMS privilege from one or multiple roles. - -| xref::access-control/manage-privileges.adoc#access-control-revoke-privileges[REVOKE [GRANT \| DENY\] ... ON GRAPH ... [NODES \| RELATIONSHIPS \| ELEMENTS\] ... FROM] -| Privilege -| Removes a graph privilege for one or multiple specified elements from one or multiple roles. - -| xref::access-control/manage-roles.adoc#access-control-revoke-roles[REVOKE ROLE[S\] ... FROM] -| User and role -| Removes one or multiple roles from one or multiple users. - -| xref::aliases.adoc#alias-management-show-alias[SHOW ALIASES FOR DATABASE] -| Database alias -| Returns information about all aliases, optionally including driver settings. - -| xref::access-control/manage-roles.adoc#access-control-list-roles[SHOW [ALL \| POPULATED\] ROLES [WITH USERS\]] -| User and role -| Returns information about all or populated roles, optionally including the assigned users. - -| xref::databases.adoc#administration-databases-show-databases[SHOW DATABASE] -| Database -| Returns information about a specified database. - -| xref::databases.adoc#administration-databases-show-databases[SHOW DATABASES] -| Database -| Returns information about all databases. - -| xref::databases.adoc#administration-databases-show-databases[SHOW DEFAULT DATABASE] -| Database -| Returns information about the default database. - -| xref::databases.adoc#administration-databases-show-databases[SHOW HOME DATABASE] -| Database -| Returns information about the current users home database. - -| xref::access-control/manage-roles.adoc#access-control-list-roles[SHOW [ROLE ... \| USER ... \| ALL \] PRIVILEGES [AS [REVOKE\] COMMAND[S\]\]] -| Privilege -| Returns information about role, user or all privileges. - -| xref::access-control/manage-users.adoc#access-control-list-users[SHOW USERS] -| User and role -| Returns information about all users. - -| xref::databases.adoc#administration-databases-start-database[START DATABASE] -| Database -| Starts up a specified database. - -| xref::databases.adoc#administration-databases-stop-database[STOP DATABASE] -| Database -| Stops a specified database. - +|Command | Admin category | Description +| xref:access-control/manage-users.adoc#access-control-alter-password[ALTER CURRENT USER SET PASSWORD FROM ... TO] | User and role | Change the password of the user that is currently logged in. +| xref:access-control/manage-users.adoc#access-control-alter-users[ALTER USER ... [IF EXISTS\] [SET [PLAINTEXT \| ENCRYPTED\] PASSWORD {password [CHANGE [NOT\] REQUIRED\] \| CHANGE [NOT\] REQUIRED}\] [SET STATUS {ACTIVE \| SUSPENDED}\] [SET HOME DATABASE name\] [REMOVE HOME DATABASE\]] | User and role | Changes a user account. Changes can include setting a new password, setting the account status, setting or removing home database and enabling that the user should change the password upon next login. +| xref:databases.adoc#administration-databases-create-database[CREATE [OR REPLACE\] DATABASE ... [IF NOT EXISTS\] [OPTIONS {optionKey: optionValue[, ...\]}\] [WAIT [n [SEC[OND[S\]\]\]\]\|NOWAIT\]] | Database | Creates a new database. +| xref:access-control/manage-roles.adoc#access-control-create-roles[CREATE [OR REPLACE\] ROLE ... [IF NOT EXISTS\] [AS COPY OF\]] | User and role | Creates new roles. +| xref:access-control/manage-users.adoc#access-control-create-users[CREATE [OR REPLACE\] USER ... [IF NOT EXISTS\] SET [PLAINTEXT \| ENCRYPTED\] PASSWORD ... [[SET PASSWORD\] CHANGE [NOT\] REQUIRED\] [SET STATUS {ACTIVE \| SUSPENDED}\] [SET HOME DATABASE name\]] | User and role | Creates a new user and sets the password for the new account. Optionally the account status and home database can also be set and if the user should change the password upon first login. +| xref:access-control/database-administration.adoc[DENY ... ON DATABASE ... TO] | Privilege | Denies a database or schema privilege to one or multiple roles. +| xref:access-control/dbms-administration.adoc[DENY ... ON DBMS TO] | Privilege | Denies a DBMS privilege to one or multiple roles. +| xref:access-control/manage-privileges.adoc#access-control-graph-privileges[DENY ... ON GRAPH ... [NODES \| RELATIONSHIPS \| ELEMENTS\] ... TO] | Privilege | Denies a graph privilege for one or multiple specified elements to one or multiple roles. +| xref:databases.adoc#administration-databases-drop-database[DROP DATABASE ... [IF EXISTS\] [DUMP DATA \| DESTROY DATA\]] | Database | Deletes a specified database. +| xref:access-control/manage-roles.adoc#access-control-drop-roles[DROP ROLE ... [IF EXISTS\]] | User and role | Deletes a specified role. +| xref:access-control/manage-users.adoc#access-control-drop-users[DROP USER ... [IF EXISTS\]] | User and role | Deletes a specified user. +| xref:access-control/database-administration.adoc[GRANT ... ON DATABASE ... TO] | Privilege | Assigns a database or schema privilege to one or multiple roles. +| xref:access-control/dbms-administration.adoc[GRANT ... ON DBMS TO] | Privilege | Assigns a DBMS privilege to one or multiple roles. +| xref:access-control/manage-privileges.adoc#access-control-graph-privileges[GRANT ... ON GRAPH ... [NODES \| RELATIONSHIPS \| ELEMENTS\] ... TO] | Privilege | Assigns a graph privilege for one or multiple specified elements to one or multiple roles. +| xref:access-control/manage-roles.adoc#access-control-assign-roles[GRANT ROLE[S\] ... TO] | User and role | Assigns one or multiple roles to one or multiple users. +| xref:access-control/manage-roles.adoc#access-control-rename-roles[RENAME ROLE ... [IF EXISTS\] TO ...] | User and role | Changes the name of a role. +| xref:access-control/manage-users.adoc#access-control-rename-users[RENAME USER ... [IF EXISTS\] TO ...] | User and role | Changes the name of a user. +| xref:access-control/database-administration.adoc[REVOKE [GRANT \| DENY\] ... ON DATABASE ... FROM] | Privilege | Removes a database or schema privilege from one or multiple roles. +| xref:access-control/dbms-administration.adoc[REVOKE [GRANT \| DENY\] ... ON DBMS FROM] | Privilege | Removes a DBMS privilege from one or multiple roles. +| xref:access-control/manage-privileges.adoc#access-control-revoke-privileges[REVOKE [GRANT \| DENY\] ... ON GRAPH ... [NODES \| RELATIONSHIPS \| ELEMENTS\] ... FROM] | Privilege | Removes a graph privilege for one or multiple specified elements from one or multiple roles +| xref:access-control/manage-roles.adoc#access-control-revoke-roles[REVOKE ROLE[S\] ... FROM] | User and role | Removes one or multiple roles from one or multiple users. +| xref:access-control/manage-roles.adoc#access-control-list-roles[SHOW [ALL \| POPULATED\] ROLES [WITH USERS\]] | User and role | Returns information about all or populated roles, optionally including the assigned users. +| xref:databases.adoc#administration-databases-show-databases[SHOW DATABASE] | Database | Returns information about a specified database. +| xref:databases.adoc#administration-databases-show-databases[SHOW DATABASES] | Database | Returns information about all databases. +| xref:databases.adoc#administration-databases-show-databases[SHOW DEFAULT DATABASE] | Database | Returns information about the default database. +| xref:databases.adoc#administration-databases-show-databases[SHOW HOME DATABASE] | Database | Returns information about the current users home database. +| xref:access-control/manage-privileges.adoc#access-control-list-privileges[SHOW [ROLE ... \| USER ... \| ALL \] PRIVILEGES [AS [REVOKE\] COMMAND[S\]\]] | Privilege | Returns information about role, user or all privileges. +| xref:access-control/manage-users.adoc#access-control-list-users[SHOW USERS] | User and role | Returns information about all users. +| xref:databases.adoc#administration-databases-start-database[START DATABASE] | Database | Starts up a specified database. +| xref:databases.adoc#administration-databases-stop-database[STOP DATABASE] | Database | Stops a specified database. |=== - [[glossary-privileges]] == Privilege Actions [options="header"] |=== -| Name | Category | Description - -| xref::access-control/database-administration.adoc#access-control-database-administration-access[ACCESS] -| Database -| Determines whether a user can access a specific database. - -| xref::access-control/database-administration.adoc#access-control-database-administration-all[ALL DATABASE PRIVILEGES] -| Database and schema -| Determines whether a user is allowed to access, create, drop, and list indexes and constraints, create new labels, types and property names on a specific database. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-all[ALL DBMS PRIVILEGES] -| DBMS -| Determines whether a user is allowed to perform role, user, database and privilege management. - -| xref::access-control/privileges-writes.adoc#access-control-privileges-writes-all[ALL GRAPH PRIVILEGES] -| GRAPH -| Determines whether a user is allowed to perform reads and writes. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-alias-management[ALTER ALIAS] -| DBMS -| Determines whether the user can modify aliases. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-database-management[ALTER DATABASE] -| DBMS -| Determines whether the user can modify databases and aliases. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[ALTER USER] -| DBMS -| Determines whether the user can modify users. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[ASSIGN PRIVILEGE] -| DBMS -| Determines whether the user can assign privileges using the `GRANT` and `DENY` commands. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[ASSIGN ROLE] -| DBMS -| Determines whether the user can grant roles. - -| xref::access-control/database-administration.adoc#access-control-database-administration-constraints[CONSTRAINT MANAGEMENT] -| Schema -| Determines whether a user is allowed to create, drop, and list constraints on a specific database. - -| xref::access-control/privileges-writes.adoc#access-control-privileges-writes-create[CREATE] -| GRAPH -| Determines whether the user can create a new element (node, relationship or both). - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-alias-management[CREATE ALIAS] -| DBMS -| Determines whether the user can create new aliases. - -| xref::access-control/database-administration.adoc#access-control-database-administration-constraints[CREATE CONSTRAINT] -| Schema -| Determines whether a user is allowed to create constraints on a specific database. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-database-management[CREATE DATABASE] -| DBMS -| Determines whether the user can create new databases and aliases. - -| xref::access-control/database-administration.adoc#access-control-database-administration-index[CREATE INDEX] -| Schema -| Determines whether a user is allowed to create indexes on a specific database. - -| xref::access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW NODE LABEL] -| Schema -| Determines whether a user is allowed to create new node labels on a specific database. - -| xref::access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW PROPERTY NAME] -| Schema -| Determines whether a user is allowed to create new property names on a specific database. - -| xref::access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW RELATIONSHIP TYPE] -| Schema -| Determines whether a user is allowed to create new relationship types on a specific database. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[CREATE ROLE] -| DBMS -| Determines whether the user can create new roles. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[CREATE USER] -| DBMS -| Determines whether the user can create new users. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-alias-management[ALIAS MANAGEMENT] -| DBMS -| Determines whether the user can create, delete, modify and list aliases. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-database-management[DATABASE MANAGEMENT] -| DBMS -| Determines whether the user can create, delete, and modify databases and aliases. - -| xref::access-control/privileges-writes.adoc#access-control-privileges-writes-delete[DELETE] -| GRAPH -| Determines whether the user can delete an element (node, relationship or both). - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-alias-management[DROP ALIAS] -| DBMS -| Determines whether the user can delete aliases. - -| xref::access-control/database-administration.adoc#access-control-database-administration-constraints[DROP CONSTRAINT] -| Schema -| Determines whether a user is allowed to drop constraints on a specific database. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-database-management[DROP DATABASE] -| DBMS -| Determines whether the user can delete databases and aliases. - -| xref::access-control/database-administration.adoc#access-control-database-administration-index[DROP INDEX] -| Schema -| Determines whether a user is allowed to drop indexes on a specific database. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DROP ROLE] -| DBMS -| Determines whether the user can delete roles. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DROP USER] -| DBMS -| Determines whether the user can delete users. - -| xref::access-control/dbms-administration.adoc#access-control-admin-procedure[EXECUTE ADMIN PROCEDURE] -| DBMS -| Determines whether the user can execute admin procedures. - -| xref::access-control/dbms-administration.adoc#access-control-execute-boosted-user-defined-function[EXECUTE BOOSTED FUNCTION] -| DBMS -| Determines whether the user can execute functions with elevated privileges. - -| xref::access-control/dbms-administration.adoc#access-control-execute-boosted-procedure[EXECUTE BOOSTED PROCEDURE] -| DBMS -| Determines whether the user can execute procedures with elevated privileges. - -| xref::access-control/dbms-administration.adoc#access-control-execute-user-defined-function[EXECUTE FUNCTION] -| DBMS -| Determines whether the user can execute functions. - -| xref::access-control/dbms-administration.adoc#access-control-execute-procedure[EXECUTE PROCEDURE] -| DBMS -| Determines whether the user can execute procedures. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-impersonation[IMPERSONATE] -| DBMS -| Determines whether a user can impersonate another one and assume their privileges. - -| xref::access-control/database-administration.adoc#access-control-database-administration-index[INDEX MANAGEMENT] -| Schema -| Determines whether a user is allowed to create, drop, and list indexes on a specific database. - -| xref::access-control/database-administration.adoc#access-control-database-administration-index[MATCH] -| GRAPH -| Determines whether the properties of an element (node, relationship or both) can be read and the element can be found and traversed while executing queries on the specified graph. - -| xref::access-control/privileges-writes.adoc#access-control-privileges-writes-merge[MERGE] -| GRAPH -| Determines whether the user can find, read, create and set properties on an element (node, relationship or both). - -| xref::access-control/database-administration.adoc#access-control-database-administration-tokens[NAME MANAGEMENT] -| Schema -| Determines whether a user is allowed to create new labels, types and property names on a specific database. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[PRIVILEGE MANAGEMENT] -| DBMS -| Determines whether the user can show, assign and remove privileges. - -| xref::access-control/privileges-reads.adoc#access-control-privileges-reads-read[READ] -| GRAPH -| Determines whether the properties of an element (node, relationship or both) can be read while executing queries on the specified graph. - -| xref::access-control/privileges-writes.adoc#access-control-privileges-writes-remove-label[REMOVE LABEL] -| GRAPH -| Determines whether the user can remove a label from a node using the `REMOVE` clause. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[REMOVE PRIVILEGE] -| DBMS -| Determines whether the user can remove privileges using the `REVOKE` command. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[REMOVE ROLE] -| DBMS -| Determines whether the user can revoke roles. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[RENAME ROLE] -| DBMS -| Determines whether the user can rename roles. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[RENAME USER] -| DBMS -| Determines whether the user can rename users. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[ROLE MANAGEMENT] -| DBMS -| Determines whether the user can create, drop, grant, revoke and show roles. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-database-management[SET DATABASE ACCESS] -| DBMS -| Determines whether the user can modify the database access mode. - -| xref::access-control/privileges-writes.adoc#access-control-privileges-writes-set-label[SET LABEL] -| GRAPH -| Determines whether the user can set a label to a node using the SET clause. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[SET PASSWORDS] -| DBMS -| Determines whether the user can modify users' passwords and whether those passwords must be changed upon first login. - -| xref::access-control/privileges-writes.adoc#access-control-privileges-writes-set-property[SET PROPERTY] -| GRAPH -| Determines whether the user can set a property to an element (node, relationship or both) using the SET clause. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[SET USER HOME DATABASE] -| DBMS -| Determines whether the user can modify the home database of users. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[SET USER STATUS] -| DBMS -| Determines whether the user can modify the account status of users. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-alias-management[SHOW ALIAS] -| DBMS -| Determines whether the user is allowed to list aliases. - -| xref::access-control/database-administration.adoc#access-control-database-administration-constraints[SHOW CONSTRAINT] -| Schema -| Determines whether the user is allowed to list constraints. - -| xref::access-control/database-administration.adoc#access-control-database-administration-index[SHOW INDEX] -| Schema -| Determines whether the user is allowed to list indexes. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[SHOW PRIVILEGE] -| DBMS -| Determines whether the user can get information about privileges assigned to users and roles. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[SHOW ROLE] -| DBMS -| Determines whether the user can get information about existing and assigned roles. - -| xref::access-control/database-administration.adoc#access-control-database-administration-transaction[SHOW TRANSACTION] -| Database -| Determines whether a user is allowed to list transactions and queries. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[SHOW USER] -| DBMS -| Determines whether the user can get information about existing users. - -| xref::access-control/database-administration.adoc#access-control-database-administration-startstop[START] -| Database -| Determines whether a user can start up a specific database. - -| xref::access-control/database-administration.adoc#access-control-database-administration-startstop[STOP] -| Database -| Determines whether a user can stop a specific running database. - -| xref::access-control/database-administration.adoc#access-control-database-administration-transaction[TERMINATE TRANSACTION] -| Database -| Determines whether a user is allowed to end running transactions and queries. - -| xref::access-control/database-administration.adoc#access-control-database-administration-transaction[TRANSACTION MANAGEMENT] -| Database -| Determines whether a user is allowed to list and end running transactions and queries. - -| xref::access-control/privileges-reads.adoc#access-control-privileges-reads-traverse[TRAVERSE] -| GRAPH -| Determines whether an element (node, relationship or both) can be found and traversed while executing queries on the specified graph. - -| xref::access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[USER MANAGEMENT] -| DBMS -| Determines whether the user can create, drop, modify and show users. - -| xref::access-control/privileges-writes.adoc#access-control-privileges-writes-write[WRITE] -| GRAPH -| Determines whether the user can execute write operations on the specified graph. - +|Name | Category | Description +| xref:access-control/database-administration.adoc#access-control-database-administration-access[ACCESS] | Database | Determines whether a user can access a specific database. +| xref:access-control/database-administration.adoc#access-control-database-administration-all[ALL DATABASE PRIVILEGES] | Database and schema | Determines whether a user is allowed to access, create, drop, and list indexes and constraints, create new labels, types and property names on a specific database. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-all[ALL DBMS PRIVILEGES] | DBMS | Determines whether a user is allowed to perform role, user, database and privilege management. +| xref:access-control/privileges-writes.adoc#access-control-privileges-writes-all[ALL GRAPH PRIVILEGES] | GRAPH | Determines whether a user is allowed to perform reads and writes. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[ALTER USER] | DBMS | Determines whether the user can modify users. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[ASSIGN PRIVILEGE] | DBMS | Determines whether the user can assign privileges using the GRANT and DENY commands. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[ASSIGN ROLE] | DBMS | Determines whether the user can grant roles. +| xref:access-control/database-administration.adoc#access-control-database-administration-constraints[CONSTRAINT MANAGEMENT] | Schema | Determines whether a user is allowed to create, drop, and list constraints on a specific database. +| xref:access-control/privileges-writes.adoc#access-control-privileges-writes-create[CREATE] | GRAPH | Determines whether the user can create a new element (node, relationship or both). +| xref:access-control/database-administration.adoc#access-control-database-administration-constraints[CREATE CONSTRAINT] | Schema | Determines whether a user is allowed to create constraints on a specific database. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-database-management[CREATE DATABASE] | DBMS | Determines whether the user can create new databases. +| xref:access-control/database-administration.adoc#access-control-database-administration-index[CREATE INDEX] | Schema | Determines whether a user is allowed to create indexes on a specific database. +| xref:access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW NODE LABEL] | Schema | Determines whether a user is allowed to create new node labels on a specific database. +| xref:access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW PROPERTY NAME] | Schema | Determines whether a user is allowed to create new property names on a specific database. +| xref:access-control/database-administration.adoc#access-control-database-administration-tokens[CREATE NEW RELATIONSHIP TYPE] | Schema | Determines whether a user is allowed to create new relationship types on a specific database. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[CREATE ROLE] | DBMS | Determines whether the user can create new roles. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[CREATE USER] | DBMS | Determines whether the user can create new users. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-database-management[DATABASE MANAGEMENT] | DBMS | Determines whether the user can create and delete databases. +| xref:access-control/privileges-writes.adoc#access-control-privileges-writes-delete[DELETE] | GRAPH | Determines whether the user can delete an element (node, relationship or both). +| xref:access-control/database-administration.adoc#access-control-database-administration-constraints[DROP CONSTRAINT] | Schema | Determines whether a user is allowed to drop constraints on a specific database. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-database-management[DROP DATABASE] | DBMS | Determines whether the user can delete databases. +| xref:access-control/database-administration.adoc#access-control-database-administration-index[DROP INDEX] | Schema | Determines whether a user is allowed to drop indexes on a specific database. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[DROP ROLE] | DBMS | Determines whether the user can delete roles. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[DROP USER] | DBMS | Determines whether the user can delete users. +| xref:access-control/dbms-administration.adoc#access-control-admin-procedure[EXECUTE ADMIN PROCEDURE] | DBMS | Determines whether the user can execute admin procedures. +| xref:access-control/dbms-administration.adoc#access-control-execute-boosted-user-defined-function[EXECUTE BOOSTED FUNCTION] | DBMS | Determines whether the user can execute functions with elevated privileges. +| xref:access-control/dbms-administration.adoc#access-control-execute-boosted-procedure[EXECUTE BOOSTED PROCEDURE] | DBMS | Determines whether the user can execute procedures with elevated privileges. +| xref:access-control/dbms-administration.adoc#access-control-execute-user-defined-function[EXECUTE FUNCTION] | DBMS | Determines whether the user can execute functions. +| xref:access-control/dbms-administration.adoc#access-control-execute-procedure[EXECUTE PROCEDURE] | DBMS | Determines whether the user can execute procedures. +| xref:access-control/database-administration.adoc#access-control-database-administration-index[INDEX MANAGEMENT] | Schema | Determines whether a user is allowed to create, drop, and list indexes on a specific database. +| xref:access-control/privileges-reads.adoc#access-control-privileges-reads-match[MATCH] | GRAPH | Determines whether the properties of an element (node, relationship or both) can be read and the element can be found and traversed while executing queries on the specified graph. +| xref:access-control/privileges-writes.adoc#access-control-privileges-writes-merge[MERGE] | GRAPH | Determines whether the user can find, read, create and set properties on an element (node, relationship or both). +| xref:access-control/database-administration.adoc#access-control-database-administration-tokens[NAME MANAGEMENT] | Schema | Determines whether a user is allowed to create new labels, types and property names on a specific database. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[PRIVILEGE MANAGEMENT] | DBMS | Determines whether the user can show, assign and remove privileges. +| xref:access-control/privileges-reads.adoc#access-control-privileges-reads-read[READ] | GRAPH | Determines whether the properties of an element (node, relationship or both) can be read while executing queries on the specified graph. +| xref:access-control/privileges-writes.adoc#access-control-privileges-writes-remove-label[REMOVE LABEL] | GRAPH | Determines whether the user can remove a label from a node using the REMOVE clause. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[REMOVE PRIVILEGE] | DBMS | Determines whether the user can remove privileges using the REVOKE command. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[REMOVE ROLE] | DBMS | Determines whether the user can revoke roles. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[RENAME ROLE] | DBMS | Determines whether the user can rename roles. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[RENAME USER] | DBMS | Determines whether the user can rename users. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[ROLE MANAGEMENT] | DBMS | Determines whether the user can create, drop, grant, revoke and show roles. +| xref:access-control/privileges-writes.adoc#access-control-privileges-writes-set-label[SET LABEL] | GRAPH | Determines whether the user can set a label to a node using the SET clause. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[SET PASSWORDS] | DBMS | Determines whether the user can modify users' passwords and whether those passwords must be changed upon first login. +| xref:access-control/privileges-writes.adoc#access-control-privileges-writes-set-property[SET PROPERTY] | GRAPH | Determines whether the user can set a property to an element (node, relationship or both) using the SET clause. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[SET USER HOME DATABASE] | DBMS | Determines whether the user can modify the home database of users. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[SET USER STATUS] | DBMS | Determines whether the user can modify the account status of users. +| xref:access-control/database-administration.adoc#access-control-database-administration-constraints[SHOW CONSTRAINT] | Schema | Determines whether the user is allowed to list constraints. +| xref:access-control/database-administration.adoc#access-control-database-administration-index[SHOW INDEX] | Schema | Determines whether the user is allowed to list indexes. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-privilege-management[SHOW PRIVILEGE] | DBMS | Determines whether the user can get information about privileges assigned to users and roles. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-role-management[SHOW ROLE] | DBMS | Determines whether the user can get information about existing and assigned roles. +| xref:access-control/database-administration.adoc#access-control-database-administration-transaction[SHOW TRANSACTION] | Database | Determines whether a user is allowed to list transactions and queries. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[SHOW USER] | DBMS | Determines whether the user can get information about existing users. +| xref:access-control/database-administration.adoc#access-control-database-administration-startstop[START] | Database | Determines whether a user can start up a specific database. +| xref:access-control/database-administration.adoc#access-control-database-administration-startstop[STOP] | Database | Determines whether a user can stop a specific running database. +| xref:access-control/database-administration.adoc#access-control-database-administration-transaction[TERMINATE TRANSACTION] | Database | Determines whether a user is allowed to end running transactions and queries. +| xref:access-control/database-administration.adoc#access-control-database-administration-transaction[TRANSACTION MANAGEMENT] | Database | Determines whether a user is allowed to list and end running transactions and queries. +| xref:access-control/privileges-reads.adoc#access-control-privileges-reads-traverse[TRAVERSE] | GRAPH | Determines whether an element (node, relationship or both) can be found and traversed while executing queries on the specified graph. +| xref:access-control/dbms-administration.adoc#access-control-dbms-administration-user-management[USER MANAGEMENT] | DBMS | Determines whether the user can create, drop, modify and show users. +| xref:access-control/privileges-writes.adoc#access-control-privileges-writes-write[WRITE] | GRAPH | Determines whether the user can execute write operations on the specified graph. |=== - diff --git a/modules/ROOT/pages/query-tuning/advanced-example.adoc b/modules/ROOT/pages/query-tuning/advanced-example.adoc index 846063427..a1c56f66c 100644 --- a/modules/ROOT/pages/query-tuning/advanced-example.adoc +++ b/modules/ROOT/pages/query-tuning/advanced-example.adoc @@ -1,15 +1,9 @@ -:description: Example of some more subtle optimizations based on native index capabilities. - [[advanced-query-tuning-example]] = Advanced query tuning example - -[abstract] --- -This section describes some more subtle optimizations based on native index capabilities. --- +:description: This section describes some more subtle optimizations based on native index capabilities. One of the most important and useful ways of optimizing Cypher queries involves creating appropriate indexes. -This is described in more detail in xref::indexes-for-search-performance.adoc[], and demonstrated in xref::query-tuning/basic-example.adoc[]. +This is described in more detail in xref:indexes-for-search-performance.adoc[], and demonstrated in xref:query-tuning/basic-example.adoc[]. In summary, an index will be based on the combination of a `Label` and a `property`. Any Cypher query that searches for nodes with a specific label and some predicate on the property (equality, range or existence) will be planned to use the index if the cost planner deems that to be the most efficient solution. @@ -20,7 +14,7 @@ Let's explain how to use these features with a more advanced query tuning exampl [NOTE] ==== If you are upgrading an existing store to {neo4j-version-exact}, it may be necessary to drop and re-create existing indexes. -For information on native index support and upgrade considerations regarding indexes, see link:{neo4j-docs-base-uri}/operations-manual/{page-version}/performance/index-configuration#index-configuration-btree[Operations Manual -> Indexes]. +For information on native index support and upgrade considerations regarding indexes, see link:{neo4j-docs-base-uri}/operations-manual/{page-version}/performance-configuration#index-configuration-btree[Operations Manual -> Indexes]. ==== @@ -43,7 +37,7 @@ The _movies.csv_ file contains two columns `title`, `released` and `tagline`. The content of the _movies.csv_ file: .movies.csv -[source, csv, role="noheader", indent=0] +[source, csv, role="noheader"] ---- title,released,tagline Something's Gotta Give,1975,null @@ -295,15 +289,14 @@ The Da Vinci Code,2006,Break The Codes The Replacements,2000,"Pain heals, Chicks dig scars... Glory lasts forever" ---- - === Actors -The _actors.csv_ file contains two columns `title`, `roles`, `name`, and `born`. +The _actors.csv_ file contains two columns `title`, `roles`, `name` and `born`. The content of the _actors.csv_ file: .actors.csv -[source, csv, role="noheader", indent=0] +[source, csv, role="noheader"] ---- title,roles,name,born Something's Gotta Give,Julian Mercer,Keanu Reeves,1964 @@ -483,12 +476,12 @@ A League of Their Own,Kit Keller,Lori Petty,1963 === Directors -The _directors.csv_ file contains two columns `title`, `name`, and `born`. +The _directors.csv_ file contains two columns `title`, `name` and `born`. The content of the _directors.csv_ file: .directors.csv -[source, csv, role="noheader", indent=0] +[source, csv, role="noheader"] ---- title,name,born Speed Racer,Andy Wachowski,1967 @@ -552,7 +545,7 @@ It assumes that your current work directory is the __ directory of t Import the _movies.csv_ file:: -[source, cypher, indent=0] +[source, cypher] ---- LOAD CSV WITH HEADERS FROM 'file:///movies.csv' AS line MERGE (m:Movie {title: line.title}) @@ -561,19 +554,17 @@ ON CREATE SET m.tagline = line.tagline ---- -//// -[source, cypher-shell, role="nocopy,norun", indent=0] ----- -bin/cypher-shell --database=neo4j --user=neo4j -"LOAD CSV WITH HEADERS FROM 'file:///movies.csv' AS line -MERGE (m:Movie {title: line.title}) -ON CREATE SET - m.released = toInteger(line.released), - m.tagline = line.tagline" ----- -//// +//[source, cypher-shell, role="nocopy,norun"] +//---- +//bin/cypher-shell --database=neo4j --user=neo4j +//"LOAD CSV WITH HEADERS FROM 'file:///movies.csv' AS line +//MERGE (m:Movie {title: line.title}) +//ON CREATE SET +// m.released = toInteger(line.released), +// m.tagline = line.tagline" +//---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- Added 38 nodes, Set 114 properties, Added 38 labels ---- @@ -581,7 +572,7 @@ Added 38 nodes, Set 114 properties, Added 38 labels Import the _actors.csv_ file:: -[source, cypher, indent=0] +[source, cypher] ---- LOAD CSV WITH HEADERS FROM 'file:///actors.csv' AS line MATCH (m:Movie {title: line.title}) @@ -590,26 +581,24 @@ ON CREATE SET p.born = toInteger(line.born) MERGE (p)-[:ACTED_IN {roles:split(line.roles, ';')}]->(m) ---- -//// -[source, cypher-shell, role="nocopy,norun", indent=0] ----- -bin/cypher-shell --database=neo4j --user=neo4j -"LOAD CSV WITH HEADERS FROM 'file:///actors.csv' AS line -MATCH (m:Movie {title: line.title}) -MERGE (p:Person {name: line.name}) -ON CREATE SET p.born = toInteger(line.born) -MERGE (p)-[:ACTED_IN {roles:split(line.roles, ';')}]->(m)" ----- -//// +//[source, cypher-shell, role="nocopy,norun"] +//---- +//bin/cypher-shell --database=neo4j --user=neo4j +//"LOAD CSV WITH HEADERS FROM 'file:///actors.csv' AS line +//MATCH (m:Movie {title: line.title}) +//MERGE (p:Person {name: line.name}) +//ON CREATE SET p.born = toInteger(line.born) +//MERGE (p)-[:ACTED_IN {roles:split(line.roles, ';')}]->(m)" +//---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- Added 102 nodes, Created 172 relationships, Set 375 properties, Added 102 labels ---- Import the _directors.csv_ file:: -[source, cypher, indent=0] +[source, cypher] ---- LOAD CSV WITH HEADERS FROM 'file:///directors.csv' AS line MATCH (m:Movie {title: line.title}) @@ -618,37 +607,35 @@ ON CREATE SET p.born = toInteger(line.born) MERGE (p)-[:DIRECTED]->(m) ---- -//// -[source, cypher-shell, role="nocopy,norun", indent=0] ----- -bin/cypher-shell --database=neo4j --user=neo4j -"LOAD CSV WITH HEADERS FROM 'file:///directors.csv' AS line -MATCH (m:Movie {title: line.title}) -MERGE (p:Person {name: line.name}) -ON CREATE SET p.born = toInteger(line.born) -MERGE (p)-[:DIRECTED]->(m)" ----- -//// +//[source, cypher-shell, role="nocopy,norun"] +//---- +//bin/cypher-shell --database=neo4j --user=neo4j +//"LOAD CSV WITH HEADERS FROM 'file:///directors.csv' AS line +//MATCH (m:Movie {title: line.title}) +//MERGE (p:Person {name: line.name}) +//ON CREATE SET p.born = toInteger(line.born) +//MERGE (p)-[:DIRECTED]->(m)" +//---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- Added 23 nodes, Created 44 relationships, Set 46 properties, Added 23 labels ---- Create an index for nodes with the `Person` label:: -[source, cypher, indent=0] +[source, cypher] ---- CREATE INDEX FOR (p:Person) ON (p.name) ---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- Added 1 indexes ---- -[source, cypher, indent=0] +[source, cypher] ---- CALL db.awaitIndexes ---- @@ -659,7 +646,7 @@ CALL db.awaitIndexes In this example you want to write a query to find persons with the name 'Tom' that acted in a movie. -[source, cypher, indent=0] +[source, cypher] ---- MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name STARTS WITH 'Tom' @@ -668,19 +655,17 @@ RETURN count(m) AS count ---- -//// -[source, cypher-shell, role="nocopy,norun", indent=0] ----- -bin/cypher-shell --database=neo4j --user=neo4j -"MATCH (p:Person)-[:ACTED_IN]->(m:Movie) -WHERE p.name STARTS WITH 'Tom' -RETURN - p.name AS name, - count(m) AS count" ----- -//// +//[source, cypher-shell, role="nocopy,norun"] +//---- +//bin/cypher-shell --database=neo4j --user=neo4j +//"MATCH (p:Person)-[:ACTED_IN]->(m:Movie) +//WHERE p.name STARTS WITH 'Tom' +//RETURN +// p.name AS name, +// count(m) AS count" +//---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- +---------------------------+ | name | count | @@ -703,7 +688,7 @@ If we profile the above query, we see that the `NodeIndexSeekByRange` in the `De which means that `p.name` is retrieved from the index. We can also see that the `OrderedAggregation` has no `DB Hits`, which means it does not have to access the database again. -[source, cypher, indent=0] +[source, cypher] ---- PROFILE MATCH (p:Person)-[:ACTED_IN]->(m:Movie) @@ -713,20 +698,18 @@ RETURN count(m) AS count ---- -//// -[source, cypher-shell, role="nocopy,norun", indent=0] ----- -bin/cypher-shell --database=neo4j --user=neo4j -"PROFILE -MATCH (p:Person)-[:ACTED_IN]->(m:Movie) -WHERE p.name STARTS WITH 'Tom' -RETURN - p.name AS name, - count(m) AS count" ----- -//// +//[source, cypher-shell, role="nocopy,norun"] +//---- +//bin/cypher-shell --database=neo4j --user=neo4j +//"PROFILE +//MATCH (p:Person)-[:ACTED_IN]->(m:Movie) +//WHERE p.name STARTS WITH 'Tom' +//RETURN +// p.name AS name, +// count(m) AS count" +//---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- +------------------------+ | name | count | @@ -763,7 +746,7 @@ RETURN If we change the query, such that it can no longer use an index, we will see that there will be no `cache[p.name]` in the `Details` column, and that the `EagerAggregation` now has `DB Hits`, since it accesses the database again to retrieve the name. -[source, cypher, indent=0] +[source, cypher] ---- PROFILE MATCH (p:Person)-[:ACTED_IN]->(m:Movie) @@ -772,19 +755,17 @@ RETURN count(m) AS count ---- -//// -[source, cypher-shell, role="nocopy,norun", indent=0] ----- -bin/cypher-shell --database=neo4j --user=neo4j -"PROFILE -MATCH (p:Person)-[:ACTED_IN]->(m:Movie) -RETURN - p.name AS name, - count(m) AS count" ----- -//// +//[source, cypher-shell, role="nocopy,norun"] +//---- +//bin/cypher-shell --database=neo4j --user=neo4j +//"PROFILE +//MATCH (p:Person)-[:ACTED_IN]->(m:Movie) +//RETURN +// p.name AS name, +// count(m) AS count" +//---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- +----------------------------------+ | name | count | @@ -932,38 +913,36 @@ Predicates that can be used to enable this optimization are: [NOTE] ==== If there is an existence constraint on the property, no predicate is required to trigger the optimization. -For example, `CREATE CONSTRAINT constraint_name FOR (p:Person) REQUIRE p.name IS NOT NULL`. +For example, `CREATE CONSTRAINT constraint_name ON (p:Person) ASSERT p.name IS NOT NULL`. ==== [[advanced-query-tuning-example-index-backed-property-lookup-aggregating-functions]] === Aggregating functions -For all xref::functions/aggregating.adoc[built-in aggregating functions] in Cypher, the _index-backed property-lookup_ optimization can be used even without a predicate. +For all xref:functions/aggregating.adoc[built-in aggregating functions] in Cypher, the _index-backed property-lookup_ optimization can be used even without a predicate. Consider this query which returns the number of distinct names of people in the movies dataset: -[source, cypher, indent=0] +[source, cypher] ---- PROFILE MATCH (p:Person) RETURN count(DISTINCT p.name) AS numberOfNames ---- -//// -[source, cypher-shell, role="nocopy,norun", indent=0] ----- -bin/cypher-shell --database=neo4j --user=neo4j -"PROFILE -MATCH (p:Person) -RETURN count(DISTINCT p.name) AS numberOfNames" ----- -//// +//[source, cypher-shell, role="nocopy,norun"] +//---- +//bin/cypher-shell --database=neo4j --user=neo4j +//"PROFILE +//MATCH (p:Person) +//RETURN count(DISTINCT p.name) AS numberOfNames" +//---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- -+---------------+ ++---------------+ | numberOfNames | +---------------+ | 125 | @@ -998,7 +977,7 @@ In this case, the semantics of aggregating functions works like an implicit exis Now consider the following refinement to the query: -[source, cypher, indent=0] +[source, cypher] ---- PROFILE MATCH (p:Person)-[:ACTED_IN]->(m:Movie) @@ -1009,21 +988,19 @@ RETURN ORDER BY name ---- -//// -[source, cypher-shell, role="nocopy,norun", indent=0] ----- -bin/cypher-shell --database=neo4j --user=neo4j -"PROFILE -MATCH (p:Person)-[:ACTED_IN]->(m:Movie) -WHERE p.name STARTS WITH 'Tom' -RETURN - p.name AS name, - count(m) AS count -ORDER BY name" ----- -//// +//[source, cypher-shell, role="nocopy,norun"] +//---- +//bin/cypher-shell --database=neo4j --user=neo4j +//"PROFILE +//MATCH (p:Person)-[:ACTED_IN]->(m:Movie) +//WHERE p.name STARTS WITH 'Tom' +//RETURN +// p.name AS name, +// count(m) AS count +//ORDER BY name" +//---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- +------------------------+ | name | count | @@ -1078,26 +1055,24 @@ In cases where the Cypher planner is unable to remove the `Sort` operator, the p For the `min` and `max` functions, the _index-backed order by_ optimization can be used to avoid aggregation and instead utilize the fact that the minimum/maximum value is the first/last one in a sorted index. Consider the following query which returns the fist actor in alphabetical order: -[source, cypher, indent=0] +[source, cypher] ---- PROFILE MATCH (p:Person)-[:ACTED_IN]->(m:Movie) RETURN min(p.name) AS name ---- -//// -[source, cypher-shell, role="nocopy,norun", indent=0] ----- -bin/cypher-shell --database=neo4j --user=neo4j -"PROFILE -MATCH (p:Person)-[:ACTED_IN]->(m:Movie) -RETURN min(p.name) AS name" ----- -//// +//[source, cypher-shell, role="nocopy,norun"] +//---- +//bin/cypher-shell --database=neo4j --user=neo4j +//"PROFILE +//MATCH (p:Person)-[:ACTED_IN]->(m:Movie) +//RETURN min(p.name) AS name" +//---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- -+----------------+ ++----------------+ | name | +----------------+ | "Aaron Sorkin" | @@ -1157,13 +1132,13 @@ Predicates that will not work: * Several predicates combined using `OR` * Equality or range predicates querying for points (e.g. `WHERE n.place > point({ x: 1, y: 2 })`) -* Spatial distance predicates (e.g. `WHERE point.distance(n.place, point({ x: 1, y: 2 })) < 2`) +* Spatial distance predicates (e.g. `WHERE distance(n.place, point({ x: 1, y: 2 })) < 2`) [NOTE] ==== -If there is an existence constraint on the property, no predicate is required to trigger the optimization. -For example, `CREATE CONSTRAINT constraint_name FOR (p:Person) REQUIRE p.name IS NOT NULL` +If there is an existence constraint on the property, no predicate is required to trigger the optimization. +For example, `CREATE CONSTRAINT constraint_name ON (p:Person) ASSERT p.name IS NOT NULL` As of Neo4j {neo4j-version-exact}, predicates with parameters, such as `WHERE n.prop > $param`, can trigger _index-backed order by_. The only exception are queries with parameters of type `Point`. diff --git a/modules/ROOT/pages/query-tuning/basic-example.adoc b/modules/ROOT/pages/query-tuning/basic-example.adoc index c6951426b..181c0006e 100644 --- a/modules/ROOT/pages/query-tuning/basic-example.adoc +++ b/modules/ROOT/pages/query-tuning/basic-example.adoc @@ -1,16 +1,11 @@ -:description: Example how to profile a query, by using optimizations based on native index capabilities. - [[cypherdoc-basic-query-tuning-example]] = Basic query tuning example +:description: This section describes how to profile a query, by using optimizations based on native index capabilities. -[abstract] --- -This section describes how to profile a query, by using optimizations based on native index capabilities. --- - -Start with a basic example to help you get the hang of profiling queries. +We'll start with a basic example to help you get the hang of profiling queries. The following examples will use a movies data set. + [[basic-query-tuning-example-data-set]] == The data set @@ -25,12 +20,12 @@ In this tutorial, you import data from the following CSV files: === Movies -The _movies.csv_ file contains two columns `title`, `released`, and `tagline`. +The _movies.csv_ file contains two columns `title`, `released` and `tagline`. The content of the _movies.csv_ file: .movies.csv -[source, csv, role="noheader", indent=0] +[source, csv, role="noheader"] ---- title,released,tagline Something's Gotta Give,1975,null @@ -284,12 +279,12 @@ The Replacements,2000,"Pain heals, Chicks dig scars... Glory lasts forever" === Actors -The _actors.csv_ file contains two columns `title`, `roles`, `name`, and `born`. +The _actors.csv_ file contains two columns `title`, `roles`, `name` and `born`. The content of the _actors.csv_ file: .actors.csv -[source, csv, role="noheader", indent=0] +[source, csv, role="noheader"] ---- title,roles,name,born Something's Gotta Give,Julian Mercer,Keanu Reeves,1964 @@ -469,12 +464,12 @@ A League of Their Own,Kit Keller,Lori Petty,1963 === Directors -The _directors.csv_ file contains two columns `title`, `name`, and `born`. +The _directors.csv_ file contains two columns `title`, `name` and `born`. The content of the _directors.csv_ file: .directors.csv -[source, csv, role="noheader", indent=0] +[source, csv, role="noheader"] ---- title,name,born Speed Racer,Andy Wachowski,1967 @@ -523,7 +518,6 @@ Bicentennial Man,Chris Columbus,1958 A League of Their Own,Penny Marshall,1943 ---- - == Prerequisites The example uses the Linux or macOS tarball installation. @@ -539,7 +533,7 @@ It assumes that your current work directory is the __ directory of t Import the _movies.csv_ file:: -[source, cypher, indent=0] +[source, cypher] ---- LOAD CSV WITH HEADERS FROM 'file:///movies.csv' AS line MERGE (m:Movie {title: line.title}) @@ -548,19 +542,17 @@ ON CREATE SET m.tagline = line.tagline ---- -//// -[source, cypher-shell, role="nocopy,norun", indent=0] ----- -bin/cypher-shell --database=neo4j --user=neo4j -"LOAD CSV WITH HEADERS FROM 'file:///movies.csv' AS line -MERGE (m:Movie {title: line.title}) -ON CREATE SET - m.released = toInteger(line.released), - m.tagline = line.tagline" ----- -//// +//[source, cypher-shell, role="nocopy,norun"] +//---- +//bin/cypher-shell --database=neo4j --user=neo4j +//"LOAD CSV WITH HEADERS FROM 'file:///movies.csv' AS line +//MERGE (m:Movie {title: line.title}) +//ON CREATE SET +// m.released = toInteger(line.released), +// m.tagline = line.tagline" +//---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- Added 38 nodes, Set 114 properties, Added 38 labels ---- @@ -568,7 +560,7 @@ Added 38 nodes, Set 114 properties, Added 38 labels Import the _actors.csv_ file:: -[source, cypher, indent=0] +[source, cypher] ---- LOAD CSV WITH HEADERS FROM 'file:///actors.csv' AS line MATCH (m:Movie {title: line.title}) @@ -577,26 +569,24 @@ ON CREATE SET p.born = toInteger(line.born) MERGE (p)-[:ACTED_IN {roles:split(line.roles, ';')}]->(m) ---- -//// -[source, cypher-shell, role="nocopy,norun", indent=0] ----- -bin/cypher-shell --database=neo4j --user=neo4j -"LOAD CSV WITH HEADERS FROM 'file:///actors.csv' AS line -MATCH (m:Movie {title: line.title}) -MERGE (p:Person {name: line.name}) -ON CREATE SET p.born = toInteger(line.born) -MERGE (p)-[:ACTED_IN {roles:split(line.roles, ';')}]->(m)" ----- -//// +//[source, cypher-shell, role="nocopy,norun"] +//---- +//bin/cypher-shell --database=neo4j --user=neo4j +//"LOAD CSV WITH HEADERS FROM 'file:///actors.csv' AS line +//MATCH (m:Movie {title: line.title}) +//MERGE (p:Person {name: line.name}) +//ON CREATE SET p.born = toInteger(line.born) +//MERGE (p)-[:ACTED_IN {roles:split(line.roles, ';')}]->(m)" +//---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- Added 102 nodes, Created 172 relationships, Set 375 properties, Added 102 labels ---- Import the _directors.csv_ file:: -[source, cypher, indent=0] +[source, cypher] ---- LOAD CSV WITH HEADERS FROM 'file:///directors.csv' AS line MATCH (m:Movie {title: line.title}) @@ -605,19 +595,17 @@ ON CREATE SET p.born = toInteger(line.born) MERGE (p)-[:DIRECTED]->(m) ---- -//// -[source, cypher-shell, role="nocopy,norun", indent=0] ----- -bin/cypher-shell --database=neo4j --user=neo4j -"LOAD CSV WITH HEADERS FROM 'file:///directors.csv' AS line -MATCH (m:Movie {title: line.title}) -MERGE (p:Person {name: line.name}) -ON CREATE SET p.born = toInteger(line.born) -MERGE (p)-[:DIRECTED]->(m)" ----- -//// +//[source, cypher-shell, role="nocopy,norun"] +//---- +//bin/cypher-shell --database=neo4j --user=neo4j +//"LOAD CSV WITH HEADERS FROM 'file:///directors.csv' AS line +//MATCH (m:Movie {title: line.title}) +//MERGE (p:Person {name: line.name}) +//ON CREATE SET p.born = toInteger(line.born) +//MERGE (p)-[:DIRECTED]->(m)" +//---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- Added 23 nodes, Created 44 relationships, Set 46 properties, Added 23 labels ---- @@ -629,7 +617,7 @@ Let's say you want to write a query to find *'Tom Hanks'*. The naive way of doing this would be to write the following: -[source, cypher, indent=0] +[source, cypher] ---- MATCH (p {name: 'Tom Hanks'}) RETURN p @@ -638,26 +626,24 @@ RETURN p This query will find the *'Tom Hanks'* node but as the number of nodes in the database increase it will become slower and slower. We can profile the query to find out why that is. -You can learn more about the options for profiling queries in xref::query-tuning/query-options.adoc[] but in this case you are going to prefix our query with `PROFILE`: +You can learn more about the options for profiling queries in xref:query-tuning/how-do-i-profile-a-query.adoc[] but in this case we're going to prefix our query with `PROFILE`: -[source, cypher, indent=0] +[source, cypher] ---- PROFILE MATCH (p {name: 'Tom Hanks'}) RETURN p ---- -//// -[source, cypher-shell, role="nocopy,norun", indent=0] ----- -bin/cypher-shell --database=neo4j --user=neo4j -"PROFILE -MATCH (p {name: 'Tom Hanks'}) -RETURN p" ----- -//// +//[source, cypher-shell, role="nocopy,norun"] +//---- +//bin/cypher-shell --database=neo4j --user=neo4j +//"PROFILE +//MATCH (p {name: 'Tom Hanks'}) +//RETURN p" +//---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- +-------------------------------------------+ | p | @@ -687,45 +673,43 @@ RETURN p" The first thing to keep in mind when reading execution plans is that you need to read from the bottom up. -In that vein, starting from the last row, the first thing you notice is that the value in the `Rows` column seems high given there is only one node with the name property *'Tom Hanks'* in the database. -If you look across to the `Operator` column, you will see that xref::execution-plans/operators.adoc#query-plan-all-nodes-scan[AllNodesScan] has been used which means that the query planner scanned through all the nodes in the database. +In that vein, starting from the last row, the first thing we notice is that the value in the `Rows` column seems high given there is only one node with the name property *'Tom Hanks'* in the database. +If we look across to the `Operator` column we'll see that xref:execution-plans/operators.adoc#query-plan-all-nodes-scan[AllNodesScan] has been used which means that the query planner scanned through all the nodes in the database. -The xref::execution-plans/operators.adoc#query-plan-filter[Filter] operator which will check the `name` property on each of the nodes passed through by `AllNodesScan`. +The xref:execution-plans/operators.adoc#query-plan-filter[Filter] operator which will check the `name` property on each of the nodes passed through by `AllNodesScan`. -This seems like an inefficient way of finding *'Tom Hanks'* given that you are looking at many nodes that are not even people and therefore are not what you are looking for. +This seems like an inefficient way of finding *'Tom Hanks'* given that we are looking at many nodes that aren't even people and therefore aren't what we're looking for. -The solution to this problem is that whenever you are looking for a node you should specify a label to help the query planner narrow down the search space. +The solution to this problem is that whenever we're looking for a node we should specify a label to help the query planner narrow down the search space. -For this query you need to add a `Person` label. +For this query we'd need to add a `Person` label. -[source, cypher, indent=0] +[source, cypher] ---- MATCH (p:Person {name: 'Tom Hanks'}) RETURN p ---- -This query will be faster than the first one, but as the number of people in your database increase you may notice that the query slows down. +This query will be faster than the first one but as the number of people in our database increase we again notice that the query slows down. -Again you can profile the query to work out why: +Again we can profile the query to work out why: -[source, cypher, indent=0] +[source,cypher] ---- PROFILE MATCH (p:Person {name: 'Tom Hanks'}) RETURN p ---- -//// -[source, cypher-shell, role="nocopy,norun", indent=0] ----- -bin/cypher-shell --database=neo4j --user=neo4j -"PROFILE -MATCH (p:Person {name: 'Tom Hanks'}) -RETURN p" ----- -//// +//[source, cypher-shell, role="nocopy,norun"] +//---- +//bin/cypher-shell --database=neo4j --user=neo4j +//"PROFILE +//MATCH (p:Person {name: 'Tom Hanks'}) +//RETURN p" +//---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- +-------------------------------------------+ | p | @@ -753,57 +737,55 @@ RETURN p" 1 row ---- -This time the `Rows` value on the last row has reduced so you are not scanning some nodes that you were before which is a good start. -The xref::execution-plans/operators.adoc#query-plan-node-by-label-scan[NodeByLabelScan] operator indicates that you achieved this by first doing a linear scan of all the `Person` nodes in the database. +This time the `Rows` value on the last row has reduced so we're not scanning some nodes that we were before which is a good start. +The xref:execution-plans/operators.adoc#query-plan-node-by-label-scan[NodeByLabelScan] operator indicates that we achieved this by first doing a linear scan of all the `Person` nodes in the database. -Once you have done that, you can again scan through all those nodes using the `Filter` operator, comparing the name property of each one. +Once we've done that we again scan through all those nodes using the `Filter` operator, comparing the name property of each one. -This might be acceptable in some cases but if you are going to be looking up people by name frequently then you will see better performance if you create an index on the `name` property for the `Person` label: +This might be acceptable in some cases but if we're going to be looking up people by name frequently then we'll see better performance if we create an index on the `name` property for the `Person` label: -[source, cypher, indent=0] +[source, cypher] ---- CREATE INDEX FOR (p:Person) ON (p.name) ---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- Added 1 indexes ---- -[source, cypher, indent=0] +[source, cypher] ---- CALL db.awaitIndexes ---- -Now if you run the query again it will run more quickly: +Now if we run the query again it will run more quickly: -[source, cypher, indent=0] +[source, cypher] ---- MATCH (p:Person {name: 'Tom Hanks'}) RETURN p ---- -A profile for the query to see why that is: +Let's profile the query to see why that is: -[source, cypher, indent=0] +[source, cypher] ---- PROFILE MATCH (p:Person {name: 'Tom Hanks'}) RETURN p ---- -//// -[source, cypher-shell, role="nocopy,norun", indent=0] ----- -bin/cypher-shell --database=neo4j --user=neo4j -"PROFILE -MATCH (p:Person {name: 'Tom Hanks'}) -RETURN p" ----- -//// +//[source, cypher-shell, role="nocopy,norun"] +//---- +//bin/cypher-shell --database=neo4j --user=neo4j +//"PROFILE +//MATCH (p:Person {name: 'Tom Hanks'}) +//RETURN p" +//---- -[source, output, role="noheader", indent=0] +[source, output, role="noheader"] ---- +-------------------------------------------+ | p | @@ -829,5 +811,5 @@ RETURN p" 1 row ---- -Our execution plan is down to a single row and uses the xref::execution-plans/operators.adoc#query-plan-node-index-seek[Node Index Seek] operator which does an index seek (see xref::indexes-for-search-performance.adoc[]) to find the appropriate node. +Our execution plan is down to a single row and uses the xref:execution-plans/operators.adoc#query-plan-node-index-seek[Node Index Seek] operator which does an index seek (see xref:indexes-for-search-performance.adoc[]) to find the appropriate node. diff --git a/modules/ROOT/pages/query-tuning/how-do-i-profile-a-query.adoc b/modules/ROOT/pages/query-tuning/how-do-i-profile-a-query.adoc new file mode 100644 index 000000000..1ce5addbb --- /dev/null +++ b/modules/ROOT/pages/query-tuning/how-do-i-profile-a-query.adoc @@ -0,0 +1,414 @@ +[[query-tuning]] += Query tuning +:description: This section describes query tuning for the Cypher query language. This section describes the query options available in Cypher. + +//Check Mark +:check-mark: icon:check[] + +//Cross Mark +:cross-mark: icon:times[] + +Neo4j aims to execute queries as fast as possible. + +However, when optimizing for maximum query execution performance, it may be helpful to rephrase queries using knowledge about the domain and the application. + +The overall goal of manual query performance optimization is to ensure that only necessary data is retrieved from the graph. +At the very least, data should get filtered out as early as possible in order to reduce the amount of work that has to be done in the later stages of query execution. +This also applies to what gets returned: returning whole nodes and relationships ought to be avoided in favour of selecting and returning only the data that is needed. +You should also make sure to set an upper limit on variable length patterns, so they don't cover larger portions of the dataset than needed. + +Each Cypher query gets optimized and transformed into an xref:execution-plans/index.adoc#execution-plan-introduction[execution plan] by the Cypher query planner. +To minimize the resources used for this, try to use parameters instead of literals when possible. +This allows Cypher to re-use your queries instead of having to parse and build new execution plans. + +To read more about the execution plan operators mentioned in this chapter, see xref:execution-plans/index.adoc[]. + +* xref:query-tuning/query-options.adoc#cypher-query-options[Cypher query options] +** xref:query-tuning/index.adoc#cypher-version[Cypher version] +** xref:query-tuning/index.adoc#cypher-runtime[Cypher runtime] +** xref:query-tuning/index.adoc#cypher-connect-components-planner[Cypher connect-components planner] +** xref:query-tuning/index.adoc#cypher-update-strategy[Cypher update strategy] +** xref:query-tuning/index.adoc#cypher-expression-engine[Cypher expression engine] +** xref:query-tuning/index.adoc#cypher-operator-engine[Cypher operator engine] +** xref:query-tuning/index.adoc#cypher-interpreted-pipes-fallback[Cypher interpreted pipes fallback] +** xref:query-tuning/index.adoc#cypher-replanning[Cypher replanning] +* xref:query-tuning/how-do-i-profile-a-query.adoc#how-do-i-profile-a-query[Profiling a query] +* xref:query-tuning/indexes.adoc[The use of indexes] +* xref:query-tuning/basic-example.adoc[Basic query tuning example] +* xref:query-tuning/advanced-example.adoc[Advanced query tuning example] +** xref:query-tuning/advanced-example.adoc#advanced-query-tuning-example-data-set[The data set] +** xref:query-tuning/advanced-example.adoc#advanced-query-tuning-example-index-backed-property-lookup[Index-backed property-lookup] +** xref:query-tuning/advanced-example.adoc#advanced-query-tuning-example-index-backed-order-by[Index-backed order by] +* xref:query-tuning/using.adoc[Planner hints and the `USING` keyword] +** xref:query-tuning/using.adoc#query-using-introduction[Introduction] +** xref:query-tuning/using.adoc#query-using-index-hint[Index hints] +** xref:query-tuning/using.adoc#query-using-scan-hint[Scan hints] +** xref:query-tuning/using.adoc#query-using-join-hint[Join hints] +** xref:query-tuning/using.adoc#query-using-periodic-commit-hint[`PERIODIC COMMIT` query hint] + + +[[cypher-query-options]] +== Cypher query options + +Query execution can be fine-tuned through the use of query options. +In order to use one or more of these options, the query must be prepended with `CYPHER`, followed by the query option(s), as exemplified thus: `CYPHER query-option [further-query-options] query`. + + +[[cypher-version]] +=== Cypher version + +Occasionally, there is a requirement to use a previous version of the Cypher compiler when running a query. +Here we detail the available versions: + +[options="header",cols="1m,3a,^1a"] +|=== +| Query option +| Description +| Default + +| 3.5 +| This will force the query to use Neo4j Cypher 3.5. +| + +| 4.2 +| This will force the query to use Neo4j Cypher 4.2. +| + +| 4.3 +| This will force the query to use Neo4j Cypher 4.3. As this is the default version, it is not necessary to use this option explicitly. +| {check-mark} +|=== + +[WARNING] +==== +In Neo4j {neo4j-version}, the support for Cypher 3.5 is provided only at the parser level. +The consequence is that some underlying features available in Neo4j 3.5 are no longer available and will result in runtime errors. + +Please refer to the discussion in xref:deprecations-additions-removals-compatibility.adoc#cypher-compatibility[Cypher Compatibility] for more information on which features are affected. +==== + + +[[cypher-runtime]] +=== Cypher runtime + +Using the execution plan, the query is executed -- and records returned -- by the Cypher _runtime_. +Depending on whether Neo4j Enterprise Edition or Neo4j Community Edition is used, there are three different runtimes available: + +Interpreted:: +In this runtime, the operators in the execution plan are chained together in a tree, where each non-leaf operator feeds from one or two child operators. +The tree thus comprises nested iterators, and the records are streamed in a pipelined manner from the top iterator, which pulls from the next iterator and so on. + +[enterprise-edition]#Slotted#:: +This is very similar to the interpreted runtime, except that there are additional optimizations regarding the way in which the records are streamed through the iterators. +This results in improvements to both the performance and memory usage of the query. +In effect, this can be thought of as the 'faster interpreted' runtime. + +[enterprise-edition]#Pipelined#:: +The pipelined runtime was introduced in Neo4j 4.0 as a replacement for the older compiled runtime used in the Neo4j 3.x versions. +It combines some of the advantages of the compiled runtime in a new architecture that allows for support of a wider range of queries. ++ +Algorithms are employed to intelligently group the operators in the execution plan in order to generate new combinations and orders of execution which are optimised for performance and memory usage. +While this should lead to superior performance in most cases (over both the interpreted and slotted runtimes), it is still under development and does not support all possible operators or queries (the slotted runtime covers all operators and queries). + +[options="header",cols="2m,2a,^1a"] +|=== +|Option +|Description +|Default + +|runtime=interpreted +|This will force the query planner to use the interpreted runtime. +|This is not used in Enterprise Edition unless explicitly asked for. +It is the only option for all queries in Community Edition--it is not necessary to specify this option in Community Edition. + +|[enterprise-edition]#runtime=slotted# +|This will cause the query planner to use the slotted runtime. +|This is the default option for all queries which are not supported by `runtime=pipelined` in Enterprise Edition. + +|[enterprise-edition]#runtime=pipelined# +|This will cause the query planner to use the pipelined runtime if it supports the query. +If the pipelined runtime does not support the query, the planner will fall back to the slotted runtime. +|This is the default option for some queries in Enterprise Edition. +|=== + +In Enterprise Edition, the Cypher query planner selects the runtime, falling back to alternative runtimes as follows: + +* Try the pipelined runtime first. +* If the pipelined runtime does not support the query, then fall back to use the slotted runtime. +* Finally, if the slotted runtime does not support the query, fall back to the interpreted runtime. + The interpreted runtime supports all queries, and is the only option in Neo4j Community Edition. + + +[[cypher-planner]] +=== Cypher planner +The Cypher planner takes a Cypher query and computes an execution plan that solves it. +For any given query there is likely a number of execution plan candidates that each solve the query in a different way. +The planner uses a search algorithm to find the execution plan with the lowest estimated execution cost. + +This table describes the available planner options: + +[options="header",cols="2m,3a,^1a"] +|=== +| Query option +| Description +| Default + +| planner=cost +| Use cost based planning with default limits on plan search space and time. +| {check-mark} + +| planner=idp +| Synonym for `planner=cost`. +| + +| planner=dp +| Use cost based planning without limits on plan search space and time to perform an exhaustive search for the best execution plan. +[NOTE] +==== +Using this option can significantly _increase_ the planning time of the query. +==== +| +|=== + + +[[cypher-connect-components-planner]] +=== Cypher connect-components planner +One part of the Cypher planner is responsible for combining sub-plans for separate patterns into larger plans - a task referred to as _connecting components_. + +This table describes the available query options for the connect-components planner: + +[options="header",cols="2m,3a,^1a"] +|=== +| Query option +| Description +| Default + +| connectComponentsPlanner=greedy +| Use a greedy approach when combining sub-plans. +[NOTE] +==== +Using this option can significantly _reduce_ the planning time of the query. +==== +| + +| connectComponentsPlanner=idp +| Use the cost based IDP search algorithm when combining sub-plans. +[NOTE] +==== +Using this option can significantly _increase_ the planning time of the query but usually finds better plans. +==== +| {check-mark} +|=== + + +[[cypher-update-strategy]] +=== Cypher update strategy +This option affects the eagerness of updating queries. + +The possible values are: + +[options="header",cols="2m,3a,^1a"] +|=== +| Query option +| Description +| Default + +| updateStrategy=default +| Update queries are executed eagerly when needed. +| {check-mark} + +| updateStrategy=eager +| Update queries are always executed eagerly. +| +|=== + + +[[cypher-expression-engine]] +=== Cypher expression engine +This option affects how the runtime evaluates expressions. + +The possible values are: + +[options="header",cols="2m,3a,^1a"] +|=== +| Query option +| Description +| Default + +| expressionEngine=default +| Compile expressions and use the compiled expression engine when needed. +| {check-mark} + +| expressionEngine=interpreted +| Always use the _interpreted_ expression engine. +| + +| expressionEngine=compiled +| Always compile expressions and use the _compiled_ expression engine. + +Cannot be used together with `runtime=interpreted`. +| +|=== + + +[[cypher-operator-engine]] +=== Cypher operator engine +This query option affects whether the pipelined runtime attempts to generate compiled code for groups of operators. + +The possible values are: + +[options="header",cols="2m,3a,^1a"] +|=== +| Query option +| Description +| Default + +| operatorEngine=default +| Attempt to generate compiled operators when applicable. +| {check-mark} + +| operatorEngine=interpreted +| Never attempt to generate compiled operators. +| + +| operatorEngine=compiled +| Always attempt to generate _compiled_ operators. + +Cannot be used together with `runtime=interpreted` or `runtime=slotted`. +| +|=== + + +[[cypher-interpreted-pipes-fallback]] +=== Cypher interpreted pipes fallback +This query option affects how the pipelined runtime behaves for operators it does not directly support. + +The available options are: + +[options="header",cols="2m,3a,^1a"] +|=== +| Query option +| Description +| Default + +| interpretedPipesFallback=default +| Equivalent to `interpretedPipesFallback=whitelisted_plans_only` +| {check-mark} + +| interpretedPipesFallback=disabled +| If the plan contains any operators not supported by the pipelined runtime then another runtime is chosen to execute the entire plan. + +Cannot be used together with `runtime=interpreted` or `runtime=slotted` +| + +| interpretedPipesFallback=whitelisted_plans_only +| Parts of the execution plan can be executed on another runtime. +Only certain operators are allowed to execute on another runtime. + +Cannot be used together with `runtime=interpreted` or `runtime=slotted`. +| + +| interpretedPipesFallback=all +| Parts of the execution plan may be executed on another runtime. +Any operator is allowed to execute on another runtime. +Queries with this option set might produce incorrect results, or fail. + +Cannot be used together with `runtime=interpreted` or `runtime=slotted`. + +[WARNING] +This setting is experimental, and using it in a production environment is discouraged. + +| +|=== + + +[[cypher-replanning]] +=== Cypher replanning + +Cypher replanning occurs in the following circumstances: + +* When the query is not in the cache. +This can either be when the server is first started or restarted, if the cache has recently been cleared, or if link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_dbms.query_cache_size[dbms.query_cache_size] was exceeded. +* When the time has past the link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_cypher.min_replan_interval[cypher.min_replan_interval] value, and the database statistics have changed more than the link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_cypher.statistics_divergence_threshold[cypher.statistics_divergence_threshold] value. + +There may be situations where xref:execution-plans/index.adoc[Cypher query planning] can occur at a non-ideal time. +For example, when a query must be as fast as possible and a valid plan is already in place. + +[NOTE] +Replanning is not performed for all queries at once; it is performed in the same thread as running the query, and can block the query. +However, replanning one query does not replan any other queries. + +There are three different replan options available: + +[options="header",cols="2m,3a,^1a"] +|=== +|Option +|Description +|Default + +|replan=default +|This is the planning and replanning option as described above. +| {check-mark} + +|replan=force +|This will force a replan, even if the plan is valid according to the planning rules. +Once the new plan is complete, it replaces the existing one in the query cache. +| + +|replan=skip +|If a valid plan already exists, it will be used even if the planning rules would normally dictate that it should be replanned. +| +|=== + +The replan option is prepended to queries. +For example: + +[source, cypher, role=noplay] +---- +CYPHER replan=force MATCH ... +---- + +In a mixed workload, you can force replanning by using the Cypher `EXPLAIN` commands. +This can be useful to schedule replanning of queries which are expensive to plan, at known times of low load. +Using `EXPLAIN` will make sure the query is only planned, but not executed. +For example: + +[source, cypher, role=noplay] +---- +CYPHER replan=force EXPLAIN MATCH ... +---- + +During times of known high load, `replan=skip` can be useful to not introduce unwanted latency spikes. + + +[[how-do-i-profile-a-query]] +== Profiling a query + +There are two options to choose from when you want to analyze a query by looking at its execution plan: + +`EXPLAIN`:: +If you want to see the execution plan but not run the statement, prepend your Cypher statement with `EXPLAIN`. +The statement will always return an empty result and make no changes to the database. + +`PROFILE`:: +If you want to run the statement and see which operators are doing most of the work, use `PROFILE`. +This will run your statement and keep track of how many rows pass through each operator, and how much each operator needs to interact with the storage layer to retrieve the necessary data. +Note that _profiling your query uses more resources,_ so you should not profile unless you are actively working on a query. + +See xref:execution-plans/index.adoc[] for a detailed explanation of each of the operators contained in an execution plan. + +[TIP] +==== +Being explicit about what types and labels you expect relationships and nodes to have in your query helps Neo4j use the best possible statistical information, which leads to better execution plans. +This means that when you know that a relationship can only be of a certain type, you should add that to the query. +The same goes for labels, where declaring labels on both the start and end nodes of a relationship helps Neo4j find the best way to execute the statement. +==== + +//cypher/cypher-docs/src/docs/dev/query-tuning-indexes.asciidoc + +//cypher/cypher-docs/src/docs/dev/basic-query-tuning-example.asciidoc + +//cypher/cypher-docs/src/docs/dev/advanced-query-tuning-example.asciidoc + +//cypher/cypher-docs/src/test/scala/org/neo4j/cypher/docgen/UsingTest.scala +//generates: cypher/cypher-docs/target/docs/dev/ql/query-using.adoc diff --git a/modules/ROOT/pages/query-tuning/index.adoc b/modules/ROOT/pages/query-tuning/index.adoc index 34af94dcb..1ce5addbb 100644 --- a/modules/ROOT/pages/query-tuning/index.adoc +++ b/modules/ROOT/pages/query-tuning/index.adoc @@ -1,12 +1,12 @@ -:description: Query tuning for the Cypher query language. - [[query-tuning]] = Query tuning +:description: This section describes query tuning for the Cypher query language. This section describes the query options available in Cypher. + +//Check Mark +:check-mark: icon:check[] -[abstract] --- -This section describes query tuning for the Cypher query language. --- +//Cross Mark +:cross-mark: icon:times[] Neo4j aims to execute queries as fast as possible. @@ -17,9 +17,398 @@ At the very least, data should get filtered out as early as possible in order to This also applies to what gets returned: returning whole nodes and relationships ought to be avoided in favour of selecting and returning only the data that is needed. You should also make sure to set an upper limit on variable length patterns, so they don't cover larger portions of the dataset than needed. -Each Cypher query gets optimized and transformed into an xref::execution-plans/index.adoc#execution-plan-introduction[execution plan] by the Cypher query planner. +Each Cypher query gets optimized and transformed into an xref:execution-plans/index.adoc#execution-plan-introduction[execution plan] by the Cypher query planner. To minimize the resources used for this, try to use parameters instead of literals when possible. This allows Cypher to re-use your queries instead of having to parse and build new execution plans. -To read more about the execution plan operators mentioned in this section, see xref::execution-plans/index.adoc[]. +To read more about the execution plan operators mentioned in this chapter, see xref:execution-plans/index.adoc[]. + +* xref:query-tuning/query-options.adoc#cypher-query-options[Cypher query options] +** xref:query-tuning/index.adoc#cypher-version[Cypher version] +** xref:query-tuning/index.adoc#cypher-runtime[Cypher runtime] +** xref:query-tuning/index.adoc#cypher-connect-components-planner[Cypher connect-components planner] +** xref:query-tuning/index.adoc#cypher-update-strategy[Cypher update strategy] +** xref:query-tuning/index.adoc#cypher-expression-engine[Cypher expression engine] +** xref:query-tuning/index.adoc#cypher-operator-engine[Cypher operator engine] +** xref:query-tuning/index.adoc#cypher-interpreted-pipes-fallback[Cypher interpreted pipes fallback] +** xref:query-tuning/index.adoc#cypher-replanning[Cypher replanning] +* xref:query-tuning/how-do-i-profile-a-query.adoc#how-do-i-profile-a-query[Profiling a query] +* xref:query-tuning/indexes.adoc[The use of indexes] +* xref:query-tuning/basic-example.adoc[Basic query tuning example] +* xref:query-tuning/advanced-example.adoc[Advanced query tuning example] +** xref:query-tuning/advanced-example.adoc#advanced-query-tuning-example-data-set[The data set] +** xref:query-tuning/advanced-example.adoc#advanced-query-tuning-example-index-backed-property-lookup[Index-backed property-lookup] +** xref:query-tuning/advanced-example.adoc#advanced-query-tuning-example-index-backed-order-by[Index-backed order by] +* xref:query-tuning/using.adoc[Planner hints and the `USING` keyword] +** xref:query-tuning/using.adoc#query-using-introduction[Introduction] +** xref:query-tuning/using.adoc#query-using-index-hint[Index hints] +** xref:query-tuning/using.adoc#query-using-scan-hint[Scan hints] +** xref:query-tuning/using.adoc#query-using-join-hint[Join hints] +** xref:query-tuning/using.adoc#query-using-periodic-commit-hint[`PERIODIC COMMIT` query hint] + + +[[cypher-query-options]] +== Cypher query options + +Query execution can be fine-tuned through the use of query options. +In order to use one or more of these options, the query must be prepended with `CYPHER`, followed by the query option(s), as exemplified thus: `CYPHER query-option [further-query-options] query`. + + +[[cypher-version]] +=== Cypher version + +Occasionally, there is a requirement to use a previous version of the Cypher compiler when running a query. +Here we detail the available versions: + +[options="header",cols="1m,3a,^1a"] +|=== +| Query option +| Description +| Default + +| 3.5 +| This will force the query to use Neo4j Cypher 3.5. +| + +| 4.2 +| This will force the query to use Neo4j Cypher 4.2. +| + +| 4.3 +| This will force the query to use Neo4j Cypher 4.3. As this is the default version, it is not necessary to use this option explicitly. +| {check-mark} +|=== + +[WARNING] +==== +In Neo4j {neo4j-version}, the support for Cypher 3.5 is provided only at the parser level. +The consequence is that some underlying features available in Neo4j 3.5 are no longer available and will result in runtime errors. + +Please refer to the discussion in xref:deprecations-additions-removals-compatibility.adoc#cypher-compatibility[Cypher Compatibility] for more information on which features are affected. +==== + + +[[cypher-runtime]] +=== Cypher runtime + +Using the execution plan, the query is executed -- and records returned -- by the Cypher _runtime_. +Depending on whether Neo4j Enterprise Edition or Neo4j Community Edition is used, there are three different runtimes available: + +Interpreted:: +In this runtime, the operators in the execution plan are chained together in a tree, where each non-leaf operator feeds from one or two child operators. +The tree thus comprises nested iterators, and the records are streamed in a pipelined manner from the top iterator, which pulls from the next iterator and so on. + +[enterprise-edition]#Slotted#:: +This is very similar to the interpreted runtime, except that there are additional optimizations regarding the way in which the records are streamed through the iterators. +This results in improvements to both the performance and memory usage of the query. +In effect, this can be thought of as the 'faster interpreted' runtime. + +[enterprise-edition]#Pipelined#:: +The pipelined runtime was introduced in Neo4j 4.0 as a replacement for the older compiled runtime used in the Neo4j 3.x versions. +It combines some of the advantages of the compiled runtime in a new architecture that allows for support of a wider range of queries. ++ +Algorithms are employed to intelligently group the operators in the execution plan in order to generate new combinations and orders of execution which are optimised for performance and memory usage. +While this should lead to superior performance in most cases (over both the interpreted and slotted runtimes), it is still under development and does not support all possible operators or queries (the slotted runtime covers all operators and queries). + +[options="header",cols="2m,2a,^1a"] +|=== +|Option +|Description +|Default + +|runtime=interpreted +|This will force the query planner to use the interpreted runtime. +|This is not used in Enterprise Edition unless explicitly asked for. +It is the only option for all queries in Community Edition--it is not necessary to specify this option in Community Edition. + +|[enterprise-edition]#runtime=slotted# +|This will cause the query planner to use the slotted runtime. +|This is the default option for all queries which are not supported by `runtime=pipelined` in Enterprise Edition. + +|[enterprise-edition]#runtime=pipelined# +|This will cause the query planner to use the pipelined runtime if it supports the query. +If the pipelined runtime does not support the query, the planner will fall back to the slotted runtime. +|This is the default option for some queries in Enterprise Edition. +|=== + +In Enterprise Edition, the Cypher query planner selects the runtime, falling back to alternative runtimes as follows: + +* Try the pipelined runtime first. +* If the pipelined runtime does not support the query, then fall back to use the slotted runtime. +* Finally, if the slotted runtime does not support the query, fall back to the interpreted runtime. + The interpreted runtime supports all queries, and is the only option in Neo4j Community Edition. + + +[[cypher-planner]] +=== Cypher planner +The Cypher planner takes a Cypher query and computes an execution plan that solves it. +For any given query there is likely a number of execution plan candidates that each solve the query in a different way. +The planner uses a search algorithm to find the execution plan with the lowest estimated execution cost. + +This table describes the available planner options: + +[options="header",cols="2m,3a,^1a"] +|=== +| Query option +| Description +| Default + +| planner=cost +| Use cost based planning with default limits on plan search space and time. +| {check-mark} + +| planner=idp +| Synonym for `planner=cost`. +| + +| planner=dp +| Use cost based planning without limits on plan search space and time to perform an exhaustive search for the best execution plan. +[NOTE] +==== +Using this option can significantly _increase_ the planning time of the query. +==== +| +|=== + + +[[cypher-connect-components-planner]] +=== Cypher connect-components planner +One part of the Cypher planner is responsible for combining sub-plans for separate patterns into larger plans - a task referred to as _connecting components_. + +This table describes the available query options for the connect-components planner: + +[options="header",cols="2m,3a,^1a"] +|=== +| Query option +| Description +| Default + +| connectComponentsPlanner=greedy +| Use a greedy approach when combining sub-plans. +[NOTE] +==== +Using this option can significantly _reduce_ the planning time of the query. +==== +| + +| connectComponentsPlanner=idp +| Use the cost based IDP search algorithm when combining sub-plans. +[NOTE] +==== +Using this option can significantly _increase_ the planning time of the query but usually finds better plans. +==== +| {check-mark} +|=== + + +[[cypher-update-strategy]] +=== Cypher update strategy +This option affects the eagerness of updating queries. + +The possible values are: + +[options="header",cols="2m,3a,^1a"] +|=== +| Query option +| Description +| Default + +| updateStrategy=default +| Update queries are executed eagerly when needed. +| {check-mark} + +| updateStrategy=eager +| Update queries are always executed eagerly. +| +|=== + + +[[cypher-expression-engine]] +=== Cypher expression engine +This option affects how the runtime evaluates expressions. + +The possible values are: + +[options="header",cols="2m,3a,^1a"] +|=== +| Query option +| Description +| Default + +| expressionEngine=default +| Compile expressions and use the compiled expression engine when needed. +| {check-mark} + +| expressionEngine=interpreted +| Always use the _interpreted_ expression engine. +| + +| expressionEngine=compiled +| Always compile expressions and use the _compiled_ expression engine. + +Cannot be used together with `runtime=interpreted`. +| +|=== + + +[[cypher-operator-engine]] +=== Cypher operator engine +This query option affects whether the pipelined runtime attempts to generate compiled code for groups of operators. + +The possible values are: + +[options="header",cols="2m,3a,^1a"] +|=== +| Query option +| Description +| Default + +| operatorEngine=default +| Attempt to generate compiled operators when applicable. +| {check-mark} + +| operatorEngine=interpreted +| Never attempt to generate compiled operators. +| + +| operatorEngine=compiled +| Always attempt to generate _compiled_ operators. + +Cannot be used together with `runtime=interpreted` or `runtime=slotted`. +| +|=== + + +[[cypher-interpreted-pipes-fallback]] +=== Cypher interpreted pipes fallback +This query option affects how the pipelined runtime behaves for operators it does not directly support. + +The available options are: + +[options="header",cols="2m,3a,^1a"] +|=== +| Query option +| Description +| Default + +| interpretedPipesFallback=default +| Equivalent to `interpretedPipesFallback=whitelisted_plans_only` +| {check-mark} + +| interpretedPipesFallback=disabled +| If the plan contains any operators not supported by the pipelined runtime then another runtime is chosen to execute the entire plan. + +Cannot be used together with `runtime=interpreted` or `runtime=slotted` +| + +| interpretedPipesFallback=whitelisted_plans_only +| Parts of the execution plan can be executed on another runtime. +Only certain operators are allowed to execute on another runtime. + +Cannot be used together with `runtime=interpreted` or `runtime=slotted`. +| + +| interpretedPipesFallback=all +| Parts of the execution plan may be executed on another runtime. +Any operator is allowed to execute on another runtime. +Queries with this option set might produce incorrect results, or fail. + +Cannot be used together with `runtime=interpreted` or `runtime=slotted`. + +[WARNING] +This setting is experimental, and using it in a production environment is discouraged. + +| +|=== + + +[[cypher-replanning]] +=== Cypher replanning + +Cypher replanning occurs in the following circumstances: + +* When the query is not in the cache. +This can either be when the server is first started or restarted, if the cache has recently been cleared, or if link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_dbms.query_cache_size[dbms.query_cache_size] was exceeded. +* When the time has past the link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_cypher.min_replan_interval[cypher.min_replan_interval] value, and the database statistics have changed more than the link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_cypher.statistics_divergence_threshold[cypher.statistics_divergence_threshold] value. + +There may be situations where xref:execution-plans/index.adoc[Cypher query planning] can occur at a non-ideal time. +For example, when a query must be as fast as possible and a valid plan is already in place. + +[NOTE] +Replanning is not performed for all queries at once; it is performed in the same thread as running the query, and can block the query. +However, replanning one query does not replan any other queries. + +There are three different replan options available: + +[options="header",cols="2m,3a,^1a"] +|=== +|Option +|Description +|Default + +|replan=default +|This is the planning and replanning option as described above. +| {check-mark} + +|replan=force +|This will force a replan, even if the plan is valid according to the planning rules. +Once the new plan is complete, it replaces the existing one in the query cache. +| + +|replan=skip +|If a valid plan already exists, it will be used even if the planning rules would normally dictate that it should be replanned. +| +|=== + +The replan option is prepended to queries. +For example: + +[source, cypher, role=noplay] +---- +CYPHER replan=force MATCH ... +---- + +In a mixed workload, you can force replanning by using the Cypher `EXPLAIN` commands. +This can be useful to schedule replanning of queries which are expensive to plan, at known times of low load. +Using `EXPLAIN` will make sure the query is only planned, but not executed. +For example: + +[source, cypher, role=noplay] +---- +CYPHER replan=force EXPLAIN MATCH ... +---- + +During times of known high load, `replan=skip` can be useful to not introduce unwanted latency spikes. + + +[[how-do-i-profile-a-query]] +== Profiling a query + +There are two options to choose from when you want to analyze a query by looking at its execution plan: + +`EXPLAIN`:: +If you want to see the execution plan but not run the statement, prepend your Cypher statement with `EXPLAIN`. +The statement will always return an empty result and make no changes to the database. + +`PROFILE`:: +If you want to run the statement and see which operators are doing most of the work, use `PROFILE`. +This will run your statement and keep track of how many rows pass through each operator, and how much each operator needs to interact with the storage layer to retrieve the necessary data. +Note that _profiling your query uses more resources,_ so you should not profile unless you are actively working on a query. + +See xref:execution-plans/index.adoc[] for a detailed explanation of each of the operators contained in an execution plan. + +[TIP] +==== +Being explicit about what types and labels you expect relationships and nodes to have in your query helps Neo4j use the best possible statistical information, which leads to better execution plans. +This means that when you know that a relationship can only be of a certain type, you should add that to the query. +The same goes for labels, where declaring labels on both the start and end nodes of a relationship helps Neo4j find the best way to execute the statement. +==== + +//cypher/cypher-docs/src/docs/dev/query-tuning-indexes.asciidoc + +//cypher/cypher-docs/src/docs/dev/basic-query-tuning-example.asciidoc + +//cypher/cypher-docs/src/docs/dev/advanced-query-tuning-example.asciidoc +//cypher/cypher-docs/src/test/scala/org/neo4j/cypher/docgen/UsingTest.scala +//generates: cypher/cypher-docs/target/docs/dev/ql/query-using.adoc diff --git a/modules/ROOT/pages/query-tuning/indexes.adoc b/modules/ROOT/pages/query-tuning/indexes.adoc index d00ce7e51..ffbccfef9 100644 --- a/modules/ROOT/pages/query-tuning/indexes.adoc +++ b/modules/ROOT/pages/query-tuning/indexes.adoc @@ -1,12 +1,6 @@ -:description: The query plans when indexes are used in various scenarios. - [[query-tuning-indexes]] = The use of indexes - -[abstract] --- -This section describes the query plans when indexes are used in various scenarios. --- +:description: This section describes the query plans when indexes are used in various scenarios. The task of tuning calls for different indexes depending on what the queries look like. Therefore, it is important to have a fundamental understanding of how the indexes operate. @@ -15,912 +9,746 @@ This section describes the query plans that result from different index scenario Node indexes and relationship indexes operate in the same way. Therefore, node and relationship indexes are used interchangeably in this section. -Please refer to xref::indexes-for-search-performance.adoc[] for instructions on how to create and maintain the indexes themselves. - -== Index types and predicate compatibility - -There are different types of indexes available in Neo4j but they are not all compatible with the same property predicates. - -Indexes are commonly used for `MATCH` and `OPTIONAL MATCH` clauses that combine a label predicate with a property predicate. -Therefore, it is important to know what kind of predicates that can be solved by the different indexes. - -The different indexes are: - -* `BTREE` -* `TEXT` - - -== BTREE indexes - -`BTREE` indexes support all types of predicates: - -[options="header"] -|=== - -| Predicate | Syntax - -| equality check| `n.prop = value` -| list membership check| `n.prop IN list` -| existence check| `n.prop IS NOT NULL` -| range search| `n.prop > value` -| prefix search| `STARTS WITH` -| suffix search| `ENDS WITH` -| substring search| `CONTAINS` - -|=== - - -== TEXT indexes - -`TEXT` indexes only work for predicates operating on strings. -That means that `TEXT` indexes are only used when it is known that the predicate evaluates to `null` for all non-string values. - -Predicates that only operate on strings are always solvable by a `TEXT` index: - -* `STARTS WITH` -* `ENDS WITH` -* `CONTAINS` - -However, other predicates are only used when it is known that the property is compared to a string: - -* `n.prop = "string"` -* `n.prop IN ["a", "b", "c"]` -* `n.prop > "string"` - -This means that a `TEXT` index is not able to solve e.g. `a.prop = b.prop`. - -In summary, `TEXT` indexes support the following predicates: - -[options="header"] -|=== -| Predicate | Syntax - -| equality check| `n.prop = "string"` -| list membership check | `n.prop IN ["a", "b", "c"]` -| range search| `n.prop > "string"` -| prefix search| `STARTS WITH` -| suffix search| `ENDS WITH` -| substring search| `CONTAINS` - -|=== - -== Index preference - -When multiple indexes are available and able to solve a predicate, there is an order defined that decides which index to use. -It is defined as such: +Please refer to xref:indexes-for-search-performance.adoc[] for instructions on how to create and maintain the indexes themselves. -* `TEXT` indexes are preferred over `BTREE` indexes for `CONTAINS` and `ENDS WITH`. -* `BTREE` indexes are preferred over `TEXT` indexes in all other cases. - -Examples: - -* xref::query-tuning/indexes.adoc#administration-indexes-relationship-btree-index-example[] -* xref::query-tuning/indexes.adoc#administration-indexes-node-text-index-example[] -* xref::query-tuning/indexes.adoc#administration-indexes-relationship-text-index-example[] -* xref::query-tuning/indexes.adoc#administration-indexes-multiple-available-index-types[] -* xref::query-tuning/indexes.adoc#administration-indexes-equality-check-using-where-single-property-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-equality-check-using-where-composite-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-range-comparisons-using-where-single-property-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-range-comparisons-using-where-composite-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-multiple-range-comparisons-using-where-single-property-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-multiple-range-comparisons-using-where-composite-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-list-membership-check-using-in-single-property-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-list-membership-check-using-in-composite-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-prefix-search-using-starts-with-single-property-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-prefix-search-using-starts-with-composite-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-suffix-search-using-ends-with-single-property-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-suffix-search-using-ends-with-composite-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-substring-search-using-contains-single-property-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-substring-search-using-contains-composite-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-existence-check-using-is-not-null-single-property-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-existence-check-using-is-not-null-composite-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-spatial-distance-searches-single-property-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-spatial-distance-searches-composite-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-spatial-bounding-box-searches-single-property-index[] -* xref::query-tuning/indexes.adoc#administration-indexes-spatial-bounding-box-searches-composite-index[] - - -[discrete] -[[administration-indexes-relationship-btree-index-example]] -=== Relationship BTREE index - -In this example, a `KNOWS(since)` relationship `BTREE` index is available. +[[administration-indexes-node-index-example]] +== Node index example == +In the example below, the query uses a `Person(firstname)` node index, if it exists. .Query -[source, cypher, indent=0] ----- -MATCH (person)-[relationship:KNOWS {since: 1992}]->(friend) -RETURN person, friend ----- - -.Query Plan -[source, query plan, role="noheader"] +[source,cypher] ---- -Compiler CYPHER 4.4 - -Planner COST - -Runtime PIPELINED - -Runtime version 4.4 - -+--------------------------------+-------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+--------------------------------+-------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | person, friend | 1 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +-------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexSeek | BTREE INDEX (person)-[relationship:KNOWS(since)]->(friend) WHERE since = $autoint_0 | 1 | 1 | 3 | 112 | 2/1 | 1.404 | Fused in Pipeline 0 | -+--------------------------------+-------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 3, total allocated memory: 176 +MATCH (person:Person {firstname: 'Andy'}) RETURN person ---- -[discrete] -[[administration-indexes-node-text-index-example]] -=== Node TEXT index - -In the example below, a `Person(surname)` node `TEXT` index is available. - -.Query -[source, cypher, indent=0] ----- -MATCH (person:Person {surname: 'Smith'}) -RETURN person ----- .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+-----------------+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | person | 2 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | TEXT INDEX person:Person(surname) WHERE surname = $autostring_0 | 2 | 1 | 2 | 112 | 2/0 | 6.367 | Fused in Pipeline 0 | -+-----------------+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 2, total allocated memory: 176 ----- ++-----------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | person | 1 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeek | person:Person(firstname) WHERE firstname = $autostring_0 | 1 | 1 | 2 | 72 | 2/1 | 0.976 | Fused in Pipeline 0 | ++-----------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Total database accesses: 2, total allocated memory: 136 -[discrete] -[[administration-indexes-relationship-text-index-example]] -=== Relationship TEXT index +---- -In this example, a `KNOWS(lastMetLocation)` relationship `TEXT` index is available. +[[administration-indexes-relationship-index-example]] +== Relationship index example == +In this example, the query uses a `KNOWS(since)` relationship index, if it exists. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (person)-[relationship:KNOWS {metIn: 'Malmo'} ]->(friend) -RETURN person, friend +MATCH (person)-[relationship:KNOWS { since: 1992 } ]->(friend) RETURN person, friend ---- -.Query Plan -[source, query plan, role="noheader"] ----- -Compiler CYPHER 4.4 -Planner COST - -Runtime PIPELINED - -Runtime version 4.4 - -+--------------------------------+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+--------------------------------+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | person, friend | 1 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexSeek | TEXT INDEX (person)-[relationship:KNOWS(metIn)]->(friend) WHERE metIn = $autostring_0 | 1 | 1 | 3 | 112 | 2/0 | 17.095 | Fused in Pipeline 0 | -+--------------------------------+---------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 3, total allocated memory: 176 - ----- - - -[discrete] -[[administration-indexes-multiple-available-index-types]] -=== Multiple available index types - -In the example below, both a `Person(middlename)` node `TEXT` index and a `Person(middlename)` node `BTREE` index are available. -The `TEXT` node index is chosen. - -.Query -[source, cypher, indent=0] ----- -MATCH (person:Person {middlename: 'Ron'}) -RETURN person ----- .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+-----------------+------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | person | 1 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX person:Person(middlename) WHERE middlename = $autostring_0 | 1 | 1 | 2 | 112 | 2/1 | 0.392 | Fused in Pipeline 0 | -+-----------------+------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++--------------------------------+-------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++--------------------------------+-------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | person, friend | 1 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +-------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +DirectedRelationshipIndexSeek | (person)-[relationship:KNOWS(since)]->(friend) WHERE since = $autoint_0 | 1 | 1 | 3 | 72 | 2/1 | 0.473 | Fused in Pipeline 0 | ++--------------------------------+-------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 2, total allocated memory: 176 +Total database accesses: 3, total allocated memory: 136 ---- - -[discrete] [[administration-indexes-equality-check-using-where-single-property-index]] -=== Equality check using `WHERE` (single-property index) - -A query containing equality comparisons of a single indexed property in the `WHERE` clause is backed automatically by the index. -It is also possible for a query with multiple `OR` predicates to use multiple indexes, if indexes exist on the properties. -For example, if indexes exist on both `:Label(p1)` and `:Label(p2)`, `MATCH (n:Label) WHERE n.p1 = 1 OR n.p2 = 2 RETURN n` will use both indexes. +== Equality check using `WHERE` (single-property index) == +A query containing equality comparisons of a single indexed property in the `WHERE` clause is backed automatically by the index. It is also possible for a query with multiple `OR` predicates to use multiple indexes, if indexes exist on the properties. For example, if indexes exist on both `:Label(p1)` and `:Label(p2)`, `MATCH (n:Label) WHERE n.p1 = 1 OR n.p2 = 2 RETURN n` will use both indexes. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (person:Person) -WHERE person.firstname = 'Andy' -RETURN person +MATCH (person:Person) WHERE person.firstname = 'Andy' RETURN person ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+-----------------+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | person | 1 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX person:Person(firstname) WHERE firstname = $autostring_0 | 1 | 1 | 2 | 112 | 2/1 | 1.208 | Fused in Pipeline 0 | -+-----------------+----------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++-----------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | person | 1 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeek | person:Person(firstname) WHERE firstname = $autostring_0 | 1 | 1 | 2 | 72 | 2/1 | 0.514 | Fused in Pipeline 0 | ++-----------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 2, total allocated memory: 176 +Total database accesses: 2, total allocated memory: 136 ---- - -[discrete] [[administration-indexes-equality-check-using-where-composite-index]] -=== Equality check using `WHERE` (composite index) - -A query containing equality comparisons for all the properties of a composite index will automatically be backed by the same index. -However, the query does not need to have equality on all properties. -It can have ranges and existence predicates as well. -But in these cases rewrites might happen depending on which properties have which predicates, see xref::indexes-for-search-performance.adoc#administration-indexes-single-vs-composite-index[composite index limitations]. - -The following query will use the composite index defined xref::indexes-for-search-performance.adoc#administration-indexes-create-a-composite-b-tree-index-for-nodes[earlier]: - -//// -CREATE INDEX node_index_name FOR (n:Person) ON (n.age, n.country) -CREATE (p0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `middlename`:"Ron", `name`:"john", `surname`:"Smith"}) -//// +== Equality check using `WHERE` (composite index) == +A query containing equality comparisons for all the properties of a composite index will automatically be backed by the same index. However, the query does not need to have equality on all properties. It can have ranges and existence predicates as well. But in these cases rewrites might happen depending on which properties have which predicates, see xref:indexes-for-search-performance.adoc#administration-indexes-single-vs-composite-index[composite index limitations]. The following query will use the composite index defined xref:indexes-for-search-performance.adoc#administration-indexes-create-a-composite-index-for-nodes[earlier]: .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (n:Person) -WHERE n.age = 35 AND n.country = 'UK' -RETURN n +MATCH (n:Person) WHERE n.age = 35 AND n.country = 'UK' RETURN n ---- -However, the query `MATCH (n:Person) WHERE n.age = 35 RETURN n` will not be backed by the composite index, as the query does not contain a predicate on the `country` property. -It will only be backed by an index on the `Person` label and `age` property defined thus: `:Person(age)`; i.e. a single-property index. + +However, the query `MATCH (n:Person) WHERE n.age = 35 RETURN n` will not be backed by the composite index, as the query does not contain a predicate on the `country` property. It will only be backed by an index on the `Person` label and `age` property defined thus: `:Person(age)`; i.e. a single-property index. .Result -[source, result, role="noheader"] +[queryresult] ---- -+------------------------------------------------------------------------------------------------------------+ -| n | -+------------------------------------------------------------------------------------------------------------+ -| Node[0]{country:"UK",firstname:"John",highScore:54321,surname:"Smith",name:"john",middlename:"Ron",age:35} | -+------------------------------------------------------------------------------------------------------------+ ++-------------------------------------------------------------------------------------------+ +| n | ++-------------------------------------------------------------------------------------------+ +| Node[0]{country:"UK",firstname:"John",highScore:54321,surname:"Smith",name:"john",age:35} | ++-------------------------------------------------------------------------------------------+ 1 row ---- -[discrete] -[[administration-indexes-range-comparisons-using-where-single-property-index]] -=== Range comparisons using `WHERE` (single-property index) +.Try this query live +[console] +---- +create index `index_58a1c03e` for (n:`Person`) ON (n.`location`); +create index `index_d7c12ba3` for (n:`Person`) ON (n.`highScore`); +create index `index_deeafdb2` for (n:`Person`) ON (n.`firstname`); +create (_0:`Person` {`age`:35, `country`:"UK", `firstname`:"John", `highScore`:54321, `name`:"john", `surname`:"Smith"}) +create (_1:`Person` {`age`:40, `country`:"Sweden", `firstname`:"Andy", `highScore`:12345, `name`:"andy", `surname`:"Jones"}) +create (_2:`Person`) +create (_3:`Person`) +create (_4:`Person`) +create (_5:`Person`) +create (_6:`Person`) +create (_7:`Person`) +create (_8:`Person`) +create (_9:`Person`) +create (_10:`Person`) +create (_11:`Person`) +create (_12:`Person`) +create (_13:`Person`) +create (_14:`Person`) +create (_15:`Person`) +create (_16:`Person`) +create (_17:`Person`) +create (_18:`Person`) +create (_19:`Person`) +create (_20:`Person`) +create (_21:`Person`) +create (_22:`Person`) +create (_23:`Person`) +create (_24:`Person`) +create (_25:`Person`) +create (_26:`Person`) +create (_27:`Person`) +create (_28:`Person`) +create (_29:`Person`) +create (_30:`Person`) +create (_31:`Person`) +create (_32:`Person`) +create (_33:`Person`) +create (_34:`Person`) +create (_35:`Person`) +create (_36:`Person`) +create (_37:`Person`) +create (_38:`Person`) +create (_39:`Person`) +create (_40:`Person`) +create (_41:`Person`) +create (_42) +create (_43) +create (_1)-[:`KNOWS`]->(_0) +create (_2)-[:`KNOWS`]->(_3) +create (_4)-[:`KNOWS`]->(_5) +create (_6)-[:`KNOWS`]->(_7) +create (_8)-[:`KNOWS`]->(_9) +create (_10)-[:`KNOWS`]->(_11) +create (_12)-[:`KNOWS`]->(_13) +create (_14)-[:`KNOWS`]->(_15) +create (_16)-[:`KNOWS`]->(_17) +create (_18)-[:`KNOWS`]->(_19) +create (_20)-[:`KNOWS`]->(_21) +create (_22)-[:`KNOWS`]->(_23) +create (_24)-[:`KNOWS`]->(_25) +create (_26)-[:`KNOWS`]->(_27) +create (_28)-[:`KNOWS`]->(_29) +create (_30)-[:`KNOWS`]->(_31) +create (_32)-[:`KNOWS`]->(_33) +create (_34)-[:`KNOWS`]->(_35) +create (_36)-[:`KNOWS`]->(_37) +create (_38)-[:`KNOWS`]->(_39) +create (_40)-[:`KNOWS`]->(_41) +create (_42)-[:`KNOWS` {`lastMet`:2021, `lastMetIn`:"Stockholm", `metIn`:"Malmo", `since`:1992}]->(_43) +; + + +MATCH (n:Person) WHERE n.age = 35 AND n.country = 'UK' RETURN n +---- + +[[administration-indexes-range-comparisons-using-where-single-property-index]] +== Range comparisons using `WHERE` (single-property index) == Single-property indexes are also automatically used for inequality (range) comparisons of an indexed property in the `WHERE` clause. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (friend)<-[r:KNOWS]-(person) -WHERE r.since < 2011 -RETURN friend, person +MATCH (friend)<-[r:KNOWS]-(person) WHERE r.since < 2011 RETURN friend, person ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+---------------------------------------+--------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+---------------------------------------+--------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | friend, person | 1 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +--------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexSeekByRange | BTREE INDEX (person)-[r:KNOWS(since)]->(friend) WHERE since < $autoint_0 | 1 | 1 | 3 | 112 | 2/1 | 1.767 | Fused in Pipeline 0 | -+---------------------------------------+--------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++---------------------------------------+--------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++---------------------------------------+--------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | friend, person | 1 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +--------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +DirectedRelationshipIndexSeekByRange | (person)-[r:KNOWS(since)]->(friend) WHERE since < $autoint_0 | 1 | 1 | 3 | 72 | 2/1 | 0.543 | Fused in Pipeline 0 | ++---------------------------------------+--------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 3, total allocated memory: 176 ----- +Total database accesses: 3, total allocated memory: 136 +---- -[discrete] [[administration-indexes-range-comparisons-using-where-composite-index]] -=== Range comparisons using `WHERE` (composite index) - -Composite indexes are also automatically used for inequality (range) comparisons of indexed properties in the `WHERE` clause. -Equality or list membership check predicates may precede the range predicate. -However, predicates after the range predicate may be rewritten as an existence check predicate and a filter as described in xref::indexes-for-search-performance.adoc#administration-indexes-single-vs-composite-index[composite index limitations]. +== Range comparisons using `WHERE` (composite index) == +Composite indexes are also automatically used for inequality (range) comparisons of indexed properties in the `WHERE` clause. Equality or list membership check predicates may precede the range predicate. However, predicates after the range predicate may be rewritten as an existence check predicate and a filter as described in xref:indexes-for-search-performance.adoc#administration-indexes-single-vs-composite-index[composite index limitations]. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH ()-[r:KNOWS]-() -WHERE r.since < 2011 AND r.lastMet > 2019 -RETURN r.since +MATCH ()-[r:KNOWS]-() WHERE r.since < 2011 AND r.lastMet > 2019 RETURN r.since ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+----------------------------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+----------------------------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | `r.since` | 2 | 2 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Projection | cache[r.since] AS `r.since` | 2 | 2 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | cache[r.lastMet] > $autoint_1 | 2 | 2 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +UndirectedRelationshipIndexSeek | BTREE INDEX (anon_0)-[r:KNOWS(since, lastMet)]-(anon_1) WHERE since < $autoint_0 AND lastMet IS NOT | 2 | 2 | 3 | 112 | 1/1 | 1.181 | Fused in Pipeline 0 | -| | NULL, cache[r.since], cache[r.lastMet] | | | | | | | | -+----------------------------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++----------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++----------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | `r.since` | 4 | 2 | 0 | | | | Fused in Pipeline 0 | +| | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Projection | cache[r.since] AS `r.since` | 4 | 2 | 0 | | | | Fused in Pipeline 0 | +| | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | cache[r.lastMet] > $autoint_1 | 4 | 2 | 0 | | | | Fused in Pipeline 0 | +| | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +UndirectedRelationshipIndexSeek | (anon_0)-[r:KNOWS(since, lastMet)]-(anon_1) WHERE since < $autoint_0 AND lastMet IS NOT NULL, cache[ | 7 | 2 | 3 | 72 | 1/1 | 1.207 | Fused in Pipeline 0 | +| | r.since], cache[r.lastMet] | | | | | | | | ++----------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 3, total allocated memory: 176 ----- +Total database accesses: 3, total allocated memory: 136 +---- -[discrete] [[administration-indexes-multiple-range-comparisons-using-where-single-property-index]] -=== Multiple range comparisons using `WHERE` (single-property index) - +== Multiple range comparisons using `WHERE` (single-property index) == When the `WHERE` clause contains multiple inequality (range) comparisons for the same property, these can be combined in a single index range seek. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (person:Person) -WHERE 10000 < person.highScore < 20000 -RETURN person +MATCH (person:Person) WHERE 10000 < person.highScore < 20000 RETURN person ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+-----------------------+----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------------+----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | person | 1 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeekByRange | BTREE INDEX person:Person(highScore) WHERE highScore > $autoint_0 AND highScore < $autoint_1 | 1 | 1 | 2 | 112 | 2/1 | 0.812 | Fused in Pipeline 0 | -+-----------------------+----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++-----------------------+----------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------------+----------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | person | 1 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeekByRange | person:Person(highScore) WHERE highScore > $autoint_0 AND highScore < $autoint_1 | 1 | 1 | 2 | 72 | 2/1 | 0.471 | Fused in Pipeline 0 | ++-----------------------+----------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 2, total allocated memory: 176 ----- +Total database accesses: 2, total allocated memory: 136 +---- -[discrete] [[administration-indexes-multiple-range-comparisons-using-where-composite-index]] -=== Multiple range comparisons using `WHERE` (composite index) - -When the `WHERE` clause contains multiple inequality (range) comparisons for the same property, these can be combined in a single index range seek. -That single range seek created in the following query will then use the composite index `Person(highScore, name)` if it exists. +== Multiple range comparisons using `WHERE` (composite index) == +When the `WHERE` clause contains multiple inequality (range) comparisons for the same property, these can be combined in a single index range seek. That single range seek created in the following query will then use the composite index `Person(highScore, name)` if it exists. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (person:Person) -WHERE 10000 < person.highScore < 20000 AND person.name IS NOT NULL -RETURN person +MATCH (person:Person) WHERE 10000 < person.highScore < 20000 AND person.name IS NOT NULL RETURN + person ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person | 1 | 1 | 0 | | | | Fused in Pipeline 0 | | | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX person:Person(highScore, name) WHERE highScore > $autoint_0 AND highScore < $autoint_1 A | 1 | 1 | 2 | 112 | 2/1 | 3.233 | Fused in Pipeline 0 | -| | ND name IS NOT NULL | | | | | | | | +| +NodeIndexSeek | person:Person(highScore, name) WHERE highScore > $autoint_0 AND highScore < $autoint_1 AND name IS N | 1 | 1 | 2 | 72 | 2/1 | 13.696 | Fused in Pipeline 0 | +| | OT NULL | | | | | | | | +-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 2, total allocated memory: 176 ----- +Total database accesses: 2, total allocated memory: 136 +---- -[discrete] [[administration-indexes-list-membership-check-using-in-single-property-index]] -=== List membership check using `IN` (single-property index) - -The `IN` predicate on `r.since` in the following query will use the single-property index `KNOWS(lastMetIn)` if it exists. +== List membership check using `IN` (single-property index) == +The `IN` predicate on `r.since` in the following query will use the single-property index `KNOWS(since)` if it exists. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (person)-[r:KNOWS]->(friend) -WHERE r.lastMetIn IN ['Malmo', 'Stockholm'] -RETURN person, friend +MATCH (person)-[r:KNOWS]->(friend) WHERE r.since IN [1992, 2017] RETURN person, friend ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+--------------------------------+------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+--------------------------------+------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | person, friend | 1 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexSeek | BTREE INDEX (person)-[r:KNOWS(lastMetIn)]->(friend) WHERE lastMetIn IN $autolist_0 | 1 | 1 | 4 | 112 | 3/1 | 0.537 | Fused in Pipeline 0 | -+--------------------------------+------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++--------------------------------+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++--------------------------------+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | person, friend | 1 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +DirectedRelationshipIndexSeek | (person)-[r:KNOWS(since)]->(friend) WHERE since IN $autolist_0 | 1 | 1 | 4 | 72 | 3/1 | 1.206 | Fused in Pipeline 0 | ++--------------------------------+----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 4, total allocated memory: 176 ----- +Total database accesses: 4, total allocated memory: 136 +---- -[discrete] [[administration-indexes-list-membership-check-using-in-composite-index]] -=== List membership check using `IN` (composite index) - -The `IN` predicates on `r.since` and `r.lastMet` in the following query will use the composite index `KNOWS(since, lastMet)` if it exists. +== List membership check using `IN` (composite index) == +The `IN` predicates on `r.since` and `r.lastMet` in the following query will use the composite index `KNOWS(since, lastMet)` if it exists. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (person)-[r:KNOWS]->(friend) -WHERE r.since IN [1992, 2017] AND r.lastMet IN [2002, 2021] -RETURN person, friend +MATCH (person)-[r:KNOWS]->(friend) WHERE r.since IN [1992, 2017] AND r.lastMet IN [2002, + 2021] RETURN person, friend ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+--------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+--------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | person, friend | 1 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexSeek | BTREE INDEX (person)-[r:KNOWS(since, lastMet)]->(friend) WHERE since IN $autolist_0 AND lastMet IN $ | 1 | 1 | 6 | 112 | 5/1 | 4.788 | Fused in Pipeline 0 | -| | autolist_1 | | | | | | | | -+--------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++--------------------------------+----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++--------------------------------+----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | person, friend | 5 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +DirectedRelationshipIndexSeek | (person)-[r:KNOWS(since, lastMet)]->(friend) WHERE since IN $autolist_0 AND lastMet IN $autolist_1 | 5 | 1 | 6 | 72 | 5/1 | 5.452 | Fused in Pipeline 0 | ++--------------------------------+----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 6, total allocated memory: 176 ----- +Total database accesses: 6, total allocated memory: 136 +---- -[discrete] [[administration-indexes-prefix-search-using-starts-with-single-property-index]] -=== Prefix search using `STARTS WITH` (single-property index) - +== Prefix search using `STARTS WITH` (single-property index) == The `STARTS WITH` predicate on `person.firstname` in the following query will use the `Person(firstname)` index, if it exists. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (person:Person) -WHERE person.firstname STARTS WITH 'And' -RETURN person +MATCH (person:Person) WHERE person.firstname STARTS WITH 'And' RETURN person ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | person | 2 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +--------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeekByRange | BTREE INDEX person:Person(firstname) WHERE firstname STARTS WITH $autostring_0 | 2 | 1 | 2 | 112 | 3/0 | 0.630 | Fused in Pipeline 0 | -+-----------------------+--------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++-----------------------+--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------------+--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | person | 2 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +--------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeekByRange | person:Person(firstname) WHERE firstname STARTS WITH $autostring_0 | 2 | 1 | 2 | 72 | 3/0 | 0.514 | Fused in Pipeline 0 | ++-----------------------+--------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 2, total allocated memory: 176 ----- +Total database accesses: 2, total allocated memory: 136 +---- -[discrete] [[administration-indexes-prefix-search-using-starts-with-composite-index]] -=== Prefix search using `STARTS WITH` (composite index) - -The `STARTS WITH` predicate on `person.firstname` in the following query will use the `Person(firstname,surname)` index, if it exists. -Any (non-existence check) predicate on `person.surname` will be rewritten as existence check with a filter. -However, if the predicate on `person.firstname` is a equality check then a `STARTS WITH` on `person.surname` would also use the index (without rewrites). -More information about how the rewriting works can be found in xref::indexes-for-search-performance.adoc#administration-indexes-single-vs-composite-index[composite index limitations]. +== Prefix search using `STARTS WITH` (composite index) == +The `STARTS WITH` predicate on `person.firstname` in the following query will use the `Person(firstname,surname)` index, if it exists. Any (non-existence check) predicate on `person.surname` will be rewritten as existence check with a filter. However, if the predicate on `person.firstname` is a equality check then a `STARTS WITH` on `person.surname` would also use the index (without rewrites). More information about how the rewriting works can be found in xref:indexes-for-search-performance.adoc#administration-indexes-single-vs-composite-index[composite index limitations]. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (person:Person) -WHERE person.firstname STARTS WITH 'And' AND person.surname IS NOT NULL -RETURN person +MATCH (person:Person) WHERE person.firstname STARTS WITH 'And' AND person.surname IS NOT NULL RETURN + person ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-----------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +ProduceResults | person | 1 | 1 | 0 | | | | Fused in Pipeline 0 | | | +-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX person:Person(firstname, surname) WHERE firstname STARTS WITH $autostring_0 AND surname | 1 | 1 | 2 | 112 | 3/0 | 0.544 | Fused in Pipeline 0 | -| | IS NOT NULL | | | | | | | | +| +NodeIndexSeek | person:Person(firstname, surname) WHERE firstname STARTS WITH $autostring_0 AND surname IS NOT NULL | 1 | 1 | 2 | 72 | 3/0 | 2.998 | Fused in Pipeline 0 | +-----------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 2, total allocated memory: 176 ----- +Total database accesses: 2, total allocated memory: 136 +---- -[discrete] [[administration-indexes-suffix-search-using-ends-with-single-property-index]] -=== Suffix search using `ENDS WITH` (single-property index) - -The `ENDS WITH` predicate on `r.metIn` in the following query uses the `KNOWS(metIn)` index, if it exists. -All values stored in the `KNOWS(metIn)` index are searched, and entries ending with `'mo'` are returned. -This means that although the search is not optimized to the extent of queries using `=`, `IN`, `>`, `<` or `STARTS WITH`, it is still faster than not using an index in the first place. +== Suffix search using `ENDS WITH` (single-property index) == +The `ENDS WITH` predicate on `r.metIn` in the following query uses the `KNOWS(metIn)` index, if it exists. All values stored in the `KNOWS(metIn)` index are searched, and entries ending with `'mo'` are returned. This means that although the search is not optimized to the extent of queries using `=`, `IN`, `>`, `<` or `STARTS WITH`, it is still faster than not using an index in the first place. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (person)-[r:KNOWS]->(friend) -WHERE r.metIn ENDS WITH 'mo' -RETURN person, friend +MATCH (person)-[r:KNOWS]->(friend) WHERE r.metIn ENDS WITH 'mo' RETURN person, friend ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+----------------------------------------+-------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+----------------------------------------+-------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | person, friend | 0 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +-------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexEndsWithScan | BTREE INDEX (person)-[r:KNOWS(metIn)]->(friend) WHERE metIn ENDS WITH $autostring_0 | 0 | 1 | 3 | 112 | 2/1 | 0.409 | Fused in Pipeline 0 | -+----------------------------------------+-------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++----------------------------------------+-------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++----------------------------------------+-------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | person, friend | 0 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +-------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +DirectedRelationshipIndexEndsWithScan | (person)-[r:KNOWS(metIn)]->(friend) WHERE metIn ENDS WITH $autostring_0 | 0 | 1 | 3 | 72 | 2/1 | 0.517 | Fused in Pipeline 0 | ++----------------------------------------+-------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 3, total allocated memory: 176 ----- +Total database accesses: 3, total allocated memory: 136 +---- -[discrete] [[administration-indexes-suffix-search-using-ends-with-composite-index]] -=== Suffix search using `ENDS WITH` (composite index) - -The `ENDS WITH` predicate on `r.metIn` in the following query uses the `KNOWS(metIn,lastMetIn)` index, if it exists. -However, it is rewritten as existence check and a filter due to the index not supporting actual suffix searches for composite indexes, this is still faster than not using an index in the first place. -Any (non-existence check) predicate on `KNOWS.lastMetIn` is also rewritten as existence check with a filter. -More information about how the rewriting works can be found in xref::indexes-for-search-performance.adoc#administration-indexes-single-vs-composite-index[composite index limitations]. +== Suffix search using `ENDS WITH` (composite index) == +The `ENDS WITH` predicate on `r.metIn` in the following query uses the `KNOWS(metIn,lastMetIn)` index, if it exists. However, it is rewritten as existence check and a filter due to the index not supporting actual suffix searches for composite indexes, this is still faster than not using an index in the first place. Any (non-existence check) predicate on `KNOWS.lastMetIn` is also rewritten as existence check with a filter. More information about how the rewriting works can be found in xref:indexes-for-search-performance.adoc#administration-indexes-single-vs-composite-index[composite index limitations]. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (person)-[r:KNOWS]->(friend) -WHERE r.metIn ENDS WITH 'mo' AND r.lastMetIn IS NOT NULL -RETURN person, friend +MATCH (person)-[r:KNOWS]->(friend) WHERE r.metIn ENDS WITH 'mo' AND r.lastMetIn IS NOT NULL RETURN + person, + friend ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 + ++--------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++--------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | person, friend | 1 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | cache[r.metIn] ENDS WITH $autostring_0 | 1 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +DirectedRelationshipIndexScan | (person)-[r:KNOWS(metIn, lastMetIn)]->(friend) WHERE metIn IS NOT NULL AND lastMetIn IS NOT NULL, ca | 6 | 1 | 3 | 72 | 2/1 | 0.490 | Fused in Pipeline 0 | +| | che[r.metIn] | | | | | | | | ++--------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -+--------------------------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+--------------------------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | person, friend | 0 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | cache[r.metIn] ENDS WITH $autostring_0 | 0 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexScan | BTREE INDEX (person)-[r:KNOWS(metIn, lastMetIn)]->(friend) WHERE metIn IS NOT NULL AND lastMetIn IS | 1 | 1 | 3 | 112 | 2/1 | 0.407 | Fused in Pipeline 0 | -| | NOT NULL, cache[r.metIn] | | | | | | | | -+--------------------------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +Total database accesses: 3, total allocated memory: 136 -Total database accesses: 3, total allocated memory: 176 ---- - -[discrete] [[administration-indexes-substring-search-using-contains-single-property-index]] -=== Substring search using `CONTAINS` (single-property index) - -The `CONTAINS` predicate on `person.firstname` in the following query will use the `Person(firstname)` index, if it exists. -All values stored in the `Person(firstname)` index will be searched, and entries containing `'h'` will be returned. -This means that although the search will not be optimized to the extent of queries using `=`, `IN`, `>`, `<` or `STARTS WITH`, it is still faster than not using an index in the first place. -Composite indexes are currently not able to support `CONTAINS`. +== Substring search using `CONTAINS` (single-property index) == +The `CONTAINS` predicate on `person.firstname` in the following query will use the `Person(firstname)` index, if it exists. All values stored in the `Person(firstname)` index will be searched, and entries containing `'h'` will be returned. This means that although the search will not be optimized to the extent of queries using `=`, `IN`, `>`, `<` or `STARTS WITH`, it is still faster than not using an index in the first place. Composite indexes are currently not able to support `CONTAINS`. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (person:Person) -WHERE person.firstname CONTAINS 'h' -RETURN person +MATCH (person:Person) WHERE person.firstname CONTAINS 'h' RETURN person ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+------------------------+-----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+------------------------+-----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | person | 2 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexContainsScan | BTREE INDEX person:Person(firstname) WHERE firstname CONTAINS $autostring_0 | 2 | 1 | 2 | 112 | 3/0 | 1.355 | Fused in Pipeline 0 | -+------------------------+-----------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++------------------------+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++------------------------+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | person | 2 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexContainsScan | person:Person(firstname) WHERE firstname CONTAINS $autostring_0 | 2 | 1 | 2 | 72 | 3/0 | 0.953 | Fused in Pipeline 0 | ++------------------------+-----------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 2, total allocated memory: 176 ----- +Total database accesses: 2, total allocated memory: 136 +---- -[discrete] [[administration-indexes-substring-search-using-contains-composite-index]] -=== Substring search using `CONTAINS` (composite index) - -The `CONTAINS` predicate on `person.country` in the following query will use the `Person(country,age)` index, if it exists. -However, it will be rewritten as existence check and a filter due to the index not supporting actual suffix searches for composite indexes, this is still faster than not using an index in the first place. -Any (non-existence check) predicate on `person.age` will also be rewritten as existence check with a filter. -More information about how the rewriting works can be found in xref::indexes-for-search-performance.adoc#administration-indexes-single-vs-composite-index[composite index limitations]. +== Substring search using `CONTAINS` (composite index) == +The `CONTAINS` predicate on `person.surname` in the following query will use the `Person(surname,age)` index, if it exists. However, it will be rewritten as existence check and a filter due to the index not supporting actual suffix searches for composite indexes, this is still faster than not using an index in the first place. Any (non-existence check) predicate on `person.age` will also be rewritten as existence check with a filter. More information about how the rewriting works can be found in xref:indexes-for-search-performance.adoc#administration-indexes-single-vs-composite-index[composite index limitations]. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (person:Person) -WHERE person.country CONTAINS '300' AND person.age IS NOT NULL -RETURN person +MATCH (person:Person) WHERE person.surname CONTAINS '300' AND person.age IS NOT NULL RETURN person ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | person | 2 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | cache[person.country] CONTAINS $autostring_0 | 2 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexScan | BTREE INDEX person:Person(country, age) WHERE country IS NOT NULL AND age IS NOT NULL, cache[person. | 303 | 303 | 304 | 112 | 5/0 | 2.171 | Fused in Pipeline 0 | -| | country] | | | | | | | | -+-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++-----------------+--------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+--------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | person | 11 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +--------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | cache[person.surname] CONTAINS $autostring_0 | 11 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +--------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexScan | person:Person(surname, age) WHERE surname IS NOT NULL AND age IS NOT NULL, cache[person.surname] | 111 | 303 | 304 | 72 | 5/0 | 2.546 | Fused in Pipeline 0 | ++-----------------+--------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 304, total allocated memory: 176 ----- +Total database accesses: 304, total allocated memory: 136 +---- -[discrete] [[administration-indexes-existence-check-using-is-not-null-single-property-index]] -=== Existence check using `IS NOT NULL` (single-property index) - -The `r.since IS NOT NULL` predicate in the following query uses the `KNOWS(since)` index, if it exists. +== Existence check using `IS NOT NULL` (single-property index) == +The `r.since IS NOT NULL` predicate in the following query uses the `KNOWS(since)` index, if it exists. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (person)-[r:KNOWS]->(friend) -WHERE r.since IS NOT NULL -RETURN person, friend +MATCH (person)-[r:KNOWS]->(friend) WHERE r.since IS NOT NULL RETURN person, friend ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+--------------------------------+-------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+--------------------------------+-------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | person, friend | 1 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +-------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexScan | BTREE INDEX (person)-[r:KNOWS(since)]->(friend) WHERE since IS NOT NULL | 1 | 1 | 3 | 112 | 2/1 | 4.300 | Fused in Pipeline 0 | -+--------------------------------+-------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++--------------------------------+-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++--------------------------------+-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | person, friend | 1 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +-------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +DirectedRelationshipIndexScan | (person)-[r:KNOWS(since)]->(friend) WHERE since IS NOT NULL | 1 | 1 | 3 | 72 | 2/1 | 0.417 | Fused in Pipeline 0 | ++--------------------------------+-------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 3, total allocated memory: 176 ----- +Total database accesses: 3, total allocated memory: 136 +---- -[discrete] [[administration-indexes-existence-check-using-is-not-null-composite-index]] -=== Existence check using `IS NOT NULL` (composite index) - -The `p.firstname IS NOT NULL` and `p.surname IS NOT NULL` predicates in the following query will use the `Person(firstname,surname)` index, if it exists. -Any (non-existence check) predicate on `person.surname` will be rewritten as existence check with a filter. +== Existence check using `IS NOT NULL` (composite index) == +The `p.firstname IS NOT NULL` and `p.surname IS NOT NULL` predicates in the following query will use the `Person(firstname,surname)` index, if it exists. Any (non-existence check) predicate on `person.surname` will be rewritten as existence check with a filter. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (p:Person) -WHERE p.firstname IS NOT NULL AND p.surname IS NOT NULL -RETURN p +MATCH (p:Person) WHERE p.firstname IS NOT NULL AND p.surname IS NOT NULL RETURN p ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+-----------------+----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | p | 1 | 2 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexScan | BTREE INDEX p:Person(firstname, surname) WHERE firstname IS NOT NULL AND surname IS NOT NULL | 1 | 2 | 3 | 112 | 2/1 | 2.915 | Fused in Pipeline 0 | -+-----------------+----------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++-----------------+----------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+----------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | p | 1 | 2 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexScan | p:Person(firstname, surname) WHERE firstname IS NOT NULL AND surname IS NOT NULL | 1 | 2 | 3 | 72 | 2/1 | 0.633 | Fused in Pipeline 0 | ++-----------------+----------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 3, total allocated memory: 176 ----- +Total database accesses: 3, total allocated memory: 136 +---- -[discrete] [[administration-indexes-spatial-distance-searches-single-property-index]] -=== Spatial distance searches (single-property index) - +== Spatial distance searches (single-property index) == If a property with point values is indexed, the index is used for spatial distance searches as well as for range queries. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH ()-[r:KNOWS]->() -WHERE point.distance(r.lastMetPoint, point({x: 1, y: 2})) < 2 -RETURN r.lastMetPoint +MATCH ()-[r:KNOWS]->() WHERE distance(r.lastMetPoint, point({x: 1, y: 2})) < 2 RETURN r.lastMetPoint ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +---------------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -929,138 +757,126 @@ Runtime version 4.4 | | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Projection | cache[r.lastMetPoint] AS `r.lastMetPoint` | 13 | 9 | 0 | | | | Fused in Pipeline 0 | | | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | point.distance(cache[r.lastMetPoint], point({x: $autoint_0, y: $autoint_1})) < $autoint_2 | 13 | 9 | 0 | | | | Fused in Pipeline 0 | +| +Filter | distance(cache[r.lastMetPoint], point({x: $autoint_0, y: $autoint_1})) < $autoint_2 | 13 | 9 | 0 | | | | Fused in Pipeline 0 | | | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexSeekByRange | BTREE INDEX (anon_0)-[r:KNOWS(lastMetPoint)]->(anon_1) WHERE point.distance(lastMetPoint, point($aut | 13 | 9 | 19 | 112 | 5/3 | 1.596 | Fused in Pipeline 0 | -| | oint_0, $autoint_1)) < $autoint_2, cache[r.lastMetPoint] | | | | | | | | +| +DirectedRelationshipIndexSeekByRange | (anon_0)-[r:KNOWS(lastMetPoint)]->(anon_1) WHERE distance(lastMetPoint, point($autoint_0, $autoint_1 | 13 | 9 | 19 | 72 | 5/3 | 1.774 | Fused in Pipeline 0 | +| | )) < $autoint_2, cache[r.lastMetPoint] | | | | | | | | +---------------------------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 19, total allocated memory: 176 ----- +Total database accesses: 19, total allocated memory: 136 +---- -[discrete] [[administration-indexes-spatial-distance-searches-composite-index]] -=== Spatial distance searches (composite index) - -If a property with point values is indexed, the index is used for spatial distance searches as well as for range queries. -Any following (non-existence check) predicates (here on property `p.name` for index `:Person(place,name)`) will be rewritten as existence check with a filter. +== Spatial distance searches (composite index) == +If a property with point values is indexed, the index is used for spatial distance searches as well as for range queries. Any following (non-existence check) predicates (here on property `p.name` for index `:Person(place,name)`) will be rewritten as existence check with a filter. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (p:Person) -WHERE point.distance(p.place, point({x: 1, y: 2})) < 2 AND p.name IS NOT NULL -RETURN p.place +MATCH (p:Person) WHERE distance(p.place, point({x: 1, + y: 2})) < 2 AND p.name IS NOT NULL RETURN p.place ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | `p.place` | 0 | 9 | 0 | | | | Fused in Pipeline 0 | -| | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Projection | cache[p.place] AS `p.place` | 0 | 9 | 0 | | | | Fused in Pipeline 0 | -| | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | point.distance(cache[p.place], point({x: $autoint_0, y: $autoint_1})) < $autoint_2 | 0 | 9 | 0 | | | | Fused in Pipeline 0 | -| | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX p:Person(place, name) WHERE point.distance(place, point($autoint_0, $autoint_1)) < $auto | 0 | 9 | 10 | 112 | 6/0 | 1.370 | Fused in Pipeline 0 | -| | int_2 AND name IS NOT NULL, cache[p.place] | | | | | | | | -+-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++-----------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | `p.place` | 72 | 9 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Projection | cache[p.place] AS `p.place` | 72 | 9 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | distance(cache[p.place], point({x: $autoint_0, y: $autoint_1})) < $autoint_2 | 72 | 9 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeek | p:Person(place, name) WHERE distance(place, point($autoint_0, $autoint_1)) < $autoint_2 AND name IS | 72 | 9 | 10 | 72 | 6/0 | 2.964 | Fused in Pipeline 0 | +| | NOT NULL, cache[p.place] | | | | | | | | ++-----------------+-----------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 10, total allocated memory: 176 ----- +Total database accesses: 10, total allocated memory: 136 +---- -[discrete] [[administration-indexes-spatial-bounding-box-searches-single-property-index]] -=== Spatial bounding box searches (single-property index) - +== Spatial bounding box searches (single-property index) == The ability to do index seeks on bounded ranges works even with the 2D and 3D spatial `Point` types. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (person:Person) -WHERE point.withinBBox(person.location, point({x: 1.2, y: 5.4}), point({x: 1.3, y: 5.5})) -RETURN person.firstname +MATCH (person:Person) WHERE point({x: 1, y: 5}) < person.location < point({x: 2, y: 6}) RETURN person ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-----------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | `person.firstname` | 0 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Projection | person.firstname AS `person.firstname` | 0 | 1 | 2 | | | | Fused in Pipeline 0 | +| +ProduceResults | person | 0 | 1 | 0 | | | | Fused in Pipeline 0 | | | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeekByRange | BTREE INDEX person:Person(location) WHERE point.withinBBox(location, point($autodouble_0, $autodoubl | 0 | 1 | 2 | 112 | 6/0 | 16.182 | Fused in Pipeline 0 | -| | e_1), point($autodouble_2, $autodouble_3)) | | | | | | | | +| +NodeIndexSeekByRange | person:Person(location) WHERE location > point({x: $autoint_0, y: $autoint_1}) AND location < point( | 0 | 1 | 2 | 72 | 8/0 | 11.041 | Fused in Pipeline 0 | +| | {x: $autoint_2, y: $autoint_3}) | | | | | | | | +-----------------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 4, total allocated memory: 176 ----- +Total database accesses: 2, total allocated memory: 136 +---- -[discrete] [[administration-indexes-spatial-bounding-box-searches-composite-index]] -=== Spatial bounding box searches (composite index) - -The ability to do index seeks on bounded ranges works even with the 2D and 3D spatial `Point` types. -Any following (non-existence check) predicates (here on property `p.firstname` for index `:Person(place,firstname)`) will be rewritten as existence check with a filter. -For index `:Person(firstname,place)`, if the predicate on `firstname` is equality or list membership then the bounded range is handled as a range itself. -If the predicate on `firstname` is anything else then the bounded range is rewritten to existence and filter. +== Spatial bounding box searches (composite index) == +The ability to do index seeks on bounded ranges works even with the 2D and 3D spatial `Point` types. Any following (non-existence check) predicates (here on property `p.firstname` for index `:Person(place,firstname)`) will be rewritten as existence check with a filter. For index `:Person(firstname,place)`, if the predicate on `firstname` is equality or list membership then the bounded range is handled as a range itself. If the predicate on `firstname` is anything else then the bounded range is rewritten to existence and filter. .Query -[source, cypher, indent=0] +[source,cypher] ---- -MATCH (person:Person) -WHERE - point.withinBBox(person.place, point({x: 1.2, y: 5.4}), point({x: 1.3, y: 5.5})) - AND person.firstname IS NOT NULL -RETURN person +MATCH (person:Person) WHERE point({x: 1, y: 5}) < person.place < point({x: 2, + y: 6}) AND person.firstname IS NOT NULL RETURN person ---- + + .Query Plan -[source, query plan, role="noheader"] +[source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | +-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | person | 1 | 1 | 0 | | | | Fused in Pipeline 0 | +| +ProduceResults | person | 0 | 1 | 0 | | | | Fused in Pipeline 0 | | | +------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX person:Person(place, firstname) WHERE point.withinBBox(place, point($autodouble_0, $auto | 1 | 1 | 2 | 112 | 6/0 | 1.065 | Fused in Pipeline 0 | -| | double_1), point($autodouble_2, $autodouble_3)) AND firstname IS NOT NULL | | | | | | | | +| +NodeIndexSeek | person:Person(place, firstname) WHERE place > point({x: $autoint_0, y: $autoint_1}) AND place < poin | 0 | 1 | 2 | 72 | 8/0 | 1.554 | Fused in Pipeline 0 | +| | t({x: $autoint_2, y: $autoint_3}) AND firstname IS NOT NULL | | | | | | | | +-----------------+------------------------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 2, total allocated memory: 176 ----- +Total database accesses: 2, total allocated memory: 136 +---- diff --git a/modules/ROOT/pages/query-tuning/query-options.adoc b/modules/ROOT/pages/query-tuning/query-options.adoc index 7befed65a..1ce5addbb 100644 --- a/modules/ROOT/pages/query-tuning/query-options.adoc +++ b/modules/ROOT/pages/query-tuning/query-options.adoc @@ -1,27 +1,70 @@ -:description: Query options available in Cypher. +[[query-tuning]] += Query tuning +:description: This section describes query tuning for the Cypher query language. This section describes the query options available in Cypher. + +//Check Mark +:check-mark: icon:check[] + +//Cross Mark +:cross-mark: icon:times[] + +Neo4j aims to execute queries as fast as possible. + +However, when optimizing for maximum query execution performance, it may be helpful to rephrase queries using knowledge about the domain and the application. + +The overall goal of manual query performance optimization is to ensure that only necessary data is retrieved from the graph. +At the very least, data should get filtered out as early as possible in order to reduce the amount of work that has to be done in the later stages of query execution. +This also applies to what gets returned: returning whole nodes and relationships ought to be avoided in favour of selecting and returning only the data that is needed. +You should also make sure to set an upper limit on variable length patterns, so they don't cover larger portions of the dataset than needed. + +Each Cypher query gets optimized and transformed into an xref:execution-plans/index.adoc#execution-plan-introduction[execution plan] by the Cypher query planner. +To minimize the resources used for this, try to use parameters instead of literals when possible. +This allows Cypher to re-use your queries instead of having to parse and build new execution plans. + +To read more about the execution plan operators mentioned in this chapter, see xref:execution-plans/index.adoc[]. + +* xref:query-tuning/query-options.adoc#cypher-query-options[Cypher query options] +** xref:query-tuning/index.adoc#cypher-version[Cypher version] +** xref:query-tuning/index.adoc#cypher-runtime[Cypher runtime] +** xref:query-tuning/index.adoc#cypher-connect-components-planner[Cypher connect-components planner] +** xref:query-tuning/index.adoc#cypher-update-strategy[Cypher update strategy] +** xref:query-tuning/index.adoc#cypher-expression-engine[Cypher expression engine] +** xref:query-tuning/index.adoc#cypher-operator-engine[Cypher operator engine] +** xref:query-tuning/index.adoc#cypher-interpreted-pipes-fallback[Cypher interpreted pipes fallback] +** xref:query-tuning/index.adoc#cypher-replanning[Cypher replanning] +* xref:query-tuning/how-do-i-profile-a-query.adoc#how-do-i-profile-a-query[Profiling a query] +* xref:query-tuning/indexes.adoc[The use of indexes] +* xref:query-tuning/basic-example.adoc[Basic query tuning example] +* xref:query-tuning/advanced-example.adoc[Advanced query tuning example] +** xref:query-tuning/advanced-example.adoc#advanced-query-tuning-example-data-set[The data set] +** xref:query-tuning/advanced-example.adoc#advanced-query-tuning-example-index-backed-property-lookup[Index-backed property-lookup] +** xref:query-tuning/advanced-example.adoc#advanced-query-tuning-example-index-backed-order-by[Index-backed order by] +* xref:query-tuning/using.adoc[Planner hints and the `USING` keyword] +** xref:query-tuning/using.adoc#query-using-introduction[Introduction] +** xref:query-tuning/using.adoc#query-using-index-hint[Index hints] +** xref:query-tuning/using.adoc#query-using-scan-hint[Scan hints] +** xref:query-tuning/using.adoc#query-using-join-hint[Join hints] +** xref:query-tuning/using.adoc#query-using-periodic-commit-hint[`PERIODIC COMMIT` query hint] -[[cypher-query-options]] -= Cypher query options -[abstract] --- -This section describes the query options available in Cypher. --- +[[cypher-query-options]] +== Cypher query options Query execution can be fine-tuned through the use of query options. In order to use one or more of these options, the query must be prepended with `CYPHER`, followed by the query option(s), as exemplified thus: `CYPHER query-option [further-query-options] query`. [[cypher-version]] -== Cypher version +=== Cypher version Occasionally, there is a requirement to use a previous version of the Cypher compiler when running a query. - Here we detail the available versions: [options="header",cols="1m,3a,^1a"] |=== -| Query option | Description | Default +| Query option +| Description +| Default | 3.5 | This will force the query to use Neo4j Cypher 3.5. @@ -32,30 +75,21 @@ Here we detail the available versions: | | 4.3 -| This will force the query to use Neo4j Cypher 4.3. -| - -| 4.4 -| -This will force the query to use Neo4j Cypher 4.4. -As this is the default version, it is not necessary to use this option explicitly. - +| This will force the query to use Neo4j Cypher 4.3. As this is the default version, it is not necessary to use this option explicitly. | {check-mark} - |=== - [WARNING] ==== In Neo4j {neo4j-version}, the support for Cypher 3.5 is provided only at the parser level. The consequence is that some underlying features available in Neo4j 3.5 are no longer available and will result in runtime errors. -Please refer to the discussion in xref::deprecations-additions-removals-compatibility.adoc#cypher-compatibility[Cypher Compatibility] for more information on which features are affected. +Please refer to the discussion in xref:deprecations-additions-removals-compatibility.adoc#cypher-compatibility[Cypher Compatibility] for more information on which features are affected. ==== [[cypher-runtime]] -== Cypher runtime +=== Cypher runtime Using the execution plan, the query is executed -- and records returned -- by the Cypher _runtime_. Depending on whether Neo4j Enterprise Edition or Neo4j Community Edition is used, there are three different runtimes available: @@ -78,25 +112,23 @@ While this should lead to superior performance in most cases (over both the inte [options="header",cols="2m,2a,^1a"] |=== -| Option | Description | Default +|Option +|Description +|Default -| runtime=interpreted -| This will force the query planner to use the interpreted runtime. -| -This is not used in Enterprise Edition unless explicitly asked for. +|runtime=interpreted +|This will force the query planner to use the interpreted runtime. +|This is not used in Enterprise Edition unless explicitly asked for. It is the only option for all queries in Community Edition--it is not necessary to specify this option in Community Edition. -| [enterprise-edition]#runtime=slotted# -| This will cause the query planner to use the slotted runtime. -| This is the default option for all queries which are not supported by `runtime=pipelined` in Enterprise Edition. +|[enterprise-edition]#runtime=slotted# +|This will cause the query planner to use the slotted runtime. +|This is the default option for all queries which are not supported by `runtime=pipelined` in Enterprise Edition. -| [enterprise-edition]#runtime=pipelined# -| -This will cause the query planner to use the pipelined runtime if it supports the query. +|[enterprise-edition]#runtime=pipelined# +|This will cause the query planner to use the pipelined runtime if it supports the query. If the pipelined runtime does not support the query, the planner will fall back to the slotted runtime. - -| This is the default option for some queries in Enterprise Edition. - +|This is the default option for some queries in Enterprise Edition. |=== In Enterprise Edition, the Cypher query planner selects the runtime, falling back to alternative runtimes as follows: @@ -108,8 +140,7 @@ In Enterprise Edition, the Cypher query planner selects the runtime, falling bac [[cypher-planner]] -== Cypher planner - +=== Cypher planner The Cypher planner takes a Cypher query and computes an execution plan that solves it. For any given query there is likely a number of execution plan candidates that each solve the query in a different way. The planner uses a search algorithm to find the execution plan with the lowest estimated execution cost. @@ -118,7 +149,9 @@ This table describes the available planner options: [options="header",cols="2m,3a,^1a"] |=== -| Query option | Description | Default +| Query option +| Description +| Default | planner=cost | Use cost based planning with default limits on plan search space and time. @@ -129,64 +162,56 @@ This table describes the available planner options: | | planner=dp -| -Use cost based planning without limits on plan search space and time to perform an exhaustive search for the best execution plan. - +| Use cost based planning without limits on plan search space and time to perform an exhaustive search for the best execution plan. [NOTE] ==== Using this option can significantly _increase_ the planning time of the query. ==== - | - |=== [[cypher-connect-components-planner]] -== Cypher connect-components planner - +=== Cypher connect-components planner One part of the Cypher planner is responsible for combining sub-plans for separate patterns into larger plans - a task referred to as _connecting components_. This table describes the available query options for the connect-components planner: [options="header",cols="2m,3a,^1a"] |=== -| Query option | Description | Default +| Query option +| Description +| Default | connectComponentsPlanner=greedy -| -Use a greedy approach when combining sub-plans. - +| Use a greedy approach when combining sub-plans. [NOTE] ==== Using this option can significantly _reduce_ the planning time of the query. ==== - -| +| | connectComponentsPlanner=idp -| -Use the cost based IDP search algorithm when combining sub-plans. - +| Use the cost based IDP search algorithm when combining sub-plans. [NOTE] ==== Using this option can significantly _increase_ the planning time of the query but usually finds better plans. ==== - | {check-mark} - |=== [[cypher-update-strategy]] -== Cypher update strategy +=== Cypher update strategy This option affects the eagerness of updating queries. The possible values are: [options="header",cols="2m,3a,^1a"] |=== -| Query option | Description | Default +| Query option +| Description +| Default | updateStrategy=default | Update queries are executed eagerly when needed. @@ -195,20 +220,20 @@ The possible values are: | updateStrategy=eager | Update queries are always executed eagerly. | - |=== [[cypher-expression-engine]] -== Cypher expression engine - +=== Cypher expression engine This option affects how the runtime evaluates expressions. The possible values are: [options="header",cols="2m,3a,^1a"] |=== -| Query option | Description | Default +| Query option +| Description +| Default | expressionEngine=default | Compile expressions and use the compiled expression engine when needed. @@ -219,26 +244,24 @@ The possible values are: | | expressionEngine=compiled -| -Always compile expressions and use the _compiled_ expression engine. +| Always compile expressions and use the _compiled_ expression engine. Cannot be used together with `runtime=interpreted`. - | - |=== [[cypher-operator-engine]] -== Cypher operator engine - +=== Cypher operator engine This query option affects whether the pipelined runtime attempts to generate compiled code for groups of operators. The possible values are: [options="header",cols="2m,3a,^1a"] |=== -| Query option | Description | Default +| Query option +| Description +| Default | operatorEngine=default | Attempt to generate compiled operators when applicable. @@ -249,68 +272,58 @@ The possible values are: | | operatorEngine=compiled -| -Always attempt to generate _compiled_ operators. +| Always attempt to generate _compiled_ operators. Cannot be used together with `runtime=interpreted` or `runtime=slotted`. - | - |=== [[cypher-interpreted-pipes-fallback]] -== Cypher interpreted pipes fallback - +=== Cypher interpreted pipes fallback This query option affects how the pipelined runtime behaves for operators it does not directly support. The available options are: [options="header",cols="2m,3a,^1a"] |=== -| Query option | Description | Default +| Query option +| Description +| Default | interpretedPipesFallback=default -| Equivalent to `interpretedPipesFallback=whitelisted_plans_only`. +| Equivalent to `interpretedPipesFallback=whitelisted_plans_only` | {check-mark} | interpretedPipesFallback=disabled -| -If the plan contains any operators not supported by the pipelined runtime then another runtime is chosen to execute the entire plan. - -Cannot be used together with `runtime=interpreted` or `runtime=slotted`. +| If the plan contains any operators not supported by the pipelined runtime then another runtime is chosen to execute the entire plan. +Cannot be used together with `runtime=interpreted` or `runtime=slotted` | | interpretedPipesFallback=whitelisted_plans_only -| -Parts of the execution plan can be executed on another runtime. +| Parts of the execution plan can be executed on another runtime. Only certain operators are allowed to execute on another runtime. Cannot be used together with `runtime=interpreted` or `runtime=slotted`. - | | interpretedPipesFallback=all -| -Parts of the execution plan may be executed on another runtime. +| Parts of the execution plan may be executed on another runtime. Any operator is allowed to execute on another runtime. Queries with this option set might produce incorrect results, or fail. Cannot be used together with `runtime=interpreted` or `runtime=slotted`. [WARNING] -==== This setting is experimental, and using it in a production environment is discouraged. -==== | - |=== [[cypher-replanning]] -== Cypher replanning +=== Cypher replanning Cypher replanning occurs in the following circumstances: @@ -318,41 +331,39 @@ Cypher replanning occurs in the following circumstances: This can either be when the server is first started or restarted, if the cache has recently been cleared, or if link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_dbms.query_cache_size[dbms.query_cache_size] was exceeded. * When the time has past the link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_cypher.min_replan_interval[cypher.min_replan_interval] value, and the database statistics have changed more than the link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_cypher.statistics_divergence_threshold[cypher.statistics_divergence_threshold] value. -There may be situations where xref::execution-plans/index.adoc[Cypher query planning] can occur at a non-ideal time. +There may be situations where xref:execution-plans/index.adoc[Cypher query planning] can occur at a non-ideal time. For example, when a query must be as fast as possible and a valid plan is already in place. [NOTE] -==== Replanning is not performed for all queries at once; it is performed in the same thread as running the query, and can block the query. However, replanning one query does not replan any other queries. -==== There are three different replan options available: [options="header",cols="2m,3a,^1a"] |=== -| Option | Description | Default +|Option +|Description +|Default -| replan=default -| This is the planning and replanning option as described above. +|replan=default +|This is the planning and replanning option as described above. | {check-mark} -| replan=force -| This will force a replan, even if the plan is valid according to the planning rules. +|replan=force +|This will force a replan, even if the plan is valid according to the planning rules. Once the new plan is complete, it replaces the existing one in the query cache. | -| replan=skip -| If a valid plan already exists, it will be used even if the planning rules would normally dictate that it should be replanned. +|replan=skip +|If a valid plan already exists, it will be used even if the planning rules would normally dictate that it should be replanned. | - |=== The replan option is prepended to queries. - For example: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- CYPHER replan=force MATCH ... ---- @@ -360,13 +371,44 @@ CYPHER replan=force MATCH ... In a mixed workload, you can force replanning by using the Cypher `EXPLAIN` commands. This can be useful to schedule replanning of queries which are expensive to plan, at known times of low load. Using `EXPLAIN` will make sure the query is only planned, but not executed. - For example: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- CYPHER replan=force EXPLAIN MATCH ... ---- During times of known high load, `replan=skip` can be useful to not introduce unwanted latency spikes. + +[[how-do-i-profile-a-query]] +== Profiling a query + +There are two options to choose from when you want to analyze a query by looking at its execution plan: + +`EXPLAIN`:: +If you want to see the execution plan but not run the statement, prepend your Cypher statement with `EXPLAIN`. +The statement will always return an empty result and make no changes to the database. + +`PROFILE`:: +If you want to run the statement and see which operators are doing most of the work, use `PROFILE`. +This will run your statement and keep track of how many rows pass through each operator, and how much each operator needs to interact with the storage layer to retrieve the necessary data. +Note that _profiling your query uses more resources,_ so you should not profile unless you are actively working on a query. + +See xref:execution-plans/index.adoc[] for a detailed explanation of each of the operators contained in an execution plan. + +[TIP] +==== +Being explicit about what types and labels you expect relationships and nodes to have in your query helps Neo4j use the best possible statistical information, which leads to better execution plans. +This means that when you know that a relationship can only be of a certain type, you should add that to the query. +The same goes for labels, where declaring labels on both the start and end nodes of a relationship helps Neo4j find the best way to execute the statement. +==== + +//cypher/cypher-docs/src/docs/dev/query-tuning-indexes.asciidoc + +//cypher/cypher-docs/src/docs/dev/basic-query-tuning-example.asciidoc + +//cypher/cypher-docs/src/docs/dev/advanced-query-tuning-example.asciidoc + +//cypher/cypher-docs/src/test/scala/org/neo4j/cypher/docgen/UsingTest.scala +//generates: cypher/cypher-docs/target/docs/dev/ql/query-using.adoc diff --git a/modules/ROOT/pages/query-tuning/query-profile.adoc b/modules/ROOT/pages/query-tuning/query-profile.adoc deleted file mode 100644 index 5dbf48eb4..000000000 --- a/modules/ROOT/pages/query-tuning/query-profile.adoc +++ /dev/null @@ -1,25 +0,0 @@ -:description: Profile a query; analyze a query by looking at its execution plan. - -[[profile-a-query]] -= Profile a query - -There are two options to choose from when you want to analyze a query by looking at its execution plan: - -`EXPLAIN`:: -If you want to see the execution plan but not run the statement, prepend your Cypher statement with `EXPLAIN`. -The statement will always return an empty result and make no changes to the database. - -`PROFILE`:: -If you want to run the statement and see which operators are doing most of the work, use `PROFILE`. -This will run your statement and keep track of how many rows pass through each operator, and how much each operator needs to interact with the storage layer to retrieve the necessary data. -Note that _profiling your query uses more resources,_ so you should not profile unless you are actively working on a query. - -See xref::execution-plans/index.adoc[] for a detailed explanation of each of the operators contained in an execution plan. - -[TIP] -==== -Being explicit about what types and labels you expect relationships and nodes to have in your query helps Neo4j use the best possible statistical information, which leads to better execution plans. -This means that when you know that a relationship can only be of a certain type, you should add that to the query. -The same goes for labels, where declaring labels on both the start and end nodes of a relationship helps Neo4j find the best way to execute the statement. -==== - diff --git a/modules/ROOT/pages/query-tuning/using.adoc b/modules/ROOT/pages/query-tuning/using.adoc index c1ee73977..ab9481aa8 100644 --- a/modules/ROOT/pages/query-tuning/using.adoc +++ b/modules/ROOT/pages/query-tuning/using.adoc @@ -1,19 +1,23 @@ -:description: A planner hint is used to influence the decisions of the planner when building an execution plan for a query. - [[query-using]] = Planner hints and the USING keyword - -[abstract] --- -A planner hint is used to influence the decisions of the planner when building an execution plan for a query. -Planner hints are specified in a query with the `USING` keyword. --- +:description: A planner hint is used to influence the decisions of the planner when building an execution plan for a query. Planner hints are specified in a query with the `USING` keyword. [CAUTION] ==== Forcing planner behavior is an advanced feature, and should be used with caution by experienced developers and/or database administrators only, as it may cause queries to perform poorly. + + ==== +* xref:query-tuning/using.adoc#query-using-introduction[Introduction] +* xref:query-tuning/using.adoc#query-using-index-hint[Index hints] +* xref:query-tuning/using.adoc#query-using-scan-hint[Scan hints] +* xref:query-tuning/using.adoc#query-using-join-hint[Join hints] +* xref:query-tuning/using.adoc#query-using-periodic-commit-hint[`PERIODIC COMMIT` query hint] + +[[query-using-introduction]] +== Introduction + When executing a query, Neo4j needs to decide where in the query graph to start matching. This is done by looking at the `MATCH` clause and the `WHERE` conditions and using that information to find useful indexes, or other starting points. @@ -21,46 +25,14 @@ However, the selected index might not always be the best choice. Sometimes multiple indexes are possible candidates, and the query planner picks the suboptimal one from a performance point of view. Moreover, in some circumstances (albeit rarely) it is better not to use an index at all. -Neo4j can be forced to use a specific starting point through the `USING` keyword. -This is called giving a planner hint. +Neo4j can be forced to use a specific starting point through the `USING` keyword. This is called giving a planner hint. +There are four types of planner hints: index hints, scan hints, join hints, and the `PERIODIC COMMIT` query hint. -There are four types of planner hints: - -* Index hints. -* Scan hints. -* Join hints. -* `PERIODIC COMMIT` query hint. label:deprecated[] - -//// -FOREACH(i IN range(1, 100) | - CREATE (:Scientist {born: 1800 + i})-[:RESEARCHED]-> - (:Science)<-[:INVENTED_BY {year: 530 + (i % 50), location: 'Location' + i}]- - (:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]-> - (:City)-[:PART_OF]-> - (:Country {formed: 400 + i, name:'Country' + i}) -) - -CREATE INDEX FOR (s:Scientist) ON (s.born) -CREATE INDEX FOR (p:Pioneer) ON (p.born) -CREATE INDEX FOR (c:Country) ON (c.formed) -CREATE INDEX FOR (c:Country) ON (c.name) -CREATE TEXT INDEX FOR (c:Country) ON (c.name) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.year) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CREATE TEXT INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CALL db.awaitIndexes -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- -MATCH - (s:Scientist {born: 1850})-[:RESEARCHED]-> - (sc:Science)<-[i:INVENTED_BY {year: 560}]- - (p:Pioneer {born: 525})-[:LIVES_IN]-> - (c:City)-[:PART_OF]-> - (cc:Country {formed: 411}) -RETURN * +MATCH (s:Scientist {born: 1850})-[:RESEARCHED]->(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) RETURN * ---- The query above will be used in some of the examples on this page. @@ -69,119 +41,89 @@ Without any hints, one index and no join is used. .Query plan [source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | c, cc, i, p, s, sc | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | s.born = $autoint_0 AND s:Scientist | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (sc)<-[anon_0:RESEARCHED]-(s) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | i.year = $autoint_1 AND sc:Science | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (p)-[i:INVENTED_BY]->(sc) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | p.born = $autoint_2 AND p:Pioneer | 0 | 0 | 2 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (c)<-[anon_1:LIVES_IN]-(p) | 1 | 1 | 3 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | c:City | 1 | 1 | 1 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (cc)<-[anon_2:PART_OF]-(c) | 1 | 1 | 2 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX cc:Country(formed) WHERE formed = $autoint_3 | 1 | 1 | 2 | 112 | 6/1 | 0.919 | Fused in Pipeline 0 | -+-----------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 10, total allocated memory: 200 ----- +Runtime version 4.3 + ++-----------------+----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | c, cc, i, p, s, sc | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | s.born = $autoint_0 AND s:Scientist | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (sc)<-[anon_0:RESEARCHED]-(s) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | i.year = $autoint_1 AND sc:Science | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (p)-[i:INVENTED_BY]->(sc) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | p.born = $autoint_2 AND p:Pioneer | 0 | 0 | 2 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (c)<-[anon_1:LIVES_IN]-(p) | 1 | 1 | 3 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | c:City | 1 | 1 | 1 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (cc)<-[anon_2:PART_OF]-(c) | 1 | 1 | 2 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeek | cc:Country(formed) WHERE formed = $autoint_3 | 1 | 1 | 2 | 72 | 6/1 | 0.718 | Fused in Pipeline 0 | ++-----------------+----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ + +Total database accesses: 10, total allocated memory: 160 + +---- + +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(:Science)<-[:INVENTED_BY {year: 530 + (i % 50)}]-(:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]->(:City)-[:PART_OF]->(:Country {formed: 400 + i}) +) + +CREATE INDEX FOR (s:Scientist) ON (s.born) +CREATE INDEX FOR (p:Pioneer) ON (p.born) +CREATE INDEX FOR (c:Country) ON (c.formed) +CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.year) +CALL db.awaitIndexes +]]>(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) RETURN * +]]> +++++ +endif::nonhtmloutput[] [[query-using-index-hint]] == Index hints -Index hints are used to specify which index the planner should use as a starting point. +Index hints are used to specify which index, the planner should use as a starting point. This can be beneficial in cases where the index statistics are not accurate for the specific values that the query at hand is known to use, which would result in the planner picking a non-optimal index. -An index hint is supplied after an applicable `MATCH` clause. - -Available index hints are: - -[options="header"] -|=== -| Hint | Fulfilled by plans - -| `USING [BTREE \| TEXT] INDEX variable:Label(property)` -| `NodeIndexScan`, `NodeIndexSeek` +To supply an index hint, use `USING INDEX variable:Label(property)` or `USING INDEX SEEK variable:Label(property)` after the applicable `MATCH` clause for node indexes, +and `USING INDEX variable:RELATIONSHIP_TYPE(property)` or `USING INDEX SEEK variable:RELATIONSHIP_TYPE(property)` for relationship indexes. -| `USING [BTREE \| TEXT] INDEX SEEK variable:Label(property)` -| `NodeIndexSeek` - -| `USING [BTREE \| TEXT] INDEX variable:RELATIONSHIP_TYPE(property)` -| `DirectedRelationshipIndexScan`, `UndirectedRelationshipIndexScan`, `DirectedRelationshipIndexSeek`, `UndirectedRelationshipIndexSeek` - -| `USING [BTREE \| TEXT] INDEX SEEK variable:RELATIONSHIP_TYPE(property)` -| `DirectedRelationshipIndexSeek`, `UndirectedRelationshipIndexSeek` - -|=== - - -When specifying an index type for a hint, e.g. `BTREE` or `TEXT`, the hint can only be fulfilled when an index of the specified type is available. -When no index type is specified, the hint can be fulfilled by any index types. - - -[CAUTION] -==== -Using a hint must never change the result of a query. -Therefore, a hint with a specified index type is only fulfillable when the planner knows that using an index of the specified type does not change the results. -Please refer to xref::query-tuning/indexes.adoc[The use of indexes] for more details. -==== +`USING INDEX` can be fulfilled by any of the following plans: +`NodeIndexScan`, `DirectedRelationshipIndexScan`, `UndirectedRelationshipIndexScan`, `NodeIndexSeek`, `DirectedRelationshipIndexSeek`, `UndirectedRelationshipIndexSeek`. +`USING INDEX SEEK` can only be fulfilled by `NodeIndexSeek`, `DirectedRelationshipIndexSeek` or `UndirectedRelationshipIndexSeek`. It is possible to supply several index hints, but keep in mind that several starting points will require the use of a potentially expensive join later in the query plan. - === Query using a node index hint The query above can be tuned to pick a different index as the starting point. -//// -FOREACH(i IN range(1, 100) | - CREATE (:Scientist {born: 1800 + i})-[:RESEARCHED]-> - (:Science)<-[:INVENTED_BY {year: 530 + (i % 50), location: 'Location' + i}]- - (:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]-> - (:City)-[:PART_OF]-> - (:Country {formed: 400 + i, name:'Country' + i}) -) - -CREATE INDEX FOR (s:Scientist) ON (s.born) -CREATE INDEX FOR (p:Pioneer) ON (p.born) -CREATE INDEX FOR (c:Country) ON (c.formed) -CREATE INDEX FOR (c:Country) ON (c.name) -CREATE TEXT INDEX FOR (c:Country) ON (c.name) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.year) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CREATE TEXT INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CALL db.awaitIndexes -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- -MATCH - (s:Scientist {born: 1850})-[:RESEARCHED]-> - (sc:Science)<-[i:INVENTED_BY {year: 560}]- - (p:Pioneer {born: 525})-[:LIVES_IN]-> - (c:City)-[:PART_OF]-> - (cc:Country {formed: 411}) +MATCH (s:Scientist {born: 1850})-[:RESEARCHED]->(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) USING INDEX p:Pioneer(born) RETURN * ---- @@ -189,131 +131,75 @@ RETURN * .Query plan [source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+-----------------+-----------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+-----------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | c, cc, i, p, s, sc | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | cc.formed = $autoint_3 AND cc:Country | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (c)-[anon_2:PART_OF]->(cc) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | c:City | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (p)-[anon_1:LIVES_IN]->(c) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | s.born = $autoint_0 AND s:Scientist | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (sc)<-[anon_0:RESEARCHED]-(s) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | i.year = $autoint_1 AND sc:Science | 0 | 0 | 2 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (p)-[i:INVENTED_BY]->(sc) | 2 | 2 | 6 | | | | Fused in Pipeline 0 | -| | +-----------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX p:Pioneer(born) WHERE born = $autoint_2 | 2 | 2 | 3 | 112 | 4/1 | 0.796 | Fused in Pipeline 0 | -+-----------------+-----------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 11, total allocated memory: 200 ----- - - -=== Query using a node text index hint - -The following query can be tuned to pick a text index. - -//// +Runtime version 4.3 + ++-----------------+-----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++-----------------+-----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | c, cc, i, p, s, sc | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | cc.formed = $autoint_3 AND cc:Country | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (c)-[anon_2:PART_OF]->(cc) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | c:City | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (p)-[anon_1:LIVES_IN]->(c) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | s.born = $autoint_0 AND s:Scientist | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (sc)<-[anon_0:RESEARCHED]-(s) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | i.year = $autoint_1 AND sc:Science | 0 | 0 | 2 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (p)-[i:INVENTED_BY]->(sc) | 2 | 2 | 6 | | | | Fused in Pipeline 0 | +| | +-----------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeek | p:Pioneer(born) WHERE born = $autoint_2 | 2 | 2 | 3 | 72 | 4/1 | 0.665 | Fused in Pipeline 0 | ++-----------------+-----------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ + +Total database accesses: 11, total allocated memory: 160 + +---- + +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + - (:Science)<-[:INVENTED_BY {year: 530 + (i % 50), location: 'Location' + i}]- - (:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]-> - (:City)-[:PART_OF]-> - (:Country {formed: 400 + i, name:'Country' + i}) + CREATE (:Scientist {born: 1800 + i})-[:RESEARCHED]->(:Science)<-[:INVENTED_BY {year: 530 + (i % 50)}]-(:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]->(:City)-[:PART_OF]->(:Country {formed: 400 + i}) ) CREATE INDEX FOR (s:Scientist) ON (s.born) CREATE INDEX FOR (p:Pioneer) ON (p.born) CREATE INDEX FOR (c:Country) ON (c.formed) -CREATE INDEX FOR (c:Country) ON (c.name) -CREATE TEXT INDEX FOR (c:Country) ON (c.name) CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.year) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CREATE TEXT INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) CALL db.awaitIndexes -//// -.Query -[source, cypher, indent=0] ----- -MATCH (c:Country) -USING TEXT INDEX c:Country(name) -WHERE c.name = 'Country7' +]]>(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) +USING INDEX p:Pioneer(born) RETURN * ----- - -.Query plan -[source] ----- -Compiler CYPHER 4.4 - -Planner COST - -Runtime PIPELINED - -Runtime version 4.4 - -+-----------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | c | 1 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | TEXT INDEX c:Country(name) WHERE name = $autostring_0 | 1 | 1 | 2 | 112 | 2/0 | 1.672 | Fused in Pipeline 0 | -+-----------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 2, total allocated memory: 176 ----- - +]]> +++++ +endif::nonhtmloutput[] === Query using a relationship index hint The query above can be tuned to pick a relationship index as the starting point. -//// -FOREACH(i IN range(1, 100) | - CREATE (:Scientist {born: 1800 + i})-[:RESEARCHED]-> - (:Science)<-[:INVENTED_BY {year: 530 + (i % 50), location: 'Location' + i}]- - (:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]-> - (:City)-[:PART_OF]-> - (:Country {formed: 400 + i, name:'Country' + i}) -) - -CREATE INDEX FOR (s:Scientist) ON (s.born) -CREATE INDEX FOR (p:Pioneer) ON (p.born) -CREATE INDEX FOR (c:Country) ON (c.formed) -CREATE INDEX FOR (c:Country) ON (c.name) -CREATE TEXT INDEX FOR (c:Country) ON (c.name) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.year) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CREATE TEXT INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CALL db.awaitIndexes -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- -MATCH - (s:Scientist {born: 1850})-[:RESEARCHED]-> - (sc:Science)<-[i:INVENTED_BY {year: 560}]- - (p:Pioneer {born: 525})-[:LIVES_IN]-> - (c:City)-[:PART_OF]-> - (cc:Country {formed: 411}) +MATCH (s:Scientist {born: 1850})-[:RESEARCHED]->(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) USING INDEX i:INVENTED_BY(year) RETURN * ---- @@ -321,95 +207,63 @@ RETURN * .Query plan [source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+--------------------------------+---------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+--------------------------------+---------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | c, cc, i, p, s, sc | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | cc.formed = $autoint_3 AND cc:Country | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (c)-[anon_2:PART_OF]->(cc) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | c:City | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (p)-[anon_1:LIVES_IN]->(c) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | s.born = $autoint_0 AND s:Scientist | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (sc)<-[anon_0:RESEARCHED]-(s) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | p.born = $autoint_2 AND sc:Science AND p:Pioneer | 0 | 0 | 4 | | | | Fused in Pipeline 0 | -| | +---------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexSeek | BTREE INDEX (p)-[i:INVENTED_BY(year)]->(sc) WHERE year = $autoint_1 | 2 | 2 | 5 | 112 | 5/1 | 0.745 | Fused in Pipeline 0 | -+--------------------------------+---------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 9, total allocated memory: 200 ----- - - -=== Query using a relationship text index hint - -The following query can be tuned to pick a text index. - -//// +Runtime version 4.3 + ++--------------------------------+---------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++--------------------------------+---------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | c, cc, i, p, s, sc | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +---------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | cc.formed = $autoint_3 AND cc:Country | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +---------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (c)-[anon_2:PART_OF]->(cc) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +---------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | c:City | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +---------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (p)-[anon_1:LIVES_IN]->(c) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +---------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | s.born = $autoint_0 AND s:Scientist | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +---------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (sc)<-[anon_0:RESEARCHED]-(s) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +---------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | p.born = $autoint_2 AND sc:Science AND p:Pioneer | 0 | 0 | 4 | | | | Fused in Pipeline 0 | +| | +---------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +DirectedRelationshipIndexSeek | (p)-[i:INVENTED_BY(year)]->(sc) WHERE year = $autoint_1 | 2 | 2 | 5 | 72 | 5/1 | 0.517 | Fused in Pipeline 0 | ++--------------------------------+---------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ + +Total database accesses: 9, total allocated memory: 160 + +---- + +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + - (:Science)<-[:INVENTED_BY {year: 530 + (i % 50), location: 'Location' + i}]- - (:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]-> - (:City)-[:PART_OF]-> - (:Country {formed: 400 + i, name:'Country' + i}) + CREATE (:Scientist {born: 1800 + i})-[:RESEARCHED]->(:Science)<-[:INVENTED_BY {year: 530 + (i % 50)}]-(:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]->(:City)-[:PART_OF]->(:Country {formed: 400 + i}) ) CREATE INDEX FOR (s:Scientist) ON (s.born) CREATE INDEX FOR (p:Pioneer) ON (p.born) CREATE INDEX FOR (c:Country) ON (c.formed) -CREATE INDEX FOR (c:Country) ON (c.name) -CREATE TEXT INDEX FOR (c:Country) ON (c.name) CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.year) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CREATE TEXT INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) CALL db.awaitIndexes -//// -.Query -[source, cypher, indent=0] ----- -MATCH ()-[i:INVENTED_BY]->() -USING TEXT INDEX i:INVENTED_BY(location) -WHERE i.location = 'Location7' +]]>(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) +USING INDEX i:INVENTED_BY(year) RETURN * ----- - -.Query plan -[source] ----- -Compiler CYPHER 4.4 - -Planner COST - -Runtime PIPELINED - -Runtime version 4.4 - -+--------------------------------+----------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+--------------------------------+----------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | i | 1 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipIndexSeek | TEXT INDEX (anon_0)-[i:INVENTED_BY(location)]->(anon_1) WHERE location = $autostring_0 | 1 | 1 | 3 | 112 | 3/0 | 2.155 | Fused in Pipeline 0 | -+--------------------------------+----------------------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 3, total allocated memory: 176 ----- - +]]> +++++ +endif::nonhtmloutput[] === Query using multiple index hints @@ -417,35 +271,11 @@ Supplying one index hint changed the starting point of the query, but the plan i only has one starting point. If we give the planner yet another index hint, we force it to use two starting points, one at each end of the match. It will then join these two branches using a join operator. -//// -FOREACH(i IN range(1, 100) | - CREATE (:Scientist {born: 1800 + i})-[:RESEARCHED]-> - (:Science)<-[:INVENTED_BY {year: 530 + (i % 50), location: 'Location' + i}]- - (:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]-> - (:City)-[:PART_OF]-> - (:Country {formed: 400 + i, name:'Country' + i}) -) - -CREATE INDEX FOR (s:Scientist) ON (s.born) -CREATE INDEX FOR (p:Pioneer) ON (p.born) -CREATE INDEX FOR (c:Country) ON (c.formed) -CREATE INDEX FOR (c:Country) ON (c.name) -CREATE TEXT INDEX FOR (c:Country) ON (c.name) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.year) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CREATE TEXT INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CALL db.awaitIndexes -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- -MATCH - (s:Scientist {born: 1850})-[:RESEARCHED]-> - (sc:Science)<-[i:INVENTED_BY {year: 560}]- - (p:Pioneer {born: 525})-[:LIVES_IN]-> - (c:City)-[:PART_OF]-> - (cc:Country {formed: 411}) +MATCH (s:Scientist {born: 1850})-[:RESEARCHED]->(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) USING INDEX s:Scientist(born) USING INDEX cc:Country(formed) RETURN * @@ -454,152 +284,85 @@ RETURN * .Query plan [source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+------------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+------------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | c, cc, i, p, s, sc | 0 | 0 | 0 | | 0/0 | 0.000 | In Pipeline 2 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +NodeHashJoin | sc | 0 | 0 | 0 | 432 | | | In Pipeline 2 | -| |\ +----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| | +Expand(All) | (s)-[anon_0:RESEARCHED]->(sc) | 1 | 0 | 0 | | | | Fused in Pipeline 1 | -| | | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +NodeIndexSeek | BTREE INDEX s:Scientist(born) WHERE born = $autoint_0 | 1 | 0 | 0 | 112 | 0/0 | 0.000 | Fused in Pipeline 1 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +Filter | i.year = $autoint_1 AND sc:Science | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (p)-[i:INVENTED_BY]->(sc) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | p.born = $autoint_2 AND p:Pioneer | 0 | 0 | 2 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (c)<-[anon_1:LIVES_IN]-(p) | 1 | 1 | 3 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Filter | c:City | 1 | 1 | 1 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Expand(All) | (cc)<-[anon_2:PART_OF]-(c) | 1 | 1 | 2 | | | | Fused in Pipeline 0 | -| | +----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX cc:Country(formed) WHERE formed = $autoint_3 | 1 | 1 | 2 | 112 | 7/0 | 1.289 | Fused in Pipeline 0 | -+------------------+----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 10, total allocated memory: 752 ----- - - -=== Query using multiple index hints with a disjunction - -Supplying multiple index hints can also be useful if the query contains a disjunction (`OR`) in the `WHERE` clause. -This makes sure that all hinted indexes are used and the results are joined together with a `Union` and a `Distinct` afterwards. - -//// +Runtime version 4.3 + ++------------------+----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++------------------+----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | c, cc, i, p, s, sc | 0 | 0 | 0 | | 0/0 | 0.000 | In Pipeline 2 | +| | +----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +NodeHashJoin | sc | 0 | 0 | 0 | 432 | | | In Pipeline 2 | +| |\ +----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| | +Expand(All) | (s)-[anon_0:RESEARCHED]->(sc) | 1 | 0 | 0 | | | | Fused in Pipeline 1 | +| | | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +NodeIndexSeek | s:Scientist(born) WHERE born = $autoint_0 | 1 | 0 | 0 | 72 | 0/0 | 0.000 | Fused in Pipeline 1 | +| | +----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +Filter | i.year = $autoint_1 AND sc:Science | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (p)-[i:INVENTED_BY]->(sc) | 0 | 0 | 0 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | p.born = $autoint_2 AND p:Pioneer | 0 | 0 | 2 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (c)<-[anon_1:LIVES_IN]-(p) | 1 | 1 | 3 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Filter | c:City | 1 | 1 | 1 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +Expand(All) | (cc)<-[anon_2:PART_OF]-(c) | 1 | 1 | 2 | | | | Fused in Pipeline 0 | +| | +----------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeek | cc:Country(formed) WHERE formed = $autoint_3 | 1 | 1 | 2 | 72 | 7/0 | 0.553 | Fused in Pipeline 0 | ++------------------+----------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ + +Total database accesses: 10, total allocated memory: 672 + +---- + +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + - (:Science)<-[:INVENTED_BY {year: 530 + (i % 50), location: 'Location' + i}]- - (:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]-> - (:City)-[:PART_OF]-> - (:Country {formed: 400 + i, name:'Country' + i}) + CREATE (:Scientist {born: 1800 + i})-[:RESEARCHED]->(:Science)<-[:INVENTED_BY {year: 530 + (i % 50)}]-(:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]->(:City)-[:PART_OF]->(:Country {formed: 400 + i}) ) CREATE INDEX FOR (s:Scientist) ON (s.born) CREATE INDEX FOR (p:Pioneer) ON (p.born) CREATE INDEX FOR (c:Country) ON (c.formed) -CREATE INDEX FOR (c:Country) ON (c.name) -CREATE TEXT INDEX FOR (c:Country) ON (c.name) CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.year) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CREATE TEXT INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) CALL db.awaitIndexes -//// -.Query -[source, cypher, indent=0] ----- -MATCH (country:Country) -USING INDEX country:Country(name) -USING INDEX country:Country(formed) -WHERE country.formed = 500 OR country.name STARTS WITH "A" +]]>(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) +USING INDEX s:Scientist(born) +USING INDEX cc:Country(formed) RETURN * ----- - -.Query plan -[source] ----- -Compiler CYPHER 4.4 - -Planner COST - -Runtime PIPELINED - -Runtime version 4.4 - -+-----------------------+------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+-----------------------+------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | country | 1 | 1 | 0 | | | | Fused in Pipeline 2 | -| | +------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Distinct | country | 1 | 1 | 0 | 224 | | | Fused in Pipeline 2 | -| | +------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +Union | | 2 | 1 | 0 | 1128 | 1/0 | 0.510 | Fused in Pipeline 2 | -| |\ +------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| | +NodeIndexSeek | BTREE INDEX country:Country(formed) WHERE formed = $autoint_0 | 1 | 1 | 2 | 112 | 1/0 | 0.268 | In Pipeline 1 | -| | +------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +NodeIndexSeekByRange | BTREE INDEX country:Country(name) WHERE name STARTS WITH $autostring_1 | 1 | 0 | 1 | 112 | 0/1 | 0.465 | In Pipeline 0 | -+-----------------------+------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 3, total allocated memory: 1208 ----- - -Cypher will usually provide a plan that uses all indexes for a disjunction without hints. -It may, however, decide to plan a `NodeByLabelScan` instead, if the predicates appear to be not very selective. -In this case, the index hints can be useful. - +]]> +++++ +endif::nonhtmloutput[] [[query-using-scan-hint]] == Scan hints If your query matches large parts of an index, it might be faster to scan the label or relationship type and filter out rows that do not match. -To do this, you can use `USING SCAN variable:Label` after the applicable `MATCH` clause for node indexes, and `USING SCAN variable:RELATIONSHIP_TYPE` for relationship indexes. +To do this, you can use `USING SCAN variable:Label` after the applicable `MATCH` clause for node indexes, +and `USING SCAN variable:RELATIONSHIP_TYPE` for relationship indexes. This will force Cypher to not use an index that could have been used, and instead do a label scan/relationship type scan. You can use the same hint to enforce a starting point where no index is applicable. - === Hinting a label scan -//// -FOREACH(i IN range(1, 100) | - CREATE (:Scientist {born: 1800 + i})-[:RESEARCHED]-> - (:Science)<-[:INVENTED_BY {year: 530 + (i % 50), location: 'Location' + i}]- - (:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]-> - (:City)-[:PART_OF]-> - (:Country {formed: 400 + i, name:'Country' + i}) -) - -CREATE INDEX FOR (s:Scientist) ON (s.born) -CREATE INDEX FOR (p:Pioneer) ON (p.born) -CREATE INDEX FOR (c:Country) ON (c.formed) -CREATE INDEX FOR (c:Country) ON (c.name) -CREATE TEXT INDEX FOR (c:Country) ON (c.name) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.year) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CREATE TEXT INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CALL db.awaitIndexes -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- -MATCH - (s:Scientist {born: 1850})-[:RESEARCHED]-> - (sc:Science)<-[i:INVENTED_BY {year: 560}]- - (p:Pioneer {born: 525})-[:LIVES_IN]-> - (c:City)-[:PART_OF]-> - (cc:Country {formed: 411}) +MATCH (s:Scientist {born: 1850})-[:RESEARCHED]->(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) USING SCAN s:Scientist RETURN * ---- @@ -607,13 +370,13 @@ RETURN * .Query plan [source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +------------------+-----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -638,45 +401,44 @@ Runtime version 4.4 | | +-----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | s.born = $autoint_0 | 1 | 1 | 200 | | | | Fused in Pipeline 0 | | | +-----------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeByLabelScan | s:Scientist | 100 | 100 | 101 | 112 | 11/0 | 1.225 | Fused in Pipeline 0 | +| +NodeByLabelScan | s:Scientist | 100 | 100 | 101 | 72 | 10/0 | 0.988 | Fused in Pipeline 0 | +------------------+-----------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 308, total allocated memory: 208 +Total database accesses: 308, total allocated memory: 168 ---- - -=== Hinting a relationship type scan - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + - (:Science)<-[:INVENTED_BY {year: 530 + (i % 50), location: 'Location' + i}]- - (:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]-> - (:City)-[:PART_OF]-> - (:Country {formed: 400 + i, name:'Country' + i}) + CREATE (:Scientist {born: 1800 + i})-[:RESEARCHED]->(:Science)<-[:INVENTED_BY {year: 530 + (i % 50)}]-(:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]->(:City)-[:PART_OF]->(:Country {formed: 400 + i}) ) CREATE INDEX FOR (s:Scientist) ON (s.born) CREATE INDEX FOR (p:Pioneer) ON (p.born) CREATE INDEX FOR (c:Country) ON (c.formed) -CREATE INDEX FOR (c:Country) ON (c.name) -CREATE TEXT INDEX FOR (c:Country) ON (c.name) CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.year) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CREATE TEXT INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) CALL db.awaitIndexes -//// + +]]>(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) +USING SCAN s:Scientist +RETURN * +]]> +++++ +endif::nonhtmloutput[] + +=== Hinting a relationship type scan + .Query -[source, cypher, indent=0] +[source, cypher] ---- -MATCH - (s:Scientist {born: 1850})-[:RESEARCHED]-> - (sc:Science)<-[i:INVENTED_BY {year: 560}]- - (p:Pioneer {born: 525})-[:LIVES_IN]-> - (c:City)-[:PART_OF]-> - (cc:Country {formed: 411}) +MATCH (s:Scientist {born: 1850})-[:RESEARCHED]->(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) USING SCAN i:INVENTED_BY RETURN * ---- @@ -684,13 +446,13 @@ RETURN * .Query plan [source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +-------------------------------+--------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -711,125 +473,58 @@ Runtime version 4.4 | | +--------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Filter | i.year = $autoint_1 AND p.born = $autoint_2 AND sc:Science AND p:Pioneer | 0 | 0 | 204 | | | | Fused in Pipeline 0 | | | +--------------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +DirectedRelationshipTypeScan | (p)-[i:INVENTED_BY]->(sc) | 100 | 100 | 201 | 112 | 9/0 | 4.182 | Fused in Pipeline 0 | +| +DirectedRelationshipTypeScan | (p)-[i:INVENTED_BY]->(sc) | 100 | 100 | 201 | 72 | 10/0 | 1.417 | Fused in Pipeline 0 | +-------------------------------+--------------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 405, total allocated memory: 200 ----- - - -=== Query using multiple scan hints with a disjunction +Total database accesses: 405, total allocated memory: 160 -Supplying multiple scan hints can also be useful if the query contains a disjunction (`OR`) in the `WHERE` clause. -This makes sure that all involved label predicates are solved by a `NodeByLabelScan` and the results are joined together with a `Union` and a `Distinct` afterwards. +---- -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + - (:Science)<-[:INVENTED_BY {year: 530 + (i % 50), location: 'Location' + i}]- - (:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]-> - (:City)-[:PART_OF]-> - (:Country {formed: 400 + i, name:'Country' + i}) + CREATE (:Scientist {born: 1800 + i})-[:RESEARCHED]->(:Science)<-[:INVENTED_BY {year: 530 + (i % 50)}]-(:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]->(:City)-[:PART_OF]->(:Country {formed: 400 + i}) ) CREATE INDEX FOR (s:Scientist) ON (s.born) CREATE INDEX FOR (p:Pioneer) ON (p.born) CREATE INDEX FOR (c:Country) ON (c.formed) -CREATE INDEX FOR (c:Country) ON (c.name) -CREATE TEXT INDEX FOR (c:Country) ON (c.name) CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.year) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CREATE TEXT INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) CALL db.awaitIndexes -//// -.Query -[source, cypher, indent=0] ----- -MATCH (person) -USING SCAN person:Pioneer -USING SCAN person:Scientist -WHERE person:Pioneer OR person:Scientist +]]>(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) +USING SCAN i:INVENTED_BY RETURN * ----- - -.Query plan -[source] ----- -Compiler CYPHER 4.4 - -Planner COST - -Runtime PIPELINED - -Runtime version 4.4 - -+--------------------+------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Ordered by | Other | -+--------------------+------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -| +ProduceResults | person | 180 | 200 | 0 | | 4/0 | 3.343 | person ASC | In Pipeline 2 | -| | +------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -| +OrderedDistinct | person | 180 | 200 | 0 | 32 | 0/0 | 1.718 | person ASC | In Pipeline 2 | -| | +------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -| +OrderedUnion | | 200 | 200 | 0 | 1128 | 0/0 | 1.198 | person ASC | In Pipeline 2 | -| |\ +------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -| | +NodeByLabelScan | person:Scientist | 100 | 100 | 101 | 112 | 1/0 | 0.387 | person ASC | In Pipeline 1 | -| | +------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ -| +NodeByLabelScan | person:Pioneer | 100 | 100 | 101 | 112 | 1/0 | 0.471 | person ASC | In Pipeline 0 | -+--------------------+------------------+----------------+------+---------+----------------+------------------------+-----------+------------+---------------+ - -Total database accesses: 202, total allocated memory: 1320 ----- - - -Cypher will usually provide a plan that uses scans for a disjunction without hints. -It may, however, decide to plan an `AllNodeScan` followed by a `Filter` instead, if the label predicates appear to be not very selective. -In this case, the scan hints can be useful. - +]]> +++++ +endif::nonhtmloutput[] [[query-using-join-hint]] == Join hints -Join hints are the most advanced type of hints, and are not used to find starting points for the query execution plan, but to enforce that joins are made at specified points. -This implies that there has to be more than one starting point (leaf) in the plan, in order for the query to be able to join the two branches ascending from these leaves. -Due to this nature, joins, and subsequently join hints, will force the planner to look for additional starting points, and in the case where there are no more good ones, potentially pick a very bad starting point. -This will negatively affect query performance. In other cases, the hint might force the planner to pick a _seemingly_ bad starting point, which in reality proves to be a very good one. - +Join hints are the most advanced type of hints, and are not used to find starting points for the +query execution plan, but to enforce that joins are made at specified points. This implies that there +has to be more than one starting point (leaf) in the plan, in order for the query to be able to join the two branches ascending +from these leaves. Due to this nature, joins, and subsequently join hints, will force +the planner to look for additional starting points, and in the case where there are no more good ones, +potentially pick a very bad starting point. This will negatively affect query performance. In other cases, +the hint might force the planner to pick a _seemingly_ bad starting point, which in reality proves to be a very good one. === Hinting a join on a single node In the example above using multiple index hints, we saw that the planner chose to do a join, but not on the `p` node. By supplying a join hint in addition to the index hints, we can enforce the join to happen on the `p` node. -//// -FOREACH(i IN range(1, 100) | - CREATE (:Scientist {born: 1800 + i})-[:RESEARCHED]-> - (:Science)<-[:INVENTED_BY {year: 530 + (i % 50), location: 'Location' + i}]- - (:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]-> - (:City)-[:PART_OF]-> - (:Country {formed: 400 + i, name:'Country' + i}) -) - -CREATE INDEX FOR (s:Scientist) ON (s.born) -CREATE INDEX FOR (p:Pioneer) ON (p.born) -CREATE INDEX FOR (c:Country) ON (c.formed) -CREATE INDEX FOR (c:Country) ON (c.name) -CREATE TEXT INDEX FOR (c:Country) ON (c.name) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.year) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CREATE TEXT INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CALL db.awaitIndexes -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- -MATCH - (s:Scientist {born: 1850})-[:RESEARCHED]-> - (sc:Science)<-[i:INVENTED_BY {year: 560}]- - (p:Pioneer {born: 525})-[:LIVES_IN]-> - (c:City)-[:PART_OF]-> - (cc:Country {formed: 411}) +MATCH (s:Scientist {born: 1850})-[:RESEARCHED]->(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) USING INDEX s:Scientist(born) USING INDEX cc:Country(formed) USING JOIN ON p @@ -839,13 +534,13 @@ RETURN * .Query plan [source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 +------------------+------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | @@ -862,7 +557,7 @@ Runtime version 4.4 | | | +------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | | +Expand(All) | (cc)<-[anon_2:PART_OF]-(c) | 1 | 0 | 0 | | | | Fused in Pipeline 1 | | | | +------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +NodeIndexSeek | BTREE INDEX cc:Country(formed) WHERE formed = $autoint_3 | 1 | 0 | 0 | 112 | 0/0 | 0.000 | Fused in Pipeline 1 | +| | +NodeIndexSeek | cc:Country(formed) WHERE formed = $autoint_3 | 1 | 0 | 0 | 72 | 0/0 | 0.000 | Fused in Pipeline 1 | | | +------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ | +Filter | i.year = $autoint_1 AND cache[p.born] = $autoint_2 AND p:Pioneer | 0 | 0 | 1 | | | | Fused in Pipeline 0 | | | +------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ @@ -872,40 +567,47 @@ Runtime version 4.4 | | +------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ | +Expand(All) | (s)-[anon_0:RESEARCHED]->(sc) | 1 | 1 | 2 | | | | Fused in Pipeline 0 | | | +------------------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX s:Scientist(born) WHERE born = $autoint_0 | 1 | 1 | 2 | 112 | 6/1 | 1.493 | Fused in Pipeline 0 | +| +NodeIndexSeek | s:Scientist(born) WHERE born = $autoint_0 | 1 | 1 | 2 | 72 | 6/1 | 0.753 | Fused in Pipeline 0 | +------------------+------------------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 9, total allocated memory: 752 ----- - +Total database accesses: 9, total allocated memory: 672 -=== Hinting a join for an OPTIONAL MATCH - -A join hint can also be used to force the planner to pick a `NodeLeftOuterHashJoin` or `NodeRightOuterHashJoin` to solve an `OPTIONAL MATCH`. -In most cases, the planner will rather use an `OptionalExpand`. +---- -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + - (:Science)<-[:INVENTED_BY {year: 530 + (i % 50), location: 'Location' + i}]- - (:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]-> - (:City)-[:PART_OF]-> - (:Country {formed: 400 + i, name:'Country' + i}) + CREATE (:Scientist {born: 1800 + i})-[:RESEARCHED]->(:Science)<-[:INVENTED_BY {year: 530 + (i % 50)}]-(:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]->(:City)-[:PART_OF]->(:Country {formed: 400 + i}) ) CREATE INDEX FOR (s:Scientist) ON (s.born) CREATE INDEX FOR (p:Pioneer) ON (p.born) CREATE INDEX FOR (c:Country) ON (c.formed) -CREATE INDEX FOR (c:Country) ON (c.name) -CREATE TEXT INDEX FOR (c:Country) ON (c.name) CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.year) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CREATE TEXT INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) CALL db.awaitIndexes -//// + +]]>(sc:Science)<-[i:INVENTED_BY {year: 560}]-(p:Pioneer {born: 525})-[:LIVES_IN]->(c:City)-[:PART_OF]->(cc:Country {formed: 411}) +USING INDEX s:Scientist(born) +USING INDEX cc:Country(formed) +USING JOIN ON p +RETURN * +]]> +++++ +endif::nonhtmloutput[] + +=== Hinting a join for an OPTIONAL MATCH + +A join hint can also be used to force the planner to pick a `NodeLeftOuterHashJoin` or `NodeRightOuterHashJoin` to solve an `OPTIONAL MATCH`. +In most cases, the planner will rather use an `OptionalExpand`. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (s:Scientist {born: 1850}) OPTIONAL MATCH (s)-[:RESEARCHED]->(sc:Science) @@ -917,50 +619,55 @@ Without any hint, the planner did not use a join to solve the `OPTIONAL MATCH`. .Query plan [source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 +Runtime version 4.3 -+----------------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+----------------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | s, sc | 1 | 1 | 0 | | | | Fused in Pipeline 0 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +OptionalExpand(All) | (s)-[anon_0:RESEARCHED]->(sc) WHERE sc:Science | 1 | 1 | 3 | | | | Fused in Pipeline 0 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| +NodeIndexSeek | BTREE INDEX s:Scientist(born) WHERE born = $autoint_0 | 1 | 1 | 2 | 112 | 6/0 | 0.865 | Fused in Pipeline 0 | -+----------------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ ++----------------------+------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++----------------------+------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | s, sc | 1 | 1 | 0 | | | | Fused in Pipeline 0 | +| | +------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +OptionalExpand(All) | (s)-[anon_0:RESEARCHED]->(sc) WHERE sc:Science | 1 | 1 | 3 | | | | Fused in Pipeline 0 | +| | +------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| +NodeIndexSeek | s:Scientist(born) WHERE born = $autoint_0 | 1 | 1 | 2 | 72 | 6/0 | 0.630 | Fused in Pipeline 0 | ++----------------------+------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -Total database accesses: 5, total allocated memory: 176 ----- +Total database accesses: 5, total allocated memory: 136 +---- -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + - (:Science)<-[:INVENTED_BY {year: 530 + (i % 50), location: 'Location' + i}]- - (:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]-> - (:City)-[:PART_OF]-> - (:Country {formed: 400 + i, name:'Country' + i}) + CREATE (:Scientist {born: 1800 + i})-[:RESEARCHED]->(:Science)<-[:INVENTED_BY {year: 530 + (i % 50)}]-(:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]->(:City)-[:PART_OF]->(:Country {formed: 400 + i}) ) CREATE INDEX FOR (s:Scientist) ON (s.born) CREATE INDEX FOR (p:Pioneer) ON (p.born) CREATE INDEX FOR (c:Country) ON (c.formed) -CREATE INDEX FOR (c:Country) ON (c.name) -CREATE TEXT INDEX FOR (c:Country) ON (c.name) CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.year) -CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) -CREATE TEXT INDEX FOR ()-[i:INVENTED_BY]-() ON (i.location) CALL db.awaitIndexes -//// + +]]>(sc:Science) +RETURN * +]]> +++++ +endif::nonhtmloutput[] + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (s:Scientist {born: 1850}) OPTIONAL MATCH (s)-[:RESEARCHED]->(sc:Science) @@ -973,40 +680,61 @@ Now the planner uses a join to solve the `OPTIONAL MATCH`. .Query plan [source] ---- -Compiler CYPHER 4.4 +Compiler CYPHER 4.3 Planner COST Runtime PIPELINED -Runtime version 4.4 - -+------------------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | -+------------------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +ProduceResults | s, sc | 1 | 1 | 0 | | 2/0 | 0.224 | In Pipeline 2 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +NodeLeftOuterHashJoin | s | 1 | 1 | 0 | 4864 | | 11.193 | In Pipeline 2 | -| |\ +-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| | +Expand(All) | (sc)<-[anon_0:RESEARCHED]-(s) | 100 | 100 | 300 | | | | Fused in Pipeline 1 | -| | | +-------------------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ -| | +NodeByLabelScan | sc:Science | 100 | 100 | 101 | 112 | 4/0 | 3.182 | Fused in Pipeline 1 | -| | +-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ -| +NodeIndexSeek | BTREE INDEX s:Scientist(born) WHERE born = $autoint_0 | 1 | 1 | 2 | 112 | 1/0 | 0.569 | In Pipeline 0 | -+------------------------+-------------------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ - -Total database accesses: 403, total allocated memory: 4944 +Runtime version 4.3 + ++------------------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| Operator | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Other | ++------------------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +ProduceResults | s, sc | 1 | 1 | 0 | | 2/0 | 0.123 | In Pipeline 2 | +| | +-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +NodeLeftOuterHashJoin | s | 1 | 1 | 0 | 3096 | | 7.145 | In Pipeline 2 | +| |\ +-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| | +Expand(All) | (sc)<-[anon_0:RESEARCHED]-(s) | 100 | 100 | 300 | | | | Fused in Pipeline 1 | +| | | +-------------------------------------------+----------------+------+---------+----------------+ | +---------------------+ +| | +NodeByLabelScan | sc:Science | 100 | 100 | 101 | 72 | 4/0 | 0.812 | Fused in Pipeline 1 | +| | +-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ +| +NodeIndexSeek | s:Scientist(born) WHERE born = $autoint_0 | 1 | 1 | 2 | 72 | 1/0 | 0.926 | In Pipeline 0 | ++------------------------+-------------------------------------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+ + +Total database accesses: 403, total allocated memory: 3176 + ---- +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(:Science)<-[:INVENTED_BY {year: 530 + (i % 50)}]-(:Pioneer {born: 500 + (i % 50)})-[:LIVES_IN]->(:City)-[:PART_OF]->(:Country {formed: 400 + i}) +) + +CREATE INDEX FOR (s:Scientist) ON (s.born) +CREATE INDEX FOR (p:Pioneer) ON (p.born) +CREATE INDEX FOR (c:Country) ON (c.formed) +CREATE INDEX FOR ()-[i:INVENTED_BY]-() ON (i.year) +CALL db.awaitIndexes + +]]>(sc:Science) +USING JOIN ON s +RETURN * +]]> +++++ +endif::nonhtmloutput[] -[role="deprecated"] [[query-using-periodic-commit-hint]] == `PERIODIC COMMIT` query hint -The `PERIODIC COMMIT` query hint will be removed in the next major release. -It is recommended to use xref::clauses/call-subquery.adoc#subquery-call-in-transactions[`+CALL { ... } IN TRANSACTIONS+`] instead. - -Importing large amounts of data using xref::clauses/load-csv.adoc[`LOAD CSV`] with a single Cypher query may fail due to memory constraints. +Importing large amounts of data using xref:clauses/load-csv.adoc[`LOAD CSV`] with a single Cypher query may fail due to memory constraints. This will manifest itself as an `OutOfMemoryError`. For this situation _only,_ Cypher provides the global `USING PERIODIC COMMIT` query hint for updating queries using `LOAD CSV`. @@ -1016,22 +744,20 @@ If required, the limit for the number of rows per commit may be set as follows: Then the current transaction will be committed and replaced with a newly opened transaction. If no limit is set, a default value will be used. -See xref::clauses/load-csv.adoc#load-csv-importing-large-amounts-of-data[Importing large amounts of data] in xref::clauses/load-csv.adoc[] for examples of `USING PERIODIC COMMIT` with and without setting the number of rows per commit. +See xref:clauses/load-csv.adoc#load-csv-importing-large-amounts-of-data[Importing large amounts of data] in xref:clauses/load-csv.adoc[] for examples of `USING PERIODIC COMMIT` with and without setting the number of rows per commit. [IMPORTANT] ==== Using `PERIODIC COMMIT` will prevent running out of memory when importing large amounts of data. However, it will also break transactional isolation and thus it should only be used where needed. -==== -[NOTE] -==== -The xref::clauses/use.adoc[`USE` clause] can not be used together with the `PERIODIC COMMIT` query hint. + ==== [NOTE] ==== -Queries with the `PERIODIC COMMIT` query hint can not be routed by link:{neo4j-docs-base-uri}/operations-manual/{page-version}/clustering/internals#causal-clustering-routing[Server-side routing]. -Such queries must rely on standard client-side routing, done by the Neo4j Driver. +The xref:clauses/use.adoc[`USE` clause] can not be used together with the `PERIODIC COMMIT` clause. + + ==== diff --git a/modules/ROOT/pages/styleguide.adoc b/modules/ROOT/pages/styleguide.adoc index c25e12428..f42b676a4 100644 --- a/modules/ROOT/pages/styleguide.adoc +++ b/modules/ROOT/pages/styleguide.adoc @@ -1,26 +1,20 @@ -:description: The recommended style when writing Cypher queries. - [appendix] [[cypher-styleguide]] = Cypher styleguide - -[abstract] --- -The recommended style when writing Cypher queries. --- +:description: This appendix contains the recommended style when writing Cypher queries. This appendix contains the following: -* xref::styleguide.adoc#cypher-styleguide-general-recommendations[General recommendations] -* xref::styleguide.adoc#cypher-styleguide-indentation-and-line-breaks[Indentations and line breaks] -* xref::styleguide.adoc#cypher-styleguide-casing[Casing] -* xref::styleguide.adoc#cypher-styleguide-spacing[Spacing] -* xref::styleguide.adoc#cypher-styleguide-patterns[Patterns] -* xref::styleguide.adoc#cypher-styleguide-meta-characters[Meta characters] +* xref:styleguide.adoc#cypher-styleguide-general-recommendations[General recommendations] +* xref:styleguide.adoc#cypher-styleguide-indentation-and-line-breaks[Indentations and line breaks] +* xref:styleguide.adoc#cypher-styleguide-casing[Casing] +* xref:styleguide.adoc#cypher-styleguide-spacing[Spacing] +* xref:styleguide.adoc#cypher-styleguide-patterns[Patterns] +* xref:styleguide.adoc#cypher-styleguide-meta-characters[Meta characters] The purpose of the styleguide is to make the code as easy to read as possible, and thereby contributing to lower cost of maintenance. -For rules and recommendations for naming of labels, relationship types and properties, please see the xref::syntax/naming.adoc[Naming rules and recommendations]. +For rules and recommendations for naming of labels, relationship types and properties, please see the xref:syntax/naming.adoc[Naming rules and recommendations]. [[cypher-styleguide-general-recommendations]] @@ -39,13 +33,13 @@ Arguments should normally not be included. * Start a new clause on a new line. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE n.name CONTAINS 's' RETURN n.name ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE n.name CONTAINS 's' @@ -56,7 +50,7 @@ RETURN n.name Put `ON CREATE` before `ON MATCH` if both are present. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MERGE (n) ON CREATE SET n.prop = 0 MERGE (a:A)-[:T]-(b:B) @@ -66,7 +60,7 @@ RETURN a.prop ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MERGE (n) ON CREATE SET n.prop = 0 @@ -80,7 +74,7 @@ RETURN a.prop Leave the closing brace on its own line. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:A) WHERE @@ -89,7 +83,7 @@ RETURN a.foo ---- + .Also bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:A) WHERE EXISTS @@ -99,7 +93,7 @@ RETURN a.foo ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:A) WHERE EXISTS { @@ -112,7 +106,7 @@ RETURN a.foo * Do not break the line if the simplified subquery form is used. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:A) WHERE EXISTS { @@ -122,7 +116,7 @@ RETURN a.prop ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:A) WHERE EXISTS { (a)-->(b:B) } @@ -136,7 +130,7 @@ RETURN a.prop * Write keywords in upper case. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- match (p:Person) where p.name starts with 'Ma' @@ -144,7 +138,7 @@ return p.name ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH (p:Person) WHERE p.name STARTS WITH 'Ma' @@ -154,30 +148,30 @@ RETURN p.name * Write the value `null` in lower case. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- WITH NULL AS n1, Null AS n2 RETURN n1 IS NULL AND n2 IS NOT NULL ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- -WITH null AS n1, null AS n2 +WITH null AS n1, null as n2 RETURN n1 IS NULL AND n2 IS NOT NULL ---- * Write boolean literals (`true` and `false`) in lower case. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- WITH TRUE AS b1, False AS b2 RETURN b1 AND b2 ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- WITH true AS b1, false AS b2 RETURN b1 AND b2 @@ -190,7 +184,7 @@ RETURN b1 AND b2 ** parameters + .Bad -[source, cypher, indent=0] +[source, cypher] ---- CREATE (N {Prop: 0}) WITH RAND() AS Rand, $pArAm AS MAP @@ -198,7 +192,7 @@ RETURN Rand, MAP.property_key, Count(N) ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- CREATE (n {prop: 0}) WITH rand() AS rand, $param AS map @@ -218,14 +212,14 @@ RETURN rand, map.propertyKey, count(n) ** No space between the last value and the closing brace + .Bad -[source, cypher, indent=0] +[source, cypher] ---- WITH { key1 :'value' ,key2 : 42 } AS map RETURN map ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- WITH {key1: 'value', key2: 42} AS map RETURN map @@ -234,14 +228,14 @@ RETURN map * One space between label/type predicates and property predicates in patterns. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH (p:Person{property: -1})-[:KNOWS {since: 2016}]->() RETURN p.name ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH (p:Person {property: -1})-[:KNOWS {since: 2016}]->() RETURN p.name @@ -250,14 +244,14 @@ RETURN p.name * No space in patterns. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH (:Person) --> (:Vehicle) RETURN count(*) ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH (:Person)-->(:Vehicle) RETURN count(*) @@ -266,7 +260,7 @@ RETURN count(*) * Use a wrapping space around operators. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH p=(s)-->(e) WHERE s.name<>e.name @@ -274,7 +268,7 @@ RETURN length(p) ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH p = (s)-->(e) WHERE s.name <> e.name @@ -284,14 +278,14 @@ RETURN length(p) * No space in label predicates. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH (person : Person : Owner ) RETURN person.name ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH (person:Person:Owner) RETURN person.name @@ -300,7 +294,7 @@ RETURN person.name * Use a space after each comma in lists and enumerations. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH (),() WITH ['a','b',3.14] AS list @@ -308,7 +302,7 @@ RETURN list,2,3,4 ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH (), () WITH ['a', 'b', 3.14] AS list @@ -318,13 +312,13 @@ RETURN list, 2, 3, 4 * No padding space within function call parentheses. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- RETURN split( 'original', 'i' ) ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- RETURN split('original', 'i') ---- @@ -332,7 +326,7 @@ RETURN split('original', 'i') * Use padding space within simple subquery expressions. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:A) WHERE EXISTS {(a)-->(b:B)} @@ -340,7 +334,7 @@ RETURN a.prop ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:A) WHERE EXISTS { (a)-->(b:B) } @@ -354,7 +348,7 @@ RETURN a.prop * When patterns wrap lines, break after arrows, not before. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH (:Person)-->(vehicle:Car)-->(:Company) <--(:Country) @@ -362,7 +356,7 @@ RETURN count(vehicle) ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH (:Person)-->(vehicle:Car)-->(:Company)<-- (:Country) @@ -372,7 +366,7 @@ RETURN count(vehicle) * Use anonymous nodes and relationships when the variable would not be used. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- CREATE (a:End {prop: 42}), (b:End {prop: 3}), @@ -380,7 +374,7 @@ CREATE (a:End {prop: 42}), ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- CREATE (a:End {prop: 42}), (:End {prop: 3}), @@ -390,14 +384,14 @@ CREATE (a:End {prop: 42}), * Chain patterns together to avoid repeating variables. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH (:Person)-->(vehicle:Car), (vehicle:Car)-->(:Company) RETURN count(vehicle) ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH (:Person)-->(vehicle:Car)-->(:Company) RETURN count(vehicle) @@ -406,7 +400,7 @@ RETURN count(vehicle) * Put named nodes before anonymous nodes. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH ()-->(vehicle:Car)-->(manufacturer:Company) WHERE manufacturer.foundedYear < 2000 @@ -414,7 +408,7 @@ RETURN vehicle.mileage ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH (manufacturer:Company)<--(vehicle:Car)<--() WHERE manufacturer.foundedYear < 2000 @@ -424,7 +418,7 @@ RETURN vehicle.mileage * Keep anchor nodes at the beginning of the `MATCH` clause. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH (:Person)-->(vehicle:Car)-->(manufacturer:Company) WHERE manufacturer.foundedYear < 2000 @@ -432,7 +426,7 @@ RETURN vehicle.mileage ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH (manufacturer:Company)<--(vehicle:Car)<--(:Person) WHERE manufacturer.foundedYear < 2000 @@ -442,14 +436,14 @@ RETURN vehicle.mileage * Prefer outgoing (left to right) pattern relationships to incoming pattern relationships. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH (:Country)-->(:Company)<--(vehicle:Car)<--(:Person) RETURN vehicle.mileage ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH (:Person)-->(vehicle:Car)-->(:Company)<--(:Country) RETURN vehicle.mileage @@ -462,13 +456,13 @@ RETURN vehicle.mileage * Use single quotes, `'`, for literal string values. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- RETURN "Cypher" ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- RETURN 'Cypher' ---- @@ -478,13 +472,13 @@ If the string has both, use the form that creates the fewest escapes. In the case of a tie, prefer single quotes. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- RETURN 'Cypher\'s a nice language', "Mats' quote: \"statement\"" ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- RETURN "Cypher's a nice language", 'Mats\' quote: "statement"' ---- @@ -492,14 +486,14 @@ RETURN "Cypher's a nice language", 'Mats\' quote: "statement"' * Avoid having to use back-ticks to escape characters and keywords. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- MATCH (`odd-ch@racter$`:`Spaced Label` {`&property`: 42}) RETURN labels(`odd-ch@racter$`) ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- MATCH (node:NonSpacedLabel {property: 42}) RETURN labels(node) @@ -508,14 +502,13 @@ RETURN labels(node) * Do not use a semicolon at the end of the statement. + .Bad -[source, cypher, indent=0] +[source, cypher] ---- RETURN 1; ---- + .Good -[source, cypher, indent=0] +[source, cypher] ---- RETURN 1 ---- - diff --git a/modules/ROOT/pages/syntax/comments.adoc b/modules/ROOT/pages/syntax/comments.adoc index 380bde029..72fb5008d 100644 --- a/modules/ROOT/pages/syntax/comments.adoc +++ b/modules/ROOT/pages/syntax/comments.adoc @@ -1,32 +1,27 @@ -:description: This section describes how how to use comments in Cypher. - [[cypher-comments]] = Comments - -[abstract] --- -This section describes how how to use comments in Cypher. --- +:description: This section describes how how to use comments in Cypher. A comment begin with double slash (`//`) and continue to the end of the line. Comments do not execute, they are for humans to read. Examples: -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n //This is an end of line comment ---- -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) //This is a whole line comment RETURN n ---- -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE n.property = '//This is NOT a comment' RETURN n ---- + diff --git a/modules/ROOT/pages/syntax/expressions.adoc b/modules/ROOT/pages/syntax/expressions.adoc index 5e637f8cf..fd1e0da75 100644 --- a/modules/ROOT/pages/syntax/expressions.adoc +++ b/modules/ROOT/pages/syntax/expressions.adoc @@ -1,12 +1,13 @@ -:description: This section contains an overview of expressions in Cypher with examples. - [[cypher-expressions]] = Expressions +:description: This section contains an overview of expressions in Cypher with examples. -[abstract] --- -This section contains an overview of expressions in Cypher with examples. --- +* xref:syntax/expressions.adoc#cypher-expressions-general[Expressions in general] +* xref:syntax/expressions.adoc#cypher-expressions-string-literals[Note on string literals] +* xref:syntax/expressions.adoc#query-syntax-case[`CASE` expressions] + ** xref:syntax/expressions.adoc#syntax-simple-case[Simple `CASE` form: comparing an expression against multiple values] + ** xref:syntax/expressions.adoc#syntax-generic-case[Generic `CASE` form: allowing for multiple conditionals to be expressed] + ** xref:syntax/expressions.adoc#syntax-distinguish-case[Distinguishing between when to use the simple and generic `CASE` forms] [[cypher-expressions-general]] == Expressions in general @@ -19,7 +20,7 @@ Notable exceptions are the operators `IS NULL` and `IS NOT NULL`. An expression in Cypher can be: -* A decimal (integer or float) literal: `13`, `-40000`, `3.14`. +* A decimal (integer or float) literal: `13`, `-40000`, `3.14` * A decimal (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` or `0`): `0o1372`, `02127`, `-0o5671`. @@ -34,8 +35,8 @@ An expression in Cypher can be: * An aggregate function: `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 predicate expression is an expression that returns `true` or `false`: `a.prop = 'Hello'`, `length(p) > 10`, `a.name IS NOT NULL`. -* An existential subquery is an expression that returns `true` or `false`: +* A predicate expression is an expression that returns true or false: `a.prop = 'Hello'`, `length(p) > 10`, `a.name IS NOT NULL`. +* An existential subquery is an expression that returns true or false: `EXISTS { MATCH (n)-[r]->(p) WHERE p.name = 'Sven' @@ -65,8 +66,6 @@ String literals can contain the following escape sequences: |`\Uxxxxxxxx`|Unicode UTF-32 code point (8 hex digits must follow the `\U`) |=================== -//neo4j-manual-modeling-antora/cypherManual/build/4.4/antora/modules/ROOT/partials/neo4j-cypher-docs/docs/dev/ql/query-syntax-case.adoc - [[query-syntax-case]] == `CASE` expressions @@ -76,25 +75,58 @@ Two variants of `CASE` exist within Cypher: the simple form, which allows an exp [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: -//// -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) -//// +.Graph +["dot", "`CASE` expressions-1.svg", "neoviz", ""] +---- + N0 [ + label = "{A|name = \'Alice\'\lage = 38\leyes = \'brown\'\l}" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 [ + label = "{B|name = \'Bob\'\lage = 25\leyes = \'blue\'\l}" + ] + N1 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 -> N4 [ + color = "#4e9a06" + fontcolor = "#4e9a06" + label = "MARRIED\n" + ] + N2 [ + label = "{C|name = \'Charlie\'\lage = 53\leyes = \'green\'\l}" + ] + N2 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N3 [ + label = "{D|name = \'Daniel\'\leyes = \'brown\'\l}" + ] + N4 [ + label = "{E|eyes = \'blue\'\larray = \[\'one\', \'two\', \'three\'\]\lname = \'Eskil\'\lage = 41\l}" + ] -image:graph3.svg[] +---- + [[syntax-simple-case]] === Simple `CASE` form: comparing an expression against multiple values @@ -105,37 +137,28 @@ However, if there is no `ELSE` case and no match is found, `null` will be return *Syntax:* -[source, cypher, role=noplay, indent=0] ----- +[source, cypher, role=noplay] 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. +| `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. |=== .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN @@ -158,6 +181,35 @@ END AS result 1+d|Rows: 5 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] [[syntax-generic-case]] === Generic `CASE` form: allowing for multiple conditionals to be expressed @@ -168,46 +220,27 @@ However, if there is no `ELSE` case and no match is found, `null` will be return *Syntax:* -[source, cypher, role=noplay, indent=0] ----- +[source, cypher, role=noplay] CASE WHEN predicate THEN result [WHEN ...] [ELSE default] END ----- + *Arguments:* [options="header"] |=== | Name | Description -| `predicate` -| A predicate that is tested to find a valid alternative. - -| `result` -| This is the expression returned as output if `predicate` evaluates to `true`. - -| `default` -| If no match is found, `default` is returned. +| `predicate` | A predicate that is tested to find a valid alternative. +| `result` | This is the expression returned as output if `predicate` evaluates to `true`. +| `default` | If no match is found, `default` is returned. |=== -//// -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) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN @@ -230,14 +263,12 @@ END AS result 1+d|Rows: 5 |=== - -[[syntax-distinguish-case]] -=== Distinguishing between when to use the simple and generic `CASE` forms - -Owing to the close similarity between the syntax of the two forms, sometimes it may not be clear at the outset as to which form to use. -We illustrate this scenario by means of the following query, in which there is an expectation that `age_10_years_ago` is `-1` if `n.age` is `null`: - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) -//// + +]]> +++++ +endif::nonhtmloutput[] + +[[syntax-distinguish-case]] +=== Distinguishing between when to use the simple and generic `CASE` forms + +Owing to the close similarity between the syntax of the two forms, sometimes it may not be clear at the outset as to which form to use. +We illustrate this scenario by means of the following query, in which there is an expectation that `age_10_years_ago` is `-1` if `n.age` is `null`: + + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n.name, @@ -279,9 +329,12 @@ This results in the `ELSE n.age - 10` branch being taken instead, returning `nul 2+d|Rows: 5 |=== -The corrected query, behaving as expected, is given by the following generic `CASE` form: - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) -//// + +]]> +++++ +endif::nonhtmloutput[] + +The corrected query, behaving as expected, is given by the following generic `CASE` form: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) RETURN n.name, @@ -320,14 +386,12 @@ We now see that the `age_10_years_ago` correctly returns `-1` for the node named 2+d|Rows: 5 |=== - -[[syntax-use-case-result]] -=== Using the result of `CASE` in the succeeding clause or statement - -You can use the result of `CASE` to set properties on a node or relationship. -For example, instead of specifying the node directly, you can set a property for a node selected by an expression: - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) -//// + +]]> +++++ +endif::nonhtmloutput[] + +[[syntax-use-case-result]] +=== Using the result of `CASE` in the succeeding clause or statement + +You can use the result of `CASE` to set properties on a node or relationship. +For example, instead of specifying the node directly, you can set a property for a node selected by an expression: + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WITH n, @@ -354,7 +435,7 @@ END AS colourCode SET n.colourCode = colourCode ---- -For more information about using the `SET` clause, see xref::clauses/set.adoc[SET]. +For more information about using the `SET` clause, see xref:clauses/set.adoc[SET]. .Result [role="queryresult",options="footer",cols="1* +Try this query live +(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) + +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/syntax/index.adoc b/modules/ROOT/pages/syntax/index.adoc index 558fd3b1d..577552eaa 100644 --- a/modules/ROOT/pages/syntax/index.adoc +++ b/modules/ROOT/pages/syntax/index.adoc @@ -1,81 +1,82 @@ -:description: This section describes the syntax of the Cypher query language. - [[query-syntax]] = Syntax +:description: This section describes the syntax of the Cypher query language. + +* xref:syntax/values.adoc[Values and types] +* xref:syntax/naming.adoc[Naming rules and recommendations] +* xref:syntax/expressions.adoc[Expressions] + ** xref:syntax/expressions.adoc#cypher-expressions-general[Expressions in general] + ** xref:syntax/expressions.adoc#cypher-expressions-string-literals[Note on string literals] + ** xref:syntax/expressions.adoc#query-syntax-case[`CASE` Expressions] +* xref:syntax/variables.adoc[Variables] +* xref:syntax/reserved.adoc[Reserved keywords] +* xref:syntax/parameters.adoc[Parameters] + ** xref:syntax/parameters.adoc#cypher-parameters-string-literal[String literal] + ** xref:syntax/parameters.adoc#cypher-parameters-regular-expression[Regular expression] + ** xref:syntax/parameters.adoc#cypher-parameters-case-sensitive-pattern-matching[Case-sensitive string pattern matching] + ** xref:syntax/parameters.adoc#cypher-parameters-create-node-with-properties[Create node with properties] + ** xref:syntax/parameters.adoc#cypher-parameters-create-multiple-nodes-with-properties[Create multiple nodes with properties] + ** xref:syntax/parameters.adoc#cypher-parameters-setting-all-properties-on-a-node[Setting all properties on a node] + ** xref:syntax/parameters.adoc#cypher-parameters-skip-and-limit[`SKIP` and `LIMIT`] + ** xref:syntax/parameters.adoc#cypher-parameters-node-id[Node id] + ** xref:syntax/parameters.adoc#cypher-parameters-multiple-node-ids[Multiple node ids] + ** xref:syntax/parameters.adoc#cypher-parameters-call-procedure[Calling procedures] +* xref:syntax/operators.adoc[Operators] + ** xref:syntax/operators.adoc#query-operators-summary[Operators at a glance] + ** xref:syntax/operators.adoc#query-operators-aggregation[Aggregation operators] + ** xref:syntax/operators.adoc#query-operators-property[Property operators] + ** xref:syntax/operators.adoc#query-operators-mathematical[Mathematical operators] + ** xref:syntax/operators.adoc#query-operators-comparison[Comparison operators] + ** xref:syntax/operators.adoc#query-operators-boolean[Boolean operators] + ** xref:syntax/operators.adoc#query-operators-string[String operators] + ** xref:syntax/operators.adoc#query-operators-temporal[Temporal operators] + ** xref:syntax/operators.adoc#query-operators-map[Map operators] + ** xref:syntax/operators.adoc#query-operators-list[List operators] +* xref:syntax/comments.adoc[Comments] +* xref:syntax/patterns.adoc[Patterns] + ** xref:syntax/patterns.adoc#cypher-pattern-node[Patterns for nodes] + ** xref:syntax/patterns.adoc#cypher-pattern-related-nodes[Patterns for related nodes] + ** xref:syntax/patterns.adoc#cypher-pattern-label[Patterns for labels] + ** xref:syntax/patterns.adoc#cypher-pattern-properties[Specifying properties] + ** xref:syntax/patterns.adoc#cypher-pattern-relationship[Patterns for relationships] + ** xref:syntax/patterns.adoc#cypher-pattern-varlength[Variable-length pattern matching] + ** xref:syntax/patterns.adoc#cypher-pattern-path-variables[Assigning to path variables] +* xref:syntax/temporal.adoc[Temporal (Date/Time) values] + ** xref:syntax/temporal.adoc#cypher-temporal-timezones[Time zones] + ** xref:syntax/temporal.adoc#cypher-temporal-instants[Temporal instants] + *** xref:syntax/temporal.adoc#cypher-temporal-specifying-temporal-instants[Specifying temporal instants] + **** xref:syntax/temporal.adoc#cypher-temporal-specify-date[Specifying dates] + **** xref:syntax/temporal.adoc#cypher-temporal-specify-time[Specifying times] + **** xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[Specifying time zones] + **** xref:syntax/temporal.adoc#cypher-temporal-specify-instant-examples[Examples] + *** xref:syntax/temporal.adoc#cypher-temporal-accessing-components-temporal-instants[Accessing components of temporal instants] + ** xref:syntax/temporal.adoc#cypher-temporal-durations[Durations] + *** xref:syntax/temporal.adoc#cypher-temporal-specifying-durations[Specifying durations] + **** xref:syntax/temporal.adoc#cypher-temporal-specify-duration-examples[Examples] + *** xref:syntax/temporal.adoc#cypher-temporal-accessing-components-durations[Accessing components of durations] + ** xref:syntax/temporal.adoc#cypher-temporal-examples[Examples] + ** xref:syntax/temporal.adoc#cypher-temporal-index[Temporal indexing] +* xref:syntax/spatial.adoc[Spatial values] + ** xref:syntax/spatial.adoc#cypher-spatial-introduction[Introduction] + ** xref:syntax/spatial.adoc#cypher-spatial-crs[Coordinate Reference Systems] + *** xref:syntax/spatial.adoc#cypher-spatial-crs-geographic[Geographic coordinate reference systems] + *** xref:syntax/spatial.adoc#cypher-spatial-crs-cartesian[Cartesian coordinate reference systems] + ** xref:syntax/spatial.adoc#cypher-spatial-instants[Spatial instants] + *** xref:syntax/spatial.adoc#cypher-spatial-specifying-spatial-instants[Creating points] + *** xref:syntax/spatial.adoc#cypher-spatial-accessing-components-spatial-instants[Accessing components of points] + ** xref:syntax/spatial.adoc#cypher-spatial-index[Spatial index] +* xref:syntax/lists.adoc[Lists] + ** xref:syntax/lists.adoc#cypher-lists-general[Lists in general] + ** xref:syntax/lists.adoc#cypher-list-comprehension[List comprehension] + ** xref:syntax/lists.adoc#cypher-pattern-comprehension[Pattern comprehension] +* xref:syntax/maps.adoc[Maps] + ** xref:syntax/maps.adoc#cypher-literal-maps[Literal maps] + ** xref:syntax/maps.adoc#cypher-map-projection[Map projection] +* xref:syntax/working-with-null.adoc[Working with `null`] + ** xref:syntax/working-with-null.adoc#cypher-null-intro[Introduction to `null` in Cypher] + ** xref:syntax/working-with-null.adoc#cypher-null-logical-operators[Logical operations with `null`] + ** xref:syntax/working-with-null.adoc#cypher-null-bracket-operator[The `[\]` operator and `null`] + ** xref:syntax/working-with-null.adoc#cypher-null-in-operator[The `IN` operator and `null`] + ** xref:syntax/working-with-null.adoc#cypher-expressions-and-null[Expressions that return `null`] + -* xref::syntax/values.adoc[Values and types] -* xref::syntax/naming.adoc[Naming rules and recommendations] -* xref::syntax/expressions.adoc[Expressions] - ** xref::syntax/expressions.adoc#cypher-expressions-general[Expressions in general] - ** xref::syntax/expressions.adoc#cypher-expressions-string-literals[Note on string literals] - ** xref::syntax/expressions.adoc#query-syntax-case[`CASE` Expressions] -* xref::syntax/variables.adoc[Variables] -* xref::syntax/reserved.adoc[Reserved keywords] -* xref::syntax/parameters.adoc[Parameters] - ** xref::syntax/parameters.adoc#cypher-parameters-string-literal[String literal] - ** xref::syntax/parameters.adoc#cypher-parameters-regular-expression[Regular expression] - ** xref::syntax/parameters.adoc#cypher-parameters-case-sensitive-pattern-matching[Case-sensitive string pattern matching] - ** xref::syntax/parameters.adoc#cypher-parameters-create-node-with-properties[Create node with properties] - ** xref::syntax/parameters.adoc#cypher-parameters-create-multiple-nodes-with-properties[Create multiple nodes with properties] - ** xref::syntax/parameters.adoc#cypher-parameters-setting-all-properties-on-a-node[Setting all properties on a node] - ** xref::syntax/parameters.adoc#cypher-parameters-skip-and-limit[`SKIP` and `LIMIT`] - ** xref::syntax/parameters.adoc#cypher-parameters-node-id[Node id] - ** xref::syntax/parameters.adoc#cypher-parameters-multiple-node-ids[Multiple node ids] - ** xref::syntax/parameters.adoc#cypher-parameters-call-procedure[Calling procedures] -* xref::syntax/operators.adoc[Operators] - ** xref::syntax/operators.adoc#query-operators-summary[Operators at a glance] - ** xref::syntax/operators.adoc#query-operators-aggregation[Aggregation operators] - ** xref::syntax/operators.adoc#query-operators-property[Property operators] - ** xref::syntax/operators.adoc#query-operators-mathematical[Mathematical operators] - ** xref::syntax/operators.adoc#query-operators-comparison[Comparison operators] - ** xref::syntax/operators.adoc#query-operators-boolean[Boolean operators] - ** xref::syntax/operators.adoc#query-operators-string[String operators] - ** xref::syntax/operators.adoc#query-operators-temporal[Temporal operators] - ** xref::syntax/operators.adoc#query-operators-map[Map operators] - ** xref::syntax/operators.adoc#query-operators-list[List operators] -* xref::syntax/comments.adoc[Comments] -* xref::syntax/patterns.adoc[Patterns] - ** xref::syntax/patterns.adoc#cypher-pattern-node[Patterns for nodes] - ** xref::syntax/patterns.adoc#cypher-pattern-related-nodes[Patterns for related nodes] - ** xref::syntax/patterns.adoc#cypher-pattern-label[Patterns for labels] - ** xref::syntax/patterns.adoc#cypher-pattern-properties[Specifying properties] - ** xref::syntax/patterns.adoc#cypher-pattern-relationship[Patterns for relationships] - ** xref::syntax/patterns.adoc#cypher-pattern-varlength[Variable-length pattern matching] - ** xref::syntax/patterns.adoc#cypher-pattern-path-variables[Assigning to path variables] -* xref::syntax/temporal.adoc[Temporal (Date/Time) values] - ** xref::syntax/temporal.adoc#cypher-temporal-timezones[Time zones] - ** xref::syntax/temporal.adoc#cypher-temporal-instants[Temporal instants] - *** xref::syntax/temporal.adoc#cypher-temporal-specifying-temporal-instants[Specifying temporal instants] - **** xref::syntax/temporal.adoc#cypher-temporal-specify-date[Specifying dates] - **** xref::syntax/temporal.adoc#cypher-temporal-specify-time[Specifying times] - **** xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[Specifying time zones] - **** xref::syntax/temporal.adoc#cypher-temporal-specify-instant-examples[Examples] - *** xref::syntax/temporal.adoc#cypher-temporal-accessing-components-temporal-instants[Accessing components of temporal instants] - ** xref::syntax/temporal.adoc#cypher-temporal-durations[Durations] - *** xref::syntax/temporal.adoc#cypher-temporal-specifying-durations[Specifying durations] - **** xref::syntax/temporal.adoc#cypher-temporal-specify-duration-examples[Examples] - *** xref::syntax/temporal.adoc#cypher-temporal-accessing-components-durations[Accessing components of durations] - ** xref::syntax/temporal.adoc#cypher-temporal-examples[Examples] - ** xref::syntax/temporal.adoc#cypher-temporal-index[Temporal indexing] -* xref::syntax/spatial.adoc[Spatial values] - ** xref::syntax/spatial.adoc#cypher-spatial-introduction[Introduction] - ** xref::syntax/spatial.adoc#cypher-spatial-crs[Coordinate Reference Systems] - *** xref::syntax/spatial.adoc#cypher-spatial-crs-geographic[Geographic coordinate reference systems] - *** xref::syntax/spatial.adoc#cypher-spatial-crs-cartesian[Cartesian coordinate reference systems] - ** xref::syntax/spatial.adoc#cypher-spatial-instants[Spatial instants] - *** xref::syntax/spatial.adoc#cypher-spatial-specifying-spatial-instants[Creating points] - *** xref::syntax/spatial.adoc#cypher-spatial-accessing-components-spatial-instants[Accessing components of points] - ** xref::syntax/spatial.adoc#cypher-spatial-index[Spatial index] -* xref::syntax/lists.adoc[Lists] - ** xref::syntax/lists.adoc#cypher-lists-general[Lists in general] - ** xref::syntax/lists.adoc#cypher-list-comprehension[List comprehension] - ** xref::syntax/lists.adoc#cypher-pattern-comprehension[Pattern comprehension] -* xref::syntax/maps.adoc[Maps] - ** xref::syntax/maps.adoc#cypher-literal-maps[Literal maps] - ** xref::syntax/maps.adoc#cypher-map-projection[Map projection] -* xref::syntax/working-with-null.adoc[Working with `null`] - ** xref::syntax/working-with-null.adoc#cypher-null-intro[Introduction to `null` in Cypher] - ** xref::syntax/working-with-null.adoc#cypher-null-logical-operators[Logical operations with `null`] - ** xref::syntax/working-with-null.adoc#cypher-null-bracket-operator[The `[\]` operator and `null`] - ** xref::syntax/working-with-null.adoc#cypher-null-in-operator[The `IN` operator and `null`] - ** xref::syntax/working-with-null.adoc#cypher-expressions-and-null[Expressions that return `null`] diff --git a/modules/ROOT/pages/syntax/lists.adoc b/modules/ROOT/pages/syntax/lists.adoc index 63e748cc8..1fa848db5 100644 --- a/modules/ROOT/pages/syntax/lists.adoc +++ b/modules/ROOT/pages/syntax/lists.adoc @@ -1,27 +1,27 @@ -:description: Cypher has comprehensive support for lists. - [[cypher-lists]] = Lists +:description: Cypher has comprehensive support for lists. -[abstract] --- -Cypher has comprehensive support for lists. --- +* xref:syntax/lists.adoc#cypher-lists-general[Lists in general] +* xref:syntax/lists.adoc#cypher-list-comprehension[List comprehension] +* xref:syntax/lists.adoc#cypher-pattern-comprehension[Pattern comprehension] [NOTE] ==== -Information regarding operators, such as list concatenation (`+`), element existence checking (`IN`), and access (`[]`) can be found xref::syntax/operators.adoc#query-operators-list[here]. -The behavior of the `IN` and `[]` operators with respect to `null` is detailed xref::syntax/working-with-null.adoc[here]. -==== +Information regarding operators, such as list concatenation (`+`), element existence checking (`IN`), and access (`[]`) can be found xref:syntax/operators.adoc#query-operators-list[here]. +The behavior of the `IN` and `[]` operators with respect to `null` is detailed xref:syntax/working-with-null.adoc[here]. + +==== [[cypher-lists-general]] == Lists in general A literal list is created by using brackets and separating the elements in the list with commas. + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] AS list ---- @@ -34,15 +34,47 @@ RETURN [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] AS list 1+d|Rows: 1 |=== -In the examples, you use the xref::functions/list.adoc#functions-range[`range`] function. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(johnnymnemonic), + (keanu)-[:ACTED_IN]->(somethingsgottagive), + (keanu)-[:ACTED_IN]->(thematrixrevolutions), + (keanu)-[:ACTED_IN]->(thematrixreloaded), + (keanu)-[:ACTED_IN]->(thereplacements), + (keanu)-[:ACTED_IN]->(thematrix), + (keanu)-[:ACTED_IN]->(thedevilsadvocate), + (keanu)-[:ACTED_IN]->(matrix4) + +]]> +++++ +endif::nonhtmloutput[] + +In the examples, you use the xref:functions/list.adoc#functions-range[`range`] function. It gives you a list containing all numbers between given start and end numbers. Range is inclusive in both ends. To access individual elements in the list, you can use the square brackets again. This extracts from the start index and up to, but not including, the end index. + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN range(0, 10)[3] ---- @@ -55,10 +87,42 @@ RETURN range(0, 10)[3] 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(johnnymnemonic), + (keanu)-[:ACTED_IN]->(somethingsgottagive), + (keanu)-[:ACTED_IN]->(thematrixrevolutions), + (keanu)-[:ACTED_IN]->(thematrixreloaded), + (keanu)-[:ACTED_IN]->(thereplacements), + (keanu)-[:ACTED_IN]->(thematrix), + (keanu)-[:ACTED_IN]->(thedevilsadvocate), + (keanu)-[:ACTED_IN]->(matrix4) + +]]> +++++ +endif::nonhtmloutput[] + You can also use negative numbers, to start from the end of the list instead. + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN range(0, 10)[-3] ---- @@ -71,10 +135,42 @@ RETURN range(0, 10)[-3] 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(johnnymnemonic), + (keanu)-[:ACTED_IN]->(somethingsgottagive), + (keanu)-[:ACTED_IN]->(thematrixrevolutions), + (keanu)-[:ACTED_IN]->(thematrixreloaded), + (keanu)-[:ACTED_IN]->(thereplacements), + (keanu)-[:ACTED_IN]->(thematrix), + (keanu)-[:ACTED_IN]->(thedevilsadvocate), + (keanu)-[:ACTED_IN]->(matrix4) + +]]> +++++ +endif::nonhtmloutput[] + Finally, you can use ranges inside the brackets to return ranges of the list. + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN range(0, 10)[0..3] ---- @@ -87,8 +183,40 @@ RETURN range(0, 10)[0..3] 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(johnnymnemonic), + (keanu)-[:ACTED_IN]->(somethingsgottagive), + (keanu)-[:ACTED_IN]->(thematrixrevolutions), + (keanu)-[:ACTED_IN]->(thematrixreloaded), + (keanu)-[:ACTED_IN]->(thereplacements), + (keanu)-[:ACTED_IN]->(thematrix), + (keanu)-[:ACTED_IN]->(thedevilsadvocate), + (keanu)-[:ACTED_IN]->(matrix4) + +]]> +++++ +endif::nonhtmloutput[] + + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN range(0, 10)[0..-5] ---- @@ -101,8 +229,40 @@ RETURN range(0, 10)[0..-5] 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(johnnymnemonic), + (keanu)-[:ACTED_IN]->(somethingsgottagive), + (keanu)-[:ACTED_IN]->(thematrixrevolutions), + (keanu)-[:ACTED_IN]->(thematrixreloaded), + (keanu)-[:ACTED_IN]->(thereplacements), + (keanu)-[:ACTED_IN]->(thematrix), + (keanu)-[:ACTED_IN]->(thedevilsadvocate), + (keanu)-[:ACTED_IN]->(matrix4) + +]]> +++++ +endif::nonhtmloutput[] + + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN range(0, 10)[-5..] ---- @@ -115,8 +275,40 @@ RETURN range(0, 10)[-5..] 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(johnnymnemonic), + (keanu)-[:ACTED_IN]->(somethingsgottagive), + (keanu)-[:ACTED_IN]->(thematrixrevolutions), + (keanu)-[:ACTED_IN]->(thematrixreloaded), + (keanu)-[:ACTED_IN]->(thereplacements), + (keanu)-[:ACTED_IN]->(thematrix), + (keanu)-[:ACTED_IN]->(thedevilsadvocate), + (keanu)-[:ACTED_IN]->(matrix4) + +]]> +++++ +endif::nonhtmloutput[] + + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN range(0, 10)[..4] ---- @@ -129,13 +321,47 @@ RETURN range(0, 10)[..4] 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(johnnymnemonic), + (keanu)-[:ACTED_IN]->(somethingsgottagive), + (keanu)-[:ACTED_IN]->(thematrixrevolutions), + (keanu)-[:ACTED_IN]->(thematrixreloaded), + (keanu)-[:ACTED_IN]->(thereplacements), + (keanu)-[:ACTED_IN]->(thematrix), + (keanu)-[:ACTED_IN]->(thedevilsadvocate), + (keanu)-[:ACTED_IN]->(matrix4) + +]]> +++++ +endif::nonhtmloutput[] + [NOTE] ==== Out-of-bound slices are simply truncated, but out-of-bound single elements return `null`. + + ==== + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN range(0, 10)[15] ---- @@ -148,8 +374,40 @@ RETURN range(0, 10)[15] 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(johnnymnemonic), + (keanu)-[:ACTED_IN]->(somethingsgottagive), + (keanu)-[:ACTED_IN]->(thematrixrevolutions), + (keanu)-[:ACTED_IN]->(thematrixreloaded), + (keanu)-[:ACTED_IN]->(thereplacements), + (keanu)-[:ACTED_IN]->(thematrix), + (keanu)-[:ACTED_IN]->(thedevilsadvocate), + (keanu)-[:ACTED_IN]->(matrix4) + +]]> +++++ +endif::nonhtmloutput[] + + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN range(0, 10)[5..15] ---- @@ -162,10 +420,42 @@ RETURN range(0, 10)[5..15] 1+d|Rows: 1 |=== -You can get the xref::functions/scalar.adoc#functions-size[`size`] of a list as follows: +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(johnnymnemonic), + (keanu)-[:ACTED_IN]->(somethingsgottagive), + (keanu)-[:ACTED_IN]->(thematrixrevolutions), + (keanu)-[:ACTED_IN]->(thematrixreloaded), + (keanu)-[:ACTED_IN]->(thereplacements), + (keanu)-[:ACTED_IN]->(thematrix), + (keanu)-[:ACTED_IN]->(thedevilsadvocate), + (keanu)-[:ACTED_IN]->(matrix4) + +]]> +++++ +endif::nonhtmloutput[] + +You can get the xref:functions/scalar.adoc#functions-size[`size`] of a list as follows: + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN size(range(0, 10)[0..3]) ---- @@ -178,6 +468,36 @@ RETURN size(range(0, 10)[0..3]) 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(johnnymnemonic), + (keanu)-[:ACTED_IN]->(somethingsgottagive), + (keanu)-[:ACTED_IN]->(thematrixrevolutions), + (keanu)-[:ACTED_IN]->(thematrixreloaded), + (keanu)-[:ACTED_IN]->(thereplacements), + (keanu)-[:ACTED_IN]->(thematrix), + (keanu)-[:ACTED_IN]->(thedevilsadvocate), + (keanu)-[:ACTED_IN]->(matrix4) + +]]> +++++ +endif::nonhtmloutput[] [[cypher-list-comprehension]] == List comprehension @@ -185,8 +505,9 @@ RETURN size(range(0, 10)[0..3]) List comprehension is a syntactic construct available in Cypher for creating a list based on existing lists. It follows the form of the mathematical set-builder notation (set comprehension) instead of the use of map and filter functions. + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN [x IN range(0,10) WHERE x % 2 = 0 | x^3 ] AS result ---- @@ -199,10 +520,42 @@ RETURN [x IN range(0,10) WHERE x % 2 = 0 | x^3 ] AS result 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(johnnymnemonic), + (keanu)-[:ACTED_IN]->(somethingsgottagive), + (keanu)-[:ACTED_IN]->(thematrixrevolutions), + (keanu)-[:ACTED_IN]->(thematrixreloaded), + (keanu)-[:ACTED_IN]->(thereplacements), + (keanu)-[:ACTED_IN]->(thematrix), + (keanu)-[:ACTED_IN]->(thedevilsadvocate), + (keanu)-[:ACTED_IN]->(matrix4) + +]]> +++++ +endif::nonhtmloutput[] + Either the `WHERE` part, or the expression, can be omitted, if you only want to filter or map respectively. + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN [x IN range(0,10) WHERE x % 2 = 0 ] AS result ---- @@ -215,8 +568,40 @@ RETURN [x IN range(0,10) WHERE x % 2 = 0 ] AS result 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(johnnymnemonic), + (keanu)-[:ACTED_IN]->(somethingsgottagive), + (keanu)-[:ACTED_IN]->(thematrixrevolutions), + (keanu)-[:ACTED_IN]->(thematrixreloaded), + (keanu)-[:ACTED_IN]->(thereplacements), + (keanu)-[:ACTED_IN]->(thematrix), + (keanu)-[:ACTED_IN]->(thedevilsadvocate), + (keanu)-[:ACTED_IN]->(matrix4) + +]]> +++++ +endif::nonhtmloutput[] + + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN [x IN range(0,10) | x^3 ] AS result ---- @@ -229,21 +614,12 @@ RETURN [x IN range(0,10) | x^3 ] AS result 1+d|Rows: 1 |=== - -[[cypher-pattern-comprehension]] -== Pattern comprehension - -Pattern comprehension is a syntactic construct available in Cypher for creating a list based on matchings of a pattern. -A pattern comprehension matches the specified pattern like a normal `MATCH` clause, with predicates like a normal `WHERE` clause, but yields a custom projection as specified. - -The following graph is used for the pattern comprehension examples: - -image:graph5.svg[] - -This example returns a list that contains the year when the movies was released. -The pattern matching in the pattern comprehension looks for `Matrix` in the movie title and that the node `a` (`Person` node with the name `Keanu Reeves`) has a relationship with the movie. - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(thematrix), (keanu)-[:ACTED_IN]->(thedevilsadvocate), (keanu)-[:ACTED_IN]->(matrix4) -//// + +]]> +++++ +endif::nonhtmloutput[] + +[[cypher-pattern-comprehension]] +== Pattern comprehension + +Pattern comprehension is a syntactic construct available in Cypher for creating a list based on matchings of a pattern. +A pattern comprehension matches the specified pattern like a normal `MATCH` clause, with predicates like a normal `WHERE` clause, but yields a custom projection as specified. + +The following graph is used for the pattern comprehension examples: + +.Graph +["dot", "Lists-1.svg", "neoviz", ""] +---- + N0 [ + label = "{Person|name = \'Keanu Reeves\'\l}" + ] + N0 -> N8 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\n" + ] + N0 -> N7 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\n" + ] + N0 -> N4 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\n" + ] + N0 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\n" + ] + N0 -> N5 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\n" + ] + N0 -> N6 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\n" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\n" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\n" + ] + N1 [ + label = "{Movie|title = \'Johnny Mnemonic\'\lreleased = 1995\l}" + ] + N2 [ + label = "{Movie|title = \'Somethings Gotta Give\'\lreleased = 2003\l}" + ] + N3 [ + label = "{Movie|title = \'The Matrix Revolutions\'\lreleased = 2003\l}" + ] + N4 [ + label = "{Movie|title = \'The Matrix Reloaded\'\lreleased = 2003\l}" + ] + N5 [ + label = "{Movie|title = \'The Replacements\'\lreleased = 2000\l}" + ] + N6 [ + label = "{Movie|title = \'The Matrix\'\lreleased = 1999\l}" + ] + N7 [ + label = "{Movie|title = \'The Devils Advocate\'\lreleased = 1997\l}" + ] + N8 [ + label = "{Movie|title = \'The Matrix Resurrections\'\lreleased = 2021\l}" + ] + +---- + + +This example returns a list that contains the year when the movies was released. +The pattern matching in the pattern comprehension looks for `Matrix` in the movie title and that the node `a` (`Person` node with the name `Keanu Reeves`) has a relationship with the movie. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:Person {name: 'Keanu Reeves'}) RETURN [(a)-->(b:Movie) WHERE b.title CONTAINS 'Matrix' | b.released] AS years @@ -279,12 +746,12 @@ RETURN [(a)-->(b:Movie) WHERE b.title CONTAINS 'Matrix' | b.released] AS years 1+d|Rows: 1 |=== -The whole predicate, including the `WHERE` keyword, is optional and may be omitted. - -This example returns a sorted list that contains years. -The pattern matching in the pattern comprehension looks for movie nodes that has a relationship with the node `a` (`Person` node with the name `Keanu Reeves`). - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(thematrix), (keanu)-[:ACTED_IN]->(thedevilsadvocate), (keanu)-[:ACTED_IN]->(matrix4) -//// + +]]>(b:Movie) WHERE b.title CONTAINS 'Matrix' | b.released] AS years +]]> +++++ +endif::nonhtmloutput[] + +The whole predicate, including the `WHERE` keyword, is optional and may be omitted. + +This example returns a sorted list that contains years. +The pattern matching in the pattern comprehension looks for movie nodes that has a relationship with the node `a` (`Person` node with the name `Keanu Reeves`). + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (a:Person {name: 'Keanu Reeves'}) WITH [(a)-->(b:Movie) | b.released] AS years @@ -323,3 +802,38 @@ RETURN COLLECT(year) AS sorted_years 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(johnnymnemonic), + (keanu)-[:ACTED_IN]->(somethingsgottagive), + (keanu)-[:ACTED_IN]->(thematrixrevolutions), + (keanu)-[:ACTED_IN]->(thematrixreloaded), + (keanu)-[:ACTED_IN]->(thereplacements), + (keanu)-[:ACTED_IN]->(thematrix), + (keanu)-[:ACTED_IN]->(thedevilsadvocate), + (keanu)-[:ACTED_IN]->(matrix4) + +]]>(b:Movie) | b.released] AS years +UNWIND years AS year +WITH year ORDER BY year +RETURN COLLECT(year) AS sorted_years +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/syntax/maps.adoc b/modules/ROOT/pages/syntax/maps.adoc index 2268b4fb8..7bb2107e6 100644 --- a/modules/ROOT/pages/syntax/maps.adoc +++ b/modules/ROOT/pages/syntax/maps.adoc @@ -1,21 +1,66 @@ -:description: This section describes how to use maps in Cyphers. - [[cypher-maps]] = Maps +:description: This section describes how to use maps in Cyphers. -[abstract] --- -This section describes how to use maps in Cyphers. --- +* xref:syntax/maps.adoc#cypher-literal-maps[Literal maps] +* xref:syntax/maps.adoc#cypher-map-projection[Map projection] +** xref:syntax/maps.adoc#cypher-map-projection-examples[Examples of map projection] The following graph is used for the examples below: -image:graph6.svg[] +.Graph +["dot", "Maps-1.svg", "neoviz", ""] +---- + N0 [ + label = "{Person|realName = \'Carlos Irwin Estévez\'\lname = \'Charlie Sheen\'\l}" + ] + N0 -> N4 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\n" + ] + N0 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\n" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\n" + ] + N1 [ + label = "{Person|name = \'Martin Sheen\'\l}" + ] + N1 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\n" + ] + N1 -> N4 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "ACTED_IN\n" + ] + N2 [ + label = "{Movie|year = 1987\ltitle = \'Wall Street\'\l}" + ] + N3 [ + label = "{Movie|year = 1984\ltitle = \'Red Dawn\'\l}" + ] + N4 [ + label = "{Movie|year = 1979\ltitle = \'Apocalypse Now\'\l}" + ] + +---- + [NOTE] ==== -Information regarding property access operators such as `.` and `[]` can be found xref::syntax/operators.adoc#query-operators-map[here]. -The behavior of the `[]` operator with respect to `null` is detailed xref::syntax/working-with-null.adoc#cypher-null-bracket-operator[here]. +Information regarding property access operators such as `.` and `[]` can be found xref:syntax/operators.adoc#query-operators-map[here]. +The behavior of the `[]` operator with respect to `null` is detailed xref:syntax/working-with-null.adoc#cypher-null-bracket-operator[here]. + + ==== [[cypher-literal-maps]] @@ -23,25 +68,12 @@ The behavior of the `[]` operator with respect to `null` is detailed xref::synta Cypher supports construction of maps. The key names in a map must be of type `String`. -If returned through an link:{neo4j-docs-base-uri}/http-api/{page-version}[HTTP API call], a JSON object will be returned. +If returned through an link:{neo4j-docs-base-uri}/http-api/{page-version}/index#http-api[HTTP API call], a JSON object will be returned. If returned in Java, an object of type `java.util.Map` will be returned. -//// -CREATE - (charlie:Person {name: 'Charlie Sheen', realName: 'Carlos Irwin Estévez'}), - (martin:Person {name: 'Martin Sheen'}), - (wallstreet:Movie {title: 'Wall Street', year: 1987}), - (reddawn:Movie {title: 'Red Dawn', year: 1984}), - (apocalypsenow:Movie {title: 'Apocalypse Now', year: 1979}), - (charlie)-[:ACTED_IN]->(wallstreet), - (charlie)-[:ACTED_IN]->(reddawn), - (charlie)-[:ACTED_IN]->(apocalypsenow), - (martin)-[:ACTED_IN]->(wallstreet), - (martin)-[:ACTED_IN]->(apocalypsenow) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN {key: 'Value', listKey: [{inner: 'Map1'}, {inner: 'Map2'}]} ---- @@ -54,6 +86,29 @@ RETURN {key: 'Value', listKey: [{inner: 'Map1'}, {inner: 'Map2'}]} 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallstreet), + (charlie)-[:ACTED_IN]->(reddawn), + (charlie)-[:ACTED_IN]->(apocalypsenow), + (martin)-[:ACTED_IN]->(wallstreet), + (martin)-[:ACTED_IN]->(apocalypsenow) + +]]> +++++ +endif::nonhtmloutput[] [[cypher-map-projection]] == Map projection @@ -62,6 +117,7 @@ Cypher supports a concept called "map projections". It allows for easily constructing map projections from nodes, relationships and other map values. A map projection begins with the variable bound to the graph entity to be projected from, and contains a body of comma-separated map elements, enclosed by `{` and `}`. + `+map_variable {map_element, [, ...n]}+` @@ -73,6 +129,7 @@ There exist four different types of map projection elements: * Variable selector - Projects a variable, with the variable name as the key, and the value the variable is pointing to as the value of the projection. Its syntax is just the variable. * All-properties selector - projects all key-value pairs from the `map_variable` value. + The following conditions apply: * If the `map_variable` points to a `null` value, the whole map projection will evaluate to `null`. @@ -85,22 +142,9 @@ The following conditions apply: Find *'Charlie Sheen'* and return data about him and the movies he has acted in. This example shows an example of map projection with a literal entry, which in turn also uses map projection inside the aggregating `collect()`. -//// -CREATE - (charlie:Person {name: 'Charlie Sheen', realName: 'Carlos Irwin Estévez'}), - (martin:Person {name: 'Martin Sheen'}), - (wallstreet:Movie {title: 'Wall Street', year: 1987}), - (reddawn:Movie {title: 'Red Dawn', year: 1984}), - (apocalypsenow:Movie {title: 'Apocalypse Now', year: 1979}), - (charlie)-[:ACTED_IN]->(wallstreet), - (charlie)-[:ACTED_IN]->(reddawn), - (charlie)-[:ACTED_IN]->(apocalypsenow), - (martin)-[:ACTED_IN]->(wallstreet), - (martin)-[:ACTED_IN]->(apocalypsenow) -//// .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (actor:Person {name: 'Charlie Sheen'})-[:ACTED_IN]->(movie:Movie) RETURN actor{.name, .realName, movies: collect(movie{.title, .year})} @@ -114,10 +158,12 @@ RETURN actor{.name, .realName, movies: collect(movie{.title, .year})} 1+d|Rows: 1 |=== -Find all persons that have acted in movies, and show number for each. -This example introduces an variable with the count, and uses a variable selector to project the value. - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(apocalypsenow), (martin)-[:ACTED_IN]->(wallstreet), (martin)-[:ACTED_IN]->(apocalypsenow) -//// + +]]>(movie:Movie) +RETURN actor{.name, .realName, movies: collect(movie{.title, .year})} +]]> +++++ +endif::nonhtmloutput[] + +Find all persons that have acted in movies, and show number for each. +This example introduces an variable with the count, and uses a variable selector to project the value. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (actor:Person)-[:ACTED_IN]->(movie:Movie) WITH actor, count(movie) AS nbrOfMovies @@ -148,11 +204,12 @@ RETURN actor{.name, nbrOfMovies} 1+d|Rows: 2 |=== -Again, focusing on *'Charlie Sheen'*, this time returning all properties from the node. -Here we use an all-properties selector to project all the node properties, and additionally, explicitly project the property `age`. -Since this property does not exist on the node, a `null` value is projected instead. - -//// +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(apocalypsenow), (martin)-[:ACTED_IN]->(wallstreet), (martin)-[:ACTED_IN]->(apocalypsenow) -//// + +]]>(movie:Movie) +WITH actor, count(movie) AS nbrOfMovies +RETURN actor{.name, nbrOfMovies} +]]> +++++ +endif::nonhtmloutput[] + +Again, focusing on *'Charlie Sheen'*, this time returning all properties from the node. +Here we use an all-properties selector to project all the node properties, and additionally, explicitly project the property `age`. +Since this property does not exist on the node, a `null` value is projected instead. + .Query -[source, cypher, indent=0] +[source, cypher] ---- MATCH (actor:Person {name: 'Charlie Sheen'}) RETURN actor{.*, .age} @@ -181,3 +250,28 @@ RETURN actor{.*, .age} 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live +(wallstreet), + (charlie)-[:ACTED_IN]->(reddawn), + (charlie)-[:ACTED_IN]->(apocalypsenow), + (martin)-[:ACTED_IN]->(wallstreet), + (martin)-[:ACTED_IN]->(apocalypsenow) + +]]> +++++ +endif::nonhtmloutput[] + diff --git a/modules/ROOT/pages/syntax/naming.adoc b/modules/ROOT/pages/syntax/naming.adoc index 565a7e55c..4bfb3357f 100644 --- a/modules/ROOT/pages/syntax/naming.adoc +++ b/modules/ROOT/pages/syntax/naming.adoc @@ -1,12 +1,6 @@ -:description: This section describes rules and recommendations for the naming of node labels, relationship types, property names, variables, indexes, and constraints. - [[cypher-naming]] = Naming rules and recommendations - -[abstract] --- -This section describes rules and recommendations for the naming of node labels, relationship types, property names, variables, indexes, and constraints. --- +:description: This section describes rules and recommendations for the naming of node labels, relationship types, property names, variables, indexes, and constraints. == Naming rules @@ -17,7 +11,7 @@ This section describes rules and recommendations for the naming of node labels, ** Names should not begin with a number. ** To illustrate, `1first` is not allowed, whereas `first1` is allowed. * Symbols: -** Names should not contain symbols, except for underscore, as in `my_variable`, or `$` as the first character to denote a xref::syntax/parameters.adoc[parameter], as given by `$myParam`. +** Names should not contain symbols, except for underscore, as in `my_variable`, or `$` as the first character to denote a xref:syntax/parameters.adoc[parameter], as given by `$myParam`. * Length: ** Can be very long, up to `65535` (`2^16 - 1`) or `65534` characters, depending on the version of Neo4j. * Case-sensitive: @@ -50,4 +44,3 @@ Here are the recommended naming conventions: | Node labels | Camel-case, beginning with an upper-case character | `:VehicleOwner` rather than `:vehicle_owner` etc. | Relationship types | Upper-case, using underscore to separate words | `:OWNS_VEHICLE` rather than `:ownsVehicle` etc. |=== - diff --git a/modules/ROOT/pages/syntax/operators.adoc b/modules/ROOT/pages/syntax/operators.adoc index c789794e6..4b0b591eb 100644 --- a/modules/ROOT/pages/syntax/operators.adoc +++ b/modules/ROOT/pages/syntax/operators.adoc @@ -1,49 +1,44 @@ -:description: This section contains an overview of operators. [[query-operators]] = Operators - -[abstract] --- -This section contains an overview of operators. --- - -* xref::syntax/operators.adoc#query-operators-summary[Operators at a glance] -* xref::syntax/operators.adoc#query-operators-aggregation[Aggregation operators] -** xref::syntax/operators.adoc#syntax-using-the-distinct-operator[Using the `DISTINCT` operator] -* xref::syntax/operators.adoc#query-operators-property[Property operators] -** xref::syntax/operators.adoc#syntax-accessing-the-property-of-a-node-or-relationship[Statically accessing a property of a node or relationship using the `.` operator] -** xref::syntax/operators.adoc#syntax-filtering-on-a-dynamically-computed-property-key[Filtering on a dynamically-computed property key using the `[\]` operator] -** xref::syntax/operators.adoc#syntax-property-replacement-operator[Replacing all properties of a node or relationship using the `=` operator] -** xref::syntax/operators.adoc#syntax-property-mutation-operator[Mutating specific properties of a node or relationship using the `+=` operator] -* xref::syntax/operators.adoc#query-operators-mathematical[Mathematical operators] -** xref::syntax/operators.adoc#syntax-using-the-exponentiation-operator[Using the exponentiation operator `^`] -** xref::syntax/operators.adoc#syntax-using-the-unary-minus-operator[Using the unary minus operator `-`] -* xref::syntax/operators.adoc#query-operators-comparison[Comparison operators] -** xref::syntax/operators.adoc#syntax-comparing-two-numbers[Comparing two numbers] -** xref::syntax/operators.adoc#syntax-using-starts-with-to-filter-names[Using `STARTS WITH` to filter names] -** xref::syntax/operators.adoc#cypher-comparison[Equality and comparison of values] -** xref::syntax/operators.adoc#cypher-ordering[Ordering and comparison of values] -** xref::syntax/operators.adoc#cypher-operations-chaining[Chaining comparison operations] -** xref::syntax/operators.adoc#syntax-using-a-regular-expression-to-filter-words[Using a regular expression with `=~` to filter words] -* xref::syntax/operators.adoc#query-operators-boolean[Boolean operators] -** xref::syntax/operators.adoc#syntax-using-boolean-operators-to-filter-numbers[Using boolean operators to filter numbers] -* xref::syntax/operators.adoc#query-operators-string[String operators] -** xref::syntax/operators.adoc#syntax-concatenating-two-strings[Concatenating two strings using `+`] -* xref::syntax/operators.adoc#query-operators-temporal[Temporal operators] -** xref::syntax/operators.adoc#syntax-add-subtract-duration-to-temporal-instant[Adding and subtracting a _Duration_ to or from a temporal instant] -** xref::syntax/operators.adoc#syntax-add-subtract-duration-to-duration[Adding and subtracting a _Duration_ to or from another _Duration_] -** xref::syntax/operators.adoc#syntax-multiply-divide-duration-number[Multiplying and dividing a _Duration_ with or by a number] -* xref::syntax/operators.adoc#query-operators-map[Map operators] -** xref::syntax/operators.adoc#syntax-accessing-the-value-of-a-nested-map[Statically accessing the value of a nested map by key using the `.` operator"] -** xref::syntax/operators.adoc#syntax-accessing-dynamic-map-parameter[Dynamically accessing the value of a map by key using the `[\]` operator and a parameter] -* xref::syntax/operators.adoc#query-operators-list[List operators] -** xref::syntax/operators.adoc#syntax-concatenating-two-lists[Concatenating two lists using `+`] -** xref::syntax/operators.adoc#syntax-using-in-to-check-if-a-number-is-in-a-list[Using `IN` to check if a number is in a list] -** xref::syntax/operators.adoc#syntax-using-in-for-more-complex-list-membership-operations[Using `IN` for more complex list membership operations] -** xref::syntax/operators.adoc#syntax-accessing-elements-in-a-list[Accessing elements in a list using the `[\]` operator] -** xref::syntax/operators.adoc#syntax-accessing-element-in-a-list-parameter[Dynamically accessing an element in a list using the `[\]` operator and a parameter] -** xref::syntax/operators.adoc#syntax-using-in-with-nested-list-subscripting[Using `IN` with `[\]` on a nested list] - +:description: This section contains an overview of operators. + +* xref:syntax/operators.adoc#query-operators-summary[Operators at a glance] +* xref:syntax/operators.adoc#query-operators-aggregation[Aggregation operators] + ** xref:syntax/operators.adoc#syntax-using-the-distinct-operator[Using the `DISTINCT` operator] +* xref:syntax/operators.adoc#query-operators-property[Property operators] + ** xref:syntax/operators.adoc#syntax-accessing-the-property-of-a-node-or-relationship[Statically accessing a property of a node or relationship using the `.` operator] + ** xref:syntax/operators.adoc#syntax-filtering-on-a-dynamically-computed-property-key[Filtering on a dynamically-computed property key using the `[\]` operator] + ** xref:syntax/operators.adoc#syntax-property-replacement-operator[Replacing all properties of a node or relationship using the `=` operator] + ** xref:syntax/operators.adoc#syntax-property-mutation-operator[Mutating specific properties of a node or relationship using the `+=` operator] +* xref:syntax/operators.adoc#query-operators-mathematical[Mathematical operators] + ** xref:syntax/operators.adoc#syntax-using-the-exponentiation-operator[Using the exponentiation operator `^`] + ** xref:syntax/operators.adoc#syntax-using-the-unary-minus-operator[Using the unary minus operator `-`] +* xref:syntax/operators.adoc#query-operators-comparison[Comparison operators] + ** xref:syntax/operators.adoc#syntax-comparing-two-numbers[Comparing two numbers] + ** xref:syntax/operators.adoc#syntax-using-starts-with-to-filter-names[Using `STARTS WITH` to filter names] + ** xref:syntax/operators.adoc#cypher-comparison[Equality and comparison of values] + ** xref:syntax/operators.adoc#cypher-ordering[Ordering and comparison of values] + ** xref:syntax/operators.adoc#cypher-operations-chaining[Chaining comparison operations] + ** xref:syntax/operators.adoc#syntax-using-a-regular-expression-to-filter-words[Using a regular expression with `=~` to filter words] +* xref:syntax/operators.adoc#query-operators-boolean[Boolean operators] + ** xref:syntax/operators.adoc#syntax-using-boolean-operators-to-filter-numbers[Using boolean operators to filter numbers] +* xref:syntax/operators.adoc#query-operators-string[String operators] + ** xref:syntax/operators.adoc#syntax-concatenating-two-strings[Concatenating two strings using `+`] +* xref:syntax/operators.adoc#query-operators-temporal[Temporal operators] + ** xref:syntax/operators.adoc#syntax-add-subtract-duration-to-temporal-instant[Adding and subtracting a _Duration_ to or from a temporal instant] + ** xref:syntax/operators.adoc#syntax-add-subtract-duration-to-duration[Adding and subtracting a _Duration_ to or from another _Duration_] + ** xref:syntax/operators.adoc#syntax-multiply-divide-duration-number[Multiplying and dividing a _Duration_ with or by a number] +* xref:syntax/operators.adoc#query-operators-map[Map operators] + ** xref:syntax/operators.adoc#syntax-accessing-the-value-of-a-nested-map[Statically accessing the value of a nested map by key using the `.` operator"] + ** xref:syntax/operators.adoc#syntax-accessing-dynamic-map-parameter[Dynamically accessing the value of a map by key using the `[\]` operator and a parameter] +* xref:syntax/operators.adoc#query-operators-list[List operators] + ** xref:syntax/operators.adoc#syntax-concatenating-two-lists[Concatenating two lists using `+`] + ** xref:syntax/operators.adoc#syntax-using-in-to-check-if-a-number-is-in-a-list[Using `IN` to check if a number is in a list] + ** xref:syntax/operators.adoc#syntax-using-in-for-more-complex-list-membership-operations[Using `IN` for more complex list membership operations] + ** xref:syntax/operators.adoc#syntax-accessing-elements-in-a-list[Accessing elements in a list using the `[\]` operator] + ** xref:syntax/operators.adoc#syntax-accessing-element-in-a-list-parameter[Dynamically accessing an element in a list using the `[\]` operator and a parameter] + ** xref:syntax/operators.adoc#syntax-using-in-with-nested-list-subscripting[Using `IN` with `[\]` on a nested list] + [[query-operators-summary]] == Operators at a glance @@ -51,16 +46,16 @@ This section contains an overview of operators. [subs=none] |=== -| xref::syntax/operators.adoc#query-operators-aggregation[Aggregation operators] | `DISTINCT` -| xref::syntax/operators.adoc#query-operators-property[Property operators] | `.` for static property access, `[]` for dynamic property access, `=` for replacing all properties, `+=` for mutating specific properties -| xref::syntax/operators.adoc#query-operators-mathematical[Mathematical operators] | `+`, `-`, `*`, `/`, `%`, `^` -| xref::syntax/operators.adoc#query-operators-comparison[Comparison operators] | `+=+`, `+<>+`, `+<+`, `+>+`, `+<=+`, `+>=+`, `IS NULL`, `IS NOT NULL` -| xref::syntax/operators.adoc#query-operators-comparison[String-specific comparison operators] | `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `=~` (regex matching) -| xref::syntax/operators.adoc#query-operators-boolean[Boolean operators] | `AND`, `OR`, `XOR`, `NOT` -| xref::syntax/operators.adoc#query-operators-string[String operators] | `+` (string concatenation) -| xref::syntax/operators.adoc#query-operators-temporal[Temporal operators] | `+` and `-` for operations between durations and temporal instants/durations, `*` and `/` for operations between durations and numbers -| xref::syntax/operators.adoc#query-operators-map[Map operators] | `.` for static value access by key, `[]` for dynamic value access by key -| xref::syntax/operators.adoc#query-operators-list[List operators] | `+` (list concatenation), `IN` to check existence of an element in a list, `[]` for accessing element(s) dynamically +| xref:syntax/operators.adoc#query-operators-aggregation[Aggregation operators] | `DISTINCT` +| xref:syntax/operators.adoc#query-operators-property[Property operators] | `.` for static property access, `[]` for dynamic property access, `=` for replacing all properties, `+=` for mutating specific properties +| xref:syntax/operators.adoc#query-operators-mathematical[Mathematical operators] | `+`, `-`, `*`, `/`, `%`, `^` +| xref:syntax/operators.adoc#query-operators-comparison[Comparison operators] | `=`, `<>`, `<`, `>`, `+<=+`, `>=`, `IS NULL`, `IS NOT NULL` +| xref:syntax/operators.adoc#query-operators-comparison[String-specific comparison operators] | `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `=~` for regex matching +| xref:syntax/operators.adoc#query-operators-boolean[Boolean operators] | `AND`, `OR`, `XOR`, `NOT` +| xref:syntax/operators.adoc#query-operators-string[String operators] | `+` for concatenation +| xref:syntax/operators.adoc#query-operators-temporal[Temporal operators] | `+` and `-` for operations between durations and temporal instants/durations, `*` and `/` for operations between durations and numbers +| xref:syntax/operators.adoc#query-operators-map[Map operators] | `.` for static value access by key, `[]` for dynamic value access by key +| xref:syntax/operators.adoc#query-operators-list[List operators] | `+` for concatenation, `IN` to check existence of an element in a list, `[]` for accessing element(s) dynamically |=== @@ -76,8 +71,9 @@ The aggregation operators comprise: Retrieve the unique eye colors from `Person` nodes. + .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (a:Person {name: 'Anne', eyeColor: 'blue'}), @@ -102,8 +98,26 @@ Properties set: 6 + Labels added: 3 |=== -`DISTINCT` is commonly used in conjunction with xref::functions/aggregating.adoc[aggregating functions]. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] +`DISTINCT` is commonly used in conjunction with xref:functions/aggregating.adoc[aggregating functions]. [[query-operators-property]] == Property operators @@ -115,12 +129,12 @@ The property operators pertain to a node or a relationship, and comprise: * property replacement `=` for replacing all properties of a node or relationship * property mutation operator `+=` for setting specific properties of a node or relationship - [[syntax-accessing-the-property-of-a-node-or-relationship]] === Statically accessing a property of a node or relationship using the `.` operator + .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (a:Person {name: 'Jane', livesIn: 'London'}), @@ -142,12 +156,30 @@ Properties set: 4 + Labels added: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[syntax-filtering-on-a-dynamically-computed-property-key]] === Filtering on a dynamically-computed property key using the `[]` operator + .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (a:Restaurant {name: 'Hungry Jo', rating_hygiene: 10, rating_food: 7}), @@ -171,19 +203,42 @@ Properties set: 8 + Labels added: 4 |=== -See xref::clauses/where.adoc#query-where-basic[Basic usage] for more details on dynamic property access. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + 6 +RETURN DISTINCT restaurant.name +]]> +++++ +endif::nonhtmloutput[] + +See xref:clauses/where.adoc#query-where-basic[Basic usage] for more details on dynamic property access. [NOTE] ==== -The behavior of the `[]` operator with respect to `null` is detailed xref::syntax/working-with-null.adoc#cypher-null-bracket-operator[here]. -==== +The behavior of the `[]` operator with respect to `null` is detailed xref:syntax/working-with-null.adoc#cypher-null-bracket-operator[here]. + +==== [[syntax-property-replacement-operator]] === Replacing all properties of a node or relationship using the `=` operator + .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (a:Person {name: 'Jane', age: 20}) WITH a @@ -205,14 +260,31 @@ Properties set: 5 + Labels added: 1 |=== -See xref::clauses/set.adoc#set-replace-properties-using-map[Replace all properties using a map and `=`] for more details on using the property replacement operator `=`. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] +See xref:clauses/set.adoc#set-replace-properties-using-map[Replace all properties using a map and `=`] for more details on using the property replacement operator `=`. [[syntax-property-mutation-operator]] === Mutating specific properties of a node or relationship using the `+=` operator + .Query -[source, cypher, indent=0] +[source, cypher] ---- CREATE (a:Person {name: 'Jane', age: 20}) WITH a @@ -234,8 +306,24 @@ Properties set: 4 + Labels added: 1 |=== -See xref::clauses/set.adoc#set-setting-properties-using-map[Mutate specific properties using a map and `+=`] for more details on using the property mutation operator `+=`. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] +See xref:clauses/set.adoc#set-setting-properties-using-map[Mutate specific properties using a map and `+=`] for more details on using the property mutation operator `+=`. [[query-operators-mathematical]] == Mathematical operators @@ -249,12 +337,12 @@ The mathematical operators comprise: * modulo division: `%` * exponentiation: `^` - [[syntax-using-the-exponentiation-operator]] === Using the exponentiation operator `^` + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH 2 AS number, 3 AS exponent RETURN number ^ exponent AS result @@ -268,12 +356,26 @@ RETURN number ^ exponent AS result 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[syntax-using-the-unary-minus-operator]] === Using the unary minus operator `-` + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH -3 AS a, 4 AS b RETURN b - a AS result @@ -287,35 +389,48 @@ RETURN b - a AS result 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[query-operators-comparison]] == Comparison operators The comparison operators comprise: -* equality: `+=+` -* inequality: `+<>+` -* less than: `+<+` -* greater than: `+>+` -* less than or equal to: `+<=+` -* greater than or equal to: `+>=+` +* equality: `=` +* inequality: `<>` +* less than: `<` +* greater than: `>` +* less than or equal to: `\<=` +* greater than or equal to: `>=` * `IS NULL` * `IS NOT NULL` - [[query-operator-comparison-string-specific]] === String-specific comparison operators comprise: * `STARTS WITH`: perform case-sensitive prefix searching on strings * `ENDS WITH`: perform case-sensitive suffix searching on strings * `CONTAINS`: perform case-sensitive inclusion searching in strings -* `=~`: regular expression for matching a pattern +* `=~`: matching a regular expression [[syntax-comparing-two-numbers]] === Comparing two numbers + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH 4 AS one, 3 AS two RETURN one > two AS result @@ -329,14 +444,28 @@ RETURN one > two AS result 1+d|Rows: 1 |=== -See xref::syntax/operators.adoc#cypher-comparison[] for more details on the behavior of comparison operators, and xref::clauses/where.adoc#query-where-ranges[Using ranges] for more examples showing how these may be used. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + two AS result +]]> +++++ +endif::nonhtmloutput[] +See xref:syntax/operators.adoc#cypher-comparison[] for more details on the behavior of comparison operators, and xref:clauses/where.adoc#query-where-ranges[Using ranges] for more examples showing how these may be used. [[syntax-using-starts-with-to-filter-names]] === Using `STARTS WITH` to filter names + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH ['John', 'Mark', 'Jonathan', 'Bill'] AS somenames UNWIND somenames AS names @@ -354,15 +483,31 @@ RETURN candidate 1+d|Rows: 2 |=== -xref::clauses/where.adoc#query-where-string[String matching] contains more information regarding the string-specific comparison operators as well as additional examples illustrating the usage thereof. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] +xref:clauses/where.adoc#query-where-string[String matching] contains more information regarding the string-specific comparison operators as well as additional examples illustrating the usage thereof. [[cypher-comparison]] -=== Equality and comparison of values +== Equality and comparison of values -==== Equality +=== Equality -Cypher supports comparing values (see xref::syntax/values.adoc[]) by equality using the `=` and `<>` operators. +Cypher supports comparing values (see xref:syntax/values.adoc[]) by equality using the `=` and `<>` operators. Values of the same type are only equal if they are the same identical value (e.g. `3 = 3` and `"x" <> "xy"`). @@ -371,10 +516,9 @@ Maps are only equal if they map exactly the same keys to equal values and lists Values of different types are considered as equal according to the following rules: * Paths are treated as lists of alternating nodes and relationships and are equal to all lists that contain that very same sequence of nodes and relationships. -* Testing any value against `null` with both the `=` and the `<>` operators always evaluates to `null`. +* Testing any value against `null` with both the `=` and the `<>` operators always is `null`. This includes `null = null` and `null <> null`. -The only way to reliably test if a value `v` is `null` is by using the special `v IS NULL`, or `v IS NOT NULL`, equality operators. -`v IS NOT NULL` is equivalent to `NOT(v IS NULL)`. +The only way to reliably test if a value `v` is `null` is by using the special `v IS NULL`, or `v IS NOT NULL` equality operators. All other combinations of types of values cannot be compared with each other. Especially, nodes, relationships, and literal maps are incomparable with each other. @@ -383,14 +527,13 @@ It is an error to compare values that cannot be compared. [[cypher-ordering]] -=== Ordering and comparison of values +== Ordering and comparison of values -The comparison operators `+<=+`, `+<+` (for ascending) and `+>=+`, `+>+` (for descending) are used to compare values for ordering. +The comparison operators `\<=`, `<` (for ascending) and `>=`, `>` (for descending) are used to compare values for ordering. The following points give some details on how the comparison is performed. * Numerical values are compared for ordering using numerical order (e.g. `3 < 4` is true). -* All comparability tests (`+<+`, `+<=+`, `+>+`, `+>=+`) with `java.lang.Double.NaN` evaluate as false. -For example, `1 > b` and `1 < b` are both false when b is NaN. +* The special value `java.lang.Double.NaN` is regarded as being larger than all other numbers. * String values are compared for ordering using lexicographic order (e.g. `"x" < "xy"`). * Boolean values are compared for ordering such that `false < true`. * *Comparison* of spatial values: @@ -401,18 +544,18 @@ For example, `1 > b` and `1 < b` are both false when b is NaN. * *Ordering* of spatial values: ** `ORDER BY` requires all values to be orderable. ** Points are ordered after arrays and before temporal types. - ** Points of different CRS are ordered by the CRS code (the value of SRID field). For the currently supported set of xref::syntax/spatial.adoc#cypher-spatial-crs[Coordinate Reference Systems] this means the order: 4326, 4979, 7302, 9157 + ** Points of different CRS are ordered by the CRS code (the value of SRID field). For the currently supported set of xref:syntax/spatial.adoc#cypher-spatial-crs[Coordinate Reference Systems] this means the order: 4326, 4979, 7302, 9157 ** Points of the same CRS are ordered by each coordinate value in turn, `x` first, then `y` and finally `z`. ** Note that this order is different to the order returned by the spatial index, which will be the order of the space filling curve. * *Comparison* of temporal values: - ** xref::syntax/temporal.adoc#cypher-temporal-instants[Temporal instant values] are comparable within the same type. + ** xref:syntax/temporal.adoc#cypher-temporal-instants[Temporal instant values] are comparable within the same type. An instant is considered less than another instant if it occurs before that instant in time, and it is considered greater than if it occurs after. ** Instant values that occur at the same point in time -- but that have a different time zone -- are not considered equal, and must therefore be ordered in some predictable way. Cypher prescribes that, after the primary order of point in time, instant values be ordered by effective time zone offset, from west (negative offset from UTC) to east (positive offset from UTC). This has the effect that times that represent the same point in time will be ordered with the time with the earliest local time first. If two instant values represent the same point in time, and have the same time zone offset, but a different named time zone (this is possible for _DateTime_ only, since _Time_ only has an offset), these values are not considered equal, and ordered by the time zone identifier, alphabetically, as its third ordering component. If the type, point in time, offset, and time zone name are all equal, then the values are equal, and any difference in order is impossible to observe. - ** xref::syntax/temporal.adoc#cypher-temporal-durations[_Duration_] values cannot be compared, since the length of a _day_, _month_ or _year_ is not known without knowing which _day_, _month_ or _year_ it is. + ** xref:syntax/temporal.adoc#cypher-temporal-durations[_Duration_] values cannot be compared, since the length of a _day_, _month_ or _year_ is not known without knowing which _day_, _month_ or _year_ it is. Since _Duration_ values are not comparable, the result of applying a comparison operator between two _Duration_ values is `null`. * *Ordering* of temporal values: ** `ORDER BY` requires all values to be orderable. @@ -426,45 +569,43 @@ For example, `1 > b` and `1 < b` are both false when b is NaN. * Comparing for ordering when one argument is `null` (e.g. `null < 3` is `null`). * *Ordering* of values with *different* types: ** The ordering is, in ascending order, defined according to the following list: - *** xref::syntax/maps.adoc#cypher-literal-maps[`Map`] - *** xref::syntax/values.adoc#structural-types[`Node`] - *** xref::syntax/values.adoc#structural-types[`Relationship`] - *** xref::syntax/lists.adoc[`List`] - *** xref::syntax/patterns.adoc#cypher-pattern-path-variables[`Path`] - *** xref::syntax/temporal.adoc[`DateTime`] - *** xref::syntax/temporal.adoc[`LocalDateTime`] - *** xref::syntax/temporal.adoc[`Date`] - *** xref::syntax/temporal.adoc[`Time`] - *** xref::syntax/temporal.adoc[`LocalTime`] - *** xref::syntax/temporal.adoc[`Duration`] - *** xref::syntax/expressions.adoc#cypher-expressions-general[`String`] - *** xref::syntax/expressions.adoc#cypher-expressions-general[`Boolean`] - *** xref::syntax/expressions.adoc#cypher-expressions-general[`Number`] + *** xref:syntax/maps.adoc#cypher-literal-maps[`Map`] + *** xref:syntax/values.adoc#structural-types[`Node`] + *** xref:syntax/values.adoc#structural-types[`Relationship`] + *** xref:syntax/lists.adoc[`List`] + *** xref:syntax/patterns.adoc#cypher-pattern-path-variables[`Path`] + *** xref:syntax/temporal.adoc[`DateTime`] + *** xref:syntax/temporal.adoc[`LocalDateTime`] + *** xref:syntax/temporal.adoc[`Date`] + *** xref:syntax/temporal.adoc[`Time`] + *** xref:syntax/temporal.adoc[`LocalTime`] + *** xref:syntax/temporal.adoc[`Duration`] + *** xref:syntax/expressions.adoc#cypher-expressions-general[`String`] + *** xref:syntax/expressions.adoc#cypher-expressions-general[`Boolean`] + *** xref:syntax/expressions.adoc#cypher-expressions-general[`Number`] ** The value `null` is considered larger than any value. * *Ordering* of composite type values: - ** For the xref::syntax/values.adoc#composite-types[composite types] (e.g. maps and lists), elements of the containers are compared pairwise for ordering and thus determine the ordering of two container types. + ** For the xref:syntax/values.adoc#composite-types[composite types] (e.g. maps and lists), elements of the containers are compared pairwise for ordering and thus determine the ordering of two container types. For example, `[1, 'foo', 3]` is ordered before `[1, 2, 'bar']` since `'foo'` is ordered before `2`. - [[cypher-operations-chaining]] -=== Chaining comparison operations - -Comparisons can be chained arbitrarily, e.g., `+x < y <= z+` is equivalent to `+x < y AND y <= z+`. +== Chaining comparison operations +Comparisons can be chained arbitrarily, e.g., `x < y \<= z` is equivalent to `x < y AND y \<= z`. -Formally, if `+a, b, c, ..., y, z+` are expressions and `+op1, op2, ..., opN+` are comparison operators, then `+a op1 b op2 c ... y opN z+` is equivalent to `+a op1 b and b op2 c and ... y opN z+`. +Formally, if `a, b, c, \..., y, z` are expressions and `op1, op2, \..., opN` are comparison operators, then `a op1 b op2 c \... y opN z` is equivalent to `a op1 b and b op2 c and \... y opN z`. Note that `a op1 b op2 c` does not imply any kind of comparison between `a` and `c`, so that, e.g., `x < y > z` is perfectly legal (although perhaps not elegant). The example: -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE 21 < n.age <= 30 RETURN n ---- is equivalent to -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE 21 < n.age AND n.age <= 30 RETURN n ---- @@ -482,24 +623,24 @@ This means that `1=1=true` is equivalent to `1=1 AND 1=true` and not to `(1=1)=t For example: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- a < b = c <= d <> e ---- Is equivalent to: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- a < b AND b = c AND c <= d AND d <> e ---- - [[syntax-using-a-regular-expression-to-filter-words]] -=== Using a regular expression with `=~` to filter words +== Using a regular expression with `=~` to filter words + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH ['mouse', 'chair', 'door', 'house'] AS wordlist UNWIND wordlist AS word @@ -517,8 +658,24 @@ RETURN word 1+d|Rows: 2 |=== -Further information and examples regarding the use of regular expressions in filtering can be found in xref::clauses/where.adoc#query-where-regex[Regular expressions]. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] +Further information and examples regarding the use of regular expressions in filtering can be found in xref:clauses/where.adoc#query-where-regex[Regular expressions]. [[query-operators-boolean]] == Boolean operators @@ -550,8 +707,9 @@ Here is the truth table for `AND`, `OR`, `XOR` and `NOT`. [[syntax-using-boolean-operators-to-filter-numbers]] === Using boolean operators to filter numbers + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH [2, 4, 7, 9, 12] AS numberlist UNWIND numberlist AS number @@ -570,6 +728,22 @@ RETURN number 1+d|Rows: 3 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + 6 AND number < 10) +RETURN number +]]> +++++ +endif::nonhtmloutput[] [[query-operators-string]] == String operators @@ -578,10 +752,10 @@ The string operators comprise: * concatenating strings: `+` - [[syntax-concatenating-two-strings]] === Concatenating two strings with `+` + .Query [source, cypher] ---- @@ -596,13 +770,25 @@ RETURN 'neo' + '4j' AS result 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[query-operators-temporal]] == Temporal operators Temporal operators comprise: -* adding a xref::syntax/temporal.adoc#cypher-temporal-durations[_Duration_] to either a xref::syntax/temporal.adoc#cypher-temporal-instants[temporal instant] or another _Duration_: `+` +* adding a xref:syntax/temporal.adoc#cypher-temporal-durations[_Duration_] to either a xref:syntax/temporal.adoc#cypher-temporal-instants[temporal instant] or another _Duration_: `+` * subtracting a _Duration_ from either a temporal instant or another _Duration_: `-` * multiplying a _Duration_ with a number: `*` * dividing a _Duration_ by a number: `/` @@ -612,55 +798,24 @@ The following table shows -- for each combination of operation and operand type [options="header"] |=== | Operator | Left-hand operand | Right-hand operand | Type of result - -| xref::syntax/operators.adoc#syntax-add-subtract-duration-to-temporal-instant[`+`] -| Temporal instant -| _Duration_ -| The type of the temporal instant - -| xref::syntax/operators.adoc#syntax-add-subtract-duration-to-temporal-instant[`+`] -| _Duration_ -| Temporal instant -| The type of the temporal instant - -| xref::syntax/operators.adoc#syntax-add-subtract-duration-to-temporal-instant[`-`] -| Temporal instant -| _Duration_ -| The type of the temporal instant - -| xref::syntax/operators.adoc#syntax-add-subtract-duration-to-duration[`+`] -| _Duration_ -| _Duration_ -| _Duration_ - -| xref::syntax/operators.adoc#syntax-add-subtract-duration-to-duration[`-`] -| _Duration_ -| _Duration_ -| _Duration_ - -| xref::syntax/operators.adoc#syntax-multiply-divide-duration-number[`*`] -| _Duration_ -| xref::syntax/values.adoc#property-types[Number] -| _Duration_ - -| xref::syntax/operators.adoc#syntax-multiply-divide-duration-number[`*`] -| xref::syntax/values.adoc#property-types[Number] -| _Duration_ -| _Duration_ - -| xref::syntax/operators.adoc#syntax-multiply-divide-duration-number[`/`] -| _Duration_ -| xref::syntax/values.adoc#property-types[Number] -| _Duration_ - +| xref:syntax/operators.adoc#syntax-add-subtract-duration-to-temporal-instant[`+`] | Temporal instant | _Duration_ | The type of the temporal instant +| xref:syntax/operators.adoc#syntax-add-subtract-duration-to-temporal-instant[`+`] | _Duration_ | Temporal instant | The type of the temporal instant +| xref:syntax/operators.adoc#syntax-add-subtract-duration-to-temporal-instant[`-`] | Temporal instant | _Duration_ | The type of the temporal instant +| xref:syntax/operators.adoc#syntax-add-subtract-duration-to-duration[`+`] | _Duration_ | _Duration_ | _Duration_ +| xref:syntax/operators.adoc#syntax-add-subtract-duration-to-duration[`-`] | _Duration_ | _Duration_ | _Duration_ +| xref:syntax/operators.adoc#syntax-multiply-divide-duration-number[`*`] | _Duration_ | xref:syntax/values.adoc#property-types[Number] | _Duration_ +| xref:syntax/operators.adoc#syntax-multiply-divide-duration-number[`*`] | xref:syntax/values.adoc#property-types[Number] | _Duration_ | _Duration_ +| xref:syntax/operators.adoc#syntax-multiply-divide-duration-number[`/`] | _Duration_ | xref:syntax/values.adoc#property-types[Number] | _Duration_ |=== + [[syntax-add-subtract-duration-to-temporal-instant]] === Adding and subtracting a _Duration_ to or from a temporal instant + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH localdatetime({year:1984, month:10, day:11, hour:12, minute:31, second:14}) AS aDateTime, @@ -676,11 +831,29 @@ RETURN aDateTime + aDuration, aDateTime - aDuration 2+d|Rows: 1 |=== -xref::syntax/temporal.adoc#cypher-temporal-duration-component[Components of a _Duration_] that do not apply to the temporal instant are ignored. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + +xref:syntax/temporal.adoc#cypher-temporal-duration-component[Components of a _Duration_] that do not apply to the temporal instant are ignored. For example, when adding a _Duration_ to a _Date_, the _hours_, _minutes_, _seconds_ and _nanoseconds_ of the _Duration_ are ignored (_Time_ behaves in an analogous manner): + + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH date({year:1984, month:10, day:11}) AS aDate, @@ -696,11 +869,28 @@ RETURN aDate + aDuration, aDate - aDuration 2+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + Adding two durations to a temporal instant is not an associative operation. This is because non-existing dates are truncated to the nearest existing date: + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN (date("2011-01-31") + duration("P1M")) + duration("P12M") AS date1, @@ -715,12 +905,27 @@ RETURN 2+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[syntax-add-subtract-duration-to-duration]] === Adding and subtracting a _Duration_ to or from another _Duration_ + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH duration({years: 12, months: 5, days: 14, hours: 16, minutes: 12, seconds: 70, nanoseconds: 1}) as duration1, @@ -736,14 +941,30 @@ RETURN duration1, duration2, duration1 + duration2, duration1 - duration2 4+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[syntax-multiply-divide-duration-number]] === Multiplying and dividing a _Duration_ with or by a number These operations are interpreted simply as component-wise operations with overflow to smaller units based on an average length of units in the case of division (and multiplication with fractions). + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH duration({days: 14, minutes: 12, seconds: 70, nanoseconds: 1}) AS aDuration RETURN aDuration, aDuration * 2, aDuration / 3 @@ -757,6 +978,19 @@ RETURN aDuration, aDuration * 2, aDuration / 3 3+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[query-operators-map]] == Map operators @@ -769,15 +1003,17 @@ The map operators comprise: [NOTE] ==== -The behavior of the `[]` operator with respect to `null` is detailed in xref::syntax/working-with-null.adoc#cypher-null-bracket-operator[]. -==== +The behavior of the `[]` operator with respect to `null` is detailed in xref:syntax/working-with-null.adoc#cypher-null-bracket-operator[]. +==== + [[syntax-accessing-the-value-of-a-nested-map]] === Statically accessing the value of a nested map by key using the `.` operator + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH {person: {name: 'Anne', age: 25}} AS p RETURN p.person.name @@ -791,22 +1027,37 @@ RETURN p.person.name 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[syntax-accessing-dynamic-map-parameter]] === Dynamically accessing the value of a map by key using the `[]` operator and a parameter A parameter may be used to specify the key of the value to access: + .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { "myKey" : "name" } ---- + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH {name: 'Anne', age: 25} AS a RETURN a[$myKey] AS result @@ -820,8 +1071,21 @@ RETURN a[$myKey] AS result 1+d|Rows: 1 |=== -More details on maps can be found in xref::syntax/maps.adoc[Maps]. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] +More details on maps can be found in xref:syntax/maps.adoc[Maps]. [[query-operators-list]] == List operators @@ -834,15 +1098,17 @@ The list operators comprise: [NOTE] ==== -The behavior of the `IN` and `[]` operators with respect to `null` is detailed xref::syntax/working-with-null.adoc[here]. -==== +The behavior of the `IN` and `[]` operators with respect to `null` is detailed xref:syntax/working-with-null.adoc[here]. + +==== [[syntax-concatenating-two-lists]] === Concatenating two lists using `+` + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN [1,2,3,4,5] + [6,7] AS myList ---- @@ -855,12 +1121,25 @@ RETURN [1,2,3,4,5] + [6,7] AS myList 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[syntax-using-in-to-check-if-a-number-is-in-a-list]] === Using `IN` to check if a number is in a list + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH [2, 3, 4, 5] AS numberlist UNWIND numberlist AS number @@ -878,6 +1157,22 @@ RETURN number 1+d|Rows: 2 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[syntax-using-in-for-more-complex-list-membership-operations]] === Using `IN` for more complex list membership operations @@ -887,14 +1182,16 @@ Lists are only comparable to other lists, and elements of a list `innerList` are The following query checks whether or not the list `[2, 1]` is an element of the list `[1, [2, 1], 3]`: + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN [2, 1] IN [1, [2, 1], 3] AS inList ---- The query evaluates to `true` as the right-hand list contains, as an element, the list `[1, 2]` which is of the same type (a list) and contains the same contents (the numbers `2` and `1` in the given order) as the left-hand operand. If the left-hand operator had been `[1, 2]` instead of `[2, 1]`, the query would have returned `false`. + .Result [role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] + At first glance, the contents of the left-hand operand and the right-hand operand _appear_ to be the same in the following query: + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN [1, 2] IN [1, 2] AS inList ---- @@ -922,23 +1233,37 @@ However, `IN` evaluates to `false` as the right-hand operand does not contain an 1+d|Rows: 1 |=== -The following query can be used to ascertain whether or not a list -- obtained from, say, the xref::functions/list.adoc#functions-labels[labels()] function -- contains at least one element that is also present in another list: +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + +The following query can be used to ascertain whether or not a list -- obtained from, say, the xref:functions/list.adoc#functions-labels[labels()] function -- contains at least one element that is also present in another list: -[source, cypher, indent=0] +[source, cypher] ---- MATCH (n) WHERE size([label IN labels(n) WHERE label IN ['Person', 'Employee'] | 1]) > 0 RETURN count(n) ---- -As long as `labels(n)` returns either `Person` or `Employee` (or both), the query will return a value greater than zero. +As long as `labels(n)` returns either `Person` or `Employee` (or both), the query will return a value greater than zero. [[syntax-accessing-elements-in-a-list]] === Accessing elements in a list using the `[]` operator + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH ['Anne', 'John', 'Bill', 'Diane', 'Eve'] AS names RETURN names[1..3] AS result @@ -954,22 +1279,37 @@ The square brackets will extract the elements from the start index `1`, and up t 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[syntax-accessing-element-in-a-list-parameter]] === Dynamically accessing an element in a list using the `[]` operator and a parameter A parameter may be used to specify the index of the element to access: + .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { "myIndex" : 1 } ---- + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH ['Anne', 'John', 'Bill', 'Diane', 'Eve'] AS names RETURN names[$myIndex] AS result @@ -983,22 +1323,37 @@ RETURN names[$myIndex] AS result 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[syntax-using-in-with-nested-list-subscripting]] === Using `IN` with `[]` on a nested list `IN` can be used in conjunction with `[]` to test whether an element exists in a nested list: + .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { "myIndex" : 1 } ---- + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH [[1, 2, 3]] AS l RETURN 3 IN l[0] AS result @@ -1012,5 +1367,19 @@ RETURN 3 IN l[0] AS result 1+d|Rows: 1 |=== -More details on lists can be found in xref::syntax/lists.adoc#cypher-lists-general[Lists in general]. +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + +More details on lists can be found in xref:syntax/lists.adoc#cypher-lists-general[Lists in general]. diff --git a/modules/ROOT/pages/syntax/parameters.adoc b/modules/ROOT/pages/syntax/parameters.adoc index 9d318ce3b..e06300d67 100644 --- a/modules/ROOT/pages/syntax/parameters.adoc +++ b/modules/ROOT/pages/syntax/parameters.adoc @@ -1,12 +1,6 @@ -:description: This section describes parameterized quering. - [[cypher-parameters]] = Parameters - -[abstract] --- -This section describes parameterized quering. --- +:description: This section describes parameterized quering. [[cypher-parameters-introduction]] == Introduction @@ -37,7 +31,7 @@ For example: * For Neo4j Browser use the same syntax as Cypher Shell, `+:param name => 'Joe'+`. * When using drivers, the syntax is dependent on the language choice. See the examples in _Transactions_ in the link:{docs-base-uri}[Neo4j Driver manuals]. -* For usage via the Neo4j HTTP API, see the link:{neo4j-docs-base-uri}/http-api/{page-version}[HTTP API documentation]. +* For usage via the Neo4j HTTP API, see the link:{neo4j-docs-base-uri}/http-api/{page-version}/index#http-api[HTTP API documentation]. We provide below a comprehensive list of examples of parameter usage. In these examples, parameters are given in JSON; the exact manner in which they are to be submitted depends upon the driver being used. @@ -47,10 +41,9 @@ In these examples, parameters are given in JSON; the exact manner in which they The old parameter syntax `+{param}+` was deprecated in Neo4j 3.0 and removed entirely in Neo4j 4.0. Using it will result in a syntax error. However, it is still possible to use it, with warnings, if you prefix the query with `CYPHER 3.5`. -See xref::deprecations-additions-removals-compatibility.adoc#cypher-compatibility[Cypher Compatibility] for further information. +See xref:deprecations-additions-removals-compatibility.adoc#cypher-compatibility[Cypher Compatibility] for further information. ==== - [[cypher-parameters-auto-parameterization]] == Auto-parameterization @@ -69,18 +62,17 @@ This means that any remaining literals will not be turned into parameters. [[cypher-parameters-string-literal]] == String literal -// neo4j-manual-modeling-antora/cypherManual/build/4.4/antora/modules/ROOT/partials/neo4j-cypher-docs/docs/dev/syntax/includes/exampleWithStringLiteralAsParameter.adoc .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { - "name": "Johan" + "name" : "Johan" } ---- .Query -[source,cypher, indent=0] +[source,cypher] ---- MATCH (n:Person) WHERE n.name = $name @@ -89,18 +81,17 @@ RETURN n You can use parameters in this syntax as well: -// neo4j-manual-modeling-antora/cypherManual/build/4.4/antora/modules/ROOT/partials/neo4j-cypher-docs/docs/dev/syntax/includes/exampleWithShortSyntaxStringLiteralAsParameter.adoc .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { - "name": "Johan" + "name" : "Johan" } ---- .Query -[source,cypher, indent=0] +[source,cypher] ---- MATCH (n:Person {name: $name}) RETURN n @@ -110,18 +101,17 @@ RETURN n [[cypher-parameters-regular-expression]] == Regular expression -// neo4j-manual-modeling-antora/cypherManual/build/4.4/antora/modules/ROOT/partials/neo4j-cypher-docs/docs/dev/syntax/includes/exampleWithParameterRegularExpression.adoc .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { - "regex": ".*h.*" + "regex" : ".*h.*" } ---- .Query -[source,cypher, indent=0] +[source,cypher] ---- MATCH (n:Person) WHERE n.name =~ $regex @@ -132,18 +122,17 @@ RETURN n.name [[cypher-parameters-case-sensitive-pattern-matching]] == Case-sensitive string pattern matching -// neo4j-manual-modeling-antora/cypherManual/build/4.4/antora/modules/ROOT/partials/neo4j-cypher-docs/docs/dev/syntax/includes/exampleWithParameterCSCIStringPatternMatching.adoc .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { - "name": "Michael" + "name" : "Michael" } ---- .Query -[source,cypher, indent=0] +[source,cypher] ---- MATCH (n:Person) WHERE n.name STARTS WITH $name @@ -154,19 +143,20 @@ RETURN n.name [[cypher-parameters-create-node-with-properties]] == Create node with properties + .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { - "props": { - "name": "Andy", - "position": "Developer" + "props" : { + "name" : "Andy", + "position" : "Developer" } } ---- .Query -[source,cypher, indent=0] +[source,cypher] ---- CREATE ($props) ---- @@ -175,26 +165,25 @@ CREATE ($props) [[cypher-parameters-create-multiple-nodes-with-properties]] == Create multiple nodes with properties -// neo4j-manual-modeling-antora/cypherManual/build/4.4/antora/modules/ROOT/partials/neo4j-cypher-docs/docs/dev/syntax/includes/create_multiple_nodes_from_map.adoc .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { - "props": [ { - "awesome": true, - "name": "Andy", - "position": "Developer" + "props" : [ { + "awesome" : true, + "name" : "Andy", + "position" : "Developer" }, { - "children": 3, - "name": "Michael", - "position": "Developer" + "children" : 3, + "name" : "Michael", + "position" : "Developer" } ] } ---- .Query -[source,cypher, indent=0] +[source,cypher] ---- UNWIND $props AS properties CREATE (n:Person) @@ -208,21 +197,20 @@ RETURN n Note that this will replace all the current properties. -// neo4j-manual-modeling-antora/cypherManual/build/4.4/antora/modules/ROOT/partials/neo4j-cypher-docs/docs/dev/syntax/includes/set_properties_on_a_node_from_a_map.adoc .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { - "props": { - "name": "Andy", - "position": "Developer" + "props" : { + "name" : "Andy", + "position" : "Developer" } } ---- .Query -[source,cypher, indent=0] +[source,cypher] ---- MATCH (n:Person) WHERE n.name = 'Michaela' @@ -233,19 +221,18 @@ SET n = $props [[cypher-parameters-skip-and-limit]] == `SKIP` and `LIMIT` -// neo4j-manual-modeling-antora/cypherManual/build/4.4/antora/modules/ROOT/partials/neo4j-cypher-docs/docs/dev/syntax/includes/exampleWithParameterForSkipLimit.adoc .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { - "s": 1, - "l": 1 + "s" : 1, + "l" : 1 } ---- .Query -[source,cypher, indent=0] +[source,cypher] ---- MATCH (n:Person) RETURN n.name @@ -257,10 +244,9 @@ LIMIT $l [[cypher-parameters-node-id]] == Node id -// neo4j-manual-modeling-antora/cypherManual/build/4.4/antora/modules/ROOT/partials/neo4j-cypher-docs/docs/dev/syntax/includes/exampleWithParameterForNodeId.adoc .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { "id" : 0 @@ -268,7 +254,7 @@ LIMIT $l ---- .Query -[source,cypher, indent=0] +[source,cypher] ---- MATCH (n) WHERE id(n) = $id @@ -279,10 +265,9 @@ RETURN n.name [[cypher-parameters-multiple-node-ids]] == Multiple node ids -// example with parameter for multiple node IDs .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { "ids" : [ 0, 1, 2 ] @@ -290,7 +275,7 @@ RETURN n.name ---- .Query -[source,cypher, indent=0] +[source,cypher] ---- MATCH (n) WHERE id(n) IN $ids @@ -301,10 +286,9 @@ RETURN n.name [[cypher-parameters-call-procedure]] == Calling procedures -// example with parameter procedure call .Parameters -[source,javascript, indent=0] +[source,javascript] ---- { "indexname" : "My index" @@ -312,8 +296,7 @@ RETURN n.name ---- .Query -[source,cypher, indent=0] +[source,cypher] ---- CALL db.resampleIndex($indexname) ---- - diff --git a/modules/ROOT/pages/syntax/patterns.adoc b/modules/ROOT/pages/syntax/patterns.adoc index a5eec11fc..26cd55dc5 100644 --- a/modules/ROOT/pages/syntax/patterns.adoc +++ b/modules/ROOT/pages/syntax/patterns.adoc @@ -1,13 +1,15 @@ -:description: This section contains an overview of data patterns in Cypher. - [[cypher-patterns]] = Patterns +:description: This section contains an overview of data patterns in Cypher. -[abstract] --- -This section contains an overview of data patterns in Cypher. --- - +* xref:syntax/patterns.adoc#cypher-pattern-node-introduction[Introduction] +* xref:syntax/patterns.adoc#cypher-pattern-node[Patterns for nodes] +* xref:syntax/patterns.adoc#cypher-pattern-related-nodes[Patterns for related nodes] +* xref:syntax/patterns.adoc#cypher-pattern-label[Patterns for labels] +* xref:syntax/patterns.adoc#cypher-pattern-properties[Specifying properties] +* xref:syntax/patterns.adoc#cypher-pattern-relationship[Patterns for relationships] +* xref:syntax/patterns.adoc#cypher-pattern-varlength[Variable-length pattern matching] +* xref:syntax/patterns.adoc#cypher-pattern-path-variables[Assigning to path variables] [[cypher-pattern-node-introduction]] == Introduction @@ -20,14 +22,13 @@ For example, in the `MATCH` clause you describe the shape with a pattern, and Cy The pattern describes the data using a form that is very similar to how one typically draws the shape of property graph data on a whiteboard: usually as circles (representing nodes) and arrows between them to represent relationships. Patterns appear in multiple places in Cypher: in `MATCH`, `CREATE` and `MERGE` clauses, and in pattern expressions. -Each of these is described in more detail in: - -* xref::clauses/match.adoc[MATCH] -* xref::clauses/optional-match.adoc[OPTIONAL MATCH] -* xref::clauses/create.adoc[CREATE] -* xref::clauses/merge.adoc[MERGE] -* xref::clauses/where.adoc#query-where-patterns[Using path patterns in `WHERE`] + Each of these is described in more detail in: +* xref:clauses/match.adoc[MATCH] +* xref:clauses/optional-match.adoc[OPTIONAL MATCH] +* xref:clauses/create.adoc[CREATE] +* xref:clauses/merge.adoc[MERGE] +* xref:clauses/where.adoc#query-where-patterns[Using path patterns in `WHERE`] [[cypher-pattern-node]] == Patterns for nodes @@ -36,13 +37,13 @@ The very simplest 'shape' that can be described in a pattern is a node. A node is described using a pair of parentheses, and is typically given a name. For example: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a) ---- -This simple pattern describes a single node, and names that node using the variable `a`. +This simple pattern describes a single node, and names that node using the variable `a`. [[cypher-pattern-related-nodes]] == Patterns for related nodes @@ -51,27 +52,29 @@ A more powerful construct is a pattern that describes multiple nodes and relatio Cypher patterns describe relationships by employing an arrow between two nodes. For example: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a)-->(b) ---- + This pattern describes a very simple data shape: two nodes, and a single relationship from one to the other. In this example, the two nodes are both named as `a` and `b` respectively, and the relationship is 'directed': it goes from `a` to `b`. This manner of describing nodes and relationships can be extended to cover an arbitrary number of nodes and the relationships between them, for example: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a)-->(b)<--(c) ---- + Such a series of connected nodes and relationships is called a "path". Note that the naming of the nodes in these patterns is only necessary should one need to refer to the same node again, either later in the pattern or elsewhere in the Cypher query. If this is not necessary, then the name may be omitted, as follows: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a)-->()<--(c) ---- @@ -84,14 +87,15 @@ In addition to simply describing the shape of a node in the pattern, one can als The most simple attribute that can be described in the pattern is a label that the node must have. For example: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a:User)-->(b) ---- + One can also describe a node that has multiple labels: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a:User:Admin)-->(b) ---- @@ -105,18 +109,20 @@ Nodes and relationships are the fundamental structures in a graph. Neo4j uses pr Properties can be expressed in patterns using a map-construct: curly brackets surrounding a number of key-expression pairs, separated by commas. E.g. a node with two properties on it would look like: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a {name: 'Andy', sport: 'Brazilian Ju-Jitsu'}) ---- + A relationship with expectations on it is given by: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a)-[{blocked: false}]->(b) ---- + When properties appear in patterns, they add an additional constraint to the shape of the data. In the case of a `CREATE` clause, the properties will be set in the newly-created nodes and relationships. In the case of a `MERGE` clause, the properties will be used as additional constraints on the shape any existing data must have (the specified properties must exactly match any existing data in the graph). @@ -125,7 +131,6 @@ If no matching data is found, then `MERGE` behaves like `CREATE` and the propert Note that patterns supplied to `CREATE` may use a single parameter to specify properties, e.g: `CREATE (node $paramName)`. This is not possible with patterns used in other clauses, as Cypher needs to know the property names at the time the query is compiled, so that matching can be done effectively. - [[cypher-pattern-relationship]] == Patterns for relationships @@ -133,42 +138,46 @@ The simplest way to describe a relationship is by using the arrow between two no Using this technique, you can describe that the relationship should exist and the directionality of it. If you don't care about the direction of the relationship, the arrow head can be omitted, as exemplified by: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a)--(b) ---- + As with nodes, relationships may also be given names. In this case, a pair of square brackets is used to break up the arrow and the variable is placed between. For example: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a)-[r]->(b) ---- + Much like labels on nodes, relationships can have types. To describe a relationship with a specific type, you can specify this as follows: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a)-[r:REL_TYPE]->(b) ---- + Unlike labels, relationships can only have one type. But if we'd like to describe some data such that the relationship could have any one of a set of types, then they can all be listed in the pattern, separating them with the pipe symbol `|` like this: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a)-[r:TYPE1|TYPE2]->(b) ---- + Note that this form of pattern can only be used to describe existing data (ie. when using a pattern with `MATCH` or as an expression). It will not work with `CREATE` or `MERGE`, since it's not possible to create a relationship with multiple types. As with nodes, the name of the relationship can always be omitted, as exemplified by: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a)-[:REL_TYPE]->(b) ---- @@ -180,32 +189,36 @@ As with nodes, the name of the relationship can always be omitted, as exemplifie [CAUTION] ==== Variable length pattern matching in versions 2.1.x and earlier does not enforce relationship uniqueness for patterns described within a single `MATCH` clause. -This means that a query such as the following: `MATCH (a)-[r]\->(b), p = (a)-[\*]\->(c) RETURN *, relationships(p) AS rs` may include `r` as part of the `rs` set. -This behavior has changed in versions 2.2.0 and later, in such a way that `r` will be excluded from the result set, as this better adheres to the rules of relationship uniqueness as documented here xref::introduction/uniqueness.adoc[]. +This means that a query such as the following: `MATCH (a)-[r]\->(b), p = (a)-[*]\->(c) RETURN *, relationships(p) AS rs` may include `r` as part of the `rs` set. +This behavior has changed in versions 2.2.0 and later, in such a way that `r` will be excluded from the result set, as this better adheres to the rules of relationship uniqueness as documented here xref:introduction/uniqueness.adoc[]. If you have a query pattern that needs to retrace relationships rather than ignoring them as the relationship uniqueness rules normally dictate, you can accomplish this using multiple match clauses, as follows: `MATCH (a)-[r]\->(b) MATCH p = (a)-[*]\->(c) RETURN *, relationships(p)`. This will work in all versions of Neo4j that support the `MATCH` clause, namely 2.0.0 and later. + + ==== Rather than describing a long path using a sequence of many node and relationship descriptions in a pattern, many relationships (and the intermediate nodes) can be described by specifying a length in the relationship description of a pattern. For example: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a)-[*2]->(b) ---- + This describes a graph of three nodes and two relationships, all in one path (a path of length 2). This is equivalent to: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a)-->()-->(b) ---- + A range of lengths can also be specified: such relationship patterns are called 'variable length relationships'. For example: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a)-[*3..5]->(b) ---- @@ -216,30 +229,109 @@ It describes a graph of either 4 nodes and 3 relationships, 5 nodes and 4 relati Either bound can be omitted. For example, to describe paths of length 3 or more, use: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a)-[*3..]->(b) ---- + To describe paths of length 5 or less, use: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a)-[*..5]->(b) ---- + Omitting both bounds is equivalent to specifying a minimum of 1, allowing paths of any positive length to be described: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- (a)-[*]->(b) ---- + As a simple example, let's take the graph and query below: -image:graph4.svg[] +.Graph +["dot", "Patterns-1.svg", "neoviz", ""] +---- + N0 [ + label = "name = \'Anders\'\l" + ] + N0 -> N3 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N2 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N0 -> N1 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N1 [ + label = "name = \'Becky\'\l" + ] + N1 -> N4 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N2 [ + label = "name = \'Cesar\'\l" + ] + N2 -> N4 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N3 [ + label = "name = \'Dilshad\'\l" + ] + N3 -> N5 [ + color = "#2e3436" + fontcolor = "#2e3436" + label = "KNOWS\n" + ] + N4 [ + label = "name = \'George\'\l" + ] + N5 [ + label = "name = \'Filipa\'\l" + ] + +---- + + + +.Query +[source, cypher] +---- +MATCH (me)-[:KNOWS*1..2]-(remote_friend) +WHERE me.name = 'Filipa' +RETURN remote_friend.name +---- + +.Result +[role="queryresult",options="header,footer",cols="1* +Try this query live +(e), (c)-[:KNOWS]->(e), (d)-[:KNOWS]->(f) -//// -.Query -[source, cypher, indent=0] ----- + + +]]> +++++ +endif::nonhtmloutput[] This query finds data in the graph with a shape that fits the pattern: specifically a node (with the name property *'Filipa'*) and then the `KNOWS` related nodes, one or two hops away. This is a typical example of finding first and second degree friends. Note that variable length relationships cannot be used with `CREATE` and `MERGE`. - [[cypher-pattern-path-variables]] == Assigning to path variables As described above, a series of connected nodes and relationships is called a "path". Cypher allows paths to be named using an identifer, as exemplified by: -[source, cypher, role=noplay, indent=0] +[source, cypher, role=noplay] ---- p = (a)-[*3..5]->(b) ---- + You can do this in `MATCH`, `CREATE` and `MERGE`, but not when using patterns as expressions. diff --git a/modules/ROOT/pages/syntax/reserved.adoc b/modules/ROOT/pages/syntax/reserved.adoc index c4b716124..264921c8a 100644 --- a/modules/ROOT/pages/syntax/reserved.adoc +++ b/modules/ROOT/pages/syntax/reserved.adoc @@ -1,12 +1,6 @@ -:description: This section contains a list of reserved keywords in Cypher. - [[cypher-reserved]] = Reserved keywords - -[abstract] --- -This section contains a list of reserved keywords in Cypher. --- +:description: This section contains a list of reserved keywords in Cypher. Reserved keywords are words that have a special meaning in Cypher. The listing of the reserved keywords are grouped by the categories from which they are drawn. @@ -117,4 +111,3 @@ If any reserved keyword is escaped -- i.e. is encapsulated by backticks ```, suc * `OF` * `REQUIRE` * `SCALAR` - diff --git a/modules/ROOT/pages/syntax/spatial.adoc b/modules/ROOT/pages/syntax/spatial.adoc index 2f07dc083..e62030c11 100644 --- a/modules/ROOT/pages/syntax/spatial.adoc +++ b/modules/ROOT/pages/syntax/spatial.adoc @@ -1,67 +1,75 @@ -:description: Cypher has built-in support for handling spatial values (points), and the underlying database supports storing these point values as properties on nodes and relationships. - [[cypher-spatial]] = Spatial values +:description: Cypher has built-in support for handling spatial values (points), and the underlying database supports storing these point values as properties on nodes and relationships. -[abstract] --- -Cypher has built-in support for handling spatial values (points), and the underlying database supports storing these point values as properties on nodes and relationships. --- +* xref:syntax/spatial.adoc#cypher-spatial-introduction[Introduction] +* xref:syntax/spatial.adoc#cypher-spatial-crs[Coordinate Reference Systems] + ** xref:syntax/spatial.adoc#cypher-spatial-crs-geographic[Geographic coordinate reference systems] + ** xref:syntax/spatial.adoc#cypher-spatial-crs-cartesian[Cartesian coordinate reference systems] +* xref:syntax/spatial.adoc#cypher-spatial-instants[Spatial instants] + ** xref:syntax/spatial.adoc#cypher-spatial-specifying-spatial-instants[Creating points] + ** xref:syntax/spatial.adoc#cypher-spatial-accessing-components-spatial-instants[Accessing components of points] +* xref:syntax/spatial.adoc#cypher-spatial-index[Spatial index] +* xref:syntax/spatial.adoc#cypher-comparability-orderability[Comparability and Orderability] [NOTE] ==== -Refer to xref::functions/spatial.adoc[Spatial functions] for information regarding spatial _functions_ allowing for the creation and manipulation of spatial values. +Refer to xref:functions/spatial.adoc[Spatial functions] for information regarding spatial _functions_ allowing for the creation and manipulation of spatial values. + +Refer to xref:syntax/operators.adoc#cypher-ordering[Ordering and comparison of values] for information regarding the comparison and ordering of spatial values. -Refer to xref::syntax/operators.adoc#cypher-ordering[Ordering and comparison of values] for information regarding the comparison and ordering of spatial values. -==== +==== [[cypher-spatial-introduction]] == Introduction + Neo4j supports only one type of spatial geometry, the _Point_ with the following characteristics: * Each point can have either 2 or 3 dimensions. This means it contains either 2 or 3 64-bit floating point values, which together are called the _Coordinate_. -* Each point will also be associated with a specific xref::syntax/spatial.adoc#cypher-spatial-crs[Coordinate Reference System] (CRS) that determines the meaning of the values in the _Coordinate_. +* Each point will also be associated with a specific xref:syntax/spatial.adoc#cypher-spatial-crs[Coordinate Reference System] (CRS) that determines the meaning of the values in the _Coordinate_. * Instances of _Point_ and lists of _Point_ can be assigned to node and relationship properties. * Nodes with _Point_ or _List(Point)_ properties can be indexed using a spatial index. This is true for all CRS (and for both 2D and 3D). - There is no special syntax for creating spatial indexes, as it is supported using the existing xref::indexes-for-search-performance.adoc#administration-indexes-create-a-single-property-b-tree-index-for-nodes[indexes]. -* The xref::functions/spatial.adoc#functions-distance[distance function] will work on points in all CRS and in both 2D and 3D but only if the two points have the same CRS (and therefore also same dimension). - + There is no special syntax for creating spatial indexes, as it is supported using the existing xref:indexes-for-search-performance.adoc#administration-indexes-create-a-single-property-index-for-nodes[indexes]. +* The xref:functions/spatial.adoc#functions-distance[distance function] will work on points in all CRS and in both 2D and 3D but only if the two points have the same CRS (and therefore also same dimension). + [[cypher-spatial-crs]] == Coordinate Reference Systems Four Coordinate Reference Systems (CRS) are supported, each of which falls within one of two types: _geographic coordinates_ modeling points on the earth, or _cartesian coordinates_ modeling points in euclidean space: -* xref::syntax/spatial.adoc#cypher-spatial-crs-geographic[Geographic coordinate reference systems] +* xref:syntax/spatial.adoc#cypher-spatial-crs-geographic[Geographic coordinate reference systems] ** WGS-84: longitude, latitude (x, y) ** WGS-84-3D: longitude, latitude, height (x, y, z) -* xref::syntax/spatial.adoc#cypher-spatial-crs-cartesian[Cartesian coordinate reference systems] +* xref:syntax/spatial.adoc#cypher-spatial-crs-cartesian[Cartesian coordinate reference systems] ** Cartesian: x, y ** Cartesian 3D: x, y, z + Data within different coordinate systems are entirely incomparable, and cannot be implicitly converted from one to the other. This is true even if they are both cartesian or both geographic. For example, if you search for 3D points using a 2D range, you will get no results. -However, they can be ordered, as discussed in more detail in xref::syntax/operators.adoc#cypher-ordering[Ordering and comparison of values]. - +However, they can be ordered, as discussed in more detail in xref:syntax/operators.adoc#cypher-ordering[Ordering and comparison of values]. + [[cypher-spatial-crs-geographic]] === Geographic coordinate reference systems Two Geographic Coordinate Reference Systems (CRS) are supported, modeling points on the earth: -* link:https://spatialreference.org/ref/epsg/4326/[WGS 84 2D] +* http://spatialreference.org/ref/epsg/4326/[WGS 84 2D] ** A 2D geographic point in the _WGS 84_ CRS is specified in one of two ways: *** `longitude` and `latitude` (if these are specified, and the `crs` is not, then the `crs` is assumed to be `WGS-84`) *** `x` and `y` (in this case the `crs` must be specified, or will be assumed to be `Cartesian`) - ** Specifying this CRS can be done using either the name 'wgs-84' or the SRID 4326 as described in xref::functions/spatial.adoc#functions-point-wgs84-2d[Point(WGS-84)] -* link:https://spatialreference.org/ref/epsg/4979/[WGS 84 3D] + ** Specifying this CRS can be done using either the name 'wgs-84' or the SRID 4326 as described in xref:functions/spatial.adoc#functions-point-wgs84-2d[Point(WGS-84)] +* http://spatialreference.org/ref/epsg/4979/[WGS 84 3D] ** A 3D geographic point in the _WGS 84_ CRS is specified one of in two ways: *** `longitude`, `latitude` and either `height` or `z` (if these are specified, and the `crs` is not, then the `crs` is assumed to be `WGS-84-3D`) *** `x`, `y` and `z` (in this case the `crs` must be specified, or will be assumed to be `Cartesian-3D`) - ** Specifying this CRS can be done using either the name 'wgs-84-3d' or the SRID 4979 as described in xref::functions/spatial.adoc#functions-point-wgs84-3d[Point(WGS-84-3D)] + ** Specifying this CRS can be done using either the name 'wgs-84-3d' or the SRID 4979 as described in xref:functions/spatial.adoc#functions-point-wgs84-3d[Point(WGS-84-3D)] + The units of the `latitude` and `longitude` fields are in decimal degrees, and need to be specified as floating point numbers using Cypher literals. It is not possible to use any other format, like 'degrees, minutes, seconds'. The units of the `height` field are in meters. When geographic points @@ -69,15 +77,17 @@ are passed to the `distance` function, the result will always be in meters. If t is necessary to explicitly convert them. For example, if the incoming `$height` is a string field in kilometers, you would need to type `height: toFloat($height) * 1000`. Likewise if the results of the `distance` function are expected to be returned in kilometers, an explicit conversion is required. -For example: `RETURN point.distance(a,b) / 1000 AS km`. An example demonstrating conversion on incoming and outgoing values is: +For example: `RETURN distance(a,b) / 1000 AS km`. An example demonstrating conversion on incoming and outgoing values is: + + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH point({latitude:toFloat('13.43'), longitude:toFloat('56.21')}) AS p1, point({latitude:toFloat('13.10'), longitude:toFloat('56.41')}) AS p2 -RETURN toInteger(point.distance(p1, p2)/1000) AS km +RETURN toInteger(distance(p1, p2)/1000) AS km ---- .Result @@ -88,34 +98,52 @@ RETURN toInteger(point.distance(p1, p2)/1000) AS km 1+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[cypher-spatial-crs-cartesian]] === Cartesian coordinate reference systems Two Cartesian Coordinate Reference Systems (CRS) are supported, modeling points in euclidean space: -* link:https://spatialreference.org/ref/sr-org/7203/[Cartesian 2D] +* http://spatialreference.org/ref/sr-org/7203/[Cartesian 2D] ** A 2D point in the _Cartesian_ CRS is specified with a map containing `x` and `y` coordinate values - ** Specifying this CRS can be done using either the name 'cartesian' or the SRID 7203 as described in xref::functions/spatial.adoc#functions-point-cartesian-2d[Point(Cartesian)] -* link:https://spatialreference.org/ref/sr-org/9157/[Cartesian 3D] + ** Specifying this CRS can be done using either the name 'cartesian' or the SRID 7203 as described in xref:functions/spatial.adoc#functions-point-cartesian-2d[Point(Cartesian)] +* http://spatialreference.org/ref/sr-org/9157/[Cartesian 3D] ** A 3D point in the _Cartesian_ CRS is specified with a map containing `x`, `y` and `z` coordinate values - ** Specifying this CRS can be done using either the name 'cartesian-3d' or the SRID 9157 as described in xref::functions/spatial.adoc#functions-point-cartesian-3d[Point(Cartesian-3D)] + ** Specifying this CRS can be done using either the name 'cartesian-3d' or the SRID 9157 as described in xref:functions/spatial.adoc#functions-point-cartesian-3d[Point(Cartesian-3D)] + The units of the `x`, `y` and `z` fields are unspecified and can mean anything the user intends them to mean. This also means that when two cartesian points are passed to the `distance` function, the resulting value will be in the same units as the original coordinates. This is true for both 2D and 3D points, as the _pythagoras_ equation used is generalized to any number of dimensions. However, just as you cannot compare geographic points to cartesian points, you cannot calculate the distance between a 2D point and a 3D point. If you need to do that, explicitly transform the one type into the other. For example: + + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH point({x: 3, y: 0}) AS p2d, point({x: 0, y: 4, z: 1}) AS p3d RETURN - point.distance(p2d, p3d) AS bad, - point.distance(p2d, point({x: p3d.x, y: p3d.y})) AS good + distance(p2d, p3d) AS bad, + distance(p2d, point({x: p3d.x, y: p3d.y})) AS good ---- .Result @@ -126,6 +154,23 @@ RETURN 2+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[cypher-spatial-instants]] == Spatial instants @@ -133,10 +178,13 @@ RETURN [[cypher-spatial-specifying-spatial-instants]] === Creating points + All point types are created from two components: * The _Coordinate_ containing either 2 or 3 floating point values (64-bit) * The Coordinate Reference System (or CRS) defining the meaning (and possibly units) of the values in the _Coordinate_ + + For most use cases it is not necessary to specify the CRS explicitly as it will be deduced from the keys used to specify the coordinate. Two rules are applied to deduce the CRS from the coordinate: @@ -147,15 +195,19 @@ are applied to deduce the CRS from the coordinate: * Number of dimensions: ** If there are 2 dimensions in the coordinate, `x` & `y` or `longitude` & `latitude` the CRS will be a 2D CRS ** If there is a third dimensions in the coordinate, `z` or `height` the CRS will be a 3D CRS + + All fields are provided to the `point` function in the form of a map of explicitly named arguments. We specifically do not support an ordered list of coordinate fields because of the contradictory conventions between geographic and cartesian coordinates, where geographic coordinates normally list `y` before `x` (`latitude` before `longitude`). See for example the following query which returns points created in each of the four supported CRS. Take particular note of the order and keys of the coordinates in the original `point` function calls, and how those values are displayed in the results: + + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN point({x: 3, y: 0}) AS cartesian_2d, @@ -172,16 +224,37 @@ RETURN 4+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + + For the geographic coordinates, it is important to note that the `latitude` value should always lie in the interval `[-90, 90]` and any other value outside this range will throw an exception. The `longitude` value should always lie in the interval `[-180, 180]` and any other value outside this range will be wrapped around to fit in this range. The `height` value and any cartesian coordinates are not explicitly restricted, and any value within the allowed range of the signed 64-bit floating point type will be accepted. - + [[cypher-spatial-accessing-components-spatial-instants]] === Accessing components of points + Just as we construct points using a map syntax, we can also access components as properties of the instance. + + .Components of point instances and where they are supported [options="header"] @@ -197,10 +270,12 @@ Just as we construct points using a map syntax, we can also access components as | `instant.srid` | The internal Neo4j ID for the CRS | Integer | One of `4326`, `4979`, `7203`, `9157` | {check-mark} | {check-mark} | {check-mark} | {check-mark} |=== + The following query shows how to extract the components of a _Cartesian 2D_ point value: + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH point({x: 3, y: 4}) AS p RETURN @@ -218,10 +293,29 @@ RETURN 4+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + The following query shows how to extract the components of a _WGS-84 3D_ point value: + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH point({latitude: 3, longitude: 4, height: 4321}) AS p RETURN @@ -243,26 +337,145 @@ RETURN 8+d|Rows: 1 |=== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] + [[cypher-spatial-index]] == Spatial index -If there is a xref::indexes-for-search-performance.adoc#administration-indexes-create-a-single-property-b-tree-index-for-nodes[index] on a particular `:Label(property)` combination, and a spatial point + +If there is a xref:indexes-for-search-performance.adoc#administration-indexes-create-a-single-property-index-for-nodes[index] on a particular `:Label(property)` combination, and a spatial point is assigned to that property on a node with that label, the node will be indexed in a spatial index. For spatial indexing, Neo4j uses space filling curves in 2D or 3D over an underlying generalized B+Tree. Points will be stored in up to four different trees, one for each of the -xref::syntax/spatial.adoc#cypher-spatial-crs[four coordinate reference systems]. -This allows for both xref::query-tuning/indexes.adoc#administration-indexes-equality-check-using-where-single-property-index[equality] -and xref::query-tuning/indexes.adoc#administration-indexes-range-comparisons-using-where-single-property-index[range] queries using exactly the same syntax and behaviour as for other property types. +xref:syntax/spatial.adoc#cypher-spatial-crs[four coordinate reference systems]. +This allows for both xref:query-tuning/indexes.adoc#administration-indexes-equality-check-using-where-single-property-index[equality] +and xref:query-tuning/indexes.adoc#administration-indexes-range-comparisons-using-where-single-property-index[range] queries using exactly the same syntax and behaviour as for other property types. If two range predicates are used, which define minimum and maximum points, this will effectively result in a -xref::query-tuning/indexes.adoc#administration-indexes-spatial-bounding-box-searches-single-property-index[bounding box query]. +xref:query-tuning/indexes.adoc#administration-indexes-spatial-bounding-box-searches-single-property-index[bounding box query]. In addition, queries using the `distance` function can, under the right conditions, also use the index, as described in the section -xref::query-tuning/indexes.adoc#administration-indexes-spatial-distance-searches-single-property-index['Spatial distance searches']. - +xref:query-tuning/indexes.adoc#administration-indexes-spatial-distance-searches-single-property-index['Spatial distance searches']. + [[cypher-comparability-orderability]] -== Comparability and orderability +== Comparability and Orderability + + +Points with different CRS are not comparable. +This means that any function operating on two points of different types will return `null`. +This is true of the xref:functions/spatial.adoc#functions-distance[distance function] as well as inequality comparisons. +If these are used in a predicate, they will cause the associated `MATCH` to return no results. + -The comparability and orderability of spacial values are due to change in an upcoming future release. -This means that queries that rely on the comparison of two points using the inequality operators, `+<+`, `+<=+`, `+>+`, and `+>=+`, or the specific order of an `ORDER BY n.point` query will need to be rewritten. -The most efficient way to do this is to explicitly specify the ordering. For example, by using `point.x`, `point.y` in _cartesian coordinates_, or `point.longitude` and `point.latitude` in _geographic coordinates_. +.Query +[source, cypher] +---- +WITH + point({x: 3, y: 0}) AS p2d, + point({x: 0, y: 4, z: 1}) AS p3d +RETURN + distance(p2d, p3d), + p2d < p3d, + p2d = p3d, + p2d <> p3d, + distance(p2d, point({x: p3d.x, y: p3d.y})) +---- + +.Result +[role="queryresult",options="header,footer",cols="5* p3d+ | +distance(p2d, point({x: p3d.x, y: p3d.y}))+ +| ++ | ++ | +false+ | +true+ | +5.0+ +5+d|Rows: 1 +|=== + +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + p3d, + distance(p2d, point({x: p3d.x, y: p3d.y})) +]]> +++++ +endif::nonhtmloutput[] + + +However, all types are orderable. +The Point types will be ordered after Numbers and before Temporal types. +Points with different CRS with be ordered by their SRID numbers. +For the current set of four xref:syntax/spatial.adoc#cypher-spatial-crs[CRS], this means the order is WGS84, WGS84-3D, Cartesian, Cartesian-3D. + + + +.Query +[source, cypher] +---- +UNWIND [ + point({x: 3, y: 0}), + point({x: 0, y: 4, z: 1}), + point({srid: 4326, x: 12, y: 56}), + point({srid: 4979, x: 12, y: 56, z: 1000}) +] AS point +RETURN point ORDER BY point +---- + +.Result +[role="queryresult",options="header,footer",cols="1* +Try this query live + +++++ +endif::nonhtmloutput[] diff --git a/modules/ROOT/pages/syntax/temporal.adoc b/modules/ROOT/pages/syntax/temporal.adoc index f5034bfc1..5f70fc33d 100644 --- a/modules/ROOT/pages/syntax/temporal.adoc +++ b/modules/ROOT/pages/syntax/temporal.adoc @@ -1,18 +1,14 @@ -:description: Cypher has built-in support for handling temporal values, and the underlying database supports storing these temporal values as properties on nodes and relationships. - [[cypher-temporal]] = Temporal (Date/Time) values - -[abstract] --- -Cypher has built-in support for handling temporal values, and the underlying database supports storing these temporal values as properties on nodes and relationships. --- +:description: Cypher has built-in support for handling temporal values, and the underlying database supports storing these temporal values as properties on nodes and relationships. [NOTE] ==== -* Refer to xref::functions/temporal/index.adoc[Temporal functions - instant types] for information regarding temporal _functions_ allowing for the creation and manipulation of temporal values. -* Refer to xref::syntax/operators.adoc#query-operators-temporal[Temporal operators] for information regarding temporal _operators_. -* Refer to xref::syntax/operators.adoc#cypher-ordering[Ordering and comparison of values] for information regarding the comparison and ordering of temporal values. +* Refer to xref:functions/temporal/index.adoc[Temporal functions - instant types] for information regarding temporal _functions_ allowing for the creation and manipulation of temporal values. +* Refer to xref:syntax/operators.adoc#query-operators-temporal[Temporal operators] for information regarding temporal _operators_. +* Refer to xref:syntax/operators.adoc#cypher-ordering[Ordering and comparison of values] for information regarding the comparison and ordering of temporal values. + + ==== The following table lists the temporal value types and supported components: @@ -28,18 +24,18 @@ The following table lists the temporal value types and supported components: | `Duration` | `-` | `-` | `-` |=== -`Date`, `Time`, `LocalTime`, `DateTime`, and `LocalDateTime` are _temporal instant_ types. + +`Date`, `Time`, `LocalTime`, `DateTime` and `LocalDateTime` are _temporal instant_ types. A temporal instant value expresses a point in time with varying degrees of precision. By contrast, `Duration` is not a temporal instant type. A `Duration` represents a temporal amount, capturing the difference in time between two instants, and can be negative. `Duration` captures the amount of time between two instants, it does not capture a start time and end time. - [[cypher-temporal-timezones]] == Time zones -Time zones are represented either as an offset from UTC, or as a logical identifier of a _named time zone_ (these are based on the link:https://www.iana.org/time-zones[IANA time zone database]). +Time zones are represented either as an offset from UTC, or as a logical identifier of a _named time zone_ (these are based on the https://www.iana.org/time-zones[IANA time zone database]). In either case the time is stored as UTC internally, and the time zone offset is only applied when the time is presented. This means that temporal instants can be ordered without taking time zone into account. If, however, two times are identical in UTC, then they are ordered by timezone. @@ -57,8 +53,6 @@ There are three ways of specifying a time zone in Cypher: * Specifying a named time zone. * Specifying both the offset and the time zone name (with the requirement that these match). -See xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[Specifying time zones] for examples. - The named time zone form uses the rules of the IANA time zone database to manage _daylight savings time_ (DST). The default time zone of the database can be configured using the configuration option link:{neo4j-docs-base-uri}/operations-manual/{page-version}/reference/configuration-settings#config_db.temporal.timezone[`db.temporal.timezone`]. @@ -70,7 +64,6 @@ This configuration option influences the creation of temporal types for the foll * Creating a temporal type by combining or selecting values that do not have a time zone component, and without specifying a time zone. * Truncating a temporal value that does not have a time zone component, and without specifying a time zone. - [[cypher-temporal-instants]] == Temporal instants @@ -97,10 +90,11 @@ The character `T` is a literal character. [[cypher-temporal-specify-date]] ==== Specifying dates + [options="header"] |=== | Component | Format | Description -| Year | `YYYY` | Specified with at least four digits (xref::syntax/temporal.adoc#cypher-temporal-year[special rules apply in certain cases]). +| Year | `YYYY` | Specified with at least four digits (xref:syntax/temporal.adoc#cypher-temporal-year[special rules apply in certain cases]). | Month | `MM` | Specified with a double digit number from `01` to `12`. | Week | `ww` | Always prefixed with `W` and specified with a double digit number from `01` to `53`. | Quarter | `q` | Always prefixed with `Q` and specified with a single digit number from `1` to `4`. @@ -147,14 +141,15 @@ The following formats are supported for specifying dates: | `YYYY` | Year | `2015` | `2015-01-01` |=== + The least significant components can be omitted. Cypher will assume omitted components to have their lowest possible value. For example, `2013-06` will be interpreted as being the same date as `2013-06-01`. - [[cypher-temporal-specify-time]] ==== Specifying times + [options="header"] |=== | Component | Format | Description @@ -167,7 +162,9 @@ This can be separated from `Second` using either a full stop (`.`) or a comma (` The `fraction` is in addition to the two digits of `Second`. |=== -Cypher does not support leap seconds; link:https://www.cl.cam.ac.uk/~mgk25/time/utc-sls/[UTC-SLS] (_UTC with Smoothed Leap Seconds_) is used to manage the difference in time between UTC and TAI (_International Atomic Time_). + +Cypher does not support leap seconds; https://www.cl.cam.ac.uk/~mgk25/time/utc-sls/[UTC-SLS] (_UTC with Smoothed Leap Seconds_) is used to manage the difference in time between UTC and TAI (_International Atomic Time_). + The following formats are supported for specifying times: @@ -183,11 +180,11 @@ The following formats are supported for specifying times: | `HH` | `Hour` | `21` | `21:00:00.000` |=== + The least significant components can be omitted. For example, a time may be specified with `Hour` and `Minute`, leaving out `Second` and `fraction`. On the other hand, specifying a time with `Hour` and `Second`, while leaving out `Minute`, is not possible. - [[cypher-temporal-specify-time-zone]] ==== Specifying time zones @@ -231,17 +228,14 @@ The following formats are supported for specifying time zones: [[cypher-temporal-specify-instant-examples]] ==== Examples -Here are examples of parsing temporal instant values using various formats. - -For more details, refer to xref::functions/temporal/index.adoc#functions-temporal-create-overview[An overview of temporal instant type creation]. - -.+datetime+ -====== +We show below examples of parsing temporal instant values using various formats. +For more details, refer to xref:functions/temporal/index.adoc#functions-temporal-create-overview[An overview of temporal instant type creation]. Parsing a _DateTime_ using the _calendar date_ format: + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN datetime('2015-06-24T12:50:35.556+0100') AS theDateTime ---- @@ -254,16 +248,24 @@ RETURN datetime('2015-06-24T12:50:35.556+0100') AS theDateTime 1+d|Rows: 1 |=== -====== - - -.+localdatetime+ -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] Parsing a _LocalDateTime_ using the _ordinal date_ format: + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN localdatetime('2015185T19:32:24') AS theLocalDateTime ---- @@ -276,16 +278,24 @@ RETURN localdatetime('2015185T19:32:24') AS theLocalDateTime 1+d|Rows: 1 |=== -====== - - -.+date+ -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] Parsing a _Date_ using the _week date_ format: + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN date('+2015-W13-4') AS theDate ---- @@ -298,15 +308,24 @@ RETURN date('+2015-W13-4') AS theDate 1+d|Rows: 1 |=== -====== - +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] -.+time+ -====== Parsing a _Time_: + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN time('125035.556+0100') AS theTime ---- @@ -319,16 +338,24 @@ RETURN time('125035.556+0100') AS theTime 1+d|Rows: 1 |=== -====== - - -.+localtime+ -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] Parsing a _LocalTime_: + .Query -[source, cypher, indent=0] +[source, cypher] ---- RETURN localtime('12:50:35.556') AS theLocalTime ---- @@ -341,285 +368,58 @@ RETURN localtime('12:50:35.556') AS theLocalTime 1+d|Rows: 1 |=== -====== - +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[cypher-temporal-accessing-components-temporal-instants]] === Accessing components of temporal instants -:astronomical_year_number_link: link:https://en.wikipedia.org/wiki/Astronomical_year_numbering[astronomical year number] - -:Gregorian_calendar_link: link:https://en.wikipedia.org/wiki/Gregorian_calendar[Gregorian calendar] - -:year_component_foot_note: footnote:[This is in accordance with the {Gregorian_calendar_link}; i.e. years AD/CE start at year 1, and the year before that (year 1 BC/BCE) is 0, while year 2 BCE is -1 etc.] - -:first_week_of_any_year_link: link:https://en.wikipedia.org/wiki/ISO_week_date#First_week[first week of any year] - -:week_component_foot_note: footnote:[The {first_week_of_any_year_link} is the week that contains the first Thursday of the year, and thus always contains January 4.] - -:weekYear_component_foot_note: footnote:[For dates from December 29, this could be the next year, and for dates until January 3 this could be the previous year, depending on how week 1 begins.] - -:second_compontent_foot_note: footnote:[Cypher does not support leap seconds; UTC-SLS (UTC with Smoothed Leap Seconds) is used to manage the difference in time between UTC and TAI (International Atomic Time).] - -// note: monospace format does not work in a footnote apparently. -:epochMillis_foot_note: footnote:[The expression datetime().epochMillis returns the equivalent value of the timestamp() function.] - -// note: italic format does not work in a footnote apparently. -:epochSeconds_foot_note: footnote:[For the nanosecond part of the epoch offset, the regular nanosecond component (instant.nanosecond) can be used.] - - Components of temporal instant values can be accessed as properties. .Components of temporal instant values and where they are supported [options="header", cols="2,2,1,2,^1,^1,^1,^1,^1"] |=== | Component | Description | Type | Range/Format | Date | DateTime | LocalDateTime | Time | LocalTime - -| `instant.year` -| The `year` component represents the {astronomical_year_number_link} of the instant.{year_component_foot_note} -| Integer -a| -At least 4 digits. -For more information, see the xref::syntax/temporal.adoc#cypher-temporal-year[rules for using the `Year` component]. -| {check-mark} -| {check-mark} -| {check-mark} -| -| - -| `instant.quarter` -| The _quarter-of-the-year_ component. -| Integer -| `1` to `4`. -| {check-mark} -| {check-mark} -| {check-mark} -| -| - -| `instant.month` -| The _month-of-the-year_ component. -| Integer -| `1` to `12`. -| {check-mark} -| {check-mark} -| {check-mark} -| -| - -| `instant.week` -| The _week-of-the-year_ component.{week_component_foot_note} -| Integer -| `1` to `53`. -| {check-mark} -| {check-mark} -| {check-mark} -| -| - -| `instant.weekYear` -| The _year_ that the _week-of-year_ component belongs to.{weekYear_component_foot_note} -| Integer -a| -At least 4 digits. -For more information, see the xref::syntax/temporal.adoc#cypher-temporal-year[rules for using the `Year` component]. -| {check-mark} -| {check-mark} -| {check-mark} -| -| - -| `instant.dayOfQuarter` -| The _day-of-the-quarter_ component. -| Integer -| `1` to `92`. -| {check-mark} -| {check-mark} -| {check-mark} -| -| - -| `instant.quarterDay` -| The _day-of-the-quarter_ component (alias for `instant.dayOfQuarter`). -| Integer -| `1` to `92`. -| {check-mark} -| {check-mark} -| {check-mark} -| -| - -| `instant.day` -| The _day-of-the-month_ component. -| Integer -| `1` to `31`. -| {check-mark} -| {check-mark} -| {check-mark} -| -| - -| `instant.ordinalDay` -| The _day-of-the-year_ component. -| Integer -| `1` to `366`. -| {check-mark} -| {check-mark} -| {check-mark} -| -| - -| `instant.dayOfWeek` -| The _day-of-the-week_ component (the first day of the week is _Monday_). -| Integer -| `1` to `7`. -| {check-mark} -| {check-mark} -| {check-mark} -| -| - -| `instant.weekDay` -| The _day-of-the-week_ component (alias for `instant.dayOfWeek`). -| Integer -| `1` to `7`. -| {check-mark} -| {check-mark} -| {check-mark} -| -| - -| `instant.hour` -| The _hour_ component. -| Integer -| `0` to `23`. -| -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} - -| `instant.minute` -| The _minute_ component. -| Integer -| `0` to `59`. -| -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} - -| `instant.second` -| The _second_ component.{second_compontent_foot_note} -| Integer -| `0` to `59`. -| -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} - -| `instant.millisecond` -| The _millisecond_ component. -| Integer -| `0` to `999`. -| -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} - -| `instant.microsecond` -| The _microsecond_ component. -| Integer -| `0` to `999999`. -| -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} - -| `instant.nanosecond` -| The _nanosecond_ component. -| Integer -| `0` to `999999999`. -| -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} - -| `instant.timezone` -| The _timezone_ component. -| String -| Depending on how the xref::syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone was specified], this is either a time zone name or an offset from UTC in the format `±HHMM`. -| -| {check-mark} -| -| {check-mark} -| - -| `instant.offset` -| The _timezone_ offset. -| String -| In the format `±HHMM`. -| -| {check-mark} -| -| {check-mark} -| - -| `instant.offsetMinutes` -| The _timezone_ offset in minutes. -| Integer -| `-1080` to `+1080`. -| -| {check-mark} -| -| {check-mark} -| - -| `instant.offsetSeconds` -| The _timezone_ offset in seconds. -| Integer -| `-64800` to `+64800`. -| -| {check-mark} -| -| {check-mark} -| - -| `instant.epochMillis` -| The number of milliseconds between `1970-01-01T00:00:00+0000` and the instant.{epochMillis_foot_note} -| Integer -| Positive for instants after and negative for instants before `1970-01-01T00:00:00+0000`. -| -| {check-mark} -| -| -| - -| `instant.epochSeconds` -| The number of seconds between `1970-01-01T00:00:00+0000` and the instant.{epochSeconds_foot_note} -| Integer -| Positive for instants after and negative for instants before `1970-01-01T00:00:00+0000`. -| -| {check-mark} -| -| -| -| - -|=== - - -.+date+ -====== +| `instant.year` | The `year` component represents the link:https://en.wikipedia.org/wiki/Astronomical_year_numbering[astronomical year number] of the instant.footnote:[This is in accordance with the link:https://en.wikipedia.org/wiki/Gregorian_calendar[Gregorian calendar]; i.e. years AD/CE start at year 1, and the year before that (year 1 BC/BCE) is 0, while year 2 BCE is -1 etc.] | Integer | At least 4 digits. For more information, see the xref:syntax/temporal.adoc#cypher-temporal-year[rules for using the `Year` component] | {check-mark} | {check-mark} | {check-mark} | | +| `instant.quarter` | The _quarter-of-the-year_ component. | Integer | `1` to `4` | {check-mark} | {check-mark} | {check-mark} | | +| `instant.month` | The _month-of-the-year_ component. | Integer | `1` to `12` | {check-mark} | {check-mark} | {check-mark} | | +| `instant.week` | The _week-of-the-year_ component.footnote:[The link:https://en.wikipedia.org/wiki/ISO_week_date#First_week[first week of any year] is the week that contains the first Thursday of the year, and thus always contains January 4.] | Integer | `1` to `53` | {check-mark} | {check-mark} | {check-mark} | | +| `instant.weekYear` | The _year_ that the _week-of-year_ component belongs to.footnote:[For dates from December 29, this could be the next year, and for dates until January 3 this could be the previous year, depending on how week 1 begins.] | Integer | At least 4 digits. For more information, see the xref:syntax/temporal.adoc#cypher-temporal-year[rules for using the `Year` component] | {check-mark} | {check-mark} | {check-mark} | | +| `instant.dayOfQuarter` | The _day-of-the-quarter_ component. | Integer | `1` to `92` | {check-mark} | {check-mark} | {check-mark} | | +| `instant.quarterDay` | The _day-of-the-quarter_ component. (alias for `instant.dayOfQuarter`) | Integer | `1` to `92` | {check-mark} | {check-mark} | {check-mark} | | +| `instant.day` | The _day-of-the-month_ component. | Integer | `1` to `31` | {check-mark} | {check-mark} | {check-mark} | | +| `instant.ordinalDay` | The _day-of-the-year_ component. | Integer | `1` to `366` | {check-mark} | {check-mark} | {check-mark} | | +| `instant.dayOfWeek` | The _day-of-the-week_ component (the first day of the week is _Monday_). | Integer | `1` to `7` | {check-mark} | {check-mark} | {check-mark} | | +| `instant.weekDay` | The _day-of-the-week_ component (alias for `instant.dayOfWeek`). | Integer | `1` to `7` | {check-mark} | {check-mark} | {check-mark} | | +| `instant.hour` | The _hour_ component. | Integer | `0` to `23` | | {check-mark} | {check-mark} | {check-mark} | {check-mark} +| `instant.minute` | The _minute_ component. | Integer | `0` to `59` | | {check-mark} | {check-mark} | {check-mark} | {check-mark} +| `instant.second` | The _second_ component.footnote:[Cypher does not support leap seconds; UTC-SLS (UTC with Smoothed Leap Seconds) is used to manage the difference in time between UTC and TAI (International Atomic Time).] | Integer | `0` to `59` | | {check-mark} | {check-mark} | {check-mark} | {check-mark} +| `instant.millisecond` | The _millisecond_ component. | Integer | `0` to `999` | | {check-mark} | {check-mark} | {check-mark} | {check-mark} +| `instant.microsecond` | The _microsecond_ component. | Integer | `0` to `999999` | | {check-mark} | {check-mark} | {check-mark} | {check-mark} +| `instant.nanosecond` | The _nanosecond_ component. | Integer | `0` to `999999999` | | {check-mark} | {check-mark} | {check-mark} | {check-mark} +| `instant.timezone` | The _timezone_ component. | String | Depending on how the xref:syntax/temporal.adoc#cypher-temporal-specify-time-zone[time zone was specified], this is either a time zone name or an offset from UTC in the format `±HHMM` | | {check-mark} | | {check-mark} | +| `instant.offset` | The _timezone_ offset | String | `±HHMM` | | {check-mark} | | {check-mark} | +| `instant.offsetMinutes` | The _timezone_ offset in minutes | Integer | `-1080` to `+1080` | | {check-mark} | | {check-mark} | +| `instant.offsetSeconds` | The _timezone_ offset in seconds | Integer | `-64800` to `+64800` | | {check-mark} | | {check-mark} | +| `instant.epochMillis` | The number of milliseconds between `1970-01-01T00:00:00+0000` and the instant.footnote:[The expression `datetime().epochMillis` returns the equivalent value of the `timestamp()` function.] | Integer | Positive for instants after and negative for instants before `1970-01-01T00:00:00+0000` | | {check-mark} | | | +| `instant.epochSeconds` | The number of seconds between `1970-01-01T00:00:00+0000` and the instant.footnote:[For the _nanosecond_ part of the _epoch_ offset, the regular _nanosecond_ component (`instant.nanosecond`) can be used.] | Integer | Positive for instants after and negative for instants before `1970-01-01T00:00:00+0000` | | {check-mark} | | | | +|=== The following query shows how to extract the components of a _Date_ value: + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH date({year: 1984, month: 10, day: 11}) AS d RETURN d.year, d.quarter, d.month, d.week, d.weekYear, d.day, d.ordinalDay, d.dayOfWeek, d.dayOfQuarter @@ -633,16 +433,25 @@ RETURN d.year, d.quarter, d.month, d.week, d.weekYear, d.day, d.ordinalDay, d.da 9+d|Rows: 1 |=== -====== - - -.+datetime+ -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] The following query shows how to extract the date related components of a _DateTime_ value: + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH datetime({ year: 1984, month: 11, day: 11, @@ -660,15 +469,29 @@ RETURN d.year, d.quarter, d.month, d.week, d.weekYear, d.day, d.ordinalDay, d.da 9+d|Rows: 1 |=== -====== - -.+datetime+ -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] The following query shows how to extract the time related components of a _DateTime_ value: + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH datetime({ year: 1984, month: 11, day: 11, @@ -686,16 +509,29 @@ RETURN d.hour, d.minute, d.second, d.millisecond, d.microsecond, d.nanosecond 6+d|Rows: 1 |=== -====== - - -.+datetime+ -====== +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] The following query shows how to extract the epoch time and timezone related components of a _DateTime_ value: + .Query -[source, cypher, indent=0] +[source, cypher] ---- WITH datetime({ year: 1984, month: 11, day: 11, @@ -713,8 +549,23 @@ RETURN d.timezone, d.offset, d.offsetMinutes, d.epochSeconds, d.epochMillis 5+d|Rows: 1 |=== -====== - +ifndef::nonhtmloutput[] +[subs="none"] +++++ + +Try this query live + +++++ +endif::nonhtmloutput[] [[cypher-temporal-durations]] == Durations @@ -728,11 +579,10 @@ The specification of a _Duration_ is prefixed with a `P`, and can use either a _ * Unit-based form: `P[nY][nM][nW][nD][T[nH][nM][nS]]` ** The square brackets (`[]`) denote an optional component (components with a zero value may be omitted). - ** The `n` denotes a numeric value within the bounds of a 64-bit integer. + ** The `n` denotes a numeric value which can be arbitrarily large. ** The value of the last -- and least significant -- component may contain a decimal fraction. ** Each component must be suffixed by a component identifier denoting the unit. ** The unit-based form uses `M` as a suffix for both months and minutes. Therefore, time parts must always be preceded with `T`, even when no components of the date part are given. - ** The maximum total length of a _Duration_ is bounded by the number of seconds that can be held in a 64-bit integer. * Date-and-time-based form: `PT