Skip to content

Commit

Permalink
Fixes #2895: update apoc.custom.* documentation (#2937)
Browse files Browse the repository at this point in the history
  • Loading branch information
vga91 authored and vga91 committed Jun 10, 2022
1 parent 3b82b9f commit 9865f8b
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,57 +7,26 @@

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.

.Type Names
The `typeParam` and `typeResult` in the signature parameter are what you would expect to see in outputs of `SHOW PROCEDURES`, `SHOW FUNCTIONS` or `CALL apoc.help('')`, just without the final `?`.
The following values are supported:
* FLOAT, DOUBLE, INT, INTEGER, NUMBER, LONG
* TEXT, STRING
* BOOL, BOOLEAN
Expand All @@ -68,47 +37,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`
== Custom Procedures with `apoc.custom.declareProcedure`

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.
include::partial$usage/apoc.custom.declareProcedure.adoc[]

.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 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 +62,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 +131,6 @@ 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.
====
====


Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
Please note that `apoc.custom.asFunction` and `apoc.custom.asProcedure` are deprecated in favor of `apoc.custom.declareFunction` and `apoc.custom.declareProcedure`.
xref::cypher-execution/cypher-based-procedures-functions.adoc[See here for more info].

This is the list of input parameters, `name` and `statement` are mandatory fields:

.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
|===

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.

The type names can be the same as `apoc.custom.declare*` analogues.


Here is a simple example:

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


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

[source,cypher]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,54 @@
Please note that `apoc.custom.asFunction` and `apoc.custom.asProcedure` are deprecated in favor of `apoc.custom.declareFunction` and `apoc.custom.declareProcedure`.
xref::cypher-execution/cypher-based-procedures-functions.adoc[See here for more info].

This is the list of input parameters, `name` and `statement` are mandatory fields:

.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
|===

Given statement will be registered as a procedure, the results will be turned into a stream of records.
The type names can be the same as `apoc.custom.declare*` analogues.


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
----



.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;
----


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]
Expand Down
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 `SHOW FUNCTIONS`.
Loading

0 comments on commit 9865f8b

Please sign in to comment.