| Name | Signature | Description |
|---|---|---|
abs | abs(NUMBER?) :: NUMBER? | absolute value of a number |
acos | acos(NUMBER?) :: FLOAT? | arcosine (in radians) of a number |
asin | asin(NUMBER?) :: FLOAT? | arcsine (in radians) of a number |
atan | atan(NUMBER?) :: FLOAT? | arctangent (in radians) of a number |
atan2 | atan2(NUMBER?, NUMBER?) :: FLOAT? | arctangent (in radians) of the quotient of its arguments |
ceil | ceil(NUMBER?) :: FLOAT? | smallest integer greater than or equal to the input |
coalesce | coalesce(ANY?, ..) :: ANY? | returns the first non- |
cos | cos(NUMBER?) :: FLOAT? | cosine of a number of radians |
cot | cot(NUMBER?) :: FLOAT? | cotangent of a number of radians |
degrees | degrees(NUMBER?) :: FLOAT? | convert radians to degrees |
e | e() :: FLOAT? | mathematical constant |
exp | exp(NUMBER?) :: FLOAT? | return the mathematical constant |
floor | floor(NUMBER?) :: FLOAT? | largest integer less than or equal to the input |
haversin | haversin(NUMBER?) :: FLOAT? | half the versine of a number |
head | head(LIST? OF ANY?) :: ANY? | extract the first element of a list |
id | id(NODE?) :: ANY? | extract the ID of a node |
keys | keys(ANY?) :: LIST? OF STRING? | extract the keys from a map, node, or relationship |
lTrim | lTrim(STRING?) :: STRING? | original string with leading whitespace removed |
labels | labels(ANY?) :: LIST? OF STRING? | extract the labels of a node or relationship |
last | last(LIST? OF ANY?) :: ANY? | extract the last element of a list |
left | left(STRING?, INTEGER?) :: STRING? | string containing the specified number of leftmost characters of the original string |
length | length(PATH?) :: INTEGER? | length of a path (ie. the number of relationships in it) |
log | log(NUMBER?) :: FLOAT? | natural logarithm of a number |
log10 | log10(NUMBER?) :: FLOAT? | common logarithm (base 10) of a number |
nodes | nodes(PATH?) :: LIST? OF NODE? | extract a list of nodes in a path |
pi | pi() :: FLOAT? | mathematical constant |
properties | properties(ANY?) :: MAP? | extract the properties from a map, node, or relationship |
rTrim | rTrim(STRING?) :: STRING? | original string with trailing whitespace removed |
radians | radians(NUMBER?) :: FLOAT? | convert degrees to radians |
rand | rand() :: FLOAT? | random float between 0 (inclusive) and 1 (exclusive) |
range | range(start :: INTEGER, end :: INTEGER, step :: INTEGER?) :: LIST? OF INTEGER? | construct a list of integers representing a range |
relationships | relationships(PATH?) :: LIST? OF RELATIONSHIP? | extract a list of relationships in a path |
replace | replace(original :: STRING?, target :: STRING?, replacement :: STRING?) :: STRING? | replace every occurrence of a target string |
reverse | reverse(ANY?) :: ANY? | reverse a string or list |
right | right(STRING?, INTEGER?) :: STRING? | string containing the specified number of rightmost characters of the original string |
round | round(input :: NUMBER?, precision :: INTEGER?, mode :: STRING?) :: FLOAT? | nearest number to the input |
sign | sign(NUMBER?) :: INTEGER? | signum of a number |
sin | sin(NUMBER?) :: FLOAT? | sine of a number of radians |
size | size(ANY?) :: INTEGER? | number of elements in a list or characters in a string |
split | split(input :: STRING?, delimiter :: STRING?) :: LIST? OF STRING? | split a string on every instance of a delimiter |
sqrt | sqrt(NUMBER?) :: FLOAT? | square root of a number |
substring | substring(original :: STRING?, start :: INTEGER? [, end :: INTEGER? ]) :: STRING? | substring of the original string, beginning with a 0-based index start and length |
tail | tail(LIST? OF ANY?) :: LIST? OF ANY? | return the list without its first element |
tan | tan(NUMBER?) :: FLOAT? | tangent of a number of radians |
timestamp | timestamp() :: INTEGER? | number of milliseconds elapsed since midnight, January 1, 1970 UTC |
toBoolean | toBoolean(STRING?) :: BOOLEAN? | convert a string into a boolean |
toFloat | toFloat(ANY?) :: FLOAT? | convert a string or integer into a float |
toInteger | toInteger(ANY?) :: INTEGER? | convert a string or float into an integer |
toLower | toLower(STRING?) :: STRING? | convert a string to lowercase |
toString | toString(ANY?) :: STRING? | convert a value to a string |
toUpper | toUpper(STRING?) :: STRING? | convert a string to uppercase |
trim | trim(STRING?) :: STRING? | removing leading and trailing whitespace from a string |
type | type(RELATIONSHIP?) :: STRING? | return the name of a relationship |
| Name | Signature | Description |
|---|---|---|
bytes | bytes(input :: STRING) :: BYTES | Returns bytes represented by a hexadecimal string |
castOrNull.boolean | castOrNull.boolean(value :: ANY) :: BOOLEAN | Casts the provided value to the type Bool. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.bytes | castOrNull.bytes(value :: ANY) :: BYTES | Casts the provided value to the type Bytes. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.datetime | castOrNull.datetime(value :: ANY) :: DATETIME | Casts the provided value to the type DateTime. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.duration | castOrNull.duration(value :: ANY) :: DURATION | Casts the provided value to the type Duration. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.float | castOrNull.float(value :: ANY) :: FLOAT | Casts the provided value to the type Floating. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.integer | castOrNull.integer(value :: ANY) :: INTEGER | Casts the provided value to the type Integer. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.list | castOrNull.list(value :: ANY) :: LIST OF ANY | Casts the provided value to the type List(Anything). If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.localdatetime | castOrNull.localdatetime(value :: ANY) :: LOCALDATETIME | Casts the provided value to the type LocalDateTime. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.map | castOrNull.map(value :: ANY) :: MAP | Casts the provided value to the type Map. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.node | castOrNull.node(value :: ANY) :: NODE | Casts the provided value to the type Node. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.path | castOrNull.path(value :: ANY) :: PATH | Casts the provided value to the type Path. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.relationship | castOrNull.relationship(value :: ANY) :: RELATIONSHIP | Casts the provided value to the type Relationship. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.string | castOrNull.string(value :: ANY) :: STRING | Casts the provided value to the type Str. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrThrow.boolean | castOrThrow.boolean(value :: ANY) :: BOOLEAN | Adds a runtime assertion that the provided |
castOrThrow.bytes | castOrThrow.bytes(value :: ANY) :: BYTES | Adds a runtime assertion that the provided |
castOrThrow.datetime | castOrThrow.datetime(value :: ANY) :: DATETIME | Adds a runtime assertion that the provided |
castOrThrow.duration | castOrThrow.duration(value :: ANY) :: DURATION | Adds a runtime assertion that the provided |
castOrThrow.float | castOrThrow.float(value :: ANY) :: FLOAT | Adds a runtime assertion that the provided |
castOrThrow.integer | castOrThrow.integer(value :: ANY) :: INTEGER | Adds a runtime assertion that the provided |
castOrThrow.list | castOrThrow.list(value :: ANY) :: LIST OF ANY | Adds a runtime assertion that the provided |
castOrThrow.localdatetime | castOrThrow.localdatetime(value :: ANY) :: LOCALDATETIME | Adds a runtime assertion that the provided |
castOrThrow.map | castOrThrow.map(value :: ANY) :: MAP | Adds a runtime assertion that the provided |
castOrThrow.node | castOrThrow.node(value :: ANY) :: NODE | Adds a runtime assertion that the provided |
castOrThrow.path | castOrThrow.path(value :: ANY) :: PATH | Adds a runtime assertion that the provided |
castOrThrow.relationship | castOrThrow.relationship(value :: ANY) :: RELATIONSHIP | Adds a runtime assertion that the provided |
castOrThrow.string | castOrThrow.string(value :: ANY) :: STRING | Adds a runtime assertion that the provided |
clusterPosition | clusterPosition() :: INTEGER | Returns the cluster position occupied by this member |
coll.max | coll.max(value :: LIST OF ANY) :: ANY | Computes the maximum of values in a list |
coll.max(input0 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY, input14 :: ANY) :: ANY | Computes the maximum argument | |
coll.min | coll.min(value :: LIST OF ANY) :: ANY | Computes the minimum of values in a list |
coll.min(input0 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY, input14 :: ANY) :: ANY | Computes the minimum argument | |
convert.stringToBytes | convert.stringToBytes(input :: STRING, encoding :: STRING) :: BYTES | Encodes a string into bytes according to the specified encoding |
date | date() :: DATE | Get the current local date |
date(options :: MAP) :: DATE | Construct a local date from the options | |
date(date :: STRING) :: DATE | Parse a local date from a string | |
date(date :: STRING, format :: STRING) :: DATE | Parse a local date from a string using a custom format | |
datetime | datetime() :: DATETIME | Get the current date time |
datetime(options :: MAP) :: DATETIME | Construct a date time from the options | |
datetime(datetime :: STRING) :: DATETIME | Parse a date time from a string | |
datetime(datetime :: STRING, format :: STRING) :: DATETIME | Parse a local date time from a string using a custom format | |
duration | duration(options :: MAP) :: DURATION | Construct a duration from the options |
duration(duration :: STRING) :: DURATION | Parse a duration from a string | |
duration.between | duration.between(date1 :: LOCALDATETIME, date2 :: LOCALDATETIME) :: DURATION | Compute the duration between two local dates |
duration.between(date1 :: DATETIME, date2 :: DATETIME) :: DURATION | Compute the duration between two dates | |
gen.boolean.from | gen.boolean.from(fromValue :: ANY) :: BOOLEAN | Deterministically generate a random boolean from the provided input. |
gen.boolean.from(fromValue :: ANY, withSize :: INTEGER) :: BOOLEAN | Deterministically generate a random boolean from the provided input. | |
gen.bytes.from | gen.bytes.from(fromValue :: ANY) :: BYTES | Deterministically generate a random bytes from the provided input. |
gen.bytes.from(fromValue :: ANY, withSize :: INTEGER) :: BYTES | Deterministically generate a random bytes from the provided input. | |
gen.float.from | gen.float.from(fromValue :: ANY) :: FLOAT | Deterministically generate a random float from the provided input. |
gen.float.from(fromValue :: ANY, withSize :: INTEGER) :: FLOAT | Deterministically generate a random float from the provided input. | |
gen.integer.from | gen.integer.from(fromValue :: ANY) :: INTEGER | Deterministically generate a random integer from the provided input. |
gen.integer.from(fromValue :: ANY, withSize :: INTEGER) :: INTEGER | Deterministically generate a random integer from the provided input. | |
gen.node.from | gen.node.from(fromValue :: ANY) :: NODE | Deterministically generate a random node from the provided input. |
gen.node.from(fromValue :: ANY, withSize :: INTEGER) :: NODE | Deterministically generate a random node from the provided input. | |
gen.string.from | gen.string.from(fromValue :: ANY) :: STRING | Deterministically generate a random string from the provided input. |
gen.string.from(fromValue :: ANY, withSize :: INTEGER) :: STRING | Deterministically generate a random string from the provided input. | |
getHost | getHost(node :: NODE) :: INTEGER | Compute which host a node should be assigned to (null if unknown without contacting the graph) |
getHost(nodeIdStr :: STRING) :: INTEGER | Compute which host a node ID (string representation) should be assigned to (null if unknown without contacting the graph) | |
getHost(nodeIdBytes :: BYTES) :: INTEGER | Compute which host a node ID (bytes representation) should be assigned to (null if unknown without contacting the graph) | |
hash | hash() :: INTEGER | Hashes the input arguments |
hash(input0 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY, input14 :: ANY) :: INTEGER | Hashes the input arguments | |
idFrom | idFrom(input0 :: ANY) :: ANY | Hashes the input arguments into a valid ID |
idFrom(input0 :: ANY, input1 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY, input14 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY, input14 :: ANY, input15 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
kafkaHash | kafkaHash(partitionKey :: STRING) :: INTEGER | Hashes a string to a (32-bit) integer using the same algorithm Apache Kafka uses for its DefaultPartitioner |
kafkaHash(partitionKey :: BYTES) :: INTEGER | Hashes a bytes value to a (32-bit) integer using the same algorithm Apache Kafka uses for its DefaultPartitioner | |
locIdFrom | locIdFrom(positionIdx :: INTEGER, input0 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY, input14 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
localdatetime | localdatetime() :: LOCALDATETIME | Get the current local date time |
localdatetime(options :: MAP) :: LOCALDATETIME | Construct a local date time from the options | |
localdatetime(datetime :: STRING) :: LOCALDATETIME | Parse a local date time from a string | |
localdatetime(datetime :: STRING, format :: STRING) :: LOCALDATETIME | Parse a local date time from a string using a custom format | |
localtime | localtime() :: LOCALTIME | Get the current local time |
localtime(options :: MAP) :: LOCALTIME | Construct a local time from the options | |
localtime(time :: STRING) :: LOCALTIME | Parse a local time from a string | |
localtime(time :: STRING, format :: STRING) :: LOCALTIME | Parse a local time from a string using a custom format | |
map.dropNullValues | map.dropNullValues(argument :: MAP) :: MAP | Keep only non-null from the map |
map.fromPairs | map.fromPairs(entries :: LIST OF LIST OF ANY) :: MAP | Construct a map from a list of [key,value] entries |
map.merge | map.merge(first :: MAP, second :: MAP) :: MAP | Merge two maps |
map.removeKey | map.removeKey(map :: MAP, key :: STRING) :: MAP | remove the key from the map |
map.sortedProperties | map.sortedProperties(map :: MAP) :: LIST OF LIST OF ANY | Extract from a map a list of [key,value] entries sorted by the key |
meta.type | meta.type(value :: ANY) :: STRING | Inspect the (name of the) type of a value |
parseJson | parseJson(jsonStr :: STRING) :: ANY | Parses jsonStr to a Cypher value |
quineId | quineId(input :: STRING) :: BYTES | Returns the Quine ID corresponding to the string |
strId | strId(input :: NODE) :: STRING | Returns a string representation of the node’s ID |
temporal.format | temporal.format(date :: DATETIME, format :: STRING) :: STRING | Convert date time into string |
temporal.format(date :: LOCALDATETIME, format :: STRING) :: STRING | Convert local date time into string | |
text.regexFirstMatch | text.regexFirstMatch(text :: STRING, regex :: STRING) :: LIST OF STRING | Parses the string |
text.regexGroups | text.regexGroups(text :: STRING, regex :: STRING) :: LIST OF STRING | Parses the string |
text.regexReplaceAll | text.regexReplaceAll(text :: STRING, regex :: STRING, replacement :: STRING) :: STRING | Replaces all instances of the regular expression |
text.split | text.split(text :: STRING, regex :: STRING) :: LIST OF STRING | Splits the string around matches of the regex |
text.split(text :: STRING, regex :: STRING, limit :: INTEGER) :: LIST OF STRING | Splits the string around the first | |
text.urldecode | text.urldecode(text :: STRING) :: LIST OF STRING | URL-decodes (x-www-form-urlencoded) the provided string |
text.urldecode(text :: STRING, decodePlusAsSpace :: BOOLEAN) :: LIST OF STRING | URL-decodes the provided string, using RFC3986 if decodePlusAsSpace = false | |
text.urlencode | text.urlencode(text :: STRING) :: LIST OF STRING | URL-encodes the provided string; additionally percent-encoding quotes, angle brackets, and curly braces |
text.urlencode(text :: STRING, usePlusForSpace :: BOOLEAN) :: LIST OF STRING | URL-encodes the provided string; additionally percent-encoding quotes, angle brackets, and curly braces; optionally using | |
text.urlencode(text :: STRING, encodeExtraChars :: STRING) :: LIST OF STRING | URL-encodes the provided string, additionally percent-encoding the characters enumerated in | |
text.urlencode(text :: STRING, usePlusForSpace :: BOOLEAN, encodeExtraChars :: STRING) :: LIST OF STRING | URL-encodes the provided string, additionally percent-encoding the characters enumerated in | |
text.utf8Decode | text.utf8Decode(bytes :: BYTES) :: STRING | Returns the bytes decoded as a UTF-8 String |
text.utf8Encode | text.utf8Encode(string :: STRING) :: BYTES | Returns the string encoded as UTF-8 bytes |
time | time() :: TIME | Get the current local time |
time(options :: MAP) :: TIME | Construct a local time from the options | |
time(time :: STRING) :: TIME | Parse a local time from a string | |
time(time :: STRING, format :: STRING) :: TIME | Parse a local time from a string using a custom format | |
toJson | toJson(x :: ANY) :: STRING | Returns x encoded as a JSON string |
| Name | Signature | Description | Mode |
|---|---|---|---|
create.relationship | create.relationship(from :: NODE, relType :: STRING, props :: MAP, to :: NODE) :: (rel :: RELATIONSHIP) | Create a relationship with a potentially dynamic name | WRITE |
create.setLabels | create.setLabels(node :: NODE, labels :: LIST OF STRING) :: VOID | Set the labels on the specified input node, overriding any previously set labels | WRITE |
create.setProperty | create.setProperty(node :: NODE, key :: STRING, value :: ANY) :: VOID | Set the property with the provided key on the specified input node | WRITE |
cypher.do.case | cypher.do.case(conditionals :: LIST OF ANY, elseQuery :: STRING, params :: MAP) :: (value :: MAP) | Given a list of conditional/query pairs, execute the first query with a true conditional | WRITE |
cypher.doIt | cypher.doIt(cypher :: STRING, params :: MAP) :: (value :: MAP) | Executes a Cypher query with the given parameters | WRITE |
cypher.runTimeboxed | cypher.runTimeboxed(cypher :: STRING, params :: MAP, timeout :: INTEGER) :: (value :: MAP) | Executes a Cypher query with the given parameters but abort after a certain number of milliseconds | WRITE |
db.indexes | db.indexes() :: (description :: ANY, indexName :: ANY, tokenNames :: ANY, properties :: ANY, state :: ANY, type :: ANY, progress :: ANY, provider :: ANY, id :: ANY, failureMessage :: ANY) | READ | |
db.propertyKeys | db.propertyKeys() :: (propertyKey :: ANY) | READ | |
db.relationshipTypes | db.relationshipTypes() :: (relationshipType :: ANY) | READ | |
dbms.labels | dbms.labels() :: (label :: ANY) | READ | |
debug.node | debug.node(node :: ANY) :: (atTime :: LOCALDATETIME, properties :: MAP, edges :: LIST OF ANY, latestUpdateMillisAfterSnapshot :: INTEGER, subscribers :: STRING, subscriptions :: STRING, multipleValuesStandingQueryStates :: LIST OF ANY, journal :: LIST OF ANY, graphNodeHashCode :: INTEGER) | Returns comprehensive internal state of a node including properties, edges, standing query states, and event journal. Useful for debugging why standing queries match or don’t match. | READ |
debug.sleep | debug.sleep(node :: ANY) :: VOID | Request a node sleep | READ |
do.when | do.when(condition :: BOOLEAN, ifQuery :: STRING, elseQuery :: STRING, params :: MAP) :: (value :: MAP) | Depending on the condition execute ifQuery or elseQuery | WRITE |
float.add | float.add(node :: NODE, key :: STRING, add :: FLOAT) :: (result :: FLOAT) | Atomically add to a floating-point property on a node by a certain amount (defaults to 1.0), returning the resultant value | WRITE |
getFilteredEdges | getFilteredEdges(node :: ANY, edgeTypes :: LIST OF STRING, directions :: LIST OF STRING, allowedNodes :: LIST OF ANY) :: (edge :: RELATIONSHIP) | Get edges from a node filtered by edge type, direction, and/or allowed destination nodes | READ |
getHost | getHost(node :: NODE) :: (host :: INTEGER) | Compute which host a node is currently located on | READ |
help.functions | help.functions() :: (name :: STRING, signature :: STRING, description :: STRING) | List registered functions | READ |
help.procedures | help.procedures() :: (name :: STRING, signature :: STRING, description :: STRING, mode :: STRING) | List registered procedures | READ |
incrementCounter | incrementCounter(node :: NODE, key :: STRING, amount :: INTEGER) :: (count :: INTEGER) | Atomically increment an integer property on a node by a certain amount, returning the resultant value | WRITE |
int.add | int.add(node :: NODE, key :: STRING, add :: INTEGER) :: (result :: INTEGER) | Atomically add to an integer property on a node by a certain amount (defaults to 1), returning the resultant value | WRITE |
loadJsonLines | loadJsonLines(url :: STRING) :: (value :: ANY) | Load a line-base JSON file, emitting one record per line | READ |
log | log(level :: STRING, value :: ANY) :: (log :: STRING) | Log a value to the system console during query execution. Supports levels: error, warn, info, debug, trace. | READ |
parseProtobuf | parseProtobuf(bytes :: BYTES, schemaUrl :: STRING, typeName :: STRING) :: (value :: MAP) | Parses a protobuf message into a Cypher map value, or null if the bytes are not parseable as the requested type | READ |
purgeNode | purgeNode(node :: ANY) :: VOID | Purge a node from history | WRITE |
random.walk | random.walk(start :: ANY, depth :: INTEGER, return :: FLOAT, in-out :: FLOAT, seed :: STRING) :: (walk :: LIST OF STRING) | Randomly walk edges from a starting node for a chosen depth. Returns a list of node IDs in the order they were encountered. | READ |
recentNodeIds | recentNodeIds(count :: INTEGER) :: (nodeId :: ANY) | Fetch the specified number of IDs of nodes from the in-memory cache | READ |
recentNodes | recentNodes(count :: INTEGER) :: (node :: NODE) | Fetch the specified number of nodes from the in-memory cache | READ |
reify.time | reify.time(timestamp :: DATETIME, periods :: LIST OF STRING) :: (node :: NODE) | Reifies the timestamp into a [sub]graph of time nodes, where each node represents one period (at the granularity of the period specifiers provided). Yields the reified nodes with the finest granularity. | WRITE |
set.insert | set.insert(node :: NODE, key :: STRING, add :: ANY) :: (result :: LIST OF ANY) | Atomically add an element to a list property treated as a set. If one or more instances of | WRITE |
set.union | set.union(node :: NODE, key :: STRING, add :: LIST OF ANY) :: (result :: LIST OF ANY) | Atomically add set of elements to a list property treated as a set. The elements in | WRITE |
standing.wiretap | standing.wiretap(options :: MAP) :: (data :: MAP, meta :: MAP) | Stream live results from a running standing query. Returns data and metadata for each match. | READ |
subscribers | subscribers(node :: ANY) :: (queryId :: INTEGER, queryDepth :: INTEGER, receiverId :: STRING, lastResult :: ANY) | Returns nodes subscribed to this node for standing query updates. Useful for tracing standing query propagation. | READ |
subscriptions | subscriptions(node :: ANY) :: (queryId :: INTEGER, queryDepth :: INTEGER, receiverId :: STRING, lastResult :: ANY) | Returns nodes this node subscribes to for standing query updates. Useful for tracing standing query propagation. | READ |
toProtobuf | toProtobuf(value :: MAP, schemaUrl :: STRING, typeName :: STRING) :: (protoBytes :: BYTES) | Serializes a Cypher value into bytes, according to a protobuf schema. Returns null if the value is not serializable as the requested type | READ |
util.sleep | util.sleep(duration :: INTEGER) :: VOID | Sleep for a certain number of milliseconds | READ |
| Name | Signature | Description |
|---|---|---|
abs | abs(NUMBER?) :: NUMBER? | absolute value of a number |
acos | acos(NUMBER?) :: FLOAT? | arcosine (in radians) of a number |
asin | asin(NUMBER?) :: FLOAT? | arcsine (in radians) of a number |
atan | atan(NUMBER?) :: FLOAT? | arctangent (in radians) of a number |
atan2 | atan2(NUMBER?, NUMBER?) :: FLOAT? | arctangent (in radians) of the quotient of its arguments |
ceil | ceil(NUMBER?) :: FLOAT? | smallest integer greater than or equal to the input |
coalesce | coalesce(ANY?, ..) :: ANY? | returns the first non- |
cos | cos(NUMBER?) :: FLOAT? | cosine of a number of radians |
cot | cot(NUMBER?) :: FLOAT? | cotangent of a number of radians |
degrees | degrees(NUMBER?) :: FLOAT? | convert radians to degrees |
e | e() :: FLOAT? | mathematical constant |
exp | exp(NUMBER?) :: FLOAT? | return the mathematical constant |
floor | floor(NUMBER?) :: FLOAT? | largest integer less than or equal to the input |
haversin | haversin(NUMBER?) :: FLOAT? | half the versine of a number |
head | head(LIST? OF ANY?) :: ANY? | extract the first element of a list |
id | id(NODE?) :: ANY? | extract the ID of a node |
keys | keys(ANY?) :: LIST? OF STRING? | extract the keys from a map, node, or relationship |
lTrim | lTrim(STRING?) :: STRING? | original string with leading whitespace removed |
labels | labels(ANY?) :: LIST? OF STRING? | extract the labels of a node or relationship |
last | last(LIST? OF ANY?) :: ANY? | extract the last element of a list |
left | left(STRING?, INTEGER?) :: STRING? | string containing the specified number of leftmost characters of the original string |
length | length(PATH?) :: INTEGER? | length of a path (ie. the number of relationships in it) |
log | log(NUMBER?) :: FLOAT? | natural logarithm of a number |
log10 | log10(NUMBER?) :: FLOAT? | common logarithm (base 10) of a number |
nodes | nodes(PATH?) :: LIST? OF NODE? | extract a list of nodes in a path |
pi | pi() :: FLOAT? | mathematical constant |
properties | properties(ANY?) :: MAP? | extract the properties from a map, node, or relationship |
rTrim | rTrim(STRING?) :: STRING? | original string with trailing whitespace removed |
radians | radians(NUMBER?) :: FLOAT? | convert degrees to radians |
rand | rand() :: FLOAT? | random float between 0 (inclusive) and 1 (exclusive) |
range | range(start :: INTEGER, end :: INTEGER, step :: INTEGER?) :: LIST? OF INTEGER? | construct a list of integers representing a range |
relationships | relationships(PATH?) :: LIST? OF RELATIONSHIP? | extract a list of relationships in a path |
replace | replace(original :: STRING?, target :: STRING?, replacement :: STRING?) :: STRING? | replace every occurrence of a target string |
reverse | reverse(ANY?) :: ANY? | reverse a string or list |
right | right(STRING?, INTEGER?) :: STRING? | string containing the specified number of rightmost characters of the original string |
round | round(input :: NUMBER?, precision :: INTEGER?, mode :: STRING?) :: FLOAT? | nearest number to the input |
sign | sign(NUMBER?) :: INTEGER? | signum of a number |
sin | sin(NUMBER?) :: FLOAT? | sine of a number of radians |
size | size(ANY?) :: INTEGER? | number of elements in a list or characters in a string |
split | split(input :: STRING?, delimiter :: STRING?) :: LIST? OF STRING? | split a string on every instance of a delimiter |
sqrt | sqrt(NUMBER?) :: FLOAT? | square root of a number |
substring | substring(original :: STRING?, start :: INTEGER? [, end :: INTEGER? ]) :: STRING? | substring of the original string, beginning with a 0-based index start and length |
tail | tail(LIST? OF ANY?) :: LIST? OF ANY? | return the list without its first element |
tan | tan(NUMBER?) :: FLOAT? | tangent of a number of radians |
timestamp | timestamp() :: INTEGER? | number of milliseconds elapsed since midnight, January 1, 1970 UTC |
toBoolean | toBoolean(STRING?) :: BOOLEAN? | convert a string into a boolean |
toFloat | toFloat(ANY?) :: FLOAT? | convert a string or integer into a float |
toInteger | toInteger(ANY?) :: INTEGER? | convert a string or float into an integer |
toLower | toLower(STRING?) :: STRING? | convert a string to lowercase |
toString | toString(ANY?) :: STRING? | convert a value to a string |
toUpper | toUpper(STRING?) :: STRING? | convert a string to uppercase |
trim | trim(STRING?) :: STRING? | removing leading and trailing whitespace from a string |
type | type(RELATIONSHIP?) :: STRING? | return the name of a relationship |
| Name | Signature | Description |
|---|---|---|
bytes | bytes(input :: STRING) :: BYTES | Returns bytes represented by a hexadecimal string |
castOrNull.boolean | castOrNull.boolean(value :: ANY) :: BOOLEAN | Casts the provided value to the type Bool. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.bytes | castOrNull.bytes(value :: ANY) :: BYTES | Casts the provided value to the type Bytes. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.datetime | castOrNull.datetime(value :: ANY) :: DATETIME | Casts the provided value to the type DateTime. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.duration | castOrNull.duration(value :: ANY) :: DURATION | Casts the provided value to the type Duration. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.float | castOrNull.float(value :: ANY) :: FLOAT | Casts the provided value to the type Floating. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.integer | castOrNull.integer(value :: ANY) :: INTEGER | Casts the provided value to the type Integer. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.list | castOrNull.list(value :: ANY) :: LIST OF ANY | Casts the provided value to the type List(Anything). If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.localdatetime | castOrNull.localdatetime(value :: ANY) :: LOCALDATETIME | Casts the provided value to the type LocalDateTime. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.map | castOrNull.map(value :: ANY) :: MAP | Casts the provided value to the type Map. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.node | castOrNull.node(value :: ANY) :: NODE | Casts the provided value to the type Node. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.path | castOrNull.path(value :: ANY) :: PATH | Casts the provided value to the type Path. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.relationship | castOrNull.relationship(value :: ANY) :: RELATIONSHIP | Casts the provided value to the type Relationship. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrNull.string | castOrNull.string(value :: ANY) :: STRING | Casts the provided value to the type Str. If the provided value is not already an instance of the requested type, this will return null. For functions that convert between types, see |
castOrThrow.boolean | castOrThrow.boolean(value :: ANY) :: BOOLEAN | Adds a runtime assertion that the provided |
castOrThrow.bytes | castOrThrow.bytes(value :: ANY) :: BYTES | Adds a runtime assertion that the provided |
castOrThrow.datetime | castOrThrow.datetime(value :: ANY) :: DATETIME | Adds a runtime assertion that the provided |
castOrThrow.duration | castOrThrow.duration(value :: ANY) :: DURATION | Adds a runtime assertion that the provided |
castOrThrow.float | castOrThrow.float(value :: ANY) :: FLOAT | Adds a runtime assertion that the provided |
castOrThrow.integer | castOrThrow.integer(value :: ANY) :: INTEGER | Adds a runtime assertion that the provided |
castOrThrow.list | castOrThrow.list(value :: ANY) :: LIST OF ANY | Adds a runtime assertion that the provided |
castOrThrow.localdatetime | castOrThrow.localdatetime(value :: ANY) :: LOCALDATETIME | Adds a runtime assertion that the provided |
castOrThrow.map | castOrThrow.map(value :: ANY) :: MAP | Adds a runtime assertion that the provided |
castOrThrow.node | castOrThrow.node(value :: ANY) :: NODE | Adds a runtime assertion that the provided |
castOrThrow.path | castOrThrow.path(value :: ANY) :: PATH | Adds a runtime assertion that the provided |
castOrThrow.relationship | castOrThrow.relationship(value :: ANY) :: RELATIONSHIP | Adds a runtime assertion that the provided |
castOrThrow.string | castOrThrow.string(value :: ANY) :: STRING | Adds a runtime assertion that the provided |
coll.max | coll.max(value :: LIST OF ANY) :: ANY | Computes the maximum of values in a list |
coll.max(input0 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY) :: ANY | Computes the maximum argument | |
coll.max(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY, input14 :: ANY) :: ANY | Computes the maximum argument | |
coll.min | coll.min(value :: LIST OF ANY) :: ANY | Computes the minimum of values in a list |
coll.min(input0 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY) :: ANY | Computes the minimum argument | |
coll.min(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY, input14 :: ANY) :: ANY | Computes the minimum argument | |
convert.stringToBytes | convert.stringToBytes(input :: STRING, encoding :: STRING) :: BYTES | Encodes a string into bytes according to the specified encoding |
date | date() :: DATE | Get the current local date |
date(options :: MAP) :: DATE | Construct a local date from the options | |
date(date :: STRING) :: DATE | Parse a local date from a string | |
date(date :: STRING, format :: STRING) :: DATE | Parse a local date from a string using a custom format | |
datetime | datetime() :: DATETIME | Get the current date time |
datetime(options :: MAP) :: DATETIME | Construct a date time from the options | |
datetime(datetime :: STRING) :: DATETIME | Parse a date time from a string | |
datetime(datetime :: STRING, format :: STRING) :: DATETIME | Parse a local date time from a string using a custom format | |
duration | duration(options :: MAP) :: DURATION | Construct a duration from the options |
duration(duration :: STRING) :: DURATION | Parse a duration from a string | |
duration.between | duration.between(date1 :: LOCALDATETIME, date2 :: LOCALDATETIME) :: DURATION | Compute the duration between two local dates |
duration.between(date1 :: DATETIME, date2 :: DATETIME) :: DURATION | Compute the duration between two dates | |
gen.boolean.from | gen.boolean.from(fromValue :: ANY) :: BOOLEAN | Deterministically generate a random boolean from the provided input. |
gen.boolean.from(fromValue :: ANY, withSize :: INTEGER) :: BOOLEAN | Deterministically generate a random boolean from the provided input. | |
gen.bytes.from | gen.bytes.from(fromValue :: ANY) :: BYTES | Deterministically generate a random bytes from the provided input. |
gen.bytes.from(fromValue :: ANY, withSize :: INTEGER) :: BYTES | Deterministically generate a random bytes from the provided input. | |
gen.float.from | gen.float.from(fromValue :: ANY) :: FLOAT | Deterministically generate a random float from the provided input. |
gen.float.from(fromValue :: ANY, withSize :: INTEGER) :: FLOAT | Deterministically generate a random float from the provided input. | |
gen.integer.from | gen.integer.from(fromValue :: ANY) :: INTEGER | Deterministically generate a random integer from the provided input. |
gen.integer.from(fromValue :: ANY, withSize :: INTEGER) :: INTEGER | Deterministically generate a random integer from the provided input. | |
gen.node.from | gen.node.from(fromValue :: ANY) :: NODE | Deterministically generate a random node from the provided input. |
gen.node.from(fromValue :: ANY, withSize :: INTEGER) :: NODE | Deterministically generate a random node from the provided input. | |
gen.string.from | gen.string.from(fromValue :: ANY) :: STRING | Deterministically generate a random string from the provided input. |
gen.string.from(fromValue :: ANY, withSize :: INTEGER) :: STRING | Deterministically generate a random string from the provided input. | |
getHost | getHost(node :: NODE) :: INTEGER | Compute which host a node should be assigned to (null if unknown without contacting the graph) |
getHost(nodeIdStr :: STRING) :: INTEGER | Compute which host a node ID (string representation) should be assigned to (null if unknown without contacting the graph) | |
getHost(nodeIdBytes :: BYTES) :: INTEGER | Compute which host a node ID (bytes representation) should be assigned to (null if unknown without contacting the graph) | |
hash | hash() :: INTEGER | Hashes the input arguments |
hash(input0 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY) :: INTEGER | Hashes the input arguments | |
hash(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY, input14 :: ANY) :: INTEGER | Hashes the input arguments | |
idFrom | idFrom(input0 :: ANY) :: ANY | Hashes the input arguments into a valid ID |
idFrom(input0 :: ANY, input1 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY, input14 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
idFrom(input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY, input14 :: ANY, input15 :: ANY) :: ANY | Hashes the input arguments into a valid ID | |
kafkaHash | kafkaHash(partitionKey :: STRING) :: INTEGER | Hashes a string to a (32-bit) integer using the same algorithm Apache Kafka uses for its DefaultPartitioner |
kafkaHash(partitionKey :: BYTES) :: INTEGER | Hashes a bytes value to a (32-bit) integer using the same algorithm Apache Kafka uses for its DefaultPartitioner | |
locIdFrom | locIdFrom(positionIdx :: INTEGER, input0 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
locIdFrom(positionIdx :: INTEGER, input0 :: ANY, input1 :: ANY, input2 :: ANY, input3 :: ANY, input4 :: ANY, input5 :: ANY, input6 :: ANY, input7 :: ANY, input8 :: ANY, input9 :: ANY, input10 :: ANY, input11 :: ANY, input12 :: ANY, input13 :: ANY, input14 :: ANY) :: ANY | Generates a consistent (based on a hash of the arguments) ID. The ID created will be managed by the cluster member whose position corresponds to the provided position index given the cluster topology. | |
localdatetime | localdatetime() :: LOCALDATETIME | Get the current local date time |
localdatetime(options :: MAP) :: LOCALDATETIME | Construct a local date time from the options | |
localdatetime(datetime :: STRING) :: LOCALDATETIME | Parse a local date time from a string | |
localdatetime(datetime :: STRING, format :: STRING) :: LOCALDATETIME | Parse a local date time from a string using a custom format | |
localtime | localtime() :: LOCALTIME | Get the current local time |
localtime(options :: MAP) :: LOCALTIME | Construct a local time from the options | |
localtime(time :: STRING) :: LOCALTIME | Parse a local time from a string | |
localtime(time :: STRING, format :: STRING) :: LOCALTIME | Parse a local time from a string using a custom format | |
map.dropNullValues | map.dropNullValues(argument :: MAP) :: MAP | Keep only non-null from the map |
map.fromPairs | map.fromPairs(entries :: LIST OF LIST OF ANY) :: MAP | Construct a map from a list of [key,value] entries |
map.merge | map.merge(first :: MAP, second :: MAP) :: MAP | Merge two maps |
map.removeKey | map.removeKey(map :: MAP, key :: STRING) :: MAP | remove the key from the map |
map.sortedProperties | map.sortedProperties(map :: MAP) :: LIST OF LIST OF ANY | Extract from a map a list of [key,value] entries sorted by the key |
meta.type | meta.type(value :: ANY) :: STRING | Inspect the (name of the) type of a value |
parseJson | parseJson(jsonStr :: STRING) :: ANY | Parses jsonStr to a Cypher value |
quineId | quineId(input :: STRING) :: BYTES | Returns the Quine ID corresponding to the string |
strId | strId(input :: NODE) :: STRING | Returns a string representation of the node’s ID |
temporal.format | temporal.format(date :: DATETIME, format :: STRING) :: STRING | Convert date time into string |
temporal.format(date :: LOCALDATETIME, format :: STRING) :: STRING | Convert local date time into string | |
text.regexFirstMatch | text.regexFirstMatch(text :: STRING, regex :: STRING) :: LIST OF STRING | Parses the string |
text.regexGroups | text.regexGroups(text :: STRING, regex :: STRING) :: LIST OF STRING | Parses the string |
text.regexReplaceAll | text.regexReplaceAll(text :: STRING, regex :: STRING, replacement :: STRING) :: STRING | Replaces all instances of the regular expression |
text.split | text.split(text :: STRING, regex :: STRING) :: LIST OF STRING | Splits the string around matches of the regex |
text.split(text :: STRING, regex :: STRING, limit :: INTEGER) :: LIST OF STRING | Splits the string around the first | |
text.urldecode | text.urldecode(text :: STRING) :: LIST OF STRING | URL-decodes (x-www-form-urlencoded) the provided string |
text.urldecode(text :: STRING, decodePlusAsSpace :: BOOLEAN) :: LIST OF STRING | URL-decodes the provided string, using RFC3986 if decodePlusAsSpace = false | |
text.urlencode | text.urlencode(text :: STRING) :: LIST OF STRING | URL-encodes the provided string; additionally percent-encoding quotes, angle brackets, and curly braces |
text.urlencode(text :: STRING, usePlusForSpace :: BOOLEAN) :: LIST OF STRING | URL-encodes the provided string; additionally percent-encoding quotes, angle brackets, and curly braces; optionally using | |
text.urlencode(text :: STRING, encodeExtraChars :: STRING) :: LIST OF STRING | URL-encodes the provided string, additionally percent-encoding the characters enumerated in | |
text.urlencode(text :: STRING, usePlusForSpace :: BOOLEAN, encodeExtraChars :: STRING) :: LIST OF STRING | URL-encodes the provided string, additionally percent-encoding the characters enumerated in | |
text.utf8Decode | text.utf8Decode(bytes :: BYTES) :: STRING | Returns the bytes decoded as a UTF-8 String |
text.utf8Encode | text.utf8Encode(string :: STRING) :: BYTES | Returns the string encoded as UTF-8 bytes |
time | time() :: TIME | Get the current local time |
time(options :: MAP) :: TIME | Construct a local time from the options | |
time(time :: STRING) :: TIME | Parse a local time from a string | |
time(time :: STRING, format :: STRING) :: TIME | Parse a local time from a string using a custom format | |
toJson | toJson(x :: ANY) :: STRING | Returns x encoded as a JSON string |
| Name | Signature | Description | Mode |
|---|---|---|---|
parseProtobuf | parseProtobuf(bytes :: BYTES, schemaUrl :: STRING, typeName :: STRING) :: (value :: MAP) | Parses a protobuf message into a Cypher map value, or null if the bytes are not parseable as the requested type | READ |
toProtobuf | toProtobuf(value :: MAP, schemaUrl :: STRING, typeName :: STRING) :: (protoBytes :: BYTES) | Serializes a Cypher value into bytes, according to a protobuf schema. Returns null if the value is not serializable as the requested type | READ |
create.relationship | create.relationship(from :: NODE, relType :: STRING, props :: MAP, to :: NODE) :: (rel :: RELATIONSHIP) | Create a relationship with a potentially dynamic name | WRITE |
create.setLabels | create.setLabels(node :: NODE, labels :: LIST OF STRING) :: VOID | Set the labels on the specified input node, overriding any previously set labels | WRITE |
create.setProperty | create.setProperty(node :: NODE, key :: STRING, value :: ANY) :: VOID | Set the property with the provided key on the specified input node | WRITE |
cypher.do.case | cypher.do.case(conditionals :: LIST OF ANY, elseQuery :: STRING, params :: MAP) :: (value :: MAP) | Given a list of conditional/query pairs, execute the first query with a true conditional | WRITE |
cypher.doIt | cypher.doIt(cypher :: STRING, params :: MAP) :: (value :: MAP) | Executes a Cypher query with the given parameters | WRITE |
cypher.runTimeboxed | cypher.runTimeboxed(cypher :: STRING, params :: MAP, timeout :: INTEGER) :: (value :: MAP) | Executes a Cypher query with the given parameters but abort after a certain number of milliseconds | WRITE |
db.indexes | db.indexes() :: (description :: ANY, indexName :: ANY, tokenNames :: ANY, properties :: ANY, state :: ANY, type :: ANY, progress :: ANY, provider :: ANY, id :: ANY, failureMessage :: ANY) | READ | |
db.propertyKeys | db.propertyKeys() :: (propertyKey :: ANY) | READ | |
db.relationshipTypes | db.relationshipTypes() :: (relationshipType :: ANY) | READ | |
dbms.labels | dbms.labels() :: (label :: ANY) | READ | |
debug.node | debug.node(node :: ANY) :: (atTime :: LOCALDATETIME, properties :: MAP, edges :: LIST OF ANY, latestUpdateMillisAfterSnapshot :: INTEGER, subscribers :: STRING, subscriptions :: STRING, multipleValuesStandingQueryStates :: LIST OF ANY, journal :: LIST OF ANY, graphNodeHashCode :: INTEGER) | Returns comprehensive internal state of a node including properties, edges, standing query states, and event journal. Useful for debugging why standing queries match or don’t match. | READ |
debug.sleep | debug.sleep(node :: ANY) :: VOID | Request a node sleep | READ |
do.when | do.when(condition :: BOOLEAN, ifQuery :: STRING, elseQuery :: STRING, params :: MAP) :: (value :: MAP) | Depending on the condition execute ifQuery or elseQuery | WRITE |
float.add | float.add(node :: NODE, key :: STRING, add :: FLOAT) :: (result :: FLOAT) | Atomically add to a floating-point property on a node by a certain amount (defaults to 1.0), returning the resultant value | WRITE |
getFilteredEdges | getFilteredEdges(node :: ANY, edgeTypes :: LIST OF STRING, directions :: LIST OF STRING, allowedNodes :: LIST OF ANY) :: (edge :: RELATIONSHIP) | Get edges from a node filtered by edge type, direction, and/or allowed destination nodes | READ |
help.functions | help.functions() :: (name :: STRING, signature :: STRING, description :: STRING) | List registered functions | READ |
help.procedures | help.procedures() :: (name :: STRING, signature :: STRING, description :: STRING, mode :: STRING) | List registered procedures | READ |
incrementCounter | incrementCounter(node :: NODE, key :: STRING, amount :: INTEGER) :: (count :: INTEGER) | Atomically increment an integer property on a node by a certain amount, returning the resultant value | WRITE |
int.add | int.add(node :: NODE, key :: STRING, add :: INTEGER) :: (result :: INTEGER) | Atomically add to an integer property on a node by a certain amount (defaults to 1), returning the resultant value | WRITE |
loadJsonLines | loadJsonLines(url :: STRING) :: (value :: ANY) | Load a line-base JSON file, emitting one record per line | READ |
log | log(level :: STRING, value :: ANY) :: (log :: STRING) | Log a value to the system console during query execution. Supports levels: error, warn, info, debug, trace. | READ |
purgeNode | purgeNode(node :: ANY) :: VOID | Purge a node from history | WRITE |
random.walk | random.walk(start :: ANY, depth :: INTEGER, return :: FLOAT, in-out :: FLOAT, seed :: STRING) :: (walk :: LIST OF STRING) | Randomly walk edges from a starting node for a chosen depth. Returns a list of node IDs in the order they were encountered. | READ |
recentNodeIds | recentNodeIds(count :: INTEGER) :: (nodeId :: ANY) | Fetch the specified number of IDs of nodes from the in-memory cache | READ |
recentNodes | recentNodes(count :: INTEGER) :: (node :: NODE) | Fetch the specified number of nodes from the in-memory cache | READ |
reify.time | reify.time(timestamp :: DATETIME, periods :: LIST OF STRING) :: (node :: NODE) | Reifies the timestamp into a [sub]graph of time nodes, where each node represents one period (at the granularity of the period specifiers provided). Yields the reified nodes with the finest granularity. | WRITE |
set.insert | set.insert(node :: NODE, key :: STRING, add :: ANY) :: (result :: LIST OF ANY) | Atomically add an element to a list property treated as a set. If one or more instances of | WRITE |
set.union | set.union(node :: NODE, key :: STRING, add :: LIST OF ANY) :: (result :: LIST OF ANY) | Atomically add set of elements to a list property treated as a set. The elements in | WRITE |
standing.wiretap | standing.wiretap(options :: MAP) :: (data :: MAP, meta :: MAP) | Stream live results from a running standing query. Returns data and metadata for each match. | READ |
subscribers | subscribers(node :: ANY) :: (queryId :: INTEGER, queryDepth :: INTEGER, receiverId :: STRING, lastResult :: ANY) | Returns nodes subscribed to this node for standing query updates. Useful for tracing standing query propagation. | READ |
subscriptions | subscriptions(node :: ANY) :: (queryId :: INTEGER, queryDepth :: INTEGER, receiverId :: STRING, lastResult :: ANY) | Returns nodes this node subscribes to for standing query updates. Useful for tracing standing query propagation. | READ |
util.sleep | util.sleep(duration :: INTEGER) :: VOID | Sleep for a certain number of milliseconds | READ |
WARN [org.apache.pekko.actor.ActorSystemImpl(graph-service)] [graph-service-pekko.actor.default-dispatcher-12] org.apache.pekko.actor.ActorSystemImpl - Illegal request, responding with status '400 Bad Request': Unsupported HTTP method: The HTTP method started with 0x16 rather than any known HTTP method. Perhaps this was an HTTPS request sent to an HTTP endpoint?
**This warning is likely harmless and requires no action.**
The warning means that Quine is serving its web UI over plain HTTP (the default behavior), but the browser is trying to connect over HTTPS. Many browsers, Safari in particular, will attempt to connect over HTTPS as an extra security measure, sometimes even when the URL explicitly starts with `http://`! When the browser receives back a 400 failure response, then it connects correctly over HTTP. If you think this is happening, then you can safely ignore these warnings.
---
# Queries
URL: https://quine.io/learn/troubleshooting/queries/
# Troubleshooting Queries
This guide covers how to debug and troubleshoot queries in Quine. Understanding the differences between query types helps you apply the right debugging approach.
## Query Types in Quine
Quine has three types of queries, each with different troubleshooting approaches:
| Query Type | Description | When It Runs | Key Debugging Tools |
|------------|-------------|--------------|---------------------|
| **Ad-hoc queries** | Interactive Cypher queries you run manually | On-demand | `EXPLAIN`, `recentNodes()` |
| **Ingest queries** | Queries that execute for each record during data ingestion | Per-record during ingest | `EXPLAIN`, `recentNodes()` |
| **Standing queries** | Incremental pattern matching that fires when data matches | Continuously as data changes | `standing.wiretap()`, `recentNodes()` |
### Ad-hoc Queries
Ad-hoc queries are interactive Cypher queries you run against the graph. Troubleshoot them when:
- Query returns unexpected results or no results
- Query is slow or times out
- Query causes high resource usage
**Debugging approach**: Use [EXPLAIN](query-execution-plans.md) to understand the execution plan.
### Ingest Queries
Ingest queries run for each record in your data pipeline. Troubleshoot them when:
- Data isn't appearing in the graph
- Nodes have wrong properties or missing edges
- Ingest is slow or causing backpressure
**Debugging approach**: Test the query as an ad-hoc query first with sample data. Use [recentNodes()](#recentnodes) to verify ingested data.
See also: [Troubleshooting Ingest](ingest.md)
### Standing Queries
Standing queries incrementally match patterns as data enters the graph. Troubleshoot them when:
- Pattern should match but doesn't fire
- Getting unexpected matches or cancellations
- Standing query causes performance issues
**Debugging approach**: First run the pattern as an ad-hoc query to verify it matches expected data. Use [standing.wiretap()](#standingwiretap) to stream live results.
See also: [Standing Queries](../standing-queries/standing-queries.md)
## Debugging Tools Reference
| Tool | Page | Use For |
|------|------|---------|
| `EXPLAIN` | [Query Execution Plans](query-execution-plans.md) | Understanding how queries execute |
| `standing.wiretap()` | [Debugging Procedures](#standingwiretap) | Streaming live standing query results |
| `recentNodes()` | [Debugging Procedures](#recentnodes) | Viewing recently accessed nodes |
## Debugging Workflow
Follow this systematic approach when queries don't behave as expected.
### Step 1: Validate Ingested Data
Before debugging queries, confirm that data is flowing into the graph correctly:
```cypher
// View recently ingested nodes
CALL recentNodes(10) YIELD node
RETURN node
```
In the Exploration UI, double-click nodes to view their edges and hover to see properties. Verify:
- Nodes exist with expected IDs
- Properties have correct values and types
- Edges connect the expected nodes
### Step 2: Run as Ad-Hoc Query
If debugging a standing query or ingest query, run it as a regular Cypher query first:
```cypher
// Your standing query pattern, run as ad-hoc
MATCH (order:Order)-[:PLACED_BY]->(customer:Customer)
WHERE order.total > 1000
RETURN order, customer
```
If this returns results but your standing query doesn't fire:
- The data may have been ingested before the standing query was registered
- Check if the query mode (`DISTINCT_ID` vs `MULTIPLE_VALUES`) is appropriate
### Step 3: Analyze Query Plan
Use [EXPLAIN](query-execution-plans.md) to understand how your query executes:
```cypher
EXPLAIN MATCH (order:Order)-[:PLACED_BY]->(customer:Customer)
WHERE order.total > 1000
RETURN order, customer
```
Look for:
- **AllNodesScan** in `AnchoredEntry`: Consider anchoring by ID
- **Filter operators**: Are conditions being applied efficiently?
- **canContainAllNodeScan: true**: Query may be slow on large graphs
## Common Failure Patterns
### Missing Data in Graph
If ingested data doesn't appear in the graph, common causes include inconsistent `idFrom` usage across data sources, ingest race conditions, and edge creation that depends on property matching instead of ID lookups.
**See**: [Troubleshooting Ingest](ingest.md) for detailed guidance on missing data and race conditions.
### Standing Query Not Matching
| Symptom | Cause | Solution |
|---------|-------|----------|
| Query works ad-hoc but not as standing query | Data ingested before standing query was registered | Propagate the standing query to existing data (`propagateTo=INCLUDE_SLEEPING` on create, or the `standingQueries:propagate` endpoint); see [Propagation](../standing-queries/standing-queries.md#propagation-to-existing-data) |
| Pattern should match but doesn't | Data shape doesn't match pattern exactly | Run pattern as ad-hoc query to verify data matches |
| Matches initially then stops | Negative match canceling positive match | Check for data updates that invalidate the pattern |
### Slow Query Performance
Slow queries typically result from full graph scans, standing query backpressure, supernodes, or cross-host messaging overhead.
**See**: [Diagnosing Bottlenecks](diagnosing-bottlenecks.md) for metrics-based diagnosis and [Troubleshooting Ingest](ingest.md#slow-ingests) for ingest-specific optimizations.
## Debugging Procedures
Quine provides several procedures for inspecting query and node state.
### standing.wiretap()
Streams live results from a running standing query. This procedure runs until the standing query is canceled, emitting results incrementally as they match.
```cypher
// Wiretap "hasMaternalGrandpaJoe" and return properties of matching nodes
CALL standing.wiretap({ name: "hasMaternalGrandpaJoe" }) YIELD meta, data
WHERE meta.isPositiveMatch
MATCH (n) WHERE id(n) = data.id
RETURN properties(n)
```
Results appear incrementally as they match. Cancel the query when you have the information you need.
!!! warning
The `standing.wiretap` procedure only stops running if the standing query is canceled (since otherwise, it can never be certain that there won't be more forthcoming match results). This means that it is risky to use the procedure in the Cypher REST API or in other places where results are not reported incrementally and queries cannot be canceled.
#### Return Fields
| Field | Type | Description |
|-------|------|-------------|
| `meta` | Map | Metadata including `isPositiveMatch` (boolean) and `isInitialResult` (boolean) |
| `data` | Map | The data returned by the standing query pattern |
### Results API Endpoint
Quine can stream standing query results outside of the Exploration UI using the server-sent events (SSE) endpoint at [Standing Query Status: `GET /api/v2/graph/quine/standingQueries/{standingQueryName}`](/reference/rest-api/?av=v2#/operations/get-standing-query-status):
```
GET /api/v2/graph/quine/standingQueries/{standingQueryName}
```
The endpoint surfaces new matches as they are produced.
```bash
$ curl http://localhost:8080/api/v2/graph/quine/standingQueries/hasMaternalGrandpaJoe
data:
data:
data:{"data":{"id":"2756309260014435"},"meta":{"isInitialResult":true,"isPositiveMatch":true,"resultId":"8f408026-8fb3-3955-c81a-7259175f41b8"}}
event:result
id:8f408026-8fb3-3955-c81a-7259175f41b8
data:{"data":{"id":"7945274922095468"},"meta":{"isInitialResult":true,"isPositiveMatch":true,"resultId":"6a83dda3-08a1-e085-ee7d-14138398f336"}}
event:result
id:6a83dda3-08a1-e085-ee7d-14138398f336
```
Using the SSE output, you can query the matching nodes directly:
```cypher
// Query for children of nodes with IDs from the SSE endpoint above
UNWIND [2756309260014435, 7945274922095468, 6994090876991233] AS personId
MATCH (person)<-[:HAS_MOTHER|:HAS_FATHER]-(child) WHERE id(person) = personId
RETURN person.name, child.name, child.yearBorn
```
### recentNodes()
Fetches recently accessed nodes from the in-memory cache. Useful for quickly verifying that data is being ingested:
```cypher
// View recently ingested nodes
CALL recentNodes(10) YIELD node
RETURN node
```
In the Exploration UI, double-click nodes to view their edges and hover to see properties.
---
# Query Execution Plans
URL: https://quine.io/learn/troubleshooting/query-execution-plans/
# Query Execution Plans
Use the `EXPLAIN` command to understand how Quine executes your Cypher queries. This helps identify performance issues and optimize query patterns.
!!! tip "When to Use EXPLAIN"
* **Ad-hoc queries**: Before running expensive queries on production data
* **Ingest queries**: When optimizing data loading performance
* **Standing queries**: To understand pattern matching behavior (though standing queries compile differently)
## Using EXPLAIN
Prefix any Cypher query with `EXPLAIN` to see the execution plan without running the query:
```cypher
EXPLAIN MATCH (n:Person)-[:KNOWS]->(m:Person)
WHERE n.age > 30
RETURN m.name
```
This returns a query plan showing how Quine will execute the query. The query is compiled but **not executed**, making it safe to use on production systems.
## Query Plan Structure
The execution plan is returned as a JSON tree structure:
```json
{
"operatorType": "Filter",
"args": {
"condition": "n.age > 30"
},
"identifiers": ["n", "m"],
"children": [
{
"operatorType": "Expand",
"args": {
"edgeName": "KNOWS",
"direction": "Outgoing"
},
"identifiers": ["n", "m"],
"children": [...]
}
],
"isReadOnly": true,
"isIdempotent": true,
"canContainAllNodeScan": true
}
```
Plans form a tree where each operator processes results from its children. Read the plan from the innermost children outward to understand execution order.
## Root-Level Flags
The root of the query plan includes metadata flags:
| Flag | Description |
|------|-------------|
| `isReadOnly` | `true` if the query performs no writes to the graph |
| `isIdempotent` | `true` if running the query multiple times produces the same result |
| `canContainAllNodeScan` | `true` if the query may scan all nodes in the graph (performance warning) |
!!! warning "Performance Warning"
If `canContainAllNodeScan` is `true`, your query may be slow on large graphs. Consider anchoring your query by node ID using `WHERE id(n) = idFrom(...)`. See [Using IDs in a Query](../../core-concepts/id-provider.md) for details.
## Query Plan Operators
The following operators appear as `operatorType` in query plans:
### Data Scanning & Entry Points
| Operator | Description |
|----------|-------------|
| `AnchoredEntry` | Starts from a specific node ID or index lookup |
| `ArgumentEntry` | Starts from an externally provided node argument |
### Graph Traversal
| Operator | Description |
|----------|-------------|
| `Expand` | Follows edges with optional length bounds and direction constraints |
| `GetDegree` | Returns the count of edges matching specified constraints |
| `LocalNode` | Checks node labels and properties, optionally binding to a variable |
### Data Flow & Combination
| Operator | Description |
|----------|-------------|
| `Apply` | Executes one query then another sequentially (flatMap) |
| `Union` | Executes two queries and concatenates their results |
| `Or` | Executes the first query; uses second only if first returns nothing |
| `ValueHashJoin` | Joins two queries on matching property values |
| `SemiApply` | Filters results based on whether a sub-query succeeds |
| `Cross` | Calculates cross product of multiple query plan results |
### Filtering & Transformation
| Operator | Description |
|----------|-------------|
| `Filter` | Filters rows by a condition |
| `FilterMap` | Projects and optionally filters data from nested query results |
| `Optional` | Emits results, or input row if no results |
| `AdjustContext` | Adds, removes, or renames columns |
| `Unwind` | Expands a list into multiple rows |
### Aggregation & Ordering
| Operator | Description |
|----------|-------------|
| `EagerAggregation` | Groups and aggregates results |
| `Return` | Applies ORDER BY, DISTINCT, SKIP, LIMIT in one operator |
| `Skip` | Drops the first N results |
| `Limit` | Keeps only the first N results |
| `Sort` | Sorts results by expression(s) |
| `Distinct` | Removes duplicate rows |
### Data Modification
| Operator | Description |
|----------|-------------|
| `SetProperty` | Sets a single node property |
| `SetProperties` | Batch updates node properties |
| `SetLabels` | Adds or removes labels from a node |
| `SetEdge` | Creates or deletes an edge |
| `Delete` | Removes a node, relationship, or path |
### Special Operations
| Operator | Description |
|----------|-------------|
| `ProcedureCall` | Calls a user-defined procedure |
| `SubQuery` | Executes a sub-query and stitches results |
| `LocalProperty` | Generates a row containing a property value from a node |
| `LoadCSV` | Loads and iterates over CSV data |
| `Empty` | Returns no results |
| `Unit` | Returns input unchanged |
## Reading an Execution Plan
Here's how to interpret a query plan for the query:
```cypher
EXPLAIN MATCH (n:Person)-[:KNOWS]->(m) WHERE n.age > 30 RETURN m.name
```
1. **Start at the innermost operator** - This is typically `AnchoredEntry` or a scan operation
2. **Follow the tree outward** - Each parent operator processes its children's output
3. **Look for performance issues**:
- `AnchoredEntry` with `AllNodesScan` indicates a full graph scan
- Multiple `Cross` operators can indicate combinatorial explosion
- `Filter` operators late in the plan may process many unnecessary rows
## Query Type Considerations
### Ad-hoc Queries
For interactive queries, use `EXPLAIN` before running expensive operations:
```cypher
// Check the plan first
EXPLAIN MATCH (n:Person)-[:KNOWS*1..5]->(m:Person)
WHERE n.name = "Alice"
RETURN m
// If plan looks reasonable, run the actual query
MATCH (n:Person)-[:KNOWS*1..5]->(m:Person)
WHERE n.name = "Alice"
RETURN m
```
### Ingest Queries
For ingest queries that run repeatedly, optimize the plan once during development. Run the query as an ad-hoc query first to test behavior and performance before deploying it as part of your ingest pipeline:
```cypher
// Test as ad-hoc query with sample data
MATCH (n) WHERE id(n) = idFrom("user", "test-user-123")
SET n.lastSeen = datetime()
RETURN n
// Then verify the plan anchors properly
EXPLAIN MATCH (n) WHERE id(n) = idFrom("user", $that.userId)
SET n.lastSeen = $that.timestamp
```
Look for `AnchoredEntry` rather than scans, since ingest queries execute for every record.
### Standing Queries
Standing queries compile into a different internal representation optimized for incremental matching. While `EXPLAIN` shows the logical plan, the actual execution differs.
Before registering a standing query, run the pattern as an ad-hoc query to verify it matches expected data:
```cypher
// Test the pattern as an ad-hoc query first
MATCH (order:Order)-[:PLACED_BY]->(customer:Customer)
WHERE order.total > 1000
RETURN order, customer
LIMIT 10
// Once satisfied, register as a standing query with the pattern
```
For debugging registered standing queries, use [debugging procedures](queries.md#debugging-procedures) to inspect runtime state.
---
# Recipes
URL: https://quine.io/recipes/
A [recipe](../learn/recipe-ref-manual.md) is a collection of configuration that sets a Quine instance up for a specific purpose. A recipe is defined in a `yaml` file and contains configuration for: ingest streams, standing queries, UI configuration, and some metadata about the recipe. They are also a great way to learn about Quine features like [ingest streams](../getting-started/ingest-streams-tutorial.md) that build the streaming graph, [standing queries](../getting-started/ingest-streams-tutorial.md) that find results you are looking for and take actions, and customizing the [exploration UI](../getting-started/exploration-ui.md).
To visualize what a recipe does and get recommendations before running it, paste it into the [Quine Recipe Analyzer](https://www.thatdot.com/quine/recipe-analyzer.html).
In order to launch Quine with a recipe, use `-r`, followed by either the short name of a sample recipe or a local YAML filename. Some recipes can expect input parameters. The parameter values are passed by using command line arguments with `-x` or `--recipe-value`.
For example, to run the [wikipedia](wikipedia.md) recipe[^1].
[^1]: When a recipe is launched that does not contain a file extension,
Quine will fetch and launch the recipe by name from the
[recipes](https://github.com/thatdot/quine/tree/main/quine/recipes/)
repository on GitHub.
For example, `java -jar quine-2.1.1.jar -r wikipedia` will download the `wikipedia.yaml` file from
from GitHub and launch that version of the recipe, even if you have a copy of `wikipedia.yaml`
your local directory. This method only works for example recipes that are distributed with Quine.
```shell
java -jar quine-2.1.1.jar -r wikipedia
```
## Quine Recipes
- #### [APT Detection](apt-detection.md)
---
Endpoint logs and network traffic data merge to auto-detect exfiltration and alert for an IoB that matches a typical malicious data exfiltration pattern.
Shared by: [Ryan Wright](https://github.com/rrwright)
- #### [CDN Cache Efficiency](cdn.md)
---
Continuous and real-time computation of CDN cache node efficiency from Fastly CDN logs, materialized by ASN, Geo, Asset and PoP.
Shared by: [Allan Konar](https://github.com/7evenbridges)
- #### [Password Spraying](password-spraying.md)
---
Detect password spraying attacks in real time. Ingests JSON-formatted IAM-style password authentication log file, creates graph, uses standing queries to sent alerts.
Shared by: [Allan Konar](https://github.com/7evenbridges)
- #### [Temporal Locality](duration.md)
---
Relate email messages sent or received by a specific user within a 4-6 minute window.
Shared by: [Michael Aglietti](https://github.com/maglietti)
- #### [Monitor an MMO](planetside-2.md)
---
Build a live event-driven model of what's currently happening in the "PlanetSide 2" MMOFPS video game. A great example of ingesting from Websockets.
Shared by: [Ethan Bell](https://github.com/emanb29)
- #### [IMDB Movie Data](movieData.md)
---
Explore a familiar graph data set using Quine to combine data from separate files, unwind nested data into unique nodes, populate node parameters, then generate a new stream from the combined data.
Shared by: [Michael Aglietti](https://github.com/maglietti)
- #### [Wikipedia Page Creation Feed](wikipedia.md)
---
Wikipedia page creation events are instantiated in the graph with relationships to a refied time model Additionally, page creation event comments are echoed to standard output.
Shared by: [Landon Kuhn](https://github.com/landon9720)
- #### [Entity Resolution](./entity-resolution.md)
---
Learn how real-time entity resolution – the deduplication of similar data – can drastically help with creating a comprehensive view of your data.
Shared by: [Ryan Wright](https://github.com/rrwright)
- #### [Webhook Data Enrichment](./webhook.md)
---
Learn how to enrich the Quine graph from an external service using the Standing Query HTTP webhook output.
Shared by: [Matthew Pagan](https://github.com/mastapegs)
- #### [Ethereum Tag Propagation](ethereum.md)
---
Ingestion a live stream of events from the Ethereum Blockchain and demonstrate real-time "dirty money" tag propagation.
Shared by: [Ethan Bell](https://github.com/emanb29)
- #### [Basic File Ingest](ingest.md)
---
Ingest each line from a file passed as `$in_file` into a disconnected graph then fill each node with a property containing the line.
Shared by: [Landon Kuhn](https://github.com/landon9720)
- #### [Conway's Game of Life](conways-gol.md)
---
Conway's Game of Life cellular automaton demonstrating Quine's Turing completeness through recursive standing queries.
Shared by: [Matthew Cullum](https://github.com/brackishman)
- #### [Harry Potter](hpotter.md)
---
Small graph of connected nodes that explore the familial relationships of Harry Potter characters.
Shared by: [Alec Theriault](https://github.com/harpocrates)
- #### [Apache Log Analytics](apache_log.md)
---
Example use of Quine's unique Standing Query function to parse incoming text for each line of an Apache web server access log into a graph.
Shared by: [Josh Cody](https://github.com/joshcody)
- #### [Certstream Firehose](certstream-firehose.md)
---
Reproduces the behavior of the [certstream website](https://certstream.calidog.io/) by connecting to the certstream firehose via SSL-encrypted websocket and printing to standard out each time a new certificate is detected.
Shared by: [Ethan Bell](https://github.com/emanb29)
- #### [Quine Logs Recipe](quine-logs-recipe.md)
---
Ingest Quine log lines into Quine!
Shared by: [Michael Aglietti](https://github.com/maglietti)
- #### [Approximating Pi](pi.md)
---
Incrementally approximates pi using Leibniz' formula in Quine.
Shared by: [Ethan Bell](https://github.com/emanb29)
---
# Apache Log Analysis
URL: https://quine.io/recipes/apache_log/
## Full Recipe
=== "Recipe v1"
Shared by: [Josh Cody](https://github.com/joshcody)
A simple recipe that parses incoming text for each line of an Apache web server access log and structures it into a graph. A useful introduction to standing queries, the powerful feature that makes Quine unique.
??? example "Apache Log Analysis Recipe"
```{ .yaml linenums="1" }
--8<-- "recipes/assets/apache_log.yaml"
```
[Download Recipe](assets/apache_log.yaml){ .md-button download="" .md-button--primary data-category="Quine Recipe Detail" data-label="Download recipe yaml" data-action="button click" }
=== "Recipe v2"
Shared by: [Josh Cody](https://github.com/joshcody)
A simple recipe that parses incoming text for each line of an Apache web server access log and structures it into a graph. A useful introduction to standing queries, the powerful feature that makes Quine unique.
??? example "Apache Log Analysis Recipe"
```{ .yaml linenums="1" }
--8<-- "recipes/assets/v2/apache_log.yaml"
```
[Download Recipe](assets/v2/apache_log.yaml){ .md-button download="" .md-button--primary data-category="Quine Recipe Detail" data-label="Download recipe yaml" data-action="button click" }
## Scenario
This recipe loads a sample Apache log file and manifests disconnected nodes in a graph for basic analysis and metric reporting.
## Sample Data
!!! note
Download the sample data to the same directory where you will run Quine.
A sample Apache web server access logs dataset downloaded from [`https://recipes.quine.io/sample_apache_logs`](https://recipes.quine.io/sample_apache_logs) or using the command below.
```shell
curl -L https://recipes.quine.io/sample_apache_logs -o apache.log
```
## How it Works
The recipe reads log entries from the sample data files using an [ingest stream](../learn/ingest-sources/index.md) to manifest a graph in Quine. A regular expression inside the ingest stream Cypher query parses the logline and populates parameters in the node.
The ingest stream processes the `apache.log` file:
=== "YAML"
```yaml
- type: FileIngest
path: $in_file
format:
type: CypherLine
query: |-
WITH text.regexFirstMatch($that, '(\S+)\s+\S+\s+(\S+)\s+\[(.+)\]\s+"(.*)\s+(.*)\s+(.*)"\s+([0-9]+)\s+(\S+)\s+"(.*)"\s+"(.*)"')
AS r
CREATE ({
sourceIp: r[1],
user: r[2],
time: datetime(r[3], 'dd/MMM/yyyy:HH:mm:ss Z'),
verb: r[4],
path: r[5],
httpVersion: r[6],
status: r[7],
size: r[8],
referrer: r[9],
agent: r[10],
type: 'log'
})
```
=== "JSON"
```json title="POST /api/v1/ingest/INGEST-1"
{
"type": "FileIngest",
"path": "$in_file",
"format": {
"type": "CypherLine",
"query": "WITH text.regexFirstMatch($that, '(\\\\S+)\\\\s+\\\\S+\\\\s+(\\\\S+)\\\\s+\\\\[(.+)\\\\]\\\\s+\"(.*)\\\\s+(.*)\\\\s+(.*)\"\\\\s+([0-9]+)\\\\s+(\\\\S+)\\\\s+\"(.*)\"\\\\s+\"(.*)\"') AS r CREATE ({ sourceIp: r[1], user: r[2], time: datetime(r[3], 'dd/MMM/yyyy:HH:mm:ss Z'), verb: r[4], path: r[5], httpVersion: r[6], status: r[7], size: r[8], referrer: r[9], agent: r[10], type: 'log' })"
}
}
```
=== "YAML"
```yaml
ingestStreams:
- name: apache-log-ingest
source:
type: File
path: $in_file
format:
type: Line
query: |-
WITH text.regexFirstMatch($that, '(\S+)\s+\S+\s+(\S+)\s+\[(.+)\]\s+"(.*)\s+(.*)\s+(.*)"\s+([0-9]+)\s+(\S+)\s+"(.*)"\s+"(.*)"')
AS r
CREATE ({
sourceIp: r[1],
user: r[2],
time: datetime(r[3], 'dd/MMM/yyyy:HH:mm:ss Z'),
verb: r[4],
path: r[5],
httpVersion: r[6],
status: r[7],
size: r[8],
referrer: r[9],
agent: r[10],
type: 'log'
})
```
=== "JSON"
```json title="POST /api/v2/graph/quine/ingests"
{
"name": "apache-log-ingest",
"source": {
"type": "File",
"path": "$in_file",
"format": {
"type": "Line"
}
},
"query": "WITH text.regexFirstMatch($that, '(\\S+)\\s+\\S+\\s+(\\S+)\\s+\\[(.+)\\]\\s+\"(.*)\\s+(.*)\\s+(.*)\"\\s+([0-9]+)\\s+(\\S+)\\s+\"(.*)\"\\s+\"(.*)\"') AS r CREATE ({ sourceIp: r[1], user: r[2], time: datetime(r[3], 'dd/MMM/yyyy:HH:mm:ss Z'), verb: r[4], path: r[5], httpVersion: r[6], status: r[7], size: r[8], referrer: r[9], agent: r[10], type: 'log' })"
}
```
A [standing query](../learn/standing-queries/standing-queries.md) is configured to detect nodes that have a `type` of `log` and then create relationships between the nodes and their verbs.
=== "YAML"
```yaml
- pattern:
type: Cypher
query: MATCH (l) WHERE l.type = 'log' RETURN DISTINCT id(l) AS id
mode: DistinctId
outputs:
verb:
type: CypherQuery
query: |-
MATCH (l) WHERE id(l) = $that.data.id
MATCH (v) WHERE id(v) = idFrom('verb', l.verb)
SET v.type = 'verb',
v.verb = l.verb
CREATE (l)-[:verb]->(v)
```
=== "JSON"
```json title="POST /api/v1/query/standing/STANDING-1"
{
"pattern": {
"type": "Cypher",
"query": "MATCH (l) WHERE l.type = 'log' RETURN DISTINCT id(l) AS id",
"mode": "DistinctId"
},
"outputs": {
"verb": {
"type": "CypherQuery",
"query": "MATCH (l) WHERE id(l) = $that.data.id MATCH (v) WHERE id(v) = idFrom('verb', l.verb) SET v.type = 'verb', v.verb = l.verb CREATE (l)-[:verb]->(v)"
}
}
}
```
=== "YAML"
```yaml
standingQueries:
- name: log-to-verb
pattern:
type: Cypher
query: MATCH (l) WHERE l.type = 'log' RETURN DISTINCT id(l) AS id
mode: DISTINCT_ID
outputs:
- name: verb
preEnrichmentTransformation:
type: InlineData
resultEnrichment:
query: |-
MATCH (l) WHERE id(l) = $that.id
MATCH (v) WHERE id(v) = idFrom('verb', l.verb)
SET v.type = 'verb',
v.verb = l.verb
CREATE (l)-[:verb]->(v)
RETURN null
parameter: that
destinations:
- type: Drop
```
=== "JSON"
```json title="POST /api/v2/graph/quine/standingQueries"
{
"name": "log-to-verb",
"pattern": {
"type": "Cypher",
"query": "MATCH (l) WHERE l.type = 'log' RETURN DISTINCT id(l) AS id",
"mode": "DISTINCT_ID"
},
"outputs": [
{
"name": "verb",
"preEnrichmentTransformation": {
"type": "InlineData"
},
"resultEnrichment": {
"query": "MATCH (l) WHERE id(l) = $that.id MATCH (v) WHERE id(v) = idFrom('verb', l.verb) SET v.type = 'verb', v.verb = l.verb CREATE (l)-[:verb]->(v) RETURN null",
"parameter": "that"
},
"destinations": [
{
"type": "Drop"
}
]
}
]
}
```
## Running the Recipe
``` shell hl_lines="1"
❯ java -jar quine-2.1.1.jar -r apache_log.yaml --recipe-value in_file=apache.log
Graph is ready
Running Recipe: Apache Log Analytics
Using 1 sample queries
Running Standing Query STANDING-1
Running Ingest Stream INGEST-1
Status query URL is http://localhost:8080#MATCH%20%28l%29%2D%5Brel%3Averb%5D%2D%3E%28v%29%20WHERE%20l%2Etype%20%3D%20%27log%27%20AND%20v%2Etype%20%3D%20%27verb%27%20AND%20v%2Everb%20%3D%20%27GET%27%20RETURN%20count%28rel%29%20AS%20get%5Fcount
Quine web server available at http://localhost:8080
INGEST-1 status is completed and ingested 10000
```
## Summary
The recipe contains a status query that will emit a link in the console window to view the results for the count of `GET` requests in the log file. The status query will also update the console with a running results count of `GET` entries as they are encountered.
``` json
Status query URL is http://localhost:8080#MATCH%20%28l%29%2D%5Brel%3Averb%5D%2D%3E%28v%29%20WHERE%20l%2Etype%20%3D%20%27log%27%20AND%20v%2Etype%20%3D%20%27verb%27%20AND%20v%2Everb%20%3D%20%27GET%27%20RETURN%20count%28rel%29%20AS%20get%5Fcount
```
At the time of writing this recipe, the sample data file contains 9951 `GET` requests.
``` text
---[ Status Query result 1 ]------------
get_count | 9951 count 10000
----------+-----------------------------
```
!!! Tip
Quick Queries are available by right clicking on a node.
| Quick Query | Node Type | Description |
| :--------------- | :-------- | :------------------------------------------------ |
| Adjacent Nodes | All | Display the nodes that are adjacent to this node. |
| Refresh | All | Refresh the content stored in a node |
| Local Properties | All | Display the properties stored by the node |
## Build your skills
What Cypher query could you write to return a count for other HTTP verbs in the log file?
??? success "Solution"
We solved this by modifying the status query to be less specific and to return unique node verb parameters as part of the results.
Enter this query into the Exploration UI and hit ++shift+enter++.
``` cypher
MATCH (l)-[rel:verb]->(v)
WHERE l.type = 'log' AND v.type = 'verb'
RETURN DISTINCT v.verb, count(rel)
```
Our results:
| v.verb | count(rel) |
| :-------- | :--------- |
| "OPTIONS" | 1 |
| "POST" | 5 |
| "HEAD" | 42 |
| "GET" | 9951 |
---
# APT Detection
URL: https://quine.io/recipes/apt-detection/
## Full Recipe
=== "Recipe v1"
Shared by: [Ryan Wright](https://github.com/rrwright)
This APT (Advanced Persistent Threat) detection recipe ingests EDR (Endpoint Detection and Response) and network traffic logs, while monitoring for an IoB (Indicator of Behavior) that matches malicious data exfiltration patterns.
??? example "APT Recipe"
```{ .yaml linenums="1" }
--8<-- "recipes/assets/apt-detection.yaml"
```
[Download Recipe](assets/apt-detection.yaml){ .md-button download="" .md-button--primary }
=== "Recipe v2"
Shared by: [Ryan Wright](https://github.com/rrwright)
This APT (Advanced Persistent Threat) detection recipe ingests EDR (Endpoint Detection and Response) and network traffic logs, while monitoring for an IoB (Indicator of Behavior) that matches malicious data exfiltration patterns.
??? example "APT Recipe"
```{ .yaml linenums="1" }
--8<-- "recipes/assets/v2/apt-detection.yaml"
```
[Download Recipe](assets/v2/apt-detection.yaml){ .md-button download="" .md-button--primary }
## Scenario
In this scenario, a malicious Excel macro collects personal data and stores it in a temporary file. The APT process `ntclean` infiltrated the system previously through an SSH exploit, and now reads from that temporary file and exfiltrates data from the network *hiding it as an HTTP GET request* before deleting the temporary file to cover its tracks.
Using a standing query, the recipe monitors for covert interprocess communication using a file to pass data. When that pattern is matched, with a network SEND event, we have our smoking gun and a URL is logged linking to the Quine Exploration UI with the full activity and context for investigation.
The source of the SSH exploit that planted the APT and the destination for exfiltrated data utilize the same IP address.
## Sample Data
Download the sample data to the same directory where Quine will be run.
* `endpoint.json` - [https://recipes.quine.io/apt-detection/endpoint-json](https://recipes.quine.io/apt-detection/endpoint-json)
* `network.json` - [https://recipes.quine.io/apt-detection/network-json](https://recipes.quine.io/apt-detection/network-json)
## How it Works
The recipe reads observations from the two sample data files using [ingest streams](../learn/ingest-sources/index.md) to manifest a graph in Quine. A separate ingest stream is configured to process each file, each containing Cypher that parses the observations, manifests nodes, and relates them to each other in the graph.
INGEST-1 processes the `endpoints.json` file:
=== "YAML"
```yaml
- type: FileIngest
path: endpoint.json
format:
type: CypherJson
query: >-
MATCH (proc), (event), (object)
WHERE id(proc) = idFrom($that.pid)
AND id(event) = idFrom($that)
AND id(object) = idFrom($that.object)
SET proc.id = $that.pid,
proc: Process,
event.type = $that.event_type,
event: EndpointEvent,
event.time = $that.time,
object.data = $that.object
CREATE (proc)-[:EVENT]->(event)-[:EVENT]->(object)
```
=== "JSON"
```json title="POST /api/v1/ingest/INGEST-1"
{
"type": "FileIngest",
"path": "endpoint.json",
"format": {
"type": "CypherJson",
"query": "MATCH (proc), (event), (object) WHERE id(proc) = idFrom($that.pid) AND id(event) = idFrom($that) AND id(object) = idFrom($that.object) SET proc.id = $that.pid, proc: Process, event.type = $that.event_type, event: EndpointEvent, event.time = $that.time, object.data = $that.object CREATE (proc)-[:EVENT]->(event)-[:EVENT]->(object)"
}
}
```
=== "YAML"
```yaml
ingestStreams:
- name: endpoint-events
source:
type: File
path: $endpoint_file
format:
type: Json
query: >-
MATCH (proc), (event), (object)
WHERE id(proc) = idFrom($that.pid)
AND id(event) = idFrom($that)
AND id(object) = idFrom($that.object)
SET proc.id = $that.pid,
proc: Process,
event.type = $that.event_type,
event: EndpointEvent,
event.time = $that.time,
object.data = $that.object
CREATE (proc)-[:EVENT]->(event)-[:EVENT]->(object)
```
=== "JSON"
```json title="POST /api/v2/graph/quine/ingests"
{
"name": "endpoint-events",
"source": {
"type": "File",
"path": "$endpoint_file",
"format": {
"type": "Json"
}
},
"query": "MATCH (proc), (event), (object) WHERE id(proc) = idFrom($that.pid) AND id(event) = idFrom($that) AND id(object) = idFrom($that.object) SET proc.id = $that.pid, proc: Process, event.type = $that.event_type, event: EndpointEvent, event.time = $that.time, object.data = $that.object CREATE (proc)-[:EVENT]->(event)-[:EVENT]->(object)"
}
```
INGEST-2 processes the `network.json` file:
=== "YAML"
```yaml
- type: FileIngest
path: network.json
format:
type: CypherJson
query: >-
MATCH (src), (dst), (event)
WHERE id(src) = idFrom($that.src_ip+":"+$that.src_port)
AND id(dst) = idFrom($that.dst_ip+":"+$that.dst_port)
AND id(event) = idFrom('network_event', $that)
SET src.ip = $that.src_ip+":"+$that.src_port,
src: IP,
dst.ip = $that.dst_ip+":"+$that.dst_port,
dst: IP,
event.proto = $that.proto,
event.time = $that.time,
event.detail = $that.detail,
event: NetTraffic
CREATE (src)-[:NET_TRAFFIC]->(event)-[:NET_TRAFFIC]->(dst)
```
=== "JSON"
```json title="POST /api/v1/ingest/INGEST-2"
{
"type": "FileIngest",
"path": "network.json",
"format": {
"type": "CypherJson",
"query": "MATCH (src), (dst), (event) WHERE id(src) = idFrom($that.src_ip+\":\"+$that.src_port)\n AND id(dst) = idFrom($that.dst_ip+\":\"+$that.dst_port)\n AND id(event) = idFrom('network_event', $that)\n\nSET src.ip = $that.src_ip+\":\"+$that.src_port,\n src: IP,\n dst.ip = $that.dst_ip+\":\"+$that.dst_port,\n dst: IP,\n event.proto = $that.proto,\n event.time = $that.time,\n event.detail = $that.detail,\n event: NetTraffic\n\nCREATE (src)-[:NET_TRAFFIC]->(event)-[:NET_TRAFFIC]->(dst)"
}
}
```
=== "YAML"
```yaml
ingestStreams:
- name: network-events
source:
type: File
path: $network_file
format:
type: Json
query: >-
MATCH (src), (dst), (event)
WHERE id(src) = idFrom($that.src_ip+":"+$that.src_port)
AND id(dst) = idFrom($that.dst_ip+":"+$that.dst_port)
AND id(event) = idFrom('network_event', $that)
SET src.ip = $that.src_ip+":"+$that.src_port,
src: IP,
dst.ip = $that.dst_ip+":"+$that.dst_port,
dst: IP,
event.proto = $that.proto,
event.time = $that.time,
event.detail = $that.detail,
event: NetTraffic
CREATE (src)-[:NET_TRAFFIC]->(event)-[:NET_TRAFFIC]->(dst)
```
=== "JSON"
```json title="POST /api/v2/graph/quine/ingests"
{
"name": "network-events",
"source": {
"type": "File",
"path": "$network_file",
"format": {
"type": "Json"
}
},
"query": "MATCH (src), (dst), (event) WHERE id(src) = idFrom($that.src_ip+\":\"+$that.src_port) AND id(dst) = idFrom($that.dst_ip+\":\"+$that.dst_port) AND id(event) = idFrom('network_event', $that) SET src.ip = $that.src_ip+\":\"+$that.src_port, src: IP, dst.ip = $that.dst_ip+\":\"+$that.dst_port, dst: IP, event.proto = $that.proto, event.time = $that.time, event.detail = $that.detail, event: NetTraffic CREATE (src)-[:NET_TRAFFIC]->(event)-[:NET_TRAFFIC]->(dst)"
}
```
A [standing query](../learn/standing-queries/standing-queries.md) is configured to detect a WRITE->READ->SEND->DELETE pattern that is typical for this type of exflitration event.
=== "YAML"
```yaml
- pattern:
type: Cypher
query: >-
MATCH (e1)-[:EVENT]->(f)<-[:EVENT]-(e2),
(f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)
WHERE e1.type = "WRITE"
AND e2.type = "READ"
AND e3.type = "DELETE"
AND e4.type = "SEND"
RETURN DISTINCT id(f) as fileId
```
=== "JSON"
```json title="POST /api/v1/query/standing/STANDING-1"
{
"pattern": {
"type": "Cypher",
"query": "MATCH (e1)-[:EVENT]->(f)<-[:EVENT]-(e2), \n (f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)\nWHERE e1.type = \"WRITE\"\n AND e2.type = \"READ\"\n AND e3.type = \"DELETE\"\n AND e4.type = \"SEND\"\nRETURN DISTINCT id(f) as fileId"
},
"outputs": {
"stolen-data": {
"type": "CypherQuery",
"query": "MATCH (p1)-[:EVENT]->(e1)-[:EVENT]->(f)<-[:EVENT]-(e2)<-[:EVENT]-(p2), \n (f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)-[:EVENT]->(ip)\nWHERE id(f) = $that.data.fileId\n AND e1.type = \"WRITE\"\n AND e2.type = \"READ\"\n AND e3.type = \"DELETE\"\n AND e4.type = \"SEND\"\n AND e1.time < e2.time\n AND e2.time < e3.time\n AND e2.time < e4.time\n\nCREATE (e1)-[:NEXT]->(e2)-[:NEXT]->(e4)-[:NEXT]->(e3)\nWITH e1, e2, e3, e4, p1, p2, f, ip, \"http://localhost:8080/#MATCH\" + text.urlencode(\" (e1),(e2),(e3),(e4),(p1),(p2),(f),(ip) WHERE id(p1)='\"+strId(p1)+\"' AND id(e1)='\"+strId(e1)+\"' AND id(f)='\"+strId(f)+\"' AND id(e2)='\"+strId(e2)+\"' AND id(p2)='\"+strId(p2)+\"' AND id(e3)='\"+strId(e3)+\"' AND id(e4)='\"+strId(e4)+\"' AND id(ip)='\"+strId(ip)+"' RETURN e1, e2, e3, e4, p1, p2, f, ip\") as URL RETURN URL",
"andThen": {
"type": "PrintToStandardOut"
}
}
}
}
```
=== "YAML"
```yaml
standingQueries:
- name: exfiltration-detection
pattern:
type: Cypher
query: >-
MATCH (e1)-[:EVENT]->(f)<-[:EVENT]-(e2),
(f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)
WHERE e1.type = "WRITE"
AND e2.type = "READ"
AND e3.type = "DELETE"
AND e4.type = "SEND"
RETURN DISTINCT id(f) as fileId
mode: DISTINCT_ID
outputs:
- name: stolen-data
preEnrichmentTransformation:
type: InlineData
resultEnrichment:
query: >-
MATCH (p1)-[:EVENT]->(e1)-[:EVENT]->(f)<-[:EVENT]-(e2)<-[:EVENT]-(p2),
(f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)-[:EVENT]->(ip)
WHERE id(f) = $that.fileId
AND e1.type = "WRITE"
AND e2.type = "READ"
AND e3.type = "DELETE"
AND e4.type = "SEND"
AND e1.time < e2.time
AND e2.time < e3.time
AND e2.time < e4.time
CREATE (e1)-[:NEXT]->(e2)-[:NEXT]->(e4)-[:NEXT]->(e3)
WITH e1, e2, e3, e4, p1, p2, f, ip, "http://localhost:8080/#MATCH" + text.urlencode(" (e1),(e2),(e3),(e4),(p1),(p2),(f),(ip) WHERE id(p1)='"+strId(p1)+"' AND id(e1)='"+strId(e1)+"' AND id(f)='"+strId(f)+"' AND id(e2)='"+strId(e2)+"' AND id(p2)='"+strId(p2)+"' AND id(e3)='"+strId(e3)+"' AND id(e4)='"+strId(e4)+"' AND id(ip)='"+strId(ip)+"' RETURN e1, e2, e3, e4, p1, p2, f, ip") as URL
RETURN URL
parameter: that
destinations:
- type: StandardOut
```
=== "JSON"
```json title="POST /api/v2/graph/quine/standingQueries"
{
"name": "exfiltration-detection",
"pattern": {
"type": "Cypher",
"query": "MATCH (e1)-[:EVENT]->(f)<-[:EVENT]-(e2), (f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4) WHERE e1.type = \"WRITE\" AND e2.type = \"READ\" AND e3.type = \"DELETE\" AND e4.type = \"SEND\" RETURN DISTINCT id(f) as fileId",
"mode": "DISTINCT_ID"
},
"outputs": [
{
"name": "stolen-data",
"preEnrichmentTransformation": {
"type": "InlineData"
},
"resultEnrichment": {
"query": "MATCH (p1)-[:EVENT]->(e1)-[:EVENT]->(f)<-[:EVENT]-(e2)<-[:EVENT]-(p2), (f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)-[:EVENT]->(ip) WHERE id(f) = $that.fileId AND e1.type = \"WRITE\" AND e2.type = \"READ\" AND e3.type = \"DELETE\" AND e4.type = \"SEND\" AND e1.time < e2.time AND e2.time < e3.time AND e2.time < e4.time CREATE (e1)-[:NEXT]->(e2)-[:NEXT]->(e4)-[:NEXT]->(e3) WITH e1, e2, e3, e4, p1, p2, f, ip, \"http://localhost:8080/#MATCH\" + text.urlencode(\" (e1),(e2),(e3),(e4),(p1),(p2),(f),(ip) WHERE id(p1)='\"+strId(p1)+\"' AND id(e1)='\"+strId(e1)+\"' AND id(f)='\"+strId(f)+\"' AND id(e2)='\"+strId(e2)+\"' AND id(p2)='\"+strId(p2)+\"' AND id(e3)='\"+strId(e3)+\"' AND id(e4)='\"+strId(e4)+\"' AND id(ip)='\"+strId(ip)+\"' RETURN e1, e2, e3, e4, p1, p2, f, ip\") as URL RETURN URL",
"parameter": "that"
},
"destinations": [
{
"type": "StandardOut"
}
]
}
]
}
```
Once Quine detects the pattern, the event is sent to a standing query output for additional processing and action.
```yaml
outputs:
stolen-data:
type: CypherQuery
query: >-
MATCH (p1)-[:EVENT]->(e1)-[:EVENT]->(f)<-[:EVENT]-(e2)<-[:EVENT]-(p2),
(f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)-[:EVENT]->(ip)
WHERE id(f) = $that.data.fileId
AND e1.type = "WRITE"
AND e2.type = "READ"
AND e3.type = "DELETE"
AND e4.type = "SEND"
AND e1.time < e2.time
AND e2.time < e3.time
AND e2.time < e4.time
CREATE (e1)-[:NEXT]->(e2)-[:NEXT]->(e4)-[:NEXT]->(e3)
With e1, e2, e3, e4, p1, p2, f, ip, "http://localhost:8080/#MATCH" + text.urlencode(" (e1),(e2),(e3),(e4),(p1),(p2),(f),(ip) WHERE id(p1)='"+strId(p1)+"' AND id(e1)='"+strId(e1)+"' AND id(f)='"+strId(f)+"' AND id(e2)='"+strId(e2)+"' AND id(p2)='"+strId(p2)+"' AND id(e3)='"+strId(e3)+"' AND id(e4)='"+strId(e4)+"' AND id(ip)='"+strId(ip)+"' RETURN e1, e2, e3, e4, p1, p2, f, ip") as URL
RETURN URL
andThen:
type: PrintToStandardOut
```
```yaml
outputs:
- name: stolen-data
resultEnrichment:
query: >-
MATCH (p1)-[:EVENT]->(e1)-[:EVENT]->(f)<-[:EVENT]-(e2)<-[:EVENT]-(p2),
(f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)-[:EVENT]->(ip)
WHERE id(f) = $that.fileId
AND e1.type = "WRITE"
AND e2.type = "READ"
AND e3.type = "DELETE"
AND e4.type = "SEND"
AND e1.time < e2.time
AND e2.time < e3.time
AND e2.time < e4.time
CREATE (e1)-[:NEXT]->(e2)-[:NEXT]->(e4)-[:NEXT]->(e3)
With e1, e2, e3, e4, p1, p2, f, ip, "http://localhost:8080/#MATCH" + text.urlencode(" (e1),(e2),(e3),(e4),(p1),(p2),(f),(ip) WHERE id(p1)='"+strId(p1)+"' AND id(e1)='"+strId(e1)+"' AND id(f)='"+strId(f)+"' AND id(e2)='"+strId(e2)+"' AND id(p2)='"+strId(p2)+"' AND id(e3)='"+strId(e3)+"' AND id(e4)='"+strId(e4)+"' AND id(ip)='"+strId(ip)+"' RETURN e1, e2, e3, e4, p1, p2, f, ip") as URL
RETURN URL
parameter: that
destinations:
- type: StandardOut
```
The result once the pattern is detected is to output a link to the console that an analyst can use to review the event further within Quine's Exploration UI.
``` { .json linenums="1" }
2022-12-15 11:28:52,413 Standing query `stolen-data` match: {"meta":{"isPositiveMatch":true,"resultId":"5bd8beb7-78cc-de3a-bf69-8ad20b90cd11"},"data":{"URL":"http://localhost:8080/#MATCH%20%28e1%29%2C%28e2%29%2C%28e3%29%2C%28e4%29%2C%28p1%29%2C%28p2%29%2C%28f%29%2C%28ip%29%20WHERE%20id%28p1%29%3D%271ca87b55-a62a-3f13-bc2d-5752ca5f4143%27%20AND%20id%28e1%29%3D%2743f26672-0c93-3e2c-84bb-88adb84454ba%27%20AND%20id%28f%29%3D%27f00ae947-3dd5-3c92-a84f-118b401c80f1%27%20AND%20id%28e2%29%3D%275a6bb84a-ed7e-3982-83d8-ea9f7b0dee9d%27%20AND%20id%28p2%29%3D%2717d3fb9a-cd9b-39ba-a087-e2f577627873%27%20AND%20id%28e3%29%3D%279fc76f93-c93d-3eb3-8730-0bca7ee079c4%27%20AND%20id%28e4%29%3D%274cc1ccc4-6286-3a32-b691-d308ba1e68e7%27%20AND%20id%28ip%29%3D%27a1ce3be6-4501-3af2-b05b-2c6ac0936cc4%27%20RETURN%20e1%2C%20e2%2C%20e3%2C%20e4%2C%20p1%2C%20p2%2C%20f%2C%20ip"}}
```
## Running the Recipe
```shell hl_lines="1"
❯ java -jar quine-2.1.1.jar -r apt-detection.yaml
Graph is ready
Running Recipe: APT Detection
Using 5 node appearances
Using 14 quick queries
Running Standing Query STANDING-1
Running Ingest Stream INGEST-1
Running Ingest Stream INGEST-2
Quine web server available at http://localhost:8080
```
## Summary
When the standing query detects the WRITE->READ->SEND->DELETE pattern, it will output a link to the console that can be copied and pasted into a browser to explore the event in the Quine Exploration UI. Copy and paste the URL section of the match JSON from your console into your browser.
The nodes will be jumbled together when you first open the graph. Arrange the nodes to look similar to the image below before you start exploring.

The recipe includes a number of [Replace Quick Queries: `PUT /api/v2/queryUi/quickQueries`](/reference/rest-api/?av=v2#/operations/replace-quick-queries) to assist in exploring the event data. Right click on a node to bring up the quick query interface.
!!! Tip
Quick Queries are available by right clicking on a node.
| Quick Query | Node Type | Description |
| :-------------------- | :-------- | :----------------------------------------------------- |
| Adjacent Nodes | All | Display the nodes that are adjacent to this node. |
| Refresh | All | Refresh the content stored in a node |
| Local Properties | All | Display the properties stored by the node |
| Files Read | Process | Load nodes representing the files read by this process |
| Files Written | Process | Load nodes representing files written by this process |
| Read By | data | Load nodes that read data from this node |
| Written By | data | Load nodes that wrote to this node |
| Received Data | Process | Where did this process receive data from |
| Sent Data | Process | Where did this process send data to |
| Started By | Process | What started this process |
| Started Other Process | Process | What process did this process start |
| Network Send | IP | Where did this IP node send data |
| Network Receive | IP | Whre did this IP node receive data from |
| Network Communication | IP | What other nodes did this IP node communicate with |
Use the quick queries to explore the graph and uncover the timeline behind the entire event.
---
# CDN Observability
URL: https://quine.io/recipes/cdn/
## Full Recipe
=== "Recipe v1"
Shared by: [Allan Konar](https://github.com/7evenbridges)
Real-time computation of CDN cache node efficiency from pseudonymized Fastly CDN logs, with graph association of each log entry to serving PoP, cache server, client, client ASN, asset and origin to identify potential root cause of issues.
??? example "Full CDN Observability Recipe"
```{ .yaml linenums="1" }
--8<-- "recipes/assets/cdn.yaml"
```
[Download Recipe](assets/cdn.yaml){ .md-button download="" .md-button--primary data-category="Quine Recipe Detail" data-label="Download recipe yaml" data-action="button click" }
=== "Recipe v2"
Shared by: [Allan Konar](https://github.com/7evenbridges)
Real-time computation of CDN cache node efficiency from pseudonymized Fastly CDN logs, with graph association of each log entry to serving PoP, cache server, client, client ASN, asset and origin to identify potential root cause of issues.
??? example "Full CDN Observability Recipe"
```{ .yaml linenums="1" }
--8<-- "recipes/assets/v2/cdn.yaml"
```
[Download Recipe](assets/v2/cdn.yaml){ .md-button download="" .md-button--primary data-category="Quine Recipe Detail" data-label="Download recipe yaml" data-action="button click" }
## Scenario
Pseudonymized CDN log data is imported from a JSON file (`cdn_data_50k.json`) via a file ingest, and nodes are manifested for the elements associated with each event (e.g., client, server, pop, etc.).
Each of the manifested nodes increment counters to track the number of cache hits and misses at each level (e.g., source ASN, server, pop, etc.). Selecting any node allows you to query at each level to identify potential root cause of poor performance.
A standing query is defined to match consecutive cache misses within a configurable fixed period of time for the purpose of alerting.
## Sample Data
Download the sample data to the same directory where Quine will be run.
* `cdn_data_50k.json` - [https://that.re/cdn_data_50k](https://that.re/cdn_data_50k)
## How it Works
The recipe reads observations from the sample data file using [ingest streams](../learn/ingest-sources/index.md) to manifest a graph in Quine. An ingest stream is configured to process the data file, containing Cypher that parses the log entries, manifests nodes, and relates them to each other in the graph.
The log entries take the form of:
```json
{
"backend_ip": "157.52.79.52",
"backend_ttlb": 73.206,
"business_unit": "68ae725c3fd8d6831735753269a727c9ce05baae6715bb0191ddb7f6d67842bd",
"bytes_in": 377,
"bytes_out": 902,
"cached": false,
"cache_shield": "false",
"cache_status": "MISS-CLUSTER",
"client_asn": 7922,
"client_geo_country": "US",
"client_ip": "1localhost",
"client_ttfb": 73.198,
"environment": "prod",
"failover_status": "",
"forward_for": "",
"host": "682d313399617e3d194679e8422a6a5f2666b60c54ab6eead5908040443c793f",
"if_modified_since": "",
"if_none_match": "",
"if_unmodified_since": "",
"method": "GET",
"path": "/flavi7d79/master/flavi7d79_6.m3u8",
"pop": "SJC",
"range_request": "(null)",
"range_response": "",
"request_id": "cache-sjc10025-SJC-2614091026",
"restarts": 0,
"retrans": 0,
"role": "edge",
"rtt_msecs": 28,
"server_id": "cache-sjc10025-SJC",
"server_ip": "2a04:4e42:a::645",
"server_ttlb": 73.261,
"shield_failover": "",
"stream": 0,
"status_code": 200,
"timestamp": "2020-07-14 22:55:31.729734",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
"workflow": "f2757f5a18302320de05caa509ac98370b6a9cec",
"origin_request_id": "",
"query": ""
}
```
INGEST-1 processes the `cdn_data_50k.json` file:
=== "YAML"
```yaml
- type: FileIngest
path: cdn_data_50k.json
format:
type: CypherJson
query: |-
MATCH (event), (client), (asset), (asn), (server), (pop), (origin), (clientGeo)
WHERE $that.cache_status IS NOT NULL
AND id(event) = idFrom('event', $that.timestamp, $that.request_id)
AND id(client) = idFrom('client', $that.client_ip, $that.business_unit)
AND id(asset) = idFrom('asset', $that.path)
AND id(asn) = idFrom('asn', toString($that.client_asn))
AND id(server) = idFrom('server', $that.pop, $that.server_id)
AND id(pop) = idFrom('pop', $that.pop)
AND id(origin) = idFrom('origin', $that.backend_ip)
AND id(clientGeo) = idFrom('clientGeo', $that.client_geo_country)
////////////////////////////////////////
//Bucketing for HITs and MISSes counters
////////////////////////////////////////
// RegEx deets here: https://regex101.com/r/uP0KMm/1
WITH *, text.regexFirstMatch($that.cache_status, '(HIT|MISS(?!.*HIT)).*') AS hmp WHERE hmp[1] IS NOT NULL
////////////////////////////////////////
// Bucketing for node type counters
////////////////////////////////////////
CALL incrementCounter(client, "count",1) YIELD count AS clientCount
CALL incrementCounter(client, toLower(hmp[1]),1) YIELD count AS clientHitMissCount
CALL incrementCounter(asset, "count",1) YIELD count AS assetCount
CALL incrementCounter(asset, toLower(hmp[1]),1) YIELD count AS assetHitMissCount
CALL incrementCounter(asn, "count",1) YIELD count AS asnCount
CALL incrementCounter(asn, toLower(hmp[1]),1) YIELD count AS asnHitMissCount
CALL incrementCounter(server, "count",1) YIELD count AS serverCount
CALL incrementCounter(server, toLower(hmp[1]),1) YIELD count AS serverHitMissCount
CALL incrementCounter(pop, "count",1) YIELD count AS popCount
CALL incrementCounter(pop, toLower(hmp[1]),1) YIELD count AS popHitMissCount
CALL incrementCounter(clientGeo, "count",1) YIELD count AS clientGeoCount
CALL incrementCounter(clientGeo, toLower(hmp[1]),1) YIELD count AS clientGeoHitMissCount
CALL incrementCounter(origin, "count",1) YIELD count AS originGeoCount
CALL incrementCounter(origin, toLower(hmp[1]),1) YIELD count AS originGeoHitMissCount
////////////////////////////////////////////////////////
// Event
////////////////////////////////////////////////////////
SET event = $that,
event.cache_class = hmp[1],
event: event
////////////////////////////////////////////////////////
// Origin
////////////////////////////////////////////////////////
SET origin.backend_ip = $that.backend_ip,
origin: origin
////////////////////////////////////////////////////////
// Client
////////////////////////////////////////////////////////
SET client.client_geo_country = $that.client_geo_country,
client.client_ip = $that.client_ip,
client.user_agent = $that.user_agent,
client: client
// Extract Browser and Version
// RegEx here: https://regex101.com/r/T0MThZ/2
WITH *, text.regexFirstMatch($that.user_agent, '\\((.*?)\\)(\\s|$)|(.*?)\\/(.*?)(\\s|$)') AS cb
SET client.browser = cb[3],
client.browserVer = cb[4],
client.first_seen = coll.min([$that.timestamp, coalesce(client.first_seen, $that.timestamp)]),
client.last_seen = coll.max([$that.timestamp, coalesce(client.last_seen, $that.timestamp)])
////////////////////////////////////////////////////////
// Client Geo
////////////////////////////////////////////////////////
SET clientGeo.client_geo_country = $that.client_geo_country,
clientGeo: clientGeo
////////////////////////////////////////////////////////
// Asset
////////////////////////////////////////////////////////
// RegEx here: https://regex101.com/r/tB8cd4/1
WITH *, text.regexFirstMatch($that.path, '^(.+\\/)([^\\/]+)$') AS ap
SET asset.path = ap[1],
asset.name = ap[2],
asset.full_path = $that.path,
asset.if_modified_since = coll.max([$that.timestamp, coalesce(asset.if_modified_since, $that.timestamp)]),
asset: asset
////////////////////////////////////////////////////////
// ASN
////////////////////////////////////////////////////////
SET asn.asn_id = toString($that.client_asn),
asn: asn
////////////////////////////////////////////////////////
// Server
////////////////////////////////////////////////////////
SET server.server_id = $that.server_id,
server.server_ip = $that.server_ip,
server.cache_shield = $that.cache_shield,
server.environment = $that.environment,
server.host = $that.host,
server.role = $that.role,
server.pop = $that.pop,
server: server
////////////////////////////////////////////////////////
// PoP
////////////////////////////////////////////////////////
SET pop.source = $that.pop,
pop.environment = $that.environment,
pop: pop
////////////////////////////////////////////////////////
// Create relationship between nodes
////////////////////////////////////////////////////////
CREATE (asset)<-[:REQUESTED]-(event)-[:REQUESTED_OVER]->(asn)-[:IN_CLIENT_GEO]->(clientGeo),
(origin)<-[:FROM]-(pop)<-[:WITHIN]-(server)<-[:TARGETED]-(event)<-[:ORIGINATED]-(client)
```
=== "JSON"
```json title="POST /api/v1/ingest/INGEST-1"
{
"type": "FileIngest",
"path": "cdn_data_50k.json",
"format": {
"type": "CypherJson",
"query": "MATCH (event), (client), (asset), (asn), (server), (pop), (origin), (clientGeo) WHERE $that.cache_status IS NOT NULL AND id(event) = idFrom('event', $that.timestamp, $that.request_id) AND id(client) = idFrom('client', $that.client_ip, $that.business_unit) AND id(asset) = idFrom('asset', $that.path) AND id(asn) = idFrom('asn', toString($that.client_asn)) AND id(server) = idFrom('server', $that.pop, $that.server_id) AND id(pop) = idFrom('pop', $that.pop) AND id(origin) = idFrom('origin', $that.backend_ip) AND id(clientGeo) = idFrom('clientGeo', $that.client_geo_country) WITH *, text.regexFirstMatch($that.cache_status, '(HIT|MISS(?!.*HIT)).*') AS hmp WHERE hmp[1] IS NOT NULL CALL incrementCounter(client, \"count\",1) YIELD count AS clientCount CALL incrementCounter(client, toLower(hmp[1]),1) YIELD count AS clientHitMissCount CALL incrementCounter(asset, \"count\",1) YIELD count AS assetCount CALL incrementCounter(asset, toLower(hmp[1]),1) YIELD count AS assetHitMissCount CALL incrementCounter(asn, \"count\",1) YIELD count AS asnCount CALL incrementCounter(asn, toLower(hmp[1]),1) YIELD count AS asnHitMissCount CALL incrementCounter(server, \"count\",1) YIELD count AS serverCount CALL incrementCounter(server, toLower(hmp[1]),1) YIELD count AS serverHitMissCount CALL incrementCounter(pop, \"count\",1) YIELD count AS popCount CALL incrementCounter(pop, toLower(hmp[1]),1) YIELD count AS popHitMissCount CALL incrementCounter(clientGeo, \"count\",1) YIELD count AS clientGeoCount CALL incrementCounter(clientGeo, toLower(hmp[1]),1) YIELD count AS clientGeoHitMissCount CALL incrementCounter(origin, \"count\",1) YIELD count AS originGeoCount CALL incrementCounter(origin, toLower(hmp[1]),1) YIELD count AS originGeoHitMissCount SET event = $that, event.cache_class = hmp[1], event: event SET origin.backend_ip = $that.backend_ip, origin: origin SET client.client_geo_country = $that.client_geo_country, client.client_ip = $that.client_ip, client.user_agent = $that.user_agent, client: client WITH *, text.regexFirstMatch($that.user_agent, '\\\\((.*?)\\\\)(\\\\s|$)|(.*?)\\\\/(.*?)(\\\\s|$)') AS cb SET client.browser = cb[3], client.browserVer = cb[4], client.first_seen = coll.min([$that.timestamp, coalesce(client.first_seen, $that.timestamp)]), client.last_seen = coll.max([$that.timestamp, coalesce(client.last_seen, $that.timestamp)]) SET clientGeo.client_geo_country = $that.client_geo_country, clientGeo: clientGeo WITH *, text.regexFirstMatch($that.path, '^(.+\\\\/)([^\\\\/]+)$') AS ap SET asset.path = ap[1], asset.name = ap[2], asset.full_path = $that.path, asset.if_modified_since = coll.max([$that.timestamp, coalesce(asset.if_modified_since, $that.timestamp)]), asset: asset SET asn.asn_id = toString($that.client_asn), asn: asn SET server.server_id = $that.server_id, server.server_ip = $that.server_ip, server.cache_shield = $that.cache_shield, server.environment = $that.environment, server.host = $that.host, server.role = $that.role, server.pop = $that.pop, server: server SET pop.source = $that.pop, pop.environment = $that.environment, pop: pop CREATE (asset)<-[:REQUESTED]-(event)-[:REQUESTED_OVER]->(asn)-[:IN_CLIENT_GEO]->(clientGeo),(origin)<-[:FROM]-(pop)<-[:WITHIN]-(server)<-[:TARGETED]-(event)<-[:ORIGINATED]-(client)"
}
}
```
=== "YAML"
```yaml
ingestStreams:
- name: cdn-file-ingest
source:
type: File
path: $in_file
format:
type: Json
query: |-
MATCH (event), (client), (asset), (asn), (server), (pop), (origin), (clientGeo)
WHERE $that.cache_status IS NOT NULL
AND id(event) = idFrom('event', $that.timestamp, $that.request_id)
AND id(client) = idFrom('client', $that.client_ip, $that.business_unit)
AND id(asset) = idFrom('asset', $that.path)
AND id(asn) = idFrom('asn', toString($that.client_asn))
AND id(server) = idFrom('server', $that.pop, $that.server_id)
AND id(pop) = idFrom('pop', $that.pop)
AND id(origin) = idFrom('origin', $that.backend_ip)
AND id(clientGeo) = idFrom('clientGeo', $that.client_geo_country)
WITH *, text.regexFirstMatch($that.cache_status, '(HIT|MISS(?!.*HIT)).*') AS hmp WHERE hmp[1] IS NOT NULL
CALL incrementCounter(client, "count",1) YIELD count AS clientCount
CALL incrementCounter(client, toLower(hmp[1]),1) YIELD count AS clientHitMissCount
// ... (additional counter calls)
SET event = $that,
event.cache_class = hmp[1],
event: event
// ... (additional SET clauses)
CREATE (asset)<-[:REQUESTED]-(event)-[:REQUESTED_OVER]->(asn)-[:IN_CLIENT_GEO]->(clientGeo),
(origin)<-[:FROM]-(pop)<-[:WITHIN]-(server)<-[:TARGETED]-(event)<-[:ORIGINATED]-(client)
```
=== "JSON"
```json title="POST /api/v2/graph/quine/ingests"
{
"name": "cdn-file-ingest",
"source": {
"type": "File",
"path": "$in_file",
"format": {
"type": "Json"
}
},
"query": "MATCH (event), (client), (asset), (asn), (server), (pop), (origin), (clientGeo) WHERE $that.cache_status IS NOT NULL AND id(event) = idFrom('event', $that.timestamp, $that.request_id) ... CREATE (asset)<-[:REQUESTED]-(event)-[:REQUESTED_OVER]->(asn)-[:IN_CLIENT_GEO]->(clientGeo),(origin)<-[:FROM]-(pop)<-[:WITHIN]-(server)<-[:TARGETED]-(event)<-[:ORIGINATED]-(client)"
}
```
A [standing query](../learn/standing-queries/standing-queries.md) is configured to look for 10 consecutive cache MISS events involving the same server and asset pair within a defined duration.
=== "YAML"
```yaml
- pattern:
type: Cypher
query: |-
MATCH (server1:server)<-[:TARGETED]-(event1 {cache_class:"MISS"})-[:REQUESTED]->(asset)<-[:REQUESTED]-(event2 {cache_class:"MISS"})-[:TARGETED]->(server2:server)
RETURN DISTINCT id(event1) AS event1
```
=== "JSON"
```json title="POST /api/v1/query/standing/STANDING-1"
{
"pattern": {
"type": "Cypher",
"query": "MATCH (server1:server)<-[:TARGETED]-(event1 {cache_class:\"MISS\"})-[:REQUESTED]->(asset)<-[:REQUESTED]-(event2 {cache_class:\"MISS\"})-[:TARGETED]->(server2:server) RETURN DISTINCT id(event1) AS event1"
},
"outputs": {
"cacheMissAlert": {
"type": "CypherQuery",
"query": "...",
"andThen": {
"type": "PrintToStandardOut"
}
}
}
}
```
=== "YAML"
```yaml
standingQueries:
- name: cache-miss-alert
pattern:
type: Cypher
query: |-
MATCH (server1:server)<-[:TARGETED]-(event1 {cache_class:"MISS"})-[:REQUESTED]->(asset)<-[:REQUESTED]-(event2 {cache_class:"MISS"})-[:TARGETED]->(server2:server)
RETURN DISTINCT id(event1) AS event1
outputs:
- name: cacheMissAlert
resultEnrichment:
query: |-
MATCH (server1:server)<-[:TARGETED]-(event1 {cache_class:"MISS"})-[:REQUESTED]->(asset)<-[:REQUESTED]-(event2 {cache_class:"MISS"})-[:TARGETED]->(server2:server)
WHERE id(event1) = $that.data.event1
AND duration("PT45M") > duration.between(localdatetime(event1.timestamp, "yyyy-MM-dd HH:mm:ss.SSSSSS"), localdatetime(event2.timestamp, "yyyy-MM-dd HH:mm:ss.SSSSSS")) > duration("PT5M")
AND event1.client_asn = event2.client_asn
AND id(server1) = id(server2)
AND id(event1) <> id(event2)
// ... additional processing
RETURN 'http://localhost:8080/#...' AS Alert
parameter: that
destinations:
- type: StandardOut
```
=== "JSON"
```json title="POST /api/v2/graph/quine/standingQueries"
{
"name": "cache-miss-alert",
"pattern": {
"type": "Cypher",
"query": "MATCH (server1:server)<-[:TARGETED]-(event1 {cache_class:\"MISS\"})-[:REQUESTED]->(asset)<-[:REQUESTED]-(event2 {cache_class:\"MISS\"})-[:TARGETED]->(server2:server) RETURN DISTINCT id(event1) AS event1"
},
"outputs": [
{
"name": "cacheMissAlert",
"resultEnrichment": {
"query": "MATCH (server1:server)<-[:TARGETED]-(event1 {cache_class:\"MISS\"})... RETURN Alert",
"parameter": "that"
},
"destinations": [
{
"type": "StandardOut"
}
]
}
]
}
```
Once Quine detects the pattern, the event is sent to a standing query output for additional processing and action.
```yaml
outputs:
cacheMissAlert:
type: CypherQuery
query: |-
query: |-
// Add constraints to the cache MISS events match involving the same server and asset pair.
MATCH (server1:server)<-[:TARGETED]-(event1 {cache_class:"MISS"})-[:REQUESTED]->(asset)<-[:REQUESTED]-(event2 {cache_class:"MISS"})-[:TARGETED]->(server2:server)
WHERE id(event1) = $that.data.event1
// Time between consecutive cache MISSes between 5-45 minutes expressed in ISO 8601 duration format (https://en.wikipedia.org/wiki/ISO_8601#Durations)
// Feel free to alter the range to meet your requirements
AND duration("PT45M") > duration.between(localdatetime(event1.timestamp, "yyyy-MM-dd HH:mm:ss.SSSSSS"), localdatetime(event2.timestamp, "yyyy-MM-dd HH:mm:ss.SSSSSS")) > duration("PT5M")
AND event1.client_asn = event2.client_asn
AND id(server1) = id(server2)
AND id(event1) <> id(event2)
////////////////////////////////////////////////////////
// missEvents
////////////////////////////////////////////////////////
// Manifest missEvents node to track metadata relative to consecutive cache MISSes that match the previous constraints
MATCH (missEvents)
WHERE id(missEvents) = idFrom('missEvents', server1.server_id, asset.full_path)
SET missEvents.asset = event1.path,
missEvents.server = event1.server_id,
missEvents.pop = event1.pop,
missEvents.firstMiss = coll.min([event1.timestamp, coalesce(missEvents.firstMiss, event1.timestamp)]),
missEvents.latestMiss = coll.max([event1.timestamp, coalesce(missEvents.latestMiss, event1.timestamp)]),
missEvents: missEvents
// Create subgraph from consecutive cache MISS events to provide a visualization in the Quine Exploration UI
CREATE (asset)-[:HAD]->(missEvents)-[:FROM]->(server1)<-[:TARGETED]-(event1),
(server1)<-[:TARGETED]-(event2)
// Increment the missEvents counter for the purpose of triggering an alert at a specified threshold
WITH missEvents CALL incrementCounter(missEvents, "cumulativeCount", 1) YIELD count AS cumulativeCount
// Trigger alert (RETURN clause) that prints URL to local running Quine instance
MATCH (missEvents)
// Threshold at which to emit alert
// Feel free to alter it to meet your requirements
WHERE missEvents.cumulativeCount = 10
RETURN 'http://localhost:8080/#' + text.urlencode('MATCH(missEvents:missEvents) WHERE id(missEvents)="' + toString(strId(missEvents)) + '" MATCH (event {cache_class:"MISS"})-[:TARGETED]->(server)<-[:FROM]-(missEvents)<-[:HAD]-(asset)<-[:REQUESTED]-(event {cache_class:"MISS"}) RETURN DISTINCT missEvents, event, server, asset LIMIT 10') AS Alert
andThen:
type: PrintToStandardOut
```
```yaml
outputs:
- name: cacheMissAlert
resultEnrichment:
query: |-
// Add constraints to the cache MISS events match involving the same server and asset pair.
MATCH (server1:server)<-[:TARGETED]-(event1 {cache_class:"MISS"})-[:REQUESTED]->(asset)<-[:REQUESTED]-(event2 {cache_class:"MISS"})-[:TARGETED]->(server2:server)
WHERE id(event1) = $that.data.event1
// Time between consecutive cache MISSes between 5-45 minutes expressed in ISO 8601 duration format (https://en.wikipedia.org/wiki/ISO_8601#Durations)
// Feel free to alter the range to meet your requirements
AND duration("PT45M") > duration.between(localdatetime(event1.timestamp, "yyyy-MM-dd HH:mm:ss.SSSSSS"), localdatetime(event2.timestamp, "yyyy-MM-dd HH:mm:ss.SSSSSS")) > duration("PT5M")
AND event1.client_asn = event2.client_asn
AND id(server1) = id(server2)
AND id(event1) <> id(event2)
////////////////////////////////////////////////////////
// missEvents
////////////////////////////////////////////////////////
// Manifest missEvents node to track metadata relative to consecutive cache MISSes that match the previous constraints
MATCH (missEvents)
WHERE id(missEvents) = idFrom('missEvents', server1.server_id, asset.full_path)
SET missEvents.asset = event1.path,
missEvents.server = event1.server_id,
missEvents.pop = event1.pop,
missEvents.firstMiss = coll.min([event1.timestamp, coalesce(missEvents.firstMiss, event1.timestamp)]),
missEvents.latestMiss = coll.max([event1.timestamp, coalesce(missEvents.latestMiss, event1.timestamp)]),
missEvents: missEvents
// Create subgraph from consecutive cache MISS events to provide a visualization in the Quine Exploration UI
CREATE (asset)-[:HAD]->(missEvents)-[:FROM]->(server1)<-[:TARGETED]-(event1),
(server1)<-[:TARGETED]-(event2)
// Increment the missEvents counter for the purpose of triggering an alert at a specified threshold
WITH missEvents CALL incrementCounter(missEvents, "cumulativeCount", 1) YIELD count AS cumulativeCount
// Trigger alert (RETURN clause) that prints URL to local running Quine instance
MATCH (missEvents)
// Threshold at which to emit alert
// Feel free to alter it to meet your requirements
WHERE missEvents.cumulativeCount = 10
RETURN 'http://localhost:8080/#' + text.urlencode('MATCH(missEvents:missEvents) WHERE id(missEvents)="' + toString(strId(missEvents)) + '" MATCH (event {cache_class:"MISS"})-[:TARGETED]->(server)<-[:FROM]-(missEvents)<-[:HAD]-(asset)<-[:REQUESTED]-(event {cache_class:"MISS"}) RETURN DISTINCT missEvents, event, server, asset LIMIT 10') AS Alert
parameter: that
destinations:
- type: StandardOut
```
The result once the pattern is detected is to output a link to the console that an analyst can use to review the event further within Quine's Exploration UI.
```json
2023-02-03 16:00:43,345 Standing query `cacheMissAlert` match: {"meta":{"isPositiveMatch":true,"resultId":"0e38c93e-338c-e964-8867-8487eb083e5b"},"data":{"Alert":"http://localhost:8080/#MATCH%28missEvents%3AmissEvents%29%20WHERE%20id%28missEvents%29%3D%2263c2f862-ea0f-3a3a-9f4a-09b11f176ad0%22%20MATCH%20%28event%20%7Bcache_class%3A%22MISS%22%7D%29-%5B%3ATARGETED%5D-%3E%28server%29%3C-%5B%3AFROM%5D-%28missEvents%29%3C-%5B%3AHAD%5D-%28asset%29%3C-%5B%3AREQUESTED%5D-%28event%20%7Bcache_class%3A%22MISS%22%7D%29%20RETURN%20DISTINCT%20missEvents%2C%20event%2C%20server%2C%20asset%20LIMIT%2010"}}
```
## Running the Recipe
```shell hl_lines="1"
❯ java -jar quine-2.1.1.jar -r cdn.yaml --recipe-value in_file=cdn_data_50k.json
Graph is ready
Running Recipe: CDN Cache Efficiency By Segment
Using 11 node appearances
Using 14 quick queries
Using 9 sample queries
2023-02-03 17:05:00,342 WARN [NotFromActor] [graph-service-akka.quine.graph-shard-dispatcher-18] com.thatdot.quine.app.StandingQueryResultOutput$ - Could not verify that the provided Cypher query is idempotent. If timeouts or external system errors occur, query execution may be retried and duplicate data may be created. To avoid this, set shouldRetry = false in the Standing Query output
Running Standing Query STANDING-1
2023-02-03 17:05:00,847 WARN [NotFromActor] [graph-service-akka.quine.graph-shard-dispatcher-18] com.thatdot.quine.app.ingest.serialization.CypherJsonInputFormat - Could not verify that the provided ingest query is idempotent. If timeouts occur, query execution may be retried and duplicate data may be created.
Running Ingest Stream INGEST-1
Quine web server available at http://localhost:8080
| => STANDING-1 count 4248
| => INGEST-1 status is running and ingested 5560
```
## Summary
When the standing query detects the cache miss pattern, it will output a link to the console that can be copied and pasted into a browser to explore the event in the Quine Exploration UI. Copy and paste the URL section of the match JSON from your console into your browser.
The nodes will be jumbled together when you first open the graph. Arrange the nodes to look similar to the image below before you start exploring.

!!! Tip
Quick Queries are available by right clicking on a node.
| Quick Query | Node Type | Description |
| ----------------------------- | ----------- | ----------------------------------------------------- |
| Adjacent Nodes | All | Display the nodes that are adjacent to this node. |
| Refresh | All | Refresh the content stored in a node |
| Local Properties | All | Display the properties stored by the node |
| Reset Counter | missedEvent | Deletes the missedEvent |
| Server Pop | server | Displays the associated PoP |
| Cache Hit/Miss Percentage | server | Calculates the Hit/Miss percentages for the server |
| PoP Hit/Miss Percentage | pop | Calculates the Hit/Miss percentages for the PoP |
| PoP Origins | pop | Displays the asset origins the PoP is serving |
| Origin Hit/Miss Percentage | origin | Calculates the Hit/Miss percentages for the origin |
| Client Hit/Miss Percentage | client | Calculates the Hit/Miss percentages for the client |
| clientGeo Hit/Miss Percentage | clientGeo | Calculates the Hit/Miss percentages for the clientGeo |
| Asset Hit/Miss Percentage | asset | Calculates the Hit/Miss percentages for the asset |
| Client Geo | asn | Displays the clientGeo associated with the ASN |
| ASN Hit/Miss Percentage | asn | |
---
# Certstream Firehose
URL: https://quine.io/recipes/certstream-firehose/
## Full Recipe
=== "Recipe v1"
Shared by: [Ethan Bell](https://github.com/emanb29)
Reproduces the behavior of the [certstream website](https://certstream.calidog.io/) by connecting to the certstream firehose via SSL-encrypted websocket and printing to standard out each time a new certificate is detected.
??? example "Certstream Firehose Recipe"
```{ .yaml linenums="1" }
--8<-- "recipes/assets/certstream-firehose.yaml"
```
[Download Recipe](assets/certstream-firehose.yaml){ .md-button download="" .md-button--primary data-category="Quine Recipe Detail" data-label="Download recipe yaml" data-action="button click" }
=== "Recipe v2"
Shared by: [Ethan Bell](https://github.com/emanb29)
Reproduces the behavior of the [certstream website](https://certstream.calidog.io/) by connecting to the certstream firehose via SSL-encrypted websocket and printing to standard out each time a new certificate is detected.
??? example "Certstream Firehose Recipe"
```{ .yaml linenums="1" }
--8<-- "recipes/assets/v2/certstream-firehose.yaml"
```
[Download Recipe](assets/v2/certstream-firehose.yaml){ .md-button download="" .md-button--primary data-category="Quine Recipe Detail" data-label="Download recipe yaml" data-action="button click" }
## Scenario
CertStream is an intelligence feed that gives you real-time updates from the [Certificate Transparency Log](https://www.certificate-transparency.org/what-is-ct) network, allowing you to use it as a building block to make tools that react to new certificates being issued in real time.
This recipe connects to the curated public [Certstream](https://certstream.calidog.io/) aggregation service managed by the team at [Cali Dog Security](https://calidog.io/).
## Sample Data
This recipe connects to the live Certstream feed eliminating the need for sample data. However, below is a typical raw certificate update object for review.
``` json
"data": {
"cert_index": 160270422,
"cert_link": "https://nessie2023.ct.digicert.com/log/ct/v1/get-entries?start=160270422&end=160270422",
"leaf_cert": {
"all_domains": [
"*.nyarkowiz.online",
"nyarkowiz.online"
],
"extensions": {
"authorityInfoAccess": "CA Issuers - URI:http://pki.goog/repo/certs/gts1p5.der\nOCSP - URI:http://ocsp.pki.goog/s/gts1p5/fKV4K079ZKo\n",
"authorityKeyIdentifier": "keyid:D5:FC:9E:0D:DF:1E:CA:DD:08:97:97:6E:2B:C5:5F:C5:2B:F5:EC:B8\n",
"basicConstraints": "CA:FALSE",
"certificatePolicies": "Policy: 1.3.6.1.4.1.11129.2.5.3\nPolicy: 2.23.140.1.2.1",
"crlDistributionPoints": "Full Name:\n URI:http://crls.pki.goog/gts1p5/oE9rr3G5TqE.crl",
"ctlPoisonByte": true,
"extendedKeyUsage": "TLS Web server authentication",
"keyUsage": "Digital Signature, Key Encipherment",
"subjectAltName": "DNS:nyarkowiz.online, DNS:*.nyarkowiz.online",
"subjectKeyIdentifier": "42:22:E3:A5:27:CB:93:B1:8F:C0:20:7C:CB:E6:11:ED:B3:A4:CB:BD"
},
"fingerprint": "B1:FE:F6:4C:D1:7E:A3:DB:A8:D9:92:EE:18:42:B7:1F:35:2F:75:68",
"issuer": {
"C": "US",
"CN": "GTS CA 1P5",
"L": null,
"O": "Google Trust Services LLC",
"OU": null,
"ST": null,
"aggregated": "/C=US/CN=GTS CA 1P5/O=Google Trust Services LLC",
"emailAddress": null
},
"not_after": 1679365412,
"not_before": 1671589413,
"serial_number": "201DF51E883B4B37139BBB17CAEACE15",
"signature_algorithm": "sha256, rsa",
"subject": {
"C": null,
"CN": "*.nyarkowiz.online",
"L": null,
"O": null,
"OU": null,
"ST": null,
"aggregated": "/CN=*.nyarkowiz.online",
"emailAddress": null
}
},
"seen": 1671638236.908937,
"source": {
"name": "DigiCert Nessie2023 Log",
"url": "https://nessie2023.ct.digicert.com/log/"
},
"update_type": "PrecertLogEntry"
},
"message_type": "certificate_update"
}
```
## How it Works
The recipe is designed to rapidly load JSON objects into Quine producing as disconnected nodes. The [ingest stream](../learn/ingest-sources/index.md) connects using a `WebsocketClient` source type and parses each record as a JSON object accessible via `$that` in the Cypher query.
INGEST-1 reads directly from the certstream web socket:
=== "YAML"
```yaml
- type: WebsocketSimpleStartupIngest
url: wss://certstream.calidog.io/
format:
type: CypherJson
query: |-
CREATE ($that)
```
=== "JSON"
```json title="POST /api/v1/ingest/INGEST-1"
{
"type": "WebsocketSimpleStartupIngest",
"url": "wss://certstream.calidog.io/",
"format": {
"type": "CypherJson",
"query": "CREATE ($that)"
}
}
```
=== "YAML"
```yaml
ingestStreams:
- name: certstream-ingest
source:
type: WebsocketClient
url: wss://certstream.calidog.io/
format:
type: Json
initMessages: []
characterEncoding: UTF-8
query: |-
CREATE ($that)
```
=== "JSON"
```json title="POST /api/v2/graph/quine/ingests"
{
"name": "certstream-ingest",
"source": {
"type": "WebsocketClient",
"url": "wss://certstream.calidog.io/",
"format": {
"type": "Json"
},
"initMessages": [],
"characterEncoding": "UTF-8"
},
"query": "CREATE ($that)"
}
```
A [standing query](../learn/standing-queries/standing-queries.md) is configured to detect new nodes in the graph and then print the event to the console.
=== "YAML"
```yaml
- pattern:
type: Cypher
query: MATCH (n) RETURN DISTINCT id(n) AS id
outputs:
log-new-certs:
type: CypherQuery
query: |-
MATCH (n)
WHERE id(n) = $that.data.id
RETURN n.data
andThen:
type: PrintToStandardOut
logMode: FastSampling
```
=== "JSON"
```json title="POST /api/v1/query/standing/STANDING-1"
{
"pattern": {
"type": "Cypher",
"query": "MATCH (n) RETURN DISTINCT id(n) AS id"
},
"outputs": {
"log-new-certs": {
"type": "CypherQuery",
"query": "MATCH (n)\nWHERE id(n) = $that.data.id\nRETURN n.data",
"andThen": {
"type": "PrintToStandardOut",
"logMode": "FastSampling"
}
}
}
}
```
=== "YAML"
```yaml
standingQueries:
- name: log-new-certs
pattern:
type: Cypher
query: MATCH (n) RETURN DISTINCT id(n) AS id
mode: DISTINCT_ID
outputs:
- name: log-new-certs
preEnrichmentTransformation:
type: InlineData
resultEnrichment:
query: |-
MATCH (n)
WHERE id(n) = $that.id
RETURN n.data AS data
parameter: that
destinations:
- type: StandardOut
```
=== "JSON"
```json title="POST /api/v2/graph/quine/standingQueries"
{
"name": "log-new-certs",
"pattern": {
"type": "Cypher",
"query": "MATCH (n) RETURN DISTINCT id(n) AS id",
"mode": "DISTINCT_ID"
},
"outputs": [
{
"name": "log-new-certs",
"preEnrichmentTransformation": {
"type": "InlineData"
},
"resultEnrichment": {
"query": "MATCH (n) WHERE id(n) = $that.id RETURN n.data AS data",
"parameter": "that"
},
"destinations": [
{
"type": "StandardOut"
}
]
}
]
}
```
The recipe will stream events to the console similar to the sample event below.
``` { .json linenums="1" }
2022-12-21 10:32:54,863 Standing query `log-new-certs` match: {"meta":{"isPositiveMatch":true,"resultId":"e8709166-1a08-df62-3419-e030ce81d09a"},"data":{"n.data":{"cert_index":545387302,"cert_link":"https://ct.googleapis.com/logs/xenon2023/ct/v1/get-entries?start=545387302&end=545387302","leaf_cert":{"all_domains":["www.gamificationbook.com"],"extensions":{"authorityInfoAccess":"CA Issuers - URI:http://r3.i.lencr.org/\nOCSP - URI:http://r3.o.lencr.org\n","authorityKeyIdentifier":"keyid:14:2E:B3:17:B7:58:56:CB:AE:50:09:40:E6:1F:AF:9D:8B:14:C2:C6\n","basicConstraints":"CA:FALSE","certificatePolicies":"Policy: 1.3.6.1.4.1.44947.1.1.1\n CPS: http://cps.letsencrypt.org","ctlPoisonByte":true,"extendedKeyUsage":"TLS Web server authentication, TLS Web client authentication","keyUsage":"Digital Signature, Key Encipherment","subjectAltName":"DNS:www.gamificationbook.com","subjectKeyIdentifier":"89:C8:C3:71:33:36:E7:37:BF:78:08:81:2F:4E:C7:74:DE:EF:9F:60"},"fingerprint":"D7:86:C7:29:54:AF:91:DF:DA:30:9D:8A:0A:AE:B0:17:1C:B6:F1:F8","issuer":{"C":"US","CN":"R3","L":null,"O":"Let's Encrypt","OU":null,"ST":null,"aggregated":"/C=US/CN=R3/O=Let's Encrypt","emailAddress":null},"not_after":1679412699,"not_before":1671636700,"serial_number":"3FFE0C620988AD6607BBFAB008D769E8BBF","signature_algorithm":"sha256, rsa","subject":{"C":null,"CN":"www.gamificationbook.com","L":null,"O":null,"OU":null,"ST":null,"aggregated":"/CN=www.gamificationbook.com","emailAddress":null}},"seen":1.671640374636495E9,"source":{"name":"Google 'Xenon2023' log","url":"https://ct.googleapis.com/logs/xenon2023/"},"update_type":"PrecertLogEntry"}}}
```
## Running the Recipe
``` shell hl_lines="1"
❯ java -jar quine-2.1.1.jar -r certstream-firehose.yaml
Graph is ready
Running Recipe: Certstream Firehose
Running Standing Query STANDING-1
Running Ingest Stream INGEST-1
Quine web server available at http://localhost:8080
```
---
# Conway's Game of Life
URL: https://quine.io/recipes/conways-gol/
## Full Recipe
=== "Recipe v1"
Shared by: [Matthew Cullum](https://github.com/brackishman)
A complete implementation of [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) using Quine's standing queries to create a real-time cellular automaton. This recipe demonstrates that Quine is Turing complete by leveraging standing query recursion to evolve cell states across generations according to Conway's famous rules.
??? example "Conway's Game of Life Recipe"
```{ .yaml linenums="1" }
--8<-- "recipes/assets/conways-gol.yaml"
```
[Download Recipe](assets/conways-gol.yaml){ .md-button download="" .md-button--primary data-category="Quine Recipe Detail" data-label="Download recipe yaml" data-action="button click" }
=== "Recipe v2"
Shared by: [Matthew Cullum](https://github.com/brackishman)
A complete implementation of [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) using Quine's standing queries to create a real-time cellular automaton. This recipe demonstrates that Quine is Turing complete by leveraging standing query recursion to evolve cell states across generations according to Conway's famous rules.
??? example "Conway's Game of Life Recipe"
```{ .yaml linenums="1" }
--8<-- "recipes/assets/v2/conways-gol.yaml"
```
[Download Recipe](assets/v2/conways-gol.yaml){ .md-button download="" .md-button--primary data-category="Quine Recipe Detail" data-label="Download recipe yaml" data-action="button click" }
## Scenario
Conway's Game of Life is a classic cellular automaton invented by mathematician John Conway in 1970. Despite its simple rules, it can produce remarkably complex patterns and behaviors. This recipe implements the complete Game of Life in Quine, where each cell is a node in the graph that evaluates its neighbors and updates its state according to Conway's rules:
1. A live cell with 2-3 live neighbors survives
2. A dead cell with exactly 3 live neighbors becomes alive
3. All other cells die or stay dead
The implementation uses a two-wave standing query pattern to compute and apply state changes across all cells simultaneously, demonstrating Quine's capability for recursive, real-time graph computation.
## Sample Configurations
This recipe requires a configuration file that defines the grid size and initial pattern of alive cells. Each configuration is a JSON file specifying the grid dimensions and which cells start alive:
```json
{
"name": "Small Conway's Game - Blinker Pattern",
"description": "7x7 grid with a simple blinker pattern in the center",
"gridWidth": 7,
"gridHeight": 7,
"initialPattern": [
{"x": 3, "y": 2, "alive": true},
{"x": 3, "y": 3, "alive": true},
{"x": 3, "y": 4, "alive": true}
]
}
```
Three pre-configured patterns are included with the recipe:
### Blinker Pattern (Recommended for first run)
A simple 7x7 grid featuring a "blinker", a pattern that oscillates between horizontal and vertical orientations every generation. This is perfect for understanding the basic mechanics.
[Download blinker.json](assets/GoL/blinker/blinker.json){ .md-button download="" }
### Gosper Glider Gun
The famous 40x15 grid containing the original Gosper Glider Gun discovered in 1970. This pattern produces new gliders every 30 generations, demonstrating emergent complexity from simple rules.
[Download glider-gun.json](assets/GoL/glider-gun/glider-gun.json){ .md-button download="" }
### Explosion Pattern
A more chaotic initial configuration that creates dynamic, unpredictable evolution across the grid.
[Download explosion.json](assets/GoL/explosion/explosion.json){ .md-button download="" }
## How it Works
This recipe demonstrates **Quine's Turing completeness** through recursive standing queries that continuously evaluate and evolve the cellular automaton.
### Graph Structure
The recipe creates a graph where:
- Each **cell** is represented as a node with properties: `x`, `y`, `alive`, `generation`, and `state`
- Cells are connected via `[:NEIGHBOR]` relationships to their 8 adjacent cells
- A central **ready** node coordinates the computation waves and tracks the current generation
### Two-Wave Standing Query Pattern
The recipe uses a sophisticated two-phase approach to ensure all cells update simultaneously:
**Wave 1: Compute Next State**
Standing queries detect when all cells are ready to compute their next state. Each cell:
1. Counts its live neighbors
2. Applies Conway's rules to determine if it should be alive in the next generation
3. Stores the result in `nextAlive` without changing its current state
**Wave 2: Apply State Changes**
Once all cells have computed their next state, a second wave of standing queries:
1. Updates each cell's `alive` property to the computed `nextAlive` value
2. Marks cells that changed as `updated`
3. Increments the generation counter
**Wave Coordination**
Standing queries monitor the ready node to coordinate the waves:
- When Wave 1 completes → Start Wave 2
- When Wave 2 completes → Start next generation's Wave 1
This recursive pattern continues indefinitely, evolving the grid through successive generations. The standing queries act as the "rules engine" that recursively applies Conway's rules, demonstrating that Quine's standing query mechanism is Turing complete.
### Visual Configuration
The recipe includes node appearances that automatically style cells:
- **Live cells**: Large orange circles (●)
- **Dead cells**: Small gray circles (○)
## Prerequisites: Install the Bookmarklet
Before running the recipe, you need to install a browser bookmarklet that enables visualization of the Game of Life animation. The bookmarklet performs two critical functions:
1. **Enables unlimited node rendering** by automatically bypassing the browser's node limit prompts
2. **Monitors generation updates** by connecting to Quine's standing query WebSocket and automatically refreshing the view to show updated cells
Without the bookmarklet, you would need to manually approve rendering hundreds of nodes and manually refresh the query after each generation, making the animation impossible to watch in real-time.
### Installation Steps
1. Download the bookmarklet JavaScript file:
[Download conways-gol-bookmarklet.js](assets/GoL/conways-gol-bookmarklet.js){ .md-button download="" }
2. Open the downloaded file in a text editor and copy the entire JavaScript code
3. Create a new bookmark in your browser (usually ++ctrl+d++ or ++cmd+d++)
4. Edit the bookmark and paste the JavaScript code as the bookmark URL
5. Name the bookmark "Quine GoL Monitor" (or any name you prefer)
Once Quine is running and you have loaded the cell nodes in the Exploration UI, click the bookmarklet in your browser's bookmark bar. You should see an alert confirming "Now monitoring Conway's Game of Life generations with unlimited node rendering..."
!!! warning "Bookmarklet Required"
The bookmarklet must be activated before starting the game, or you will not see the animation. If you forget to activate it, simply click the bookmarklet and restart the game.
## Running the Recipe
### Step 1: Start Quine with the Recipe
Download one of the sample configurations (we'll use `blinker.json` for this walkthrough) and start Quine. Make sure the configuration file is in the same directory as your Quine JAR file, or provide the correct relative path:
```shell hl_lines="1"
❯ java -jar quine-2.1.1.jar -r conways-gol.yaml --recipe-value config_file=blinker.json
Graph is ready
Running Recipe: Conway's Game of Life
Using 2 node appearances
Using 2 quick queries
Using 1 sample query
Running Standing Query STANDING-1
Running Standing Query STANDING-2
Running Standing Query STANDING-3
Running Standing Query STANDING-4
Quine web server available at http://localhost:8080
```
!!! note
The configuration file path is relative to where you run the `java` command. If you organize your files in subdirectories, adjust the path accordingly (e.g., `config_file=configs/blinker.json`).
### Step 2: Load the Cell Nodes
Open your browser to [http://localhost:8080](http://localhost:8080) and click the sample query **● Show All Cells** to load all cell nodes into the Exploration UI.
You should see all cells in the grid displayed as small gray circles (all dead initially) or a mix of orange (alive) and gray (dead) circles depending on your initial pattern.

### Step 3: Load the Grid Layout
The cell nodes are currently displayed in a random arrangement. To visualize the Game of Life properly, you need to load a layout file that positions each cell at its correct x,y coordinates in a grid formation. Each configuration comes with a matching layout file that contains the precise coordinates for every cell node.
1. In the Exploration UI, click the layout dropdown menu (top right of the graph view)
2. Select "Load Layout from File"
3. Choose the corresponding layout file that matches your configuration (e.g., `blinker-layout.json` for the blinker configuration)
The cells will now be arranged in a proper grid formation, with each cell positioned at its x,y coordinate. Live cells will appear as large orange circles and dead cells as small gray circles.

### Step 4: Activate the Bookmarklet
Click the bookmarklet you installed earlier in your browser's bookmark bar. You should see an alert confirming "Now monitoring Conway's Game of Life generations with unlimited node rendering..."
The bookmarklet is now connected to Quine and ready to automatically refresh the view as cells change.
### Step 5: Start the Game
Right-click on any cell node and select the **▶️ START Game** quick query.

The game will immediately begin evolving! You'll see:
- Cells changing color as they become alive or dead
- The generation counter incrementing rapidly
- Patterns evolving according to Conway's rules
For the blinker pattern, you'll observe the three cells oscillating between horizontal and vertical orientations.

### Step 6: Stop the Game
To pause the evolution, right-click any cell and select the **⏸️ STOP Game** quick query.
### View Configuration Details
You can check the current game configuration and statistics at any time with the **📊 Show Game Configuration** sample query. Hold ++shift++ while clicking the sample query to view the results as tabular data rather than updating the exploration canvas.
```cypher
MATCH (ready) WHERE id(ready) = idFrom("ready")
MATCH (c:Cell)
RETURN
ready.name AS setup,
ready.description AS description,
ready.gridWidth AS width,
ready.gridHeight AS height,
ready.totalCells AS totalCells,
count(CASE WHEN c.alive = true THEN 1 END) AS liveCells,
ready.generation AS currentGeneration
```
This displays the grid size, total cells, live cell count, and current generation as a table.

## Trying Other Patterns
To run different Game of Life patterns:
1. Stop the current game with the **⏸️ STOP Game** quick query
2. Shut down Quine by either typing ++ctrl+c++ in the terminal window or issuing a [Graceful Shutdown: `POST /api/v2/system:shutdown`](/reference/rest-api/?av=v2#/operations/initiate-shutdown):
```shell
curl -X "POST" "http://127.0.0.1:8080/api/v2/system:shutdown"
```
3. Restart Quine with a different configuration file:
```shell
❯ java -jar quine-2.1.1.jar -r conways-gol.yaml --recipe-value config_file=glider-gun.json
```
4. Load the cells using the **● Show All Cells** sample query
5. Apply the corresponding layout file (`glider-gun-layout.json`)
6. Activate the bookmarklet again
7. Start the game with the **▶️ START Game** quick query
The Gosper Glider Gun is particularly fascinating to watch as it continuously creates gliders that move across the grid.
## Performance Considerations
For larger grids (50x50 or more), you may notice the visualization slowing down. This is **not** a limitation of Quine's computation - the standing queries continue to process generations at high speed. The performance bottleneck is primarily the browser UI continuously querying and rendering hundreds of nodes in real-time.
The Quine graph continues to evolve rapidly even when the UI struggles to keep up with the visualization. You can verify this by checking the generation count, which will continue incrementing quickly regardless of grid size.
!!! tip "Performance Tip"
For very large grids, consider periodically stopping the game to examine the current state rather than trying to watch continuous animation.
## Creating Custom Patterns
Advanced users can create their own Game of Life configurations and layouts:
### Custom Configuration
Create a JSON file following this schema:
```json
{
"name": "My Custom Pattern",
"description": "Description of your pattern",
"gridWidth": 20,
"gridHeight": 20,
"initialPattern": [
{"x": 10, "y": 10, "alive": true},
{"x": 11, "y": 10, "alive": true}
]
}
```
### Generating Layout Files
The included Python script can generate layout JSON files for your custom configurations. This tool queries a running Quine instance to discover all cell nodes and their x,y coordinates, then generates a layout file that positions each node properly in a grid formation.
[Download generate-conways-layout.py](assets/GoL/generate-conways-layout.py){ .md-button download="" }
To use the layout generator:
1. Start Quine with your custom configuration
2. Load all cells into the Exploration UI using the **● Show All Cells** sample query
3. Run the Python script: `python generate-conways-layout.py`
4. The script generates a layout JSON file mapping each cell to its grid coordinates
5. Load this layout file in the Exploration UI
This tool is useful when creating new patterns, as it automatically calculates proper spacing and positioning for any grid size.
## Summary
This recipe demonstrates a fundamental computer science concept - Turing completeness - through an elegant implementation of Conway's Game of Life. By using standing queries that recursively evaluate and update cell states, we prove that Quine can perform arbitrary computation. The two-wave pattern ensures synchronized updates across the entire grid, while the recursive nature of the standing queries drives the continuous evolution of the cellular automaton.
The Game of Life is a perfect demonstration of emergent complexity from simple rules, and implementing it in Quine showcases the power of recursive graph computation.
!!! tip "Turing Completeness"
The ability to implement Conway's Game of Life demonstrates that Quine's standing query system is Turing complete. Since Game of Life itself is Turing complete, and we've implemented it entirely through standing queries, this proves that standing queries can perform any computable function.
---
# Temporal Locality
URL: https://quine.io/recipes/duration/
## Full Recipe
=== "Recipe v1"
Shared by: [Michael Aglietti](https://github.com/maglietti)
This recipe looks for emails sent or received by `cto@company.com` within a sliding window as a means of highlighting a technique for matching on temporal locality of nodes in standing queries.
??? example "Temporal Locality Recipe"
```{ .yaml linenums="1" }
--8<-- "recipes/assets/duration.yaml"
```
[Download Recipe](assets/duration.yaml){ .md-button download="" .md-button--primary data-category="Quine Recipe Detail" data-label="Download recipe yaml" data-action="button click" }
=== "Recipe v2"
Shared by: [Michael Aglietti](https://github.com/maglietti)
This recipe looks for emails sent or received by `cto@company.com` within a sliding window as a means of highlighting a technique for matching on temporal locality of nodes in standing queries.
??? example "Temporal Locality Recipe"
```{ .yaml linenums="1" }
--8<-- "recipes/assets/v2/duration.yaml"
```
[Download Recipe](assets/v2/duration.yaml){ .md-button download="" .md-button--primary data-category="Quine Recipe Detail" data-label="Download recipe yaml" data-action="button click" }
## Scenario
This scenario processes records containing metadata for almost 295,000 emails are ingested for the purpose of identifying emails to/from a specific email address within a sliding 2-minute window.
``` json
{
"from":