]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Add opsclass to exclusion constraint
authorAlonM <alon.menczer@gmail.com>
Sun, 15 Nov 2020 13:13:25 +0000 (08:13 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 16 Nov 2020 19:07:26 +0000 (14:07 -0500)
Added new parameter :paramref:`_postgresql.ExcludeConstraint.ops` to the
:class:`_postgresql.ExcludeConstraint` object, to support operator class
specification with this constraint.  Pull request courtesy Alon Menczer.

Fixes: #5604
Closes: #5700
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/5700
Pull-request-sha: f8613e100792fc0bb3cf300ec6aebc78ecdf0361

Change-Id: Iaf664131ec84f8c2fb05edf799198b8d3bb83597
(cherry picked from commit 018aa9870110ef97316e9984c7ecd7f3357b501a)

doc/build/changelog/unreleased_13/5604.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/postgresql/base.py
lib/sqlalchemy/dialects/postgresql/ext.py
test/dialect/postgresql/test_compiler.py

diff --git a/doc/build/changelog/unreleased_13/5604.rst b/doc/build/changelog/unreleased_13/5604.rst
new file mode 100644 (file)
index 0000000..dd2b52b
--- /dev/null
@@ -0,0 +1,8 @@
+.. change::
+    :tags: usecase, postgresql
+    :tickets: 5604
+    :versions: 1.4.0b2
+
+    Added new parameter :paramref:`_postgresql.ExcludeConstraint.ops` to the
+    :class:`_postgresql.ExcludeConstraint` object, to support operator class
+    specification with this constraint.  Pull request courtesy Alon Menczer.
index 82c05ab840f295956268d63e3539600a4fd1d1c6..8b1a5bca16d61006aea52d5abd9a8eae98a4d065 100644 (file)
@@ -614,6 +614,8 @@ using the ``postgresql_where`` keyword argument::
 
   Index('my_index', my_table.c.id, postgresql_where=my_table.c.value > 10)
 
+.. _postgresql_operator_classes:
+
 Operator Classes
 ^^^^^^^^^^^^^^^^
 
@@ -630,11 +632,10 @@ The :class:`.Index` construct allows these to be specified via the
             'id': 'int4_ops'
         })
 
-Note that the keys in the ``postgresql_ops`` dictionary are the "key" name of
-the :class:`_schema.Column`, i.e. the name used to access it from the ``.c``
-collection of :class:`_schema.Table`,
-which can be configured to be different than
-the actual name of the column as expressed in the database.
+Note that the keys in the ``postgresql_ops`` dictionaries are the
+"key" name of the :class:`_schema.Column`, i.e. the name used to access it from
+the ``.c`` collection of :class:`_schema.Table`, which can be configured to be
+different than the actual name of the column as expressed in the database.
 
 If ``postgresql_ops`` is to be used against a complex SQL expression such
 as a function call, then to apply to the column it must be given a label
@@ -648,6 +649,14 @@ that is identified in the dictionary by name, e.g.::
             'id': 'int4_ops'
         })
 
+Operator classes are also supported by the
+:class:`_postgresql.ExcludeConstraint` construct using the
+:paramref:`_postgresql.ExcludeConstraint.ops` parameter. See that parameter for
+details.
+
+.. versionadded:: 1.3.20 added support for operator classes with
+   :class:`_postgresql.ExcludeConstraint`.
+
 
 Index Types
 ^^^^^^^^^^^
@@ -2170,9 +2179,13 @@ class PGDDLCompiler(compiler.DDLCompiler):
         elements = []
         for expr, name, op in constraint._render_exprs:
             kw["include_table"] = False
-            elements.append(
-                "%s WITH %s" % (self.sql_compiler.process(expr, **kw), op)
+            exclude_element = self.sql_compiler.process(expr, **kw) + (
+                (" " + constraint.ops[expr.key])
+                if hasattr(expr, "key") and expr.key in constraint.ops
+                else ""
             )
+
+            elements.append("%s WITH %s" % (exclude_element, op))
         text += "EXCLUDE USING %s (%s)" % (
             self.preparer.validate_sql_phrase(
                 constraint.using, IDX_USING
index 89482614a3a3d2f69406d6d52737b85172bcee15..ecbb542a21d80a8f66a7822ab8bad5c1cfc5bd9b 100644 (file)
@@ -105,7 +105,8 @@ class ExcludeConstraint(ColumnCollectionConstraint):
             const = ExcludeConstraint(
                 (Column('period'), '&&'),
                 (Column('group'), '='),
-                where=(Column('group') != 'some group')
+                where=(Column('group') != 'some group'),
+                ops={'group': 'my_operator_class'}
             )
 
         The constraint is normally embedded into the :class:`_schema.Table`
@@ -124,7 +125,8 @@ class ExcludeConstraint(ColumnCollectionConstraint):
                     (some_table.c.period, '&&'),
                     (some_table.c.group, '='),
                     where=some_table.c.group != 'some group',
-                    name='some_table_excl_const'
+                    name='some_table_excl_const',
+                    ops={'group': 'my_operator_class'}
                 )
             )
 
@@ -162,6 +164,19 @@ class ExcludeConstraint(ColumnCollectionConstraint):
           If set, emit WHERE <predicate> when issuing DDL
           for this constraint.
 
+        :param ops:
+          Optional dictionary.  Used to define operator classes for the
+          elements; works the same way as that of the
+          :ref:`postgresql_ops <postgresql_operator_classes>`
+          parameter specified to the :class:`_schema.Index` construct.
+
+          .. versionadded:: 1.3.21
+
+          .. seealso::
+
+            :ref:`postgresql_operator_classes` - general description of how
+            PostgreSQL operator classes are specified.
+
         """
         columns = []
         render_exprs = []
@@ -201,6 +216,8 @@ class ExcludeConstraint(ColumnCollectionConstraint):
                 where, allow_coercion_to_text=True
             )
 
+        self.ops = kw.get("ops", {})
+
     def copy(self, **kw):
         elements = [(col, self.operators[col]) for col in self.columns.keys()]
         c = self.__class__(
index 1a92f6b66f0e41b8cf008ba8d0e2f9fc3751b0d2..98b32e8312a364eb44a87b8cd941695825bc1fa4 100644 (file)
@@ -780,13 +780,14 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
             where="room > 100",
             deferrable=True,
             initially="immediate",
+            ops={"room": "my_opclass"},
         )
         tbl.append_constraint(cons)
         self.assert_compile(
             schema.AddConstraint(cons),
             "ALTER TABLE testtbl ADD CONSTRAINT my_name "
             "EXCLUDE USING gist "
-            "(room WITH =, during WITH "
+            "(room my_opclass WITH =, during WITH "
             "&&) WHERE "
             "(room > 100) DEFERRABLE INITIALLY immediate",
             dialect=postgresql.dialect(),
@@ -895,6 +896,24 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
             dialect=postgresql.dialect(),
         )
 
+    def test_exclude_constraint_ops_many(self):
+        m = MetaData()
+        tbl = Table(
+            "testtbl", m, Column("room", String), Column("during", TSRANGE)
+        )
+        cons = ExcludeConstraint(
+            ("room", "="),
+            ("during", "&&"),
+            ops={"room": "first_opsclass", "during": "second_opclass"},
+        )
+        tbl.append_constraint(cons)
+        self.assert_compile(
+            schema.AddConstraint(cons),
+            "ALTER TABLE testtbl ADD EXCLUDE USING gist "
+            "(room first_opsclass WITH =, during second_opclass WITH &&)",
+            dialect=postgresql.dialect(),
+        )
+
     def test_substring(self):
         self.assert_compile(
             func.substring("abc", 1, 2),