Skip to content

Fix has_been_used? for Postgres table names near the identifier length limit - #133

Open
dduugg wants to merge 4 commits into
DatabaseCleaner:mainfrom
dduugg:fix/pg-serial-sequence-name-truncation
Open

Fix has_been_used? for Postgres table names near the identifier length limit#133
dduugg wants to merge 4 commits into
DatabaseCleaner:mainfrom
dduugg:fix/pg-serial-sequence-name-truncation

Conversation

@dduugg

@dduugg dduugg commented Jul 28, 2026

Copy link
Copy Markdown

What

Truncation's Postgres adapter guesses a table's auto-increment sequence name as "#{table}_id_seq" and looks it up with a naive pg_class string match. Once that guess exceeds Postgres' 63-byte NAMEDATALEN limit, Postgres silently truncates the identifier, so the guess can land on the table's own name (or some unrelated relation) instead of the real, differently-truncated sequence name Postgres actually generated for it.

Concretely, for a table whose name is already at or near 63 characters:

table:           plan_recommendation_engine_lvl_fund_gusto_ignor_waiting_periods (63 chars)
naive guess:      ..._periods_id_seq  (70 chars, silently truncated by Postgres to 63)
truncated guess:  plan_recommendation_engine_lvl_fund_gusto_ignor_waiting_periods  <- the table itself!
real sequence:    plan_recommendation_engine_lvl_fund_gusto_ignor_waiting__id_seq  <- different truncation

has_sequence? then false-positives against the table's own pg_class row, and has_been_used?'s follow-up query blows up:

PG::UndefinedColumn: ERROR:  column "last_value" does not exist
LINE 1: SELECT last_value from plan_recommendation_engine_lvl_fund_g...

This is a regression surfaced by #103 (2.2.0): the old currval(...) call was wrapped in a bare rescue that happened to swallow this case too; the last_value rewrite dropped that safety net.

Fix

Use pg_get_serial_sequence(table, 'id') — the Postgres builtin purpose-built for this — instead of string-guessing. It correctly resolves the real (possibly truncated) sequence name regardless of identifier length. Falls back to has_rows? when the table has no id column at all, since pg_get_serial_sequence raises rather than returning nil for that case.

Testing

Added a regression test under truncation_spec.rb (Postgres-only) that creates a table with a name at the exact 63-byte limit and exercises pre_count's has_been_used? path against it, both for a never-touched table and one with real activity. Ran the full spec suite locally against Postgres and SQLite (no regressions); didn't have MySQL/Trilogy available locally, but the change is scoped entirely to PostgreSQLAdapter so those adapters are untouched.

Happy to adjust style/approach if you'd prefer a different fix shape.

dduugg added 4 commits July 28, 2026 15:14
…h limit

pre_count's has_been_used? guessed a table's sequence name as
"#{table}_id_seq" and looked it up with a naive pg_class match. Once that
guess exceeds Postgres' 63-byte NAMEDATALEN limit, Postgres silently
truncates it, so the guess can collide with the table's own name (or some
other relation) instead of the real, differently-truncated sequence name
Postgres actually generated. has_sequence? then false-positives on that
wrong relation, and the follow-up "SELECT last_value from ..." blows up
with `PG::UndefinedColumn: column "last_value" does not exist` since it's
querying a table, not a sequence.

Use pg_get_serial_sequence(table, 'id') instead of guessing, which is what
it's for. Falls back to has_rows? when the table has no "id" column at all
(pg_get_serial_sequence raises rather than returning nil for that case).
rescue ::ActiveRecord::StatementInvalid was catching any statement
failure, not just the "no id column" case it's meant to handle, silently
falling back to has_rows? for genuine SQL/connection errors too. Check
the underlying PG::UndefinedColumn cause and re-raise anything else.
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