]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
improve autogen rendering for PG ExcludeConstraint
authorJan Katins <jasc@gmx.net>
Mon, 6 Mar 2023 21:18:17 +0000 (16:18 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 7 Mar 2023 00:25:59 +0000 (19:25 -0500)
Fixed issue regarding PostgreSQL :class:`.ExcludeConstraint`, where
constraint elements which made use of :func:`.literal_column` could not be
rendered for autogenerate. Additionally, using SQLAlchemy 2.0.5 or greater,
:func:`.text()` constructs are also supported within PostgreSQL
:class:`.ExcludeConstraint` objects for autogenerate render. Pull request
courtesy Jan Katins.

Fixes: #1184
Closes: #1185
Pull-request: https://github.com/sqlalchemy/alembic/pull/1185
Pull-request-sha: 68360ce9aa746a85407c5e1e04b8021123d98504

Change-Id: I302f3f4007a186ffac13b344ff6769dd302f28f5

alembic/ddl/postgresql.py
docs/build/unreleased/1184.rst [new file with mode: 0644]
tests/test_postgresql.py

index 994d7cbf22caa54f3f8cbda0a160afc14df04e3c..4ffc2eb99bd1e437104a60c398757cf423aab205 100644 (file)
@@ -22,6 +22,7 @@ from sqlalchemy.dialects.postgresql import ExcludeConstraint
 from sqlalchemy.dialects.postgresql import INTEGER
 from sqlalchemy.schema import CreateIndex
 from sqlalchemy.sql.elements import ColumnClause
+from sqlalchemy.sql.elements import TextClause
 from sqlalchemy.types import NULLTYPE
 
 from .base import alter_column
@@ -650,7 +651,7 @@ def _exclude_constraint(
         args = [
             "(%s, %r)"
             % (_render_potential_column(sqltext, autogen_context), opstring)
-            for sqltext, name, opstring in constraint._render_exprs  # type:ignore[attr-defined] # noqa
+            for sqltext, name, opstring in constraint._render_exprs
         ]
         if constraint.where is not None:
             args.append(
@@ -667,17 +668,21 @@ def _exclude_constraint(
 
 
 def _render_potential_column(
-    value: Union[ColumnClause, Column], autogen_context: AutogenContext
+    value: Union[ColumnClause, Column, TextClause],
+    autogen_context: AutogenContext,
 ) -> str:
     if isinstance(value, ColumnClause):
-        template = "%(prefix)scolumn(%(name)r)"
+        if value.is_literal:
+            # like literal_column("int8range(from, to)") in ExcludeConstraint
+            template = "%(prefix)sliteral_column(%(name)r)"
+        else:
+            template = "%(prefix)scolumn(%(name)r)"
 
         return template % {
             "prefix": render._sqlalchemy_autogenerate_prefix(autogen_context),
             "name": value.name,
         }
-
     else:
         return render._render_potential_expr(
-            value, autogen_context, wrap_in_text=False
+            value, autogen_context, wrap_in_text=isinstance(value, TextClause)
         )
diff --git a/docs/build/unreleased/1184.rst b/docs/build/unreleased/1184.rst
new file mode 100644 (file)
index 0000000..3d26f3e
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: bug, postgresql
+    :tickets: 1184
+
+    Fixed issue regarding PostgreSQL :class:`.ExcludeConstraint`, where
+    constraint elements which made use of :func:`.literal_column` could not be
+    rendered for autogenerate. Additionally, using SQLAlchemy 2.0.5 or greater,
+    :func:`.text()` constructs are also supported within PostgreSQL
+    :class:`.ExcludeConstraint` objects for autogenerate render. Pull request
+    courtesy Jan Katins.
\ No newline at end of file
index a8c284de4d12e60f0b35ea685b5f470b7d8e11fd..18f50ce1eceb1b53c66e071acea70b7e07b86a95 100644 (file)
@@ -28,6 +28,7 @@ from sqlalchemy.dialects.postgresql import UUID
 from sqlalchemy.sql import column
 from sqlalchemy.sql import false
 from sqlalchemy.sql import table
+from sqlalchemy.sql.expression import literal_column
 
 from alembic import autogenerate
 from alembic import command
@@ -1156,6 +1157,64 @@ class PostgresqlAutogenRenderTest(TestBase):
             "name='TExclX'))",
         )
 
+    def test_inline_exclude_constraint_literal_column(self):
+        """test for #1184"""
+
+        autogen_context = self.autogen_context
+
+        m = MetaData()
+        t = Table(
+            "TTable",
+            m,
+            Column("id", String()),
+            ExcludeConstraint(
+                (literal_column("id + 2"), "="), name="TExclID", using="gist"
+            ),
+        )
+
+        op_obj = ops.CreateTableOp.from_table(t)
+
+        eq_ignore_whitespace(
+            autogenerate.render_op_text(autogen_context, op_obj),
+            "op.create_table('TTable',sa.Column('id', sa.String(), "
+            "nullable=True),"
+            "postgresql.ExcludeConstraint((sa.literal_column('id + 2'), '='), "
+            "using='gist', "
+            "name='TExclID'))",
+        )
+
+    @config.requirements.sqlalchemy_2
+    def test_inline_exclude_constraint_text(self):
+        """test for #1184.
+
+        Requires SQLAlchemy 2.0.5 due to issue
+        https://github.com/sqlalchemy/sqlalchemy/issues/9401
+
+        """
+
+        autogen_context = self.autogen_context
+
+        m = MetaData()
+        t = Table(
+            "TTable",
+            m,
+            Column("id", String()),
+            ExcludeConstraint(
+                (text("id + 2"), "="), name="TExclID", using="gist"
+            ),
+        )
+
+        op_obj = ops.CreateTableOp.from_table(t)
+
+        eq_ignore_whitespace(
+            autogenerate.render_op_text(autogen_context, op_obj),
+            "op.create_table('TTable',sa.Column('id', sa.String(), "
+            "nullable=True),"
+            "postgresql.ExcludeConstraint((sa.text('id + 2'), '='), "
+            "using='gist', "
+            "name='TExclID'))",
+        )
+
     def test_json_type(self):
         eq_ignore_whitespace(
             autogenerate.render._repr_type(JSON(), self.autogen_context),