Skip to content

fix(drizzle): validate companion table name length against 63-char limit - #17563

Merged
nathanlentz merged 3 commits into
payloadcms:3.xfrom
zawoj:fix/3.x/validate-companion-table-name-length
Jul 30, 2026
Merged

fix(drizzle): validate companion table name length against 63-char limit#17563
nathanlentz merged 3 commits into
payloadcms:3.xfrom
zawoj:fix/3.x/validate-companion-table-name-length

Conversation

@zawoj

@zawoj zawoj commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

The bug

Payload validates base table names against Postgres's 63-char limit, but the companion tables (_locales, _rels, _texts, _numbers) are built by concatenation afterward and never checked. A base name in the 56-63 char window passes, while its companion silently overflows and Postgres truncates it - leaving the DB schema out of sync with what Payload thinks it created (in dev, push then prompts Is <table> created or renamed? on every boot).

{ slug: 'a_fairly_long_collection_slug_that_is_still_under_63_chars', // base ≤ 63 → OK
  fields: [{ name: 'title', type: 'text', localized: true }] }        // ..._locales (+8) → silently truncated

The fix

Extract the 63-char check out of createTableName into a reusable validateIdentifierLength, and apply it at the 4 companion-table definition sites in schema/build.ts. Over-long companions now fail fast with the existing error + dbName tip:

Exceeded max identifier length for table or enum name of 63 characters. Invalid name: ..._locales.
Tip: You can use the dbName property to reduce the table name length.

Tests & why this is the right fix

  • Unit - validateIdentifierLength.spec.ts: throws over 63, passes at/under.
  • Regression - buildRawSchema.spec.ts: an overflowing _locales now throws in-memory.
  • Integration - test/database on Postgres: 154 passed, 0 failed (no fixture relied on truncation).

It's one shared validator reused everywhere a final identifier is produced - no duplication - and it turns Postgres's silent truncation into an explicit, actionable error, matching how base names already behave. Arrays/blocks funnel through the same buildTable choke point, so those 4 sites cover the whole class without touching traverseFields.ts.

@nathanlentz nathanlentz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this change, @zawoj

I've created a mirror PR to get this fix into 4.0 as well.

We're working on getting a few of these flakey tests fixed up so the gates can pass before this gets merged.

nathanlentz
nathanlentz previously approved these changes Jul 30, 2026
PatrikKozak added a commit that referenced this pull request Jul 30, 2026
…mit (#17567)

Mirror of #17563 which targets `3.x`

## The bug

Payload validates base table names against Postgres's 63-char limit, but
the companion tables (`_locales`, `_rels`, `_texts`, `_numbers`) are
built by concatenation afterward and never checked. A base name in the
56-63 char window passes, while its companion silently overflows and
Postgres truncates it - leaving the DB schema out of sync with what
Payload thinks it created (in dev, `push` then prompts `Is <table>
created or renamed?` on every boot).

```ts
{ slug: 'a_fairly_long_collection_slug_that_is_still_under_63_chars', // base ≤ 63 → OK
  fields: [{ name: 'title', type: 'text', localized: true }] }        // ..._locales (+8) → silently truncated
```

## The fix

Extract the 63-char check out of `createTableName` into a reusable
`validateIdentifierLength`, and apply it at the 4 companion-table
definition sites in `schema/build.ts`. Over-long companions now fail
fast with the existing error + `dbName` tip.

## Tests

- Unit - `validateIdentifierLength.spec.ts`: throws over 63, passes
at/under.
- Regression - `buildRawSchema.spec.ts`: an overflowing `_locales` now
throws in-memory.
- Cherry-picked the two new `buildRawSchema.spec.ts` fixtures now
explicitly set `versions: false` since main defaults collections to
`versions: true` as of #16871 (landed after this PR's original base),
which otherwise requires `timestamps` and fails these fixtures for
unrelated reasons.

---------

Co-authored-by: Kacper Zawojski <zawojskikacperkontakt@gmail.com>
Co-authored-by: Patrik Kozak <35232443+PatrikKozak@users.noreply.github.com>
@nathanlentz
nathanlentz enabled auto-merge (squash) July 30, 2026 14:58
@nathanlentz
nathanlentz merged commit fd629f2 into payloadcms:3.x Jul 30, 2026
339 of 342 checks passed
@zawoj
zawoj deleted the fix/3.x/validate-companion-table-name-length branch July 30, 2026 17:28
@github-actions

Copy link
Copy Markdown
Contributor

🚀 This is included in version v3.87.0

PatrikKozak added a commit that referenced this pull request Aug 14, 2026
)

Surfaces Postgres identifier truncation instead of letting it silently
desync the schema.

Postgres truncates any identifier over 63 characters. Payload builds
column and inline index/foreign-key names by concatenating nested field
paths, so deeply nested fields can exceed the limit. Postgres then
stores a shorter name than Payload's schema and migration snapshot
expect, and dev `push` hangs on a repeated `is <column> created or
renamed?` prompt on every boot.

#17563 addressed this for table names by throwing, but that only reaches
base and companion tables. Columns and inline index/foreign-key names
were still truncated silently, and throwing outright blocks existing
installs that already carry a truncated name from upgrading.

Adds `checkTruncatedIdentifiers`, run during postgres `init` after
`beforeSchemaInit`:

- Warns (dev only) when a single identifier exceeds 63 characters, so
existing installs keep booting.
- Throws when two identifiers truncate to the same name, since Postgres
cannot create that schema anyway — and a colliding schema could never
have booted, so this never breaks a running app.

Base table and enum names still throw during construction via
`validateIdentifierLength`, unchanged.

Related: softens the companion-table checks from #17563 (`_locales`,
`_rels`, `_texts`, `_numbers`) from a hard throw to the same
warn-or-collision handling.
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.

3 participants