ryanzidago

ryanzidago

Hi all,

I’ve released bylaw_ecto_query, a small OSS library for validating prepared Ecto.Query structs before they run.

Why I wrote it

Imagine you ship an API endpoint that does not order the results determinisitcally. A customer sees inconsistent results, and need to be bothered enough to write support ticket. Now the issue has to be prioritised, investigated, fixed, reviewed, deployed, and communicated back, involving multiple stakeholders in the process.

You can test for this, but then you need to remember that deterministic ordering is the convention, remember to write a test for each relevant endpoint or query, and keep doing that consistently. With bylaw_ecto_query, you define that rule once as an invariant and have it checked wherever the relevant query shape appears.

The package makes application-specific query rules explicit and easy to enforce from c:Ecto.Repo.prepare_query/3. It is intended for teams that want checks around query safety, tenancy, visibility, ordering, joins, and similar conventions.

This is different from source-level linting tools like Credo or security-focused tools like Sobelow. Those inspect source code. bylaw_ecto_query validates the prepared %Ecto.Query{} struct at the repo boundary, which means it catches queries built dynamically at runtime that no source-level tool can see.

This is part of a small family of Bylaw packages I’m writing to help encode project conventions around application code, database schemas, and queries. The general idea is to make those conventions executable so they are easier for both humans and AI coding agents to follow consistently.

I’m still very much experimenting with the shape of these packages and how far I can push the boundaries of what can be validated. The checks aren’t meant to catch all cases, but help us steer the code in some desired direction.

Would especially appreciate feedback around:

  • how easy it is to set up
  • how useful the checks are
  • how many false positives or false negatives you encounter

Example setup

# config/dev.exs and config/test.exs
config :my_app, :bylaw, validate_ecto_queries?: true

# config/prod.exs
config :my_app, :bylaw, validate_ecto_queries?: false
defmodule MyApp.Repo do
  use Ecto.Repo,
    otp_app: :my_app,
    adapter: Ecto.Adapters.Postgres

  @query_checks [
    Bylaw.Ecto.Query.Checks.RequiredOrder,
    {Bylaw.Ecto.Query.Checks.MandatoryWhereKeys, keys: [:organization_id]}
  ]

  @impl Ecto.Repo
  def prepare_query(operation, query, opts) do
    if bylaw_ecto_query_enabled?() do
      validate_query!(operation, query)
    end

    {query, opts}
  end

  defp validate_query!(operation, query) do
    case Bylaw.Ecto.Query.validate(operation, query, @query_checks) do
      :ok -> :ok
      {:error, issues} -> raise Bylaw.Ecto.Query.Issue.format_many(issues)
    end
  end

  defp bylaw_ecto_query_enabled? do
    :my_app
    |> Application.get_env(:bylaw, [])
    |> Keyword.get(:validate_ecto_queries?, false)
  end
end

Included checks

Check Description
CartesianJoins Prevents explicit cartesian joins.
ConflictingWherePredicates Detects root where predicates that cannot all be satisfied.
DateDatetimeMixedComparisons Catches date fields compared to datetime fields without explicit truncation.
DeterministicOrder Requires ordered queries to include the root schema primary key.
DuplicateJoins Detects repeated equivalent joins.
EmptyInPredicates Catches root where predicates that rely on empty in lists.
ExplicitVisibilityPredicates Requires configured visibility-sensitive fields to be constrained explicitly.
HalfOpenTemporalIntervals Requires temporal interval predicates to use half-open ranges.
HardDeleteOnSoftDeleteSchema Prevents delete_all against schemas with soft-delete fields.
LeftJoinWherePredicates Catches root where predicates that null-reject left_join bindings.
MandatoryJoinKeys Requires explicit schema joins to preserve configured mandatory keys.
MandatoryWhereKeys Requires root where predicates to reference configured keys.
ManualJoinInsteadOfAssoc Encourages assoc/2 when a root association exists.
NamedBindings Requires query expressions to use named binding aliases.
OffsetWithoutLimit Prevents offset without limit.
RequiredOrder Requires order_by for query shapes that need stable row order.
UnboundedDeletes Requires delete_all queries to be bounded.
UnboundedUpdates Requires update_all queries to be bounded.
UtcDatetimeNaiveComparisons Catches UTC datetime fields compared with NaiveDateTime values.

Caveat

This package inspects prepared %Ecto.Query{} structs. Ecto exposes Ecto.Query.t(), but the internal shape of query expressions is not a stable extension API, so enabled checks may need to be reviewed when upgrading Ecto.

That tradeoff is acceptable for me for now. Partly because of Hyrum’s Law, and partly because I think the practical benefit can outweigh the maintenance cost.

Next steps

One thing I’m considering next is adding more configuration so checks can run only for certain schemas and/or tables.

Links

Install with:

def deps do
  [
    {:bylaw_ecto_query, "~> 0.1.0"}
  ]
end

HexDocs:

Hex:

GitHub:

Showing Posts 1 to 1

binarytemple

binarytemple

Love it l, really interesting approach

— All posts loaded —

Where Next? Top

Trending in Announcing Top

woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
MRdotB
I needed to reuse React components from my Chrome extension in my Phoenix/LiveView backend. I noticed that for Svelte/Vue, there are live...
New
woylie
I released Doggo, a collection of unstyled Phoenix components. https://github.com/woylie/doggo Features Unstyled Phoenix components....
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
anuaralfetahe
Hello Published a new library - ProcessHub! ProcessHub is a library designed to manage process distribution within the Elixir cluster. ...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
sergio
It’s not that it’s vocabulary is too advanced. It’s something worse. I get lost trying to follow even a paragraph written by Claude. It’...
New
AstonJ
This showed up on my feed.. anyone heard of it? Just hype? Ox Alpha is a reasoning model designed for coding, sustained ag...
New
sorenone
Today we’re releasing Oban for Python. Not an Oban client in Python. Not a pythonx wrapper embedded in Elixir. Nope, it’s a fully operati...
New
akoutmos
@hugobarauna, Dr. Dimitrios Koutmos (my brother) and I (Alex Koutmos) have been hard at work on writing a book on how you can use Elixir ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews