AshAge.Query (AshAge v2.0.0)

Copy Markdown View Source

Query structure for AGE graph queries.

Summary

Functions

Adds a parameter to the query, returning the updated query and a $paramN reference.

Builds the Cypher for a single aggregate over the query's filtered set.

Builds the Cypher for a bulk destroy by query.

Pre-write duplicate-PK check: groups the WHERE-matched set by primary key and returns one row iff any PK matches 2+ vertices. AGE enforces no PK uniqueness, so a bulk SET could write multiple physical rows for one input PK — this is run BEFORE the SET so the anomaly fails closed without writing (works under any transaction mode, and keys on the SOURCE PK so a SET that rewrites the PK can't evade it). Verified AGE 1.6.0 form (aliased grouping keys + WITH...WHERE). Returns {cypher, params}; a non-empty result means a duplicate exists.

Converts a query to Cypher with parameters.

Builds the bulk-update Cypher for update_query/4: applies one set of changes (plain attributes + translated atomics) to every record the query matches.

Types

t()

@type t() :: %AshAge.Query{
  aggregates: [Ash.Query.Aggregate.t()],
  expression: Ash.Filter.t() | nil,
  filters: [String.t()],
  graph: atom() | String.t(),
  label: atom() | String.t(),
  limit: non_neg_integer() | nil,
  offset: non_neg_integer() | nil,
  params: map(),
  repo: module(),
  resource: module(),
  sort: [{atom(), :asc | :desc}],
  tenant: term() | nil
}

Functions

add_param(query, value)

@spec add_param(t(), term()) :: {t(), String.t()}

Adds a parameter to the query, returning the updated query and a $paramN reference.

aggregate_cypher(query, aggregate, label)

@spec aggregate_cypher(t(), Ash.Query.Aggregate.t(), atom() | String.t()) ::
  {String.t(), map()}

Builds the Cypher for a single aggregate over the query's filtered set.

MATCH (n:LABEL) WHERE <main filter> [AND <aggregate sub-filter>] RETURN <expr> AS agg. Deliberately emits NO LIMIT/SKIP/ORDER BY even when the query carries them (the AshPostgres/ETS contract): an aggregate is computed over the FULL filtered set, not a page of it. The aggregate's own sub-query filter (its query field) is AND-ed into the WHERE so each aggregate narrows independently (one query is issued per aggregate by run_aggregate_query/3).

delete_cypher(query, label)

@spec delete_cypher(t(), atom() | String.t()) :: {String.t(), map()}

Builds the Cypher for a bulk destroy by query.

MATCH (n:LABEL) WHERE <translated filter> [WITH n SKIP .. LIMIT ..] DETACH DELETE n. Uses the SAME build_where as the read path, so destroy_query deletes exactly the rows a read would return — including the tenant predicate Ash attaches for :attribute multitenancy. LIMIT/SKIP are honored via a WITH n pass-through: Ash does NOT slice the query above the data layer for destroy_query (bulk.ex:622-723 do_atomic_destroy passes it through), so a user-supplied limit must bound the deletion here or it over-deletes (the ETS destroy_query reference honors limit too). No ORDER BY: without a deterministic sort, which rows die is unspecified — same as ETS, which sorts by PK only.

duplicate_pk_cypher(query, label, pk_fields)

@spec duplicate_pk_cypher(t(), atom() | String.t(), [atom()]) :: {String.t(), map()}

Pre-write duplicate-PK check: groups the WHERE-matched set by primary key and returns one row iff any PK matches 2+ vertices. AGE enforces no PK uniqueness, so a bulk SET could write multiple physical rows for one input PK — this is run BEFORE the SET so the anomaly fails closed without writing (works under any transaction mode, and keys on the SOURCE PK so a SET that rewrites the PK can't evade it). Verified AGE 1.6.0 form (aliased grouping keys + WITH...WHERE). Returns {cypher, params}; a non-empty result means a duplicate exists.

to_cypher(query)

@spec to_cypher(t()) :: {String.t(), map()}

Converts a query to Cypher with parameters.

Returns {cypher_string, params_map}.

update_cypher(query, label, set_clauses_str)

@spec update_cypher(t(), atom() | String.t(), String.t()) :: {String.t(), map()}

Builds the bulk-update Cypher for update_query/4: applies one set of changes (plain attributes + translated atomics) to every record the query matches.

Mirrors delete_cypher/2, replacing DETACH DELETE n with SET <clauses> RETURN n. Scoping source is build_where(query) — the :attribute tenant predicate arrives in query.expression via Ash's handle_attribute_multitenancy (NOT changeset.filter, which Ash nils into query.filter before the data layer runs — adversarial Challenge 1). LIMIT/SKIP honored via the same WITH n pass-through as destroy_query. RETURN n so updated records can be decoded when return_records? is set.