Skip to content

Fix usage counters, address_tx key, tx address folding, insert defaults - #6

Merged
markmnl merged 2 commits into
mainfrom
fix-usage-counters
Oct 10, 2026
Merged

markmnl merged 2 commits into
mainfrom
fix-usage-counters

Conversation

@markmnl

@markmnl markmnl commented Oct 10, 2026

Copy link
Copy Markdown
Owner

Summary

Four bugs in recording and reporting address usage:

  1. Usage fields swapped. GET /fmsgid/:address scanned sqlActuals in a different order from its SELECT, so e.g. recvSizeTotal held the 1-day received size and sendSizePer1d held the total received size. The query now selects in the order the handler scans (via QueryRow; the aggregate always returns one row).
  2. Messages with the same timestamp lost. address_tx had primary key (address_lower, ts), so a second message to an address at the same timestamp failed with 500 and was not counted. It now has a bigserial id primary key; the existing non-unique (address_lower, type, ts desc) index stays.
  3. Address not folded on send/recv. POST /fmsgid/send and /recv inserted the address as given into address_lower, so any case difference failed the foreign key to address. The address is now validated (400 if invalid) and case folded with cases.Fold(), the same as address rows are keyed.
  4. Hard-coded limits on create. POST /fmsgid inserted literal limit values; it now inserts only address_lower, address and display_name, so accepting_new and every limit take the table's column defaults. The CSV upsert path is unchanged; its comment wrongly said missing limits default to -1 and now says they match the table defaults.

Also includes a whitespace-only commit converting src/sql.go from CRLF to LF (gofmt -l flagged it), so the query diffs are readable.

Schema migration

dd.sql stays idempotent and migrates an existing, populated address_tx when re-run:

  • a do block adds id bigserial only if the column is missing (not add column if not exists, which on older PostgreSQL versions leaves an orphan sequence on every run),
  • drops the primary key if it is on anything other than id, and
  • adds primary key (id) if there is none.

Adding the column rewrites the table and briefly holds an exclusive lock on it. The script must run as the table's owner. The role the service connects as needs USAGE on the new sequence address_tx_id_seq to insert. The old binary keeps working against the migrated schema (it never names id), so the schema can be applied before the new binary starts.

Tests

  • New database tests in src/usage_test.go (helpers in src/db_test.go), skipped unless FMSGID_TEST_DATABASE_URL is set; documented under Build > Test in the README:
    • TestAddressDetail_Usage records sends and receives through the API (some older than a day, some sharing a timestamp, some in different case) and asserts each of the six usage fields.
    • TestAddressDetail_NoUsage: an unused address reports zeros.
    • TestPostCreateAddress_ColumnDefaults copies the address table into a scratch schema with different column defaults and checks a created address takes them.
    • TestPostAddressTx_InvalidAddress (no database needed).
  • Against main these tests fail: the same-timestamp/mixed-case receive returns 500, and the invalid-address request reaches the database.
  • go vet ./... is clean. go test -race ./... passes with and without a database (PostgreSQL 16 in a throwaway container).
  • Ran dd.sql three times on a database built from the old dd.sql with rows in it, and twice on a fresh database. Both ended with address_tx_pkey on (id) and exactly one sequence, existing rows were kept and given ids, and duplicate (address_lower, ts) inserts then succeeded.

CI does not start PostgreSQL, so it skips the database tests.

🤖 Generated with Claude Code

markmnl and others added 2 commits October 10, 2026 13:27
The file had CRLF line endings, so `gofmt -l .` flagged it. Whitespace
only; no change to any query.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- GET /fmsgid/:address scanned the usage query into the wrong fields
  (e.g. recvSizeTotal got the 1-day received size). sqlActuals now
  selects in the order the handler scans, via QueryRow since the
  aggregate always returns one row.
- address_tx had primary key (address_lower, ts), so a second message
  to an address with the same timestamp failed with 500 and wasn't
  counted. It now has a bigserial id primary key; the existing
  (address_lower, type, ts desc) index stays. dd.sql migrates an
  existing table idempotently: adds id, drops the old primary key and
  adds the new one, each only when needed.
- POST /fmsgid/send and /recv inserted the address as given into
  address_lower, so any case difference failed the foreign key. The
  address is now validated (400 if invalid) and case folded, the same
  as address rows are keyed.
- POST /fmsgid inserted hard-coded limits; it now gives only the
  identity columns so accepting_new and the limits take the table's
  column defaults.
- Correct parseCSV's comment: missing limits default to the table's
  defaults, not -1.

Adds database tests (skipped unless FMSGID_TEST_DATABASE_URL is set)
that record messages of both types, with shared timestamps and mixed
case, and assert every usage field, and that check a new address takes
the column defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@markmnl
markmnl merged commit 37b65bc into main Oct 10, 2026
1 check passed
@markmnl
markmnl deleted the fix-usage-counters branch October 10, 2026 05:34
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