Validates that a query has a root where predicate referencing configured keys.
This is useful for tenant boundaries where every query should include a
predicate for fields such as :organization_id or :user_id.
Examples
Bad:
from(Post, as: :post)
|> where([post: p], p.status == ^:published)Why this is bad:
If :organization_id is configured as mandatory, this query has no visible
tenant boundary in the root where clause. It can read rows across
organizations.
Better:
from(Post, as: :post)
|> where([post: p], p.organization_id == ^organization_id)
|> where([post: p], p.status == ^:published)Why this is better:
The configured field appears in a supported root predicate, so the query is explicitly scoped to one organization.
Notes
This check accepts supported direct root == and in predicates. It does not
prove tenant safety when keys are hidden inside fragments or field-to-field
comparisons.
The check is static. It accepts configured root fields directly in == and in
predicates inside where expressions, but it cannot prove fields hidden
inside raw SQL fragments. Combination queries such as union, union_all,
except, and intersect validate the parent query and every combination
branch independently.
When the root query uses an Ecto schema, the configured fields are first narrowed
to fields that exist on that schema. If none of the configured fields exist, the
check is not applicable and returns :ok. Schema-less sources are still
validated because there is no schema reflection signal.
Options
:validate- explicitfalsedisables this check. It can be used in the repo-wide check list or in call-site overrides passed toBylaw.Ecto.Query.validate/4.:rules- required rule keyword list or non-empty list of rule keyword lists. A single-rule shorthand such asrules: [fields: [:organization_id]]is normalized to one rule.:fields- required non-empty list of root field names inside each rule.:match-:anyor:allinside each rule. Defaults to:any.:whereand:except- optional rule matchers for scoping rules. Matchers use plural keys with list values, such asecto_schemas: [Post],tables: ["posts"],db_schemas: ["tenant_a"], andoperations: [:all].
This check requires :fields, so bare-module configuration is not valid.
Run globally:
{Bylaw.Ecto.Query.Checks.MandatoryWhereKeys,
rules: [fields: [:organization_id]]}Run only for matching rule scopes:
{Bylaw.Ecto.Query.Checks.MandatoryWhereKeys,
rules: [
[where: [ecto_schemas: [Post]], fields: [:organization_id]],
[where: [tables: ["comments"]], fields: [:organization_id]]
]}Require all configured fields instead of any one field:
{Bylaw.Ecto.Query.Checks.MandatoryWhereKeys,
rules: [fields: [:organization_id, :user_id], match: :all]}Usage
Add this module to the explicit check list passed through Bylaw.Ecto.Query.
See Bylaw.Ecto.Query for the full Ecto.Repo.prepare_query/3 setup.
Summary
Functions
Implements the Bylaw.Ecto.Query.Check validation callback.
Functions
@spec validate( Bylaw.Ecto.Query.Check.operation(), Bylaw.Ecto.Query.Check.query(), opts() ) :: Bylaw.Ecto.Query.Check.result()
Implements the Bylaw.Ecto.Query.Check validation callback.