]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
Fix ExcludeConstraint with func.
authorFederico Caselli <cfederico87@gmail.com>
Fri, 28 Apr 2023 19:24:43 +0000 (21:24 +0200)
committerFederico Caselli <cfederico87@gmail.com>
Fri, 28 Apr 2023 19:24:43 +0000 (21:24 +0200)
Fix autogenerate issue with PostgreSQL :class:`.ExcludeConstraint`
that included sqlalchemy functions. The function text was previously
rendered as a plain string without surrounding with ``text()``.

Fixes: #1230
Change-Id: I0d59e04d5a64132ed86f92a27e4247ac9703f3e0

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

index 5fb981dc814e8dafe6f19585eba4101bf19116b8..cc0488b8d8a7e91f404c08b7cffd7d1c77977b8c 100644 (file)
@@ -25,6 +25,7 @@ from sqlalchemy.sql import operators
 from sqlalchemy.sql.elements import ColumnClause
 from sqlalchemy.sql.elements import TextClause
 from sqlalchemy.sql.elements import UnaryExpression
+from sqlalchemy.sql.functions import FunctionElement
 from sqlalchemy.types import NULLTYPE
 
 from .base import alter_column
@@ -662,22 +663,15 @@ def _exclude_constraint(
             ("name", render._render_gen_name(autogen_context, constraint.name))
         )
 
-    if alter:
+    def do_expr_where_opts():
         args = [
-            repr(render._render_gen_name(autogen_context, constraint.name))
+            "(%s, %r)"
+            % (
+                _render_potential_column(sqltext, autogen_context),
+                opstring,
+            )
+            for sqltext, name, opstring in constraint._render_exprs  # type:ignore[attr-defined] # noqa
         ]
-        if not has_batch:
-            args += [repr(render._ident(constraint.table.name))]
-        args.extend(
-            [
-                "(%s, %r)"
-                % (
-                    _render_potential_column(sqltext, autogen_context),
-                    opstring,
-                )
-                for sqltext, name, opstring in constraint._render_exprs  # type:ignore[attr-defined] # noqa
-            ]
-        )
         if constraint.where is not None:
             args.append(
                 "where=%s"
@@ -686,24 +680,21 @@ def _exclude_constraint(
                 )
             )
         args.extend(["%s=%r" % (k, v) for k, v in opts])
+        return args
+
+    if alter:
+        args = [
+            repr(render._render_gen_name(autogen_context, constraint.name))
+        ]
+        if not has_batch:
+            args += [repr(render._ident(constraint.table.name))]
+        args.extend(do_expr_where_opts())
         return "%(prefix)screate_exclude_constraint(%(args)s)" % {
             "prefix": render._alembic_autogenerate_prefix(autogen_context),
             "args": ", ".join(args),
         }
     else:
-        args = [
-            "(%s, %r)"
-            % (_render_potential_column(sqltext, autogen_context), opstring)
-            for sqltext, name, opstring in constraint._render_exprs
-        ]
-        if constraint.where is not None:
-            args.append(
-                "where=%s"
-                % render._render_potential_expr(
-                    constraint.where, autogen_context
-                )
-            )
-        args.extend(["%s=%r" % (k, v) for k, v in opts])
+        args = do_expr_where_opts()
         return "%(prefix)sExcludeConstraint(%(args)s)" % {
             "prefix": _postgresql_autogenerate_prefix(autogen_context),
             "args": ", ".join(args),
@@ -711,7 +702,7 @@ def _exclude_constraint(
 
 
 def _render_potential_column(
-    value: Union[ColumnClause, Column, TextClause],
+    value: Union[ColumnClause, Column, TextClause, FunctionElement],
     autogen_context: AutogenContext,
 ) -> str:
     if isinstance(value, ColumnClause):
@@ -727,5 +718,7 @@ def _render_potential_column(
         }
     else:
         return render._render_potential_expr(
-            value, autogen_context, wrap_in_text=isinstance(value, TextClause)
+            value,
+            autogen_context,
+            wrap_in_text=isinstance(value, (TextClause, FunctionElement)),
         )
diff --git a/docs/build/unreleased/1230.rst b/docs/build/unreleased/1230.rst
new file mode 100644 (file)
index 0000000..7d902d5
--- /dev/null
@@ -0,0 +1,7 @@
+.. change::
+    :tags: bug, postgresql
+    :tickets: 1230
+
+    Fix autogenerate issue with PostgreSQL :class:`.ExcludeConstraint`
+    that included sqlalchemy functions. The function text was previously
+    rendered as a plain string without surrounding with ``text()``.
index 18f50ce1eceb1b53c66e071acea70b7e07b86a95..77ed4dabfcefdb46e4eb065872645f7eff5364e2 100644 (file)
@@ -1053,8 +1053,6 @@ class PostgresqlAutogenRenderTest(TestBase):
         )
 
     def test_add_exclude_constraint(self):
-        from sqlalchemy.dialects.postgresql import ExcludeConstraint
-
         autogen_context = self.autogen_context
 
         m = MetaData()
@@ -1074,8 +1072,6 @@ class PostgresqlAutogenRenderTest(TestBase):
         )
 
     def test_add_exclude_constraint_case_sensitive(self):
-        from sqlalchemy.dialects.postgresql import ExcludeConstraint
-
         autogen_context = self.autogen_context
 
         m = MetaData()
@@ -1100,8 +1096,6 @@ class PostgresqlAutogenRenderTest(TestBase):
         )
 
     def test_inline_exclude_constraint(self):
-        from sqlalchemy.dialects.postgresql import ExcludeConstraint
-
         autogen_context = self.autogen_context
 
         m = MetaData()
@@ -1130,8 +1124,6 @@ class PostgresqlAutogenRenderTest(TestBase):
         )
 
     def test_inline_exclude_constraint_case_sensitive(self):
-        from sqlalchemy.dialects.postgresql import ExcludeConstraint
-
         autogen_context = self.autogen_context
 
         m = MetaData()
@@ -1183,6 +1175,39 @@ class PostgresqlAutogenRenderTest(TestBase):
             "name='TExclID'))",
         )
 
+    @config.requirements.sqlalchemy_2
+    def test_inline_exclude_constraint_fn(self):
+        """test for #1230"""
+
+        autogen_context = self.autogen_context
+
+        effective_time = Column("effective_time", DateTime(timezone=True))
+        expiry_time = Column("expiry_time", DateTime(timezone=True))
+
+        m = MetaData()
+        t = Table(
+            "TTable",
+            m,
+            effective_time,
+            expiry_time,
+            ExcludeConstraint(
+                (func.tstzrange(effective_time, expiry_time), "&&"),
+                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('effective_time', "
+            "sa.DateTime(timezone=True), nullable=True),"
+            "sa.Column('expiry_time', sa.DateTime(timezone=True), "
+            "nullable=True),postgresql.ExcludeConstraint("
+            "(sa.text('tstzrange(effective_time, expiry_time)'), "
+            "'&&'), using='gist'))",
+        )
+
     @config.requirements.sqlalchemy_2
     def test_inline_exclude_constraint_text(self):
         """test for #1184.