Skip to content

fix: use pg_depend ownership instead of name guessing for SERIAL detection#10167

Open
kundansable wants to merge 4 commits into
pgadmin-org:masterfrom
kundansable:fix-10100-10101-serial-pgdepend
Open

fix: use pg_depend ownership instead of name guessing for SERIAL detection#10167
kundansable wants to merge 4 commits into
pgadmin-org:masterfrom
kundansable:fix-10100-10101-serial-pgdepend

Conversation

@kundansable

@kundansable kundansable commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #10100 and #10101 — Schema Diff's "Generate Script" (and the browser tree's CREATE Script view) can emit wrong SQL for SERIAL/identity columns, in two opposite ways: it fails to recognize a genuine SERIAL column after a rename, and it wrongly promotes an unrelated column to SERIAL when a coincidentally-named sequence exists.

Root Cause

get_formatted_columns() detects whether a column owns a sequence (and should be rendered as SERIAL) by guessing the sequence's name as <table>_<col>_seq and checking whether that literal string appears in the column's nextval(...) default. This guess breaks in both directions:

Both are the same underlying problem: guessing by name instead of asking PostgreSQL which sequence a column actually owns. That ownership is already tracked in pg_depend, and the properties template already exposes it via a LEFT JOIN as seqrelid — it just wasn't being used for this check.

Fix

Replace the name-guessing check in get_formatted_columns() with a check against col.get('seqrelid') (plus the existing nextval(' + integer-type guards), and additionally exclude identity columns (attidentity). SERIAL is now emitted if and only if a genuine pg_depend ownership dependency exists, independent of naming.

Test Steps

  1. Rename case (Issue with CREATE script: Schema Diff generates wrong SQL for SERIAL columns after renaming, or when a sequence name accidentally matches #10100):
    CREATE TABLE orders (id serial PRIMARY KEY);
    ALTER TABLE orders RENAME TO invoices;
    Right-click invoices → CREATE Script (or Schema Diff → Generate Script). Expect id serial NOT NULL — not DEFAULT nextval('orders_id_seq'::regclass).
  2. Coincidental name case (If sequence with same name exists new generated sequence in CREATE script overwrites the script silently #10101):
    CREATE SEQUENCE mytable_id_seq;
    CREATE TABLE mytable (
      id integer NOT NULL DEFAULT nextval('mytable_id_seq')
    );
    Generate the CREATE script for mytable. Expect the column to stay as DEFAULT nextval('mytable_id_seq'::regclass) — it must not become SERIAL.
  3. Regression — genuine untouched SERIAL: create a plain CREATE TABLE t (id serial PRIMARY KEY) with no rename, generate its script, confirm it still correctly emits SERIAL.
  4. Run the reverse-engineering / RE-SQL regression suite for tables/columns if available (web/regression/runtests.py --pkg databases.schemas.tables), confirming no unrelated column tests regress.

Summary by CodeRabbit

  • Bug Fixes

    • Improved detection of PostgreSQL SERIAL, SMALLSERIAL, and BIGSERIAL columns using sequence ownership information.
    • More reliably reclassifies columns to the correct serial type (including nextval(...) cases) and removes redundant stored defaults.
    • Prevents identity columns from being incorrectly shown as serials.
  • Tests

    • Added unit tests covering matching/non-matching sequence scenarios and identity-column exclusions.
  • Documentation

    • Updated internal documentation for the current SERIAL detection strategy and references.

…ction

get_formatted_columns() guessed a column's owned sequence via
make_object_name(table, col, 'seq') and rewrote nextval(...) defaults
to SERIAL only when that literal name matched. This breaks in two
ways:

- Renaming a table/column changes the guessed name but not the
  sequence's actual name, so the column stops being recognized as
  SERIAL and the reverse-engineered script emits a broken
  nextval('old_seq'::regclass) that fails on a fresh database.
- A column with a nextval() default that happens to reference an
  unrelated, coincidentally-named sequence gets silently rewritten as
  SERIAL, orphaning the real sequence.

The properties template already exposes the column's actual owned
sequence (seqrelid) via a LEFT JOIN on pg_depend. Use that instead of
guessing, so SERIAL is only emitted when a genuine ownership
dependency exists, independent of naming.

Fixes pgadmin-org#10100, pgadmin-org#10101
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 964c657a-8b6d-49a4-9643-ac953d18496c

📥 Commits

Reviewing files that changed from the base of the PR and between 8f1ca94 and 55d5cd5.

📒 Files selected for processing (1)
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/default/properties.sql
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/default/properties.sql

Walkthrough

get_formatted_columns now detects SERIAL columns using sequence ownership from pg_depend, checks for nextval(...), excludes identity columns, and removes the stored default when formatting SERIAL types.

Changes

SERIAL detection

Layer / File(s) Summary
Sequence dependency metadata and SERIAL formatting
web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/default/properties.sql, web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py, web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/test_serial_detection_unit.py
Exposes default sequence dependencies through pg_depend, replaces guessed sequence-name matching, excludes identity columns, formats matching columns as serial, smallserial, or bigserial, and tests split, matching, and identity cases.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant properties_sql
  participant pg_depend
  participant get_formatted_columns
  properties_sql->>pg_depend: resolve default sequence dependency
  pg_depend-->>properties_sql: return defseqrelid
  properties_sql-->>get_formatted_columns: provide column metadata
  get_formatted_columns->>get_formatted_columns: compare seqrelid and defseqrelid
  get_formatted_columns-->>get_formatted_columns: reproject eligible columns as SERIAL
Loading

Possibly related issues

  • 10100: Uses sequence ownership metadata to address incorrect SERIAL detection after table or column renames.
  • 10101: Replaces guessed sequence-name matching with sequence-ownership checks for SERIAL detection.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: replacing SERIAL detection name guessing with pg_depend ownership.
Linked Issues check ✅ Passed The changes address #10100 by using sequence ownership to emit SERIAL correctly after renames and avoid stale sequence names.
Out of Scope Changes check ✅ Passed The added SQL output and unit tests support the SERIAL-detection fix and do not introduce unrelated scope.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py`:
- Around line 288-290: Update the serial-detection condition around
col.get('seqrelid') to require that the column default expression is also
recorded as depending on the same sequence OID (cs.oid) from properties.sql.
Ensure split ownership/default cases are excluded from serial conversion and
retain their original nextval default, and add a regression test covering a
column owned by one sequence whose default references another.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3275255d-ffcc-438c-9356-dcab5eae5b1e

📥 Commits

Reviewing files that changed from the base of the PR and between b15c745 and da9395c.

📒 Files selected for processing (1)
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py

Ownership and default are independent dependencies in PostgreSQL: a
column can own one sequence (pg_depend deptype='a', the existing
seqrelid check) while its DEFAULT's nextval() call names a completely
different one (pg_depend deptype='n' from the pg_attrdef entry). The
previous check only verified ownership, so a column in this split
ownership/default state was still wrongly reprojected as SERIAL,
discarding its real, distinct nextval() default.

Expose the default's own sequence dependency as defseqrelid in
properties.sql and require it to equal seqrelid before treating a
column as SERIAL, so split ownership/default columns keep their
original explicit nextval() default.

Add a mock-based regression test covering the split case, a genuine
SERIAL column, and identity columns, following the pattern already
used for shared-server unit tests since BaseTestGenerator's setUp()
needs a live server connection.

Verified against a real PostgreSQL 13 instance: a column owning
seq_owned but defaulting to nextval('seq_used') round-trips as
DEFAULT nextval('seq_used'::regclass), not SERIAL.
E127 continuation-line indent and E501 line-length (82 > 79) on the
UTILS_MODULE string and the _make_column() defaults dict.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/test_serial_detection_unit.py (1)

47-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Annotate scenarios as ClassVar.

Ruff RUF012 flags this mutable class attribute. It is intentionally shared test metadata, so annotate it as ClassVar rather than suppressing the warning.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/test_serial_detection_unit.py`
around lines 47 - 54, Annotate the shared mutable scenarios class attribute with
ClassVar in the relevant test class, adding the necessary typing import if
absent. Preserve the existing scenario metadata and avoid suppressing Ruff
RUF012.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/default/properties.sql`:
- Around line 40-47: Update the defseqdep join and defseqrelid derivation so
only sequence relations are considered and multiple dependency rows resolve
deterministically. Join defseqdep through pg_class with relkind = 'S', then add
the required stable tie-breaker or pre-collapse before comparing defseqrelid
with seqrelid; preserve the existing sequence dependency behavior.

---

Nitpick comments:
In
`@web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/test_serial_detection_unit.py`:
- Around line 47-54: Annotate the shared mutable scenarios class attribute with
ClassVar in the relevant test class, adding the necessary typing import if
absent. Preserve the existing scenario metadata and avoid suppressing Ruff
RUF012.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1f48241f-1544-4b4e-b4c2-e1813acc51ab

📥 Commits

Reviewing files that changed from the base of the PR and between da9395c and 8f1ca94.

📒 Files selected for processing (3)
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/tests/test_serial_detection_unit.py
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/default/properties.sql
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py

The defseqdep join filtered the default's own pg_depend row down to
classid='pg_attrdef' and deptype='n', but never checked that the
referenced object (refobjid) is actually a sequence. A default
expression can carry other deptype='n' dependencies on non-sequence
pg_class objects too - e.g. a composite type referenced via a cast
inside the expression - which would fan the LEFT JOIN out to multiple
rows for the same column and let SELECT DISTINCT ON (att.attnum) pick
an arbitrary one, occasionally resolving defseqrelid to something
other than the intended sequence.

Verified against a real server: a default expression referencing both
a sequence and a composite type produces two deptype='n' pg_depend
rows, one per object.

Join through pg_class with relkind='S' so only sequence dependencies
are considered, and collapse the lookup into a scalar subquery (the
same pattern already used for typnspname/isdup elsewhere in this
template) with an ORDER BY ... LIMIT 1 tie-breaker, so it can never
fan out the outer query regardless of how many dependencies exist.
Existing single-dependency behavior (the common case) is unchanged.
@kundansable kundansable self-assigned this Jul 22, 2026
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.

Issue with CREATE script: Schema Diff generates wrong SQL for SERIAL columns after renaming, or when a sequence name accidentally matches

1 participant