What
crates/rustango/src/sql/executor.rs is at 4338 lines mixing 8 distinct concerns (PG-only path, &Pool dispatch, &mut PoolTx ops, FromRow markers, pure projection, prefetch, ChunkedIter, get_or_create, traits, bind helpers). The file already has 5 hand-rolled // ===== section markers — a visible signal the author hit the same navigation pain.
Goal
Convert executor.rs into src/sql/executor/ directory with 12 files of 50–1100 LOC each. Public API stays bit-identical via pub use re-exports in mod.rs — zero downstream breakage.
Proposed layout
| File |
LOC est |
Contents |
mod.rs |
~80 |
Re-export-only. |
bind.rs |
~250 |
bind_match! macro + bind_query family + per-dialect cell_to_sqlvalue decoders. |
traits.rs |
~600 |
All trait surface (Fetcher / FetcherPool / FetcherTx, Counter*, Updater*, Deleter, HasPkValue, LoadRelated*, MaybePgFromRow family, MaybePgScalar family). |
pg.rs |
~1100 |
PG-only path (&PgPool / &mut PgConnection) — insert(_on) / update(_on) / delete(_on) / select_rows(_on) / count_rows(_on) / bulk_* / raw_query(_on) / raw_execute(_on) / fetch_aggregate(_on) / transaction. |
row_to_json.rs |
~370 |
Tri-dialect dynamic-row decoders (row_to_json / _my / _sqlite / select_rows_as_json / select_one_row_as_json + augment_joined_columns_*). |
pool.rs |
~1100 |
&Pool dispatch — the canonical bi-dialect path. insert_pool / update_pool / delete_pool / count_rows_pool / bulk_*_pool / raw_*_pool / select_*_pool / fetch_aggregate_pool / fetch_paginated_pool / select_rows_pool_with_related + FetcherPool impl. |
tx.rs |
~370 |
PoolTx wrapper, transaction_pool, insert_tx / update_tx / delete_tx / select_rows_tx_with_related / FetcherTx impl. |
values.rs |
~310 |
Issue #22 — fetch_values_dict / fetch_values_list / fetch_values_flat + the three ValuesQuerySet::fetch bridge impls. |
prefetch.rs |
~330 |
annotate_count_children(_on/_pool) / fetch_with_prefetch(_on/_pool). |
iter.rs |
~165 |
ChunkedIter (issue #23). |
get_or_create.rs |
~140 |
get_or_create / update_or_create / ensure_pk_ordering. |
page.rs |
~50 |
Page<T> struct + inject_total_count. |
Migration mechanics
Five-step plan, each step compiles + passes tests independently (git-bisect-able and reversible):
- Create
mod.rs + traits.rs — move all trait defs first.
- Extract
bind.rs — unblocks every other extraction.
- Extract
pg.rs + row_to_json.rs — cleanest pure cut. After this, executor.rs is half-size.
- Extract
pool.rs + tx.rs — the bi-dialect surface. Largest single cut.
- Extract
values.rs + prefetch.rs + iter.rs + get_or_create.rs + page.rs — the leaf modules. Delete the now-empty executor.rs.
Run cargo test --workspace --all-features --no-run after each step.
Why not now
Doing this reorg invalidates every open PR's merge base. Trigger condition: empty open-PR queue between parity batches, or pair with a planned breaking-change release. Estimated 1-2 days of focused work.
Acceptance
Related followup
writers.rs (2597 lines) probably wants the same treatment but lower priority — defer to a separate plan.
What
crates/rustango/src/sql/executor.rsis at 4338 lines mixing 8 distinct concerns (PG-only path,&Pooldispatch,&mut PoolTxops, FromRow markers, pure projection, prefetch, ChunkedIter, get_or_create, traits, bind helpers). The file already has 5 hand-rolled// =====section markers — a visible signal the author hit the same navigation pain.Goal
Convert
executor.rsintosrc/sql/executor/directory with 12 files of 50–1100 LOC each. Public API stays bit-identical viapub usere-exports inmod.rs— zero downstream breakage.Proposed layout
mod.rsbind.rsbind_match!macro +bind_queryfamily + per-dialectcell_to_sqlvaluedecoders.traits.rsFetcher/FetcherPool/FetcherTx,Counter*,Updater*,Deleter,HasPkValue,LoadRelated*,MaybePgFromRowfamily,MaybePgScalarfamily).pg.rs&PgPool/&mut PgConnection) —insert(_on)/update(_on)/delete(_on)/select_rows(_on)/count_rows(_on)/bulk_*/raw_query(_on)/raw_execute(_on)/fetch_aggregate(_on)/transaction.row_to_json.rsrow_to_json/_my/_sqlite/select_rows_as_json/select_one_row_as_json+augment_joined_columns_*).pool.rs&Pooldispatch — the canonical bi-dialect path.insert_pool/update_pool/delete_pool/count_rows_pool/bulk_*_pool/raw_*_pool/select_*_pool/fetch_aggregate_pool/fetch_paginated_pool/select_rows_pool_with_related+FetcherPoolimpl.tx.rsPoolTxwrapper,transaction_pool,insert_tx/update_tx/delete_tx/select_rows_tx_with_related/FetcherTximpl.values.rsfetch_values_dict/fetch_values_list/fetch_values_flat+ the threeValuesQuerySet::fetchbridge impls.prefetch.rsannotate_count_children(_on/_pool)/fetch_with_prefetch(_on/_pool).iter.rsChunkedIter(issue #23).get_or_create.rsget_or_create/update_or_create/ensure_pk_ordering.page.rsPage<T>struct +inject_total_count.Migration mechanics
Five-step plan, each step compiles + passes tests independently (git-bisect-able and reversible):
mod.rs+traits.rs— move all trait defs first.bind.rs— unblocks every other extraction.pg.rs+row_to_json.rs— cleanest pure cut. After this, executor.rs is half-size.pool.rs+tx.rs— the bi-dialect surface. Largest single cut.values.rs+prefetch.rs+iter.rs+get_or_create.rs+page.rs— the leaf modules. Delete the now-emptyexecutor.rs.Run
cargo test --workspace --all-features --no-runafter each step.Why not now
Doing this reorg invalidates every open PR's merge base. Trigger condition: empty open-PR queue between parity batches, or pair with a planned breaking-change release. Estimated 1-2 days of focused work.
Acceptance
cargo test --workspace --all-featurespasses with zero failures.cargo test --no-default-features --features sqlite,tenancy(the litmus from CLAUDE.md) passes.cargo doc --no-depsproduces no broken intra-doc links.grep -rn 'sql::executor::' .returns zero results outside the executor dir.executor.rsare updated.Related followup
writers.rs(2597 lines) probably wants the same treatment but lower priority — defer to a separate plan.