Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #2895: update apoc.custom.* documentation #2937

Merged
merged 3 commits into from
Jun 10, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,55 +7,23 @@

I wanted for a long time to be able to register Cypher statements as proper procedures and functions, so that they become callable in a standalone way.

You can achieve that with the `apoc.custom.asProcedure` and `apoc.custom.asFunction` procedure calls.
You can achieve that with the `apoc.custom.declareProcedure` and `apoc.custom.declareFunction` procedure calls.
Those register a given Cypher statement, prefixed with the `custom.*` namespace, overriding potentially existing ones, so you can redefine them as needed.

Here is a simple example:
NOTE: APOC provides also xref::overview/apoc.custom/apoc.custom.asFunction.adoc[apoc.custom.asFunction] and xref::overview/apoc.custom/apoc.custom.asProcedure.adoc[apoc.custom.asProcedure] procedures,
but they have been deprecated in favor of `apoc.custom.declare*`s.

[source,cypher]
----
CALL apoc.custom.asProcedure('answer','RETURN 42 as answer')
----

This registers the statement as procedure `custom.answer` that you then can call.
As no information on parameter and return types is given, it just returns a stream of columns of maps called `row`.

[source,cypher]
----
CALL custom.answer YIELD row
RETURN row.answer
----

The same is possible as a function:

[source,cypher]
----
CALL apoc.custom.asFunction('answer','RETURN 42')
----

NOTE: If you override procedures or functions you might need to call `call dbms.clearQueryCaches()` as lookups to internal id's are kept in compiled query plans.
The first parameter of the `apoc.custom.declareProcedure` and `apoc.custom.declareFunction` procedures,
is the signature of the procedure/function you want to create.
This looks similar to the `signature` results returned by the `SHOW PROCEDURES YIELD signature` and `SHOW FUNCTIONS YIELD signature` cypher commands,
that is:
- for a procedure: `nameProcedure(firstParam = defaultValue :: typeParam , secondParam = defaultValue :: typeParam, ....) :: (firstResult :: typeResult, secondResult :: typeResult, ... )`
- for a function: `nameFunction(firstParam = defaultValue :: typeParam , secondParam = defaultValue :: typeParam, ....) :: typeResult`

== Custom Procedures with `apoc.custom.asProcedure`
Note that, for both procedures and functions, the `= defaultValue` are optionals.

Given statement will be registered as a procedure, the results will be turned into a stream of records.

.Parameters
[%autowidth,opts=header,cols="m,m,a"]
|===
| name
| default
| description

| name | none | dot-separated name, will be prefixed with `custom`
| statement | none | cypher statement to run, can use $parameters
| mode | read | execution mode of the procedure: read, write, or schema
| outputs | [["row","MAP"]] | List of pairs of name-type to be used as output columns, need to be in-order with the cypher statement, the default is a special case, that will collect all columns of the statement result into a map
| inputs | [["params","MAP","{}"]] | Pairs or triples of name-type-default, to be used as input parameters. The default just takes an optional map, otherwise they will become proper paramters in order
| description | "" | A general description about the business rules implemented into the procedure
|===

The type names are what you would expect and see in outputs of `dbms.procedures` or `apoc.help` just without the `?`.
The default values are parsed as JSON.
The `type`s are what you would expect to see in outputs of `SHOW PROCEDURES`, `SHOW FUNCTIONS` or `apoc.help` just without the `?`.
Lojjs marked this conversation as resolved.
Show resolved Hide resolved

.Type Names
* FLOAT, DOUBLE, INT, INTEGER, NUMBER, LONG
Expand All @@ -68,47 +36,23 @@ The default values are parsed as JSON.
* LIST TYPE, LIST OF TYPE (where `TYPE` can be one of the previous values)
* ANY

.Find neighbours of a node by name
[source,cypher]
----
CALL apoc.custom.asProcedure('neighbours',
'MATCH (n:Person {name:$name})-->(nb) RETURN nb as neighbour','read',
[['neighbour','NODE']],[['name','STRING']], 'get neighbours of a person');

CALL custom.neighbours('Keanu Reeves') YIELD neighbour;
----

NOTE: If you override procedures or functions you might need to call `call db.clearQueryCaches()` as lookups to internal id's are kept in compiled query plans.

== Custom Functions with `apoc.custom.asFunction`

Given statement will be registered as a statement, the results into a single value.
If the given output type is a list, results will be collected into a list, otherwise the first row will be used.
The statement needs to return a single column, otherwise an error is thrown.
== Custom Procedures with `apoc.custom.declareProcedure`

.Parameters
[%autowidth,opts=header, cols="m,m,a"]
|===
| name
| default
| description
include::partial$usage/apoc.custom.declareProcedure.adoc[]

| name | none | dot-separated name, will be prefixed with `custom`
| statement | none | cypher statement to run, can use $parameters
| outputs | "LIST OF MAP" | Output type for single output, if the type is a list, then all rows will be collected, otherwise just the first row. Only single column results are allowed.
If your single row result is a list you can force a single row by setting the last parameter to `true`
| inputs | [["params","MAP","{}"]] | Pairs or triples of name-type-default, to be used as input parameters. The default just takes an optional map, otherwise they will become proper paramters in order
| singleRow | false | If set to true, the statement is treated as single row even with the list result type, then your statement has to return a list.
| description | "" | A general description about the business rules implemented into the function
|===
== Custom Functions with `apoc.custom.declareFunction`

The type names are what you would expect and see in outputs of `dbms.procedures` or `apoc.help` just without the `?`.
The default values are parsed as JSON.
include::partial$usage/apoc.custom.declareFunction.adoc[]


== List of registered procedures/function with `apoc.custom.list`

The procedure `apoc.custom.list` provide a list of all registered procedures/function via
`apoc.custom.asProcedure` and `apoc.custom.asFunction`
The procedure `apoc.custom.list` provide a list of all registered procedures/function via
`apoc.custom.declareProcedure` and `apoc.custom.declareFunction`, `apoc.custom.asProcedure` and `apoc.custom.asFunction`.

Given the this call:

Expand All @@ -117,7 +61,7 @@ Given the this call:
CALL apoc.custom.list
----

The the output will look like the following table:
The output will look like the following table:

[%autowidth,opts=header]
|===
Expand Down Expand Up @@ -186,4 +130,88 @@ changes to each cluster member
====
To import custom procedures in another database (for example after a `./neo4j-admin backup` and `/neo4j-admin restore`),
please see the xref::overview/apoc.systemdb/apoc.systemdb.export.metadata.adoc[apoc.systemdb.export.metadata] procedure.
====
====


== [DEPRECATED] Custom Procedures with `apoc.custom.asProcedure`

Here is a simple example:

[source,cypher]
----
CALL apoc.custom.asProcedure('answer','RETURN 42 as answer')
----

This registers the statement as procedure `custom.answer` that you then can call.
As no information on a parameter and return types is given, it just returns a stream of columns of maps called `row`.

[source,cypher]
----
CALL custom.answer() YIELD row
RETURN row.answer
----

Given statement will be registered as a procedure, the results will be turned into a stream of records.

.Parameters
[%autowidth,opts=header,cols="m,m,a"]
|===
| name
| default
| description

| name | none | dot-separated name, will be prefixed with `custom`
| statement | none | cypher statement to run, can use $parameters
| mode | read | execution mode of the procedure: read, write, or schema
| outputs | [["row","MAP"]] | List of pairs of name-type to be used as output columns, need to be in-order with the cypher statement, the default is a special case, that will collect all columns of the statement result into a map
| inputs | [["params","MAP","{}"]] | Pairs or triples of name-type-default, to be used as input parameters. The default just takes an optional map, otherwise they will become proper paramters in order
| description | "" | A general description about the business rules implemented into the procedure
|===

The type names can be the same as `apoc.custom.declare*` analogues.
The default values are parsed as JSON.

.Find neighbours of a node by name
[source,cypher]
----
CALL apoc.custom.asProcedure('neighbours',
'MATCH (n:Person {name:$name})-->(nb) RETURN nb as neighbour','read',
[['neighbour','NODE']],[['name','STRING']], 'get neighbours of a person');

CALL custom.neighbours('Keanu Reeves') YIELD neighbour;
----



== [DEPRECATED] Custom Functions with `apoc.custom.asFunction`

The same is possible as a function:

[source,cypher]
----
CALL apoc.custom.asFunction('answer','RETURN 42')
----

Given statement will be registered as a statement, the results into a single value.
If the given output type is a list, results will be collected into a list, otherwise the first row will be used.
The statement needs to return a single column, otherwise an error is thrown.

.Parameters
[%autowidth,opts=header, cols="m,m,a"]
|===
| name
| default
| description

| name | none | dot-separated name, will be prefixed with `custom`
| statement | none | cypher statement to run, can use $parameters
| outputs | "LIST OF MAP" | Output type for single output, if the type is a list, then all rows will be collected, otherwise just the first row. Only single column results are allowed.
If your single row result is a list you can force a single row by setting the last parameter to `true`
| inputs | [["params","MAP","{}"]] | Pairs or triples of name-type-default, to be used as input parameters. The default just takes an optional map, otherwise they will become proper parameters in order
| singleRow | false | If set to true, the statement is treated as single row even with the list result type, then your statement has to return a list.
| description | "" | A general description about the business rules implemented into the function
|===

The type names are what you would expect and see in outputs of `dbms.procedures` or `apoc.help` just without the `?`.
The default values are parsed as JSON.
Lojjs marked this conversation as resolved.
Show resolved Hide resolved

Original file line number Diff line number Diff line change
@@ -1,3 +1,64 @@
Here is a simple example:

[source,cypher]
----
CALL apoc.custom.declareFunction('answerFun() :: INT', 'RETURN 42 as answer')
----

This registers the statement as procedure `custom.answer` that you then can call.
[source,cypher]
----
RETURN custom.answerFun()
----

.Results
[opts="header"]
|===
| answer
| 42
|===

Or you can also write in this way:

[source,cypher]
----
CALL apoc.custom.declareFunction('answerFunMap() :: MAP', 'RETURN 42 as answer')
----

In this case the result is wrapped in a stream of maps called `row`. Therefore, you can do:

[source,cypher]
----
WITH custom.answerFunMap() YIELD row
RETURN row.answer
----

.Results
[opts="header"]
|===
| answer
| 42
|===

which is equivalent to deprecated one:

[source,cypher]
----
CALL apoc.custom.asFunction('answer','RETURN 42 as answer')
----


We can create the function `custom.powers` that returns a stream of the powers of the first parameter, up to and including the power provided by the second parameter:

[source,cypher]
----
CALL apoc.custom.declareProcedure(
'powers(input::INT, power::INT) :: (answer::INT)',
'UNWIND range(0, $power) AS power
RETURN $input ^ power AS answer'
);
----

We can create the function `custom.double`, that doubles the provided value, by running the following function:

[source,cypher]
Expand All @@ -24,4 +85,42 @@ RETURN custom.double(83) AS value;
| 166
|===

If we don't need fine grained control over our function's signature, see xref::overview/apoc.custom/apoc.custom.asFunction.adoc[].
Furthermore, we can pass as a 3rd parameter a boolean (with default false) which, if true,
in case the function returns a list of a single element, it will return only the single element itself and not the list.

For example:

[source,cypher]
----
CALL apoc.custom.declareFunction('forceSingleTrue(input::ANY) :: LIST OF INT',
'RETURN 1',
true
);
----

.Results
[opts="header"]
|===
| value
| 1
|===

otherwise with false the result will be a singleton list:

[source,cypher]
----
CALL apoc.custom.declareFunction('forceSingleFalse(input::ANY) :: LIST OF INT',
'RETURN 1',
false
);
----

.Results
[opts="header"]
|===
| value
| [1]
|===

Moreover, we can pass a `description` parameter as the 4th parameter,
which will be returned by the `call apoc.custom.list` and the `show functions`.
Lojjs marked this conversation as resolved.
Show resolved Hide resolved
Loading