Skip to content

fix(schema-diff): reverse-engineer SERIAL columns by default#10020

Open
asheshv wants to merge 1 commit into
pgadmin-org:masterfrom
asheshv:fix/schemadiff-serial-roundtrip
Open

fix(schema-diff): reverse-engineer SERIAL columns by default#10020
asheshv wants to merge 1 commit into
pgadmin-org:masterfrom
asheshv:fix/schemadiff-serial-roundtrip

Conversation

@asheshv

@asheshv asheshv commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #9896.

Schema Diff's Generate Script emitted CREATE TABLE DDL with the libpq-expanded form of SERIAL columns:

col integer NOT NULL DEFAULT nextval('table_col_seq'::regclass)

instead of preserving SERIAL. Applied to a clean target, that script fails with relation "table_col_seq" does not exist because the sequence is never created — exactly what the reporter ran into.

Root cause

SERIAL detection already existed in columns/utils.py:get_formatted_columns (added in 115208c8d, Nov 2023, for ERD generation), but was gated behind a with_serial_cols parameter that defaulted to False. Only ERD and the Schema Diff comparison phase opted in via explicit True. The Generate Script path and the browser tree's CREATE Script view went through _get_resql_for_table_formatter(data) without specifying the flag, so SERIAL detection was skipped and #9896 reproduced.

There's no legitimate use case where pgAdmin should knowingly emit the expanded libpq form: SERIAL is purely a CREATE-time macro that PostgreSQL expands into an integer column plus an implicit sequence owned by the column. The detection logic is reversing that expansion, which is the correct interpretation in every context (DDL emission, comparison, ERD, properties dialog).

Fix

Flip the default to True at all four declaration sites:

  • columns/utils.py: get_formatted_columns
  • utils.py: BaseTableView._formatter
  • utils.py: BaseTableView.fetch_tables
  • __init__.py: TableView.fetch_tables

All four existing explicit-True callers (ERD × 2, Schema Diff compare × 2) are unaffected. Silent callers that previously got the buggy False now correctly get SERIAL detection:

Caller What it does Why it's safe
_get_resql_for_table (utils.py:682) Schema Diff Generate Script + browser tree CREATE Script The intended fix.
select_sql / insert_sql / update_sql (tables/init.py) Sample SELECT/INSERT/UPDATE templates They decide whether to include a column based on has_default_val, computed from att.atthasdef in the column SQL template — not from the defval string that SERIAL detection clears. SERIAL columns continue to be correctly excluded from INSERT/UPDATE samples.
Partition path (utils.py:1470) Partition reverse-engineered SQL Same template, same SERIAL detection behavior.

Regression test

Added a CREATE TABLE scenario covering serial / bigserial / smallserial columns plus a plain text column to pg/12_plus, pg/14_plus, and pg/16_plus test dirs, with the expected reverse-engineered DDL preserving the SERIAL keywords. Without this fix the test fails with the nextval('…_seq') form.

Known coverage gaps (deliberately not in this PR)

  • PG11 — source fix applies, regression not locked. PG11 still emits WITH (OIDS = FALSE) in CREATE TABLE; without a live PG11 instance I can't confidently produce the expected SQL variant. Test scenario therefore not added to pg/11_plus or pg/default.
  • PPAS (ppas/* test dirs) — source fix applies, regression not locked. PPAS DDL output can diverge from vanilla PG in subtle ways (oraplus, hyphenated identifiers). No PPAS instance available to verify.
  • The SERIAL detection heuristic is name-based (looks for <table>_<col>_seq in defval). Sequence renames, table renames, column renames, and pathological substring collisions all break detection. Pre-existing from 115208c8d; worth a follow-up that uses pg_depend to derive the linkage directly rather than reconstructing the conventional name.

Test plan

  • pycodestyle --config=.pycodestyle clean on the four modified Python files.
  • python regression/runtests.py --pkg resqlCreate Table with serial columns (MSQL)/(SQL)/Delete all pass on PG18; full resql suite 0 failures.
  • python regression/runtests.py --pkg tools.schema_diff — 2/2 pass.
  • python regression/runtests.py --pkg browser.server_groups.servers.databases.schemas.tables — 464/464 pass.
  • Manual: run the issue's reproducer (SERIAL column on source, empty target) through Schema Diff → Generate Script → confirm the emitted DDL says SERIAL and applies cleanly to the target.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Enhanced schema reverse-engineering to properly detect and preserve SERIAL, BIGSERIAL, and SMALLSERIAL column definitions, generating DDL that can be re-executed without modification.
    • Improved schema diff functionality to correctly handle serial column detection by default across PostgreSQL versions.
  • Tests

    • Added comprehensive test coverage for serial column handling.

Schema Diff's "Generate Script" emitted CREATE TABLE DDL with
the libpq-expanded form of SERIAL columns:

    col integer NOT NULL DEFAULT nextval('table_col_seq'::regclass)

instead of preserving the SERIAL declaration. Applied to a clean
target, that script fails with `relation "table_col_seq" does
not exist` because the sequence is never created — exactly the
symptom in issue pgadmin-org#9896.

SERIAL detection already existed in
columns/utils.py:get_formatted_columns (added in 115208c for
ERD generation, Nov 2023), but was gated behind a
``with_serial_cols`` parameter that defaulted to False. Only ERD
and the Schema Diff *comparison* phase opted in via explicit
True. The Schema Diff *Generate Script* path and the browser
tree's *CREATE Script* view went through `_get_resql_for_table`
→ `_formatter(data)` without specifying it, so SERIAL detection
was skipped and pgadmin-org#9896 reproduced.

There is no use case where pgAdmin should knowingly emit the
expanded libpq form: SERIAL is purely a CREATE-time macro that
PostgreSQL expands into an integer column plus an implicit
sequence owned by the column. The detection logic is reversing
that expansion, which is the correct interpretation in all
contexts (DDL emission, comparison, ERD, properties dialog).

Flip the default to True at all four declaration sites:

- columns/utils.py: get_formatted_columns
- utils.py: BaseTableView._formatter
- utils.py: BaseTableView.fetch_tables
- __init__.py: TableView.fetch_tables

All four existing explicit-True callers (ERD x2, Schema Diff
compare x2) are unaffected. Silent callers that previously got
the buggy False — `_get_resql_for_table` (Schema Diff Generate
Script and browser CREATE Script), `select_sql` / `insert_sql` /
`update_sql` in the table node, and one path in the partition
flow — now correctly get SERIAL detection. The INSERT/UPDATE
generators stay safe because they exclude default-bearing columns
via the `has_default_val` flag (computed from `att.atthasdef` in
the column SQL template), not from the `defval` string that
SERIAL detection clears.

Adds a resql regression in pg/{12_plus, 14_plus, 16_plus}: a
CREATE TABLE scenario covering ``serial`` / ``bigserial`` /
``smallserial`` columns plus a plain ``text`` column, with the
expected reverse-engineered DDL preserving the SERIAL keywords.
Would have failed before this fix.

Known coverage gaps (not blockers for this PR):

- PG11 (uses ``pg/11_plus`` / ``pg/default``): test intentionally
  not added there because PG11 still emits ``WITH (OIDS = FALSE)``
  which the existing expected-output convention reflects, and
  without a live PG11 instance I can't verify the expected SQL
  variant. The source-side fix still applies to PG11.
- PPAS (``ppas/*`` test dirs): not added because PPAS output may
  diverge subtly (oraplus / hyphenated identifiers) and I don't
  have a PPAS instance to verify against. The source-side fix
  applies equally to PPAS.
- The SERIAL detection heuristic is name-based
  (``<table>_<col>_seq``); sequence/table/column renames break
  detection. Pre-existing in 115208c; could be replaced with
  proper ``pg_depend``-based detection in a follow-up.

Fixes pgadmin-org#9896
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR enables SERIAL column reverse-engineering by default in pgAdmin's table introspection APIs to fix Schema Diff DDL generation. Four public methods now default with_serial_cols=True, allowing them to reconstruct SERIAL columns instead of expanding them to plain integer DEFAULT nextval(...). Comprehensive test fixtures and scenarios validate the round-trip behavior across PostgreSQL 12, 14, and 16 version tiers.

Changes

SERIAL Column Round-Trip Fix for Schema Diff

Layer / File(s) Summary
Core API Defaults for SERIAL Column Detection
web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py, web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py, web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py
BaseTableView._formatter(), BaseTableView.fetch_tables(), TableView.fetch_tables(), and get_formatted_columns() now default with_serial_cols/with_serial to True, enabling SERIAL/BIGSERIAL/SMALLSERIAL detection during table metadata introspection. Docstrings updated to document round-trip DDL intent and reference issue #9896.
Test Coverage - PostgreSQL 12 Plus
web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/pg/12_plus/*
SQL and MSQL fixture files define public.table_with_serial_cols with three serial-type columns. JSON test scenario covers table creation and deletion with expected DDL validation.
Test Coverage - PostgreSQL 14 Plus
web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/pg/14_plus/*
SQL and MSQL fixture files define public.table_with_serial_cols for version 14+. JSON test scenario validates create and delete operations for this version tier.
Test Coverage - PostgreSQL 16 Plus
web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/pg/16_plus/*
SQL and MSQL fixture files define public.table_with_serial_cols for version 16+. JSON test scenario validates round-trip behavior for the latest PostgreSQL version tier.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% 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 accurately describes the main change: changing default behavior to reverse-engineer SERIAL columns in schema-diff operations.
Linked Issues check ✅ Passed The PR fully addresses issue #9896 by changing four default parameters from False to True to enable SERIAL column detection and preservation in DDL generation.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing issue #9896: four parameter defaults modified, function/method docstrings updated, and comprehensive test fixtures added across PostgreSQL versions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

@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.

🧹 Nitpick comments (1)
web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/pg/12_plus/create_table_with_serial_cols.sql (1)

10-10: 💤 Low value

Minor observation: COLLATE clause on text column.

The payload column includes COLLATE pg_catalog."default". For a round-trip SERIAL test, this clause is likely unnecessary unless the test also validates collation preservation. Consider simplifying to match the MSQL fixture (line 6 in create_table_with_serial_cols_msql.sql) if collation is not part of the test objective.

🤖 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/tests/pg/12_plus/create_table_with_serial_cols.sql`
at line 10, The payload column in create_table_with_serial_cols.sql currently
specifies COLLATE pg_catalog."default"; remove the COLLATE clause from the
payload text column so the DDL matches the MSQL fixture
(create_table_with_serial_cols_msql.sql) and keeps the test focused on SERIAL
round-trip behavior; update the column definition for payload in the CREATE
TABLE statement to simply be "payload text" (no COLLATE) so collation is not
part of this test.
🤖 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.

Nitpick comments:
In
`@web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/pg/12_plus/create_table_with_serial_cols.sql`:
- Line 10: The payload column in create_table_with_serial_cols.sql currently
specifies COLLATE pg_catalog."default"; remove the COLLATE clause from the
payload text column so the DDL matches the MSQL fixture
(create_table_with_serial_cols_msql.sql) and keeps the test focused on SERIAL
round-trip behavior; update the column definition for payload in the CREATE
TABLE statement to simply be "payload text" (no COLLATE) so collation is not
part of this test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f0396229-3241-4fcd-ba66-2afa2c141989

📥 Commits

Reviewing files that changed from the base of the PR and between 92dd7c7 and fb8d51c.

📒 Files selected for processing (12)
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/pg/12_plus/create_table_with_serial_cols.sql
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/pg/12_plus/create_table_with_serial_cols_msql.sql
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/pg/12_plus/test.json
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/pg/14_plus/create_table_with_serial_cols.sql
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/pg/14_plus/create_table_with_serial_cols_msql.sql
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/pg/14_plus/test.json
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/pg/16_plus/create_table_with_serial_cols.sql
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/pg/16_plus/create_table_with_serial_cols_msql.sql
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/pg/16_plus/test.json
  • web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py

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.

Schema Diff: invalid DDL reconstruction for SERIAL columns (broken DEFAULT nextval syntax)

1 participant