Skip to content

Apply the parts of a query an adapter doesn't run in memory - #2450

Draft
Flo0807 wants to merge 1 commit into
feature/backpex-queryfrom
feature/query-runtime
Draft

Flo0807 wants to merge 1 commit into
feature/backpex-queryfrom
feature/query-runtime

Conversation

@Flo0807

@Flo0807 Flo0807 commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Stack (merge in this order)

  1. Build queries only in the Ecto adapter #2338 Build queries only in the Ecto adapter, based on develop
  2. Let the adapter turn filter conditions and metric aggregates into queries #2340 Let the adapter turn filter conditions and metric aggregates into queries
  3. Configure filters in the LiveResource like fields #2431 Configure filters in the LiveResource like fields
  4. Describe the resource in the adapter and cast expressions with it #2448 Describe the resource in the adapter and cast expressions with it
  5. Read through a Backpex.Query #2449 Read through a Backpex.Query
  6. Apply the parts of a query an adapter doesn't run in memory #2450 Apply the parts of a query an adapter doesn't run in memory ← this PR
  7. Write through neutral changes and let adapters declare writes #2451 Write through neutral changes and let adapters declare writes
  8. Replace item_query, :select and :custom_alias with scope, load and computed values #2454 Replace item_query, :select and :custom_alias with scope, load and computed values
  9. Compute aggregates of related items #2455 Compute aggregates of related items

Step 4 of the data source abstraction: adapters say which parts of a Backpex.Query they run, and Backpex applies the rest to the items in memory. An adapter that runs nothing only returns all items.

supports?/2

New optional callback c:Backpex.Adapter.supports?/2, or the :supports option of use Backpex.Adapter (:all by default). Features: :filter (and {:filter, operator} for single operators such as :contains or :exists), :search, :sort, :offset, :limit, :load, :count, :aggregate.

The parts depend on each other in the order filter → search → sort → offset → limit. Backpex.Resource.read/1 gives the adapter the parts before the first unsupported one and applies that part and all later ones in memory, e.g. an adapter that sorts but can't filter gets neither. Relationships the in-memory parts read through paths are added to :load. count/1 and aggregate/1 are optional now: without them, or without support for the filter/search of the query, Backpex counts and aggregates the items of read/1.

In memory

Backpex.Query.apply_to/2 and Backpex.Query.aggregate_items/2 (in Backpex.Query.Runtime):

  • expressions with the three-valued logic of SQL (a comparison with nil keeps nothing, not even negated), in, is_nil, contains/starts_with/ends_with with ignore_case, paths and exists over loaded relationships; dates, times and decimals compare with their modules
  • search: case-insensitive contains over all targets, values cast to text
  • sorts with the nulls rules of PostgreSQL (nulls last ascending, first descending) and the explicit *_nulls_* directions; strings sort by code points
  • aggregates :count, {:count | :sum | :avg | :min | :max, field} with where, nil for no values, Decimal sums
  • native expressions raise

Backpex.Adapters.Memory

Keeps the items of a LiveResource in an Agent ({Backpex.Adapters.Memory, name: ..., items: ...}), describes them with an (embedded) Ecto.Schema and builds changesets like the Ecto adapter. It runs no part of a query, so it exercises the whole in-memory path. It passes the adapter conformance suite, which now reads through Backpex.Resource like the views, and Backpex.Test.MemoryNoteLive covers index (scope, search, sort, metric), show, form and delete.

Docs

New guide guides/adapters/custom-adapter.md with a read-only CSV adapter (its code was checked with a temporary test), a section in the v0.25 upgrade guide, and Backpex.Query/Backpex.Expr in one "Queries" module group.

Review fixes

f422081:

  • An adapter that lists single operators (supports: [{:filter, :<=}]) now runs the filters that only use them. Before, every filter and all later parts ran in memory.
  • In-memory parts load whole paths, nested like Ecto preloads (tags: [:category]), also for the where of aggregates; counts and aggregates only go to the adapter if it supports the operators of these expressions.
  • in with nil among the values is unknown for other values, as in SQL.
  • The search in memory no longer raises for lists and maps.
  • Backpex.Adapters.Memory.update_all/3 changes the stored items instead of writing back the given ones.

Testing

On f422081:

  • mix test: 1515 passed (188 doctests, 1327 tests)
  • mix test --only e2e: 60 passed
  • MIX_ENV=test mix lint: clean, mix docs without warnings
  • merged coverage (measured on f279c9b): 90.67 %
  • demo (on f279c9b): bun run test 51 passed

@codecov

codecov Bot commented Nov 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.94009% with 24 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
lib/backpex/query/runtime.ex 83.80% 17 Missing ⚠️
lib/backpex/adapters/memory.ex 89.79% 5 Missing ⚠️
lib/backpex/resource.ex 96.36% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

- Apply the parts of a query an adapter doesn't run in memory
- Run single filter operators in the adapter and load whole paths in memory
@Flo0807
Flo0807 force-pushed the feature/query-runtime branch from 6f7fbdd to 918e9e3 Compare November 13, 2026 09:46

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change A breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant