AshAge.Cypher.Expr (AshAge v2.0.0)

Copy Markdown View Source

Translates an Ash.Expr AST fragment into a Cypher expression string + a param map, for use in SET clauses (atomic updates) and expression filters.

Pure: holds no state, performs no I/O, reads no clock. Every dynamic value becomes a positional $param (AGENTS.md Critical Security Rule 1 — no value is ever interpolated into the Cypher string). Every property reference is backtick-quoted and identifier-validated: a property whose name collides with a Cypher keyword (count, label, …) mis-parses when bare, a vulnerability closed by mandatory n.`attr` quoting (AGE probe D-row). Returns {:error, UnsupportedExpression} for any node it cannot safely map — never a silent drop.

Param allocation

Param names are positional (p0, p1, …) allocated against the caller's taken set so they cannot collide with attribute-name params at the JSON boundary (an atom :p0 and string "p0" in the merged param map would produce a duplicate JSON key, silently dropping one value — plan-review N1).

Summary

Functions

Translate expr into a Cypher fragment and a map of $param => value.

Types

acc()

@type acc() :: %{taken: MapSet.t(String.t()), count: non_neg_integer()}

result()

@type result() :: {:ok, String.t(), map()} | {:error, term()}

Functions

translate(expr, map)

@spec translate(term(), acc()) :: result()

Translate expr into a Cypher fragment and a map of $param => value.

acc carries the caller's already-taken param names (:taken) and an allocation counter (:count); the translator allocates collision-free positional names against them.