Skip to content

count() returns a stale total for default-settings tables#61

Merged
roed314 merged 1 commit into
roed314:masterfrom
roed-math:fix/totals
Jul 20, 2026
Merged

count() returns a stale total for default-settings tables#61
roed314 merged 1 commit into
roed314:masterfrom
roed-math:fix/totals

Conversation

@roed-math

Copy link
Copy Markdown

meta_tables.total was only ever written inside if self.stats.saving: blocks, and saving defaults to False — so a table created and filled through psycodict reported count() == 0 from any process, forever, and rewrite()/update_from_file() refused to run (their label validation compares count_distinct() against the stale count()).

The write paths (insert_many, upsert, delete, copy_from) now maintain the total through _update_total, which applies the delta in the database itself (GREATEST(total + %s, 0) ... RETURNING) so concurrent writers compose instead of losing each other's rows; _set_total handles the recount cases — reload_final_swap recounts the swapped-in table just before it re-reads meta_tables, and refresh_stats(total=True) persists what it recomputes. Suffixed recounts (a staged _tmp table during reload) touch neither the in-memory total nor meta_tables, and the same gate is added to _record_count's own meta update, which had the same flaw. saving now gates only what its docstring says: recording statistics in the counts table. The precedent for writing meta_tables outside the saving guard is _break_stats, which already does exactly that on the same code paths.

For LMFDB (saving = True) observable behaviour is unchanged. New regression tests cover the fresh-connection repro, the staged-suffix isolation, and the two-writer race.


Split out of #60 (one PR per issue). This branch carries only this issue's fix and its tests; the rest of the known-bug inventory stays xfailed, so the full suite is green here and the strict markers guarantee no cross-issue leakage.

meta_tables.total was only ever written inside if self.stats.saving: blocks, and saving defaults to False — so a table created and filled through psycodict reported count() == 0 from any process, forever, and rewrite()/update_from_file() refused to run (their label validation compares count_distinct() against the stale count()).

The write paths (insert_many, upsert, delete, copy_from) now maintain the total through _update_total, which applies the delta in the database itself (GREATEST(total + %s, 0) ... RETURNING) so concurrent writers compose instead of losing each other's rows; _set_total handles the recount cases — reload_final_swap recounts the swapped-in table just before it re-reads meta_tables, and refresh_stats(total=True) persists what it recomputes. Suffixed recounts (a staged _tmp table during reload) touch neither the in-memory total nor meta_tables, and the same gate is added to _record_count's own meta update, which had the same flaw. saving now gates only what its docstring says: recording statistics in the counts table. The precedent for writing meta_tables outside the saving guard is _break_stats, which already does exactly that on the same code paths.

For LMFDB (saving = True) observable behaviour is unchanged. New regression tests cover the fresh-connection repro, the staged-suffix isolation, and the two-writer race.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@roed314
roed314 merged commit 56bd3f5 into roed314:master Jul 20, 2026
16 checks passed
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.

2 participants