From 86b8dd7d8edfcaf7411b5bd2ab5ba89d298d0a16 Mon Sep 17 00:00:00 2001 From: Sebastian Cao Date: Sun, 5 Jul 2026 09:47:43 +0800 Subject: [PATCH 1/2] Strip PostgreSQL type cast specifiers when comparing computed defaults --- alembic/autogenerate/compare/server_defaults.py | 5 ++++- docs/build/unreleased/1462.rst | 10 ++++++++++ tests/test_postgresql.py | 17 +++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 docs/build/unreleased/1462.rst diff --git a/alembic/autogenerate/compare/server_defaults.py b/alembic/autogenerate/compare/server_defaults.py index 1e09e8e21..b33524066 100644 --- a/alembic/autogenerate/compare/server_defaults.py +++ b/alembic/autogenerate/compare/server_defaults.py @@ -55,7 +55,10 @@ def _normalize_computed_default(sqltext: str) -> str: """ - return re.sub(r"[ \(\)'\"`\[\]\t\r\n]", "", sqltext).lower() + normalized_sqltext = re.sub(r"[ \(\)'\"`\[\]\t\r\n]", "", sqltext).lower() + # strip postgresql type cast specifiers, e.g. ``::regconfig``, ``::text``, + # which can appear inconsistently and cause false-positive warnings. + return re.sub(r"::\w+", "", normalized_sqltext) def _compare_computed_default( diff --git a/docs/build/unreleased/1462.rst b/docs/build/unreleased/1462.rst new file mode 100644 index 000000000..fd919066e --- /dev/null +++ b/docs/build/unreleased/1462.rst @@ -0,0 +1,10 @@ +.. change:: + :tags: bug, autogenerate, postgresql + :tickets: 1462 + + Fixed false-positive "Computed default cannot be modified" warning emitted + during autogenerate when comparing PostgreSQL ``Computed`` columns whose + reflected SQL expression includes type cast specifiers such as + ``::regconfig`` or ``::text``. These cast specifiers are now stripped + before comparison so that semantically equivalent expressions no longer + trigger the warning. diff --git a/tests/test_postgresql.py b/tests/test_postgresql.py index d0304651d..77e614bf3 100644 --- a/tests/test_postgresql.py +++ b/tests/test_postgresql.py @@ -72,6 +72,9 @@ from alembic.autogenerate.compare.server_defaults import ( _dialect_impl_compare_server_default as _compare_server_default, ) + from alembic.autogenerate.compare.server_defaults import ( + _normalize_computed_default, + ) class PostgresqlOpTest(TestBase): @@ -395,6 +398,20 @@ def test_alter_column_computed_not_supported(self, sd, esd): existing_server_default=esd(), ) + def test_normalize_computed_default_strips_type_casts(self): + # type cast specifiers such as ``::regconfig`` only appear on the + # reflected (connection) side and would otherwise cause a + # false-positive "cannot be modified" warning. issue #1462 + eq_( + _normalize_computed_default( + "setweight(to_tsvector('english', title), 'a')" + ), + _normalize_computed_default( + "setweight(to_tsvector('english'::regconfig, " + "title::text), 'a'::\"char\")" + ), + ) + @combinations( ({}, None), (dict(always=True), None), From 92cad373b68195b6e418ba05c8c21319ab20cdfc Mon Sep 17 00:00:00 2001 From: Sebastian Cao Date: Sat, 11 Jul 2026 15:10:31 +0800 Subject: [PATCH 2/2] only strip postgresql type casts under the postgresql dialect --- alembic/autogenerate/compare/server_defaults.py | 16 ++++++++++------ tests/test_postgresql.py | 7 +++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/alembic/autogenerate/compare/server_defaults.py b/alembic/autogenerate/compare/server_defaults.py index b33524066..ed9136979 100644 --- a/alembic/autogenerate/compare/server_defaults.py +++ b/alembic/autogenerate/compare/server_defaults.py @@ -21,6 +21,7 @@ if TYPE_CHECKING: from sqlalchemy.sql.elements import quoted_name from sqlalchemy.sql.schema import Column + from sqlalchemy.engine import Dialect from ...autogenerate.api import AutogenContext from ...operations.ops import AlterColumnOp @@ -48,7 +49,7 @@ def _render_server_default_for_compare( return None -def _normalize_computed_default(sqltext: str) -> str: +def _normalize_computed_default(sqltext: str, dialect: Dialect) -> str: """we want to warn if a computed sql expression has changed. however we don't want false positives and the warning is not that critical. so filter out most forms of variability from the SQL text. @@ -56,9 +57,12 @@ def _normalize_computed_default(sqltext: str) -> str: """ normalized_sqltext = re.sub(r"[ \(\)'\"`\[\]\t\r\n]", "", sqltext).lower() - # strip postgresql type cast specifiers, e.g. ``::regconfig``, ``::text``, - # which can appear inconsistently and cause false-positive warnings. - return re.sub(r"::\w+", "", normalized_sqltext) + if dialect.name == "postgresql": + # strip postgresql type cast specifiers, e.g. ``::regconfig``, + # ``::text``, which can appear inconsistently on the reflected side + # only and cause false-positive warnings. + normalized_sqltext = re.sub(r"::\w+", "", normalized_sqltext) + return normalized_sqltext def _compare_computed_default( @@ -97,7 +101,7 @@ def _compare_computed_default( # get a minimal comparison just to emit a warning. rendered_metadata_default = _normalize_computed_default( - rendered_metadata_default + rendered_metadata_default, autogen_context.dialect ) if isinstance(conn_col.server_default, sa_schema.Computed): @@ -108,7 +112,7 @@ def _compare_computed_default( ) ) rendered_conn_default = _normalize_computed_default( - rendered_conn_default + rendered_conn_default, autogen_context.dialect ) else: rendered_conn_default = "" diff --git a/tests/test_postgresql.py b/tests/test_postgresql.py index 77e614bf3..76b84a541 100644 --- a/tests/test_postgresql.py +++ b/tests/test_postgresql.py @@ -21,6 +21,7 @@ from sqlalchemy import text from sqlalchemy import types from sqlalchemy import UniqueConstraint +from sqlalchemy.dialects import postgresql from sqlalchemy.dialects.postgresql import ARRAY from sqlalchemy.dialects.postgresql import BYTEA from sqlalchemy.dialects.postgresql import ExcludeConstraint @@ -402,13 +403,15 @@ def test_normalize_computed_default_strips_type_casts(self): # type cast specifiers such as ``::regconfig`` only appear on the # reflected (connection) side and would otherwise cause a # false-positive "cannot be modified" warning. issue #1462 + dialect = postgresql.dialect() eq_( _normalize_computed_default( - "setweight(to_tsvector('english', title), 'a')" + "setweight(to_tsvector('english', title), 'a')", dialect ), _normalize_computed_default( "setweight(to_tsvector('english'::regconfig, " - "title::text), 'a'::\"char\")" + "title::text), 'a'::\"char\")", + dialect, ), )