Skip to content

Binds the reap's ids as one array on Postgres - #151

Merged
johnnyt merged 1 commit into
mainfrom
sr-7zxe-reap-array-form-on-postgres
Oct 2, 2026
Merged

johnnyt merged 1 commit into
mainfrom
sr-7zxe-reap-array-form-on-postgres

Conversation

@johnnyt

@johnnyt johnnyt commented Oct 2, 2026

Copy link
Copy Markdown
Member

What

StatifierRouter.Addresses.reap/3 binds each batch of ids as one array on
Postgres again, and keeps the spliced IN list on every other adapter.
Ruled by the operator, 2026-10-01: the array form on Postgres, the IN
list elsewhere, counts unchanged, with Postgres and SQLite tests in the
same request.

Since the SQLite reap fix, the private stamp/3 and delete/2 named their
rows in fragment("? IN (?)", a.id, splice(^batch)) on every adapter. On
Postgres that statement's text varies with the batch's length, so Postgres
prepared a statement for each distinct length where the array form had one.

How

  • The private postgres?/1 reads the repo's adapter from __adapter__/0.
    Only Ecto.Adapters.Postgres takes the array form.
  • On Postgres, the private stamp_query/2 and delete_query/2 bind
    fragment("? = ANY(?)", a.id, ^batch): one parameter for the whole batch,
    so every batch of a write is the same statement.
  • Every other adapter keeps the IN list. On SQLite the statements are
    byte-identical to the ones this branch's base sends (checked by capturing
    every reap statement before and after the change over a reap of 1201 rows
    and comparing them).
  • Ids are still bound uncast. Postgres types the parameter as an array of
    the id column's own type, so a text id made of digits alone stays a string.
  • Batches stay at most 500 ids a statement on every adapter; stamped,
    deleted and next are unchanged.

Record

A dated Note at the foot of ADR-0002, "the address sweep binds one array per
batch on Postgres again", after the outbound-send Amendment and the token
Note. It names the adapter branch, the two forms, the array's type, what
does not change and the tests. git diff origin/main -- docs/adr/ shows zero
removed lines.

Tests

  • StatifierRouter.PostgresReapTest, "binds each batch of ids as one array,
    so every batch of a write is one statement" (new module): a reap of 1201
    rows writes three batches (500, 500, 201) of stamps, then three of
    deletes. It reads the statements from the repo's query telemetry event and
    asserts each write sent one = ANY( statement text for all three batches,
    and the counts.
  • StatifierRouter.SQLiteReapTest, "binds each batch of ids as a spliced IN
    list": the same read on SQLite, asserting every write carries IN ( and no
    ANY(.
  • StatifierRouter.PrimaryKeyTest, "never casts a text id made of digits
    alone" (unchanged, isolated stage) now runs the array form under a text key
    of digits on Postgres, and passes.

Sabotage, each run on this branch and restored byte-equal before the next:

  • postgres?/1 answering false (the IN list on Postgres): the Postgres
    test went red on its single-statement assertion (two statement texts, a
    500-parameter list and a 201-parameter one).
  • postgres?/1 answering true (the array on SQLite): the new SQLite test
    went red, the first reap raising "no such function: ANY".
  • postgres?/1 without its __adapter__/0 check: the existing
    StatifierRouter.CreateModesTest test "never moves a terminal_seen_at
    another writer set between the read and the write", whose repo module
    delegates to the test repo and defines no __adapter__/0, raised
    UndefinedFunctionError (an error, not an assertion).

Changelog

changelog.d/sr-7zxe.md, under Changed. By changelog.d/README.md's lists
this is a change in observable behaviour rather than an internal refactor
with no visible effect: a host that reads its repo's query telemetry event
or Postgres's statement statistics sees one reap statement per write where
it saw one per batch length.

Provenance

  • An engineering choice not named in the ruling: a repo module that defines
    no __adapter__/0 (one that delegates to an Ecto repo rather than being
    one) takes the IN list, as before, rather than raising. The test support
    repo above is such a module. Recorded in the Note.
  • The Postgres test is a new module rather than a case in an existing
    Postgres reap test file, so it can attach its own query telemetry handler.

Gate

Full mix quality green locally on the committed tree (431 of 431 tests,
the isolated stage, Dialyzer, Credo, Docs, Doc links). The commit was made
without the commit skill's internal re-run: the staged tree was
byte-identical to the tree the gate had just run green on, and the repo gate
lock and a machine slot were held continuously across both.

StatifierRouter.Addresses.reap/3 named its rows in an IN list of one
bound parameter per id on every adapter, so on Postgres the statement
text varied with the batch's length and Postgres prepared one statement
per distinct length where the array form had one.

The private stamp/3 and delete/2 now branch on the repo's adapter: on
Postgres each batch is one bound array, fragment("? = ANY(?)", a.id,
^batch), the same statement for every batch length; every other
adapter, and a repo module that defines no __adapter__/0, keeps the
spliced IN list, byte-identical to before. Ids are still bound uncast;
Postgres types the array from the id column. Batches of 500 and every
count and cursor are unchanged.

Adds StatifierRouter.PostgresReapTest and a SQLiteReapTest case, each
reading the statements from the repo's query telemetry event. ADR-0002
gets a dated foot Note; a changelog fragment records the change.

Refs: sr-7zxe
@johnnyt
johnnyt merged commit a7d2b97 into main Oct 2, 2026
1 check passed
@johnnyt
johnnyt deleted the sr-7zxe-reap-array-form-on-postgres branch October 2, 2026 10:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant