Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions alembic/autogenerate/compare/server_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -48,14 +49,20 @@ 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.

"""

return re.sub(r"[ \(\)'\"`\[\]\t\r\n]", "", sqltext).lower()
normalized_sqltext = re.sub(r"[ \(\)'\"`\[\]\t\r\n]", "", sqltext).lower()
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(
Expand Down Expand Up @@ -94,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):
Expand All @@ -105,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 = ""
Expand Down
10 changes: 10 additions & 0 deletions docs/build/unreleased/1462.rst
Original file line number Diff line number Diff line change
@@ -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.
20 changes: 20 additions & 0 deletions tests/test_postgresql.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -72,6 +73,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):
Expand Down Expand Up @@ -395,6 +399,22 @@ 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
dialect = postgresql.dialect()
eq_(
_normalize_computed_default(
"setweight(to_tsvector('english', title), 'a')", dialect
),
_normalize_computed_default(
"setweight(to_tsvector('english'::regconfig, "
"title::text), 'a'::\"char\")",
dialect,
),
)

@combinations(
({}, None),
(dict(always=True), None),
Expand Down