AshNeo4j.Cypher.Query (AshNeo4j v0.7.0)

Copy Markdown View Source

Typed representation of a Cypher query, and builders for constructing common patterns.

The struct holds an ordered list of typed clause structs and a params map. Callers build a query via the builder functions, then pass it to AshNeo4j.Cypher.render/1 or AshNeo4j.Cypher.run/1.

Clause structs

Read: Match, OptionalMatch, Where, With, Return, OrderBy, Skip, Limit Write: Create, Merge, Set, Remove, Delete, DetachDelete

Summary

Types

A single property filter condition for node_read_filtered/2: {property, operator_atom, value, case_insensitive?}

t()

Functions

Appends a LIMIT clause. No-op when n is nil.

Appends an ORDER BY clause. No-op when terms is empty.

Appends a SKIP clause. No-op when n is nil or 0.

Per-record aggregate — returns one row per source node with the aggregate value.

Total aggregate — returns a single row with the aggregate value across all source nodes.

MATCH (s:L1:L2) [WHERE <conditions>] RETURN s — a single combination-query branch, sized to fit inside a CALL { … } block.

Same as branch_node_read/3 but returns just the Neo4j internal id of s (as sid) instead of the node itself. Used to cheaply materialise the id set per branch in the in-memory orchestration path for INTERSECT / EXCEPT combination queries.

Wraps a list of branch queries (built via branch_node_read/3) in a CALL { … UNION/UNION ALL … } block followed by the outer OPTIONAL MATCH enrichment and RETURN s, r, d.

CREATE (n:L1:L2 {props}) RETURN n

MATCH (n:L1:L2 {props}) DETACH DELETE n

MATCH (n:L1:L2 {props}) WHERE NOT guard1 AND NOT guard2 DETACH DELETE n

MATCH (n:Labels {props}) RETURN n

MERGE (n:Label {props}) RETURN n

MATCH (s:L1:L2) OPTIONAL MATCH (s)-[r]-(d) RETURN s, r, d

MATCH (s:L1:L2) WHERE id(s) IN $ids OPTIONAL MATCH (s)-[r]-(d) RETURN s, r, d.

MATCH (s:L1:L2) WHERE <conditions> OPTIONAL MATCH (s)-[r]-(d) RETURN s, r, d

MATCH (n:L1:L2 {props}) OPTIONAL MATCH (n)-[r]-(d) RETURN n, r, d

MATCH (s:SrcLabel {s_props}) OPTIONAL MATCH (d:DestLabel {d_props}) MERGE (s)-[r:EDGE]->(d) RETURN s, r, d

Relates two nodes, removing existing edges from source AND to destination.

Relates two nodes, first removing any existing edge of the same type pointing to the destination.

Relates two nodes, first removing any existing edge of the same type from the source.

Related-nodes query — returns one row per (source, destination) pair for expression-based aggregates that need full destination records for Elixir-side evaluation.

MATCH (s:SrcLabels)-[r:EdgeLabel]-(d:DestLabel) WHERE d.prop <op> $param WITH s MATCH (s)-[r0]-(d0) RETURN s, r0, d0

MATCH (s:SrcLabel {s_props})-[r:EDGE]->(d:DestLabel {d_props}) DELETE r RETURN s, d

MATCH (n:L1:L2 {match_props}) SET n += {set_props} REMOVE n.p1, n.p2 RETURN n

Types

clause()

condition()

@type condition() :: {String.t(), atom(), any(), boolean()}

A single property filter condition for node_read_filtered/2: {property, operator_atom, value, case_insensitive?}

t()

@type t() :: %AshNeo4j.Cypher.Query{clauses: [clause()], params: map()}

Functions

add_limit(query, n)

@spec add_limit(t(), pos_integer() | nil) :: t()

Appends a LIMIT clause. No-op when n is nil.

add_order_by(query, terms)

@spec add_order_by(t(), [{atom() | String.t(), :asc | :desc}]) :: t()

Appends an ORDER BY clause. No-op when terms is empty.

add_skip(query, n)

@spec add_skip(t(), non_neg_integer() | nil) :: t()

Appends a SKIP clause. No-op when n is nil or 0.

aggregate_per_record(source_label, pk_field, ids, path_segments, kind, field, name, uniq? \\ false, dest_conditions \\ [])

@spec aggregate_per_record(
  atom() | [atom()],
  atom(),
  [any()],
  [{atom(), atom(), atom()}],
  atom(),
  atom() | nil,
  atom(),
  boolean(),
  [{String.t(), any()}]
) :: t()

Per-record aggregate — returns one row per source node with the aggregate value.

MATCH (s:L1:L2) WHERE s.pk IN $agg_ids OPTIONAL MATCH (s)<path>(d) RETURN s.pk AS source_id, agg_fn AS name

path_segments is a list of {edge_label, direction, dest_label} tuples describing the traversal from source to the node being aggregated.

aggregate_total(source_label, pk_field, ids, path_segments, kind, field, name, uniq? \\ false, dest_conditions \\ [])

@spec aggregate_total(
  atom() | [atom()],
  atom(),
  [any()],
  [{atom(), atom(), atom()}],
  atom(),
  atom() | nil,
  atom(),
  boolean(),
  [{String.t(), any()}]
) :: t()

Total aggregate — returns a single row with the aggregate value across all source nodes.

MATCH (s:L1:L2) WHERE s.pk IN $agg_ids OPTIONAL MATCH (s)<path>(d) RETURN agg_fn AS name

branch_node_read(label, conditions \\ [], opts \\ [])

@spec branch_node_read(atom() | [atom()], [condition()], keyword()) :: t()

MATCH (s:L1:L2) [WHERE <conditions>] RETURN s — a single combination-query branch, sized to fit inside a CALL { … } block.

No OPTIONAL MATCH (the outer combination query owns enrichment) and only the s column is returned (Cypher's UNION/UNION ALL requires identical column shapes across branches).

Supports param_prefix: per branch — pass distinct prefixes for distinct branches so their param keys (b0_s_name_0 vs b1_s_name_0) don't collide when merged.

Examples

iex> q = AshNeo4j.Cypher.Query.branch_node_read(:Place)
iex> {cypher, _} = AshNeo4j.Cypher.render(q)
iex> cypher
"MATCH (s:Place) RETURN s"

iex> q = AshNeo4j.Cypher.Query.branch_node_read(:Place, [{"name", :==, "Sydney", false}], param_prefix: "b0_")
iex> {cypher, params} = AshNeo4j.Cypher.render(q)
iex> cypher
"MATCH (s:Place) WHERE s.name = $b0_s_name_0 RETURN s"
iex> params
%{"b0_s_name_0" => "Sydney"}

branch_node_read_ids(label, conditions \\ [], opts \\ [])

@spec branch_node_read_ids(atom() | [atom()], [condition()], keyword()) :: t()

Same as branch_node_read/3 but returns just the Neo4j internal id of s (as sid) instead of the node itself. Used to cheaply materialise the id set per branch in the in-memory orchestration path for INTERSECT / EXCEPT combination queries.

Examples

iex> q = AshNeo4j.Cypher.Query.branch_node_read_ids(:Place, [{"name", :==, "Sydney", false}], param_prefix: "b0_")
iex> {cypher, _} = AshNeo4j.Cypher.render(q)
iex> cypher
"MATCH (s:Place) WHERE s.name = $b0_s_name_0 RETURN id(s) AS sid"

combination_block(branches, opts \\ [])

@spec combination_block(
  [t()],
  keyword()
) :: t()

Wraps a list of branch queries (built via branch_node_read/3) in a CALL { … UNION/UNION ALL … } block followed by the outer OPTIONAL MATCH enrichment and RETURN s, r, d.

Branch params are merged into the outer query's params map. Branches are rendered to Cypher strings before being placed in the Call clause.

Opts:

  • :union_type:union or :union_all (default :union_all)

Examples

iex> b0 = AshNeo4j.Cypher.Query.branch_node_read(:Place, [{"name", :==, "Sydney", false}], param_prefix: "b0_")
iex> b1 = AshNeo4j.Cypher.Query.branch_node_read(:Place, [{"name", :==, "Melbourne", false}], param_prefix: "b1_")
iex> q = AshNeo4j.Cypher.Query.combination_block([b0, b1])
iex> {cypher, params} = AshNeo4j.Cypher.render(q)
iex> cypher
"CALL { MATCH (s:Place) WHERE s.name = $b0_s_name_0 RETURN s UNION ALL MATCH (s:Place) WHERE s.name = $b1_s_name_0 RETURN s } WITH s OPTIONAL MATCH (s)-[r]-(d) RETURN s, r, d"
iex> params
%{"b0_s_name_0" => "Sydney", "b1_s_name_0" => "Melbourne"}

create_node(labels, properties)

@spec create_node(atom() | [atom()], map()) :: t()

CREATE (n:L1:L2 {props}) RETURN n

delete_nodes(label, properties \\ %{})

@spec delete_nodes(atom() | [atom()], map()) :: t()

MATCH (n:L1:L2 {props}) DETACH DELETE n

delete_nodes_guarded(label, properties, guards)

@spec delete_nodes_guarded(atom() | [atom()], map(), list()) :: t()

MATCH (n:L1:L2 {props}) WHERE NOT guard1 AND NOT guard2 DETACH DELETE n

guards is a list of {edge_label, direction, dest_label} tuples. Falls back to delete_nodes/2 when guards is empty.

match_nodes(labels, properties \\ %{})

@spec match_nodes(atom() | [atom()], map()) :: t()

MATCH (n:Labels {props}) RETURN n

merge_node(label, properties)

@spec merge_node(atom(), map()) :: t()

MERGE (n:Label {props}) RETURN n

node_read(label)

@spec node_read(atom() | [atom()]) :: t()

MATCH (s:L1:L2) OPTIONAL MATCH (s)-[r]-(d) RETURN s, r, d

node_read_by_ids(label, ids)

@spec node_read_by_ids(atom() | [atom()], [integer()]) :: t()

MATCH (s:L1:L2) WHERE id(s) IN $ids OPTIONAL MATCH (s)-[r]-(d) RETURN s, r, d.

Used as the final read after in-memory combination orchestration computes the keep-set of node ids.

node_read_filtered(label, conditions, opts \\ [])

@spec node_read_filtered(atom() | [atom()], [condition()], keyword()) :: t()

MATCH (s:L1:L2) WHERE <conditions> OPTIONAL MATCH (s)-[r]-(d) RETURN s, r, d

Returns node_read/1 when conditions is empty.

node_read_with_properties(label, properties)

@spec node_read_with_properties(atom() | [atom()], map()) :: t()

MATCH (n:L1:L2 {props}) OPTIONAL MATCH (n)-[r]-(d) RETURN n, r, d

Like node_read/1 but matches by properties in the MATCH pattern (not a WHERE clause).

relate(src_label, src_props, dest_label, dest_props, edge_label, direction)

@spec relate(atom() | [atom()], map(), atom() | [atom()], map(), atom(), atom()) ::
  t()

MATCH (s:SrcLabel {s_props}) OPTIONAL MATCH (d:DestLabel {d_props}) MERGE (s)-[r:EDGE]->(d) RETURN s, r, d

relate_unrelating_both(src_label, src_props, dest_label, dest_props, edge_label, direction)

@spec relate_unrelating_both(atom() | [atom()], map(), atom(), map(), atom(), atom()) ::
  t()

Relates two nodes, removing existing edges from source AND to destination.

MATCH (s:SrcLabel {s_props}) WITH s
OPTIONAL MATCH (s)-[r0:EDGE]->(d:DestLabel {d_props}) DELETE r0 WITH s
OPTIONAL MATCH (d:DestLabel {d_props}) WITH s, d
OPTIONAL MATCH (s0:SrcLabel)-[r0:EDGE]->(d) WHERE s0 <> s DELETE r0
WITH s, d MERGE (s)-[r:EDGE]->(d) RETURN s, r, d

relate_unrelating_destination(src_label, src_props, dest_label, dest_props, edge_label, direction)

@spec relate_unrelating_destination(
  atom() | [atom()],
  map(),
  atom(),
  map(),
  atom(),
  atom()
) :: t()

Relates two nodes, first removing any existing edge of the same type pointing to the destination.

MATCH (s:SrcLabel {s_props}) OPTIONAL MATCH (d:DestLabel {d_props})
WITH s, d OPTIONAL MATCH (s0:SrcLabel)-[r0:EDGE]->(d) WHERE s0 <> s
DELETE r0 WITH s, d MERGE (s)-[r:EDGE]->(d) RETURN s, r, d

relate_unrelating_source(src_label, src_props, dest_label, dest_props, edge_label, direction)

@spec relate_unrelating_source(
  atom() | [atom()],
  map(),
  atom(),
  map(),
  atom(),
  atom()
) :: t()

Relates two nodes, first removing any existing edge of the same type from the source.

MATCH (s:SrcLabel {s_props})
WITH s OPTIONAL MATCH (s)-[r0:EDGE]->(d0:DestLabel)
DELETE r0 WITH s MATCH (d:DestLabel {d_props})
MERGE (s)-[r:EDGE]->(d) RETURN s, r, d

relationship_read(src_label, edge_label, direction, dest_label, dest_property, operator, value)

@spec relationship_read(
  atom() | [atom()],
  atom(),
  atom(),
  atom(),
  String.t(),
  atom(),
  any()
) :: t()

MATCH (s:SrcLabels)-[r:EdgeLabel]-(d:DestLabel) WHERE d.prop <op> $param WITH s MATCH (s)-[r0]-(d0) RETURN s, r0, d0

unrelate(src_label, src_props, dest_label, dest_props, edge_label, direction)

@spec unrelate(atom() | [atom()], map(), atom(), map(), atom(), atom()) :: t()

MATCH (s:SrcLabel {s_props})-[r:EDGE]->(d:DestLabel {d_props}) DELETE r RETURN s, d

update_node(label, match_props, set_props, remove_props \\ [])

@spec update_node(atom() | [atom()], map(), map(), [atom()]) :: t()

MATCH (n:L1:L2 {match_props}) SET n += {set_props} REMOVE n.p1, n.p2 RETURN n

Handles all combinations of empty/non-empty set_props and remove_props.