AshArcadic.Query.Write (AshArcadic v0.1.0)

Copy Markdown View Source

Builds the Cypher SET clause for a query-scoped bulk write (update_query) and for the atomic surface of create/upsert, from an Ash changeset's atomic + static changes. Pure — no transport.

changeset.atomics ([{field, ash_expr}], e.g. [age: %Plus{}]) become n.<field> = <cypher> fragments — each RHS is HYDRATED (Ash.Filter.hydrate_refs/2, idempotent: fully_atomic_changeset pre-hydrates the update_query/upsert inputs, but atomic_set-on-create and unit-test-built atomics arrive as un-hydrated %Ash.Query.Call{}; hydration resolves both — live-verified scratchpad/probe_hydrate_idempotent.exs) then translated by AshArcadic.Query.Expression (which emits n.<field> for a stored ref and fails closed value-free on sensitive/non-stored/ :binary/:decimal/relationship/aggregate refs and un-mapped ops). changeset.attributes (the statically-cast map) become n += $static. A write to the multitenancy discriminator (atomic OR static) is rejected value-free (a tenant-hop of the row); a fully-empty SET is rejected value-free. Every value rides a bound $param — the RHS via Expression/add_param, the static map as $static (Rule 1).

Summary

Functions

Atomic SET fragments (["n.<f> = <cypher>", …]) + params for a list of {field, hydrated_expr} atomics, seed_params-threaded. Value-free fail-closed on a discriminator target or a sensitive/non-stored/un-mapped RHS. Shared by update_query (via build_set), atomic create, and atomic upsert.

Builds {:ok, set_clause, params} from a changeset's atomic + static changes, or {:error, %UnsupportedFilter{}} fail-closed value-free. seed_params pre-seeds the $paramN accumulator (the WHERE clause's bound params) so an atomic RHS literal never collides with a WHERE param. When static changes are present, params["static"] carries the cast property map (for the caller's encode-gate) and the clause ends …, n += $static.

Functions

atomic_fragments(resource, atomics, seed_params)

@spec atomic_fragments(Ash.Resource.t(), keyword(), map()) ::
  {:ok, [String.t()], map()} | {:error, Exception.t()}

Atomic SET fragments (["n.<f> = <cypher>", …]) + params for a list of {field, hydrated_expr} atomics, seed_params-threaded. Value-free fail-closed on a discriminator target or a sensitive/non-stored/un-mapped RHS. Shared by update_query (via build_set), atomic create, and atomic upsert.

build_set(resource, changeset, seed_params)

@spec build_set(Ash.Resource.t(), Ash.Changeset.t(), map()) ::
  {:ok, String.t(), map()} | {:error, Exception.t()}

Builds {:ok, set_clause, params} from a changeset's atomic + static changes, or {:error, %UnsupportedFilter{}} fail-closed value-free. seed_params pre-seeds the $paramN accumulator (the WHERE clause's bound params) so an atomic RHS literal never collides with a WHERE param. When static changes are present, params["static"] carries the cast property map (for the caller's encode-gate) and the clause ends …, n += $static.