]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Broaden is[not]_distinct_from support
authorGord Thompson <gord@gordthompson.com>
Thu, 2 Apr 2020 17:51:11 +0000 (11:51 -0600)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 6 Apr 2020 22:00:40 +0000 (18:00 -0400)
Added support for .is[not]_distinct_from to SQL Server, MySQL, and Oracle.

Fixes: #5137
Change-Id: I3b4d3b199821a55687f83c9a5b63a95d07a64cd5
(cherry picked from commit 207e0b2fc0b36acca398b163c698412deec7077e)

doc/build/changelog/unreleased_13/5137.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/mssql/base.py
lib/sqlalchemy/dialects/mysql/base.py
lib/sqlalchemy/dialects/oracle/base.py
lib/sqlalchemy/dialects/sqlite/base.py
lib/sqlalchemy/engine/default.py
lib/sqlalchemy/testing/requirements.py
lib/sqlalchemy/testing/suite/test_select.py

diff --git a/doc/build/changelog/unreleased_13/5137.rst b/doc/build/changelog/unreleased_13/5137.rst
new file mode 100644 (file)
index 0000000..c53ca01
--- /dev/null
@@ -0,0 +1,7 @@
+.. change::
+    :tags: mssql, mysql, oracle, usecase
+    :tickets: 5137
+
+    Added support for :meth:`.ColumnOperators.is_distinct_from` and
+    :meth:`.ColumnOperators.isnot_distinct_from` to SQL Server,
+    MySQL, and Oracle.
index b03300a6ff30c74cc701edd1eebb29a551bc22f9..6e2b2f94c7176ae863f61de3b4b5886fd8e82296 100644 (file)
@@ -1868,6 +1868,18 @@ class MSSQLCompiler(compiler.SQLCompiler):
     def visit_empty_set_expr(self, type_):
         return "SELECT 1 WHERE 1!=1"
 
+    def visit_is_distinct_from_binary(self, binary, operator, **kw):
+        return "NOT EXISTS (SELECT %s INTERSECT SELECT %s)" % (
+            self.process(binary.left),
+            self.process(binary.right),
+        )
+
+    def visit_isnot_distinct_from_binary(self, binary, operator, **kw):
+        return "EXISTS (SELECT %s INTERSECT SELECT %s)" % (
+            self.process(binary.left),
+            self.process(binary.right),
+        )
+
 
 class MSSQLStrictCompiler(MSSQLCompiler):
 
index 82a5a6b2209ad9d791cb7fa63903f52b38266345..0d4c92410c2169d07571f1f60f1fa757c334f0ea 100644 (file)
@@ -1551,6 +1551,18 @@ class MySQLCompiler(compiler.SQLCompiler):
             }
         )
 
+    def visit_is_distinct_from_binary(self, binary, operator, **kw):
+        return "NOT (%s <=> %s)" % (
+            self.process(binary.left),
+            self.process(binary.right),
+        )
+
+    def visit_isnot_distinct_from_binary(self, binary, operator, **kw):
+        return "%s <=> %s" % (
+            self.process(binary.left),
+            self.process(binary.right),
+        )
+
 
 class MySQLDDLCompiler(compiler.DDLCompiler):
     def get_column_specification(self, column, **kw):
index 312823b0040db1790316551e3826cedec74fcb60..d565fd7f0c9f5b3b36b0d1345cd9467fdce695b5 100644 (file)
@@ -1123,6 +1123,18 @@ class OracleCompiler(compiler.SQLCompiler):
 
         return tmp
 
+    def visit_is_distinct_from_binary(self, binary, operator, **kw):
+        return "DECODE(%s, %s, 0, 1) = 1" % (
+            self.process(binary.left),
+            self.process(binary.right),
+        )
+
+    def visit_isnot_distinct_from_binary(self, binary, operator, **kw):
+        return "DECODE(%s, %s, 0, 1) = 0" % (
+            self.process(binary.left),
+            self.process(binary.right),
+        )
+
 
 class OracleDDLCompiler(compiler.DDLCompiler):
     def define_constraint_cascades(self, constraint):
index dda2161e2f4d72f07bb9376f8e4af4701b52fc3c..3e8d2aaeb4df7a67e73f3607599db17f96dcf1d9 100644 (file)
@@ -1450,9 +1450,6 @@ class SQLiteDialect(default.DefaultDialect):
     colspecs = colspecs
     isolation_level = None
 
-    supports_cast = True
-    supports_default_values = True
-
     construct_arguments = [
         (sa_schema.Table, {"autoincrement": False}),
         (sa_schema.Index, {"where": None}),
index 62ceb5ccc8fa8122de8c9b9c91821bff8096d115..dfbebbc7fb2d9fd0ede10851679dbf635706c866 100644 (file)
@@ -129,6 +129,8 @@ class DefaultDialect(interfaces.Dialect):
     supports_empty_insert = True
     supports_multivalues_insert = False
 
+    supports_is_distinct_from = True
+
     supports_server_side_cursors = False
 
     server_version_info = None
index 7a1f532a66db4988e9ac7a6f25d36f3a0fb49c09..8720dbae6dadc7156561fb3c72b42e5dd9d0a18d 100644 (file)
@@ -1100,3 +1100,19 @@ class SuiteRequirements(Requirements):
         """If persistence information is returned by the reflection of
         computed columns"""
         return exclusions.closed()
+
+    @property
+    def supports_is_distinct_from(self):
+        """Supports some form of "x IS [NOT] DISTINCT FROM y" construct.
+        Different dialects will implement their own flavour, e.g.,
+        sqlite will emit "x IS NOT y" instead of "x IS DISTINCT FROM y".
+
+        .. seealso::
+
+            :meth:`.ColumnOperators.is_distinct_from`
+
+        """
+        return exclusions.skip_if(
+            lambda config: not config.db.dialect.supports_is_distinct_from,
+            "driver doesn't support an IS DISTINCT FROM construct",
+        )
index 1253ca81cd23493cef4fdb46446671a580458bc9..bd46b95e919662b8b50fd14ba8b12232ca17af72 100644 (file)
@@ -702,3 +702,52 @@ class ComputedColumnTest(fixtures.TablesTest):
                 .order_by(self.tables.square.c.id)
             ).fetchall()
             eq_(res, [(100, 40), (1764, 168)])
+
+
+class IsOrIsNotDistinctFromTest(fixtures.TablesTest):
+    __backend__ = True
+    __requires__ = ("supports_is_distinct_from",)
+
+    @testing.provide_metadata
+    @testing.combinations(
+        ("both_int_different", 0, 1, 1),
+        ("both_int_same", 1, 1, 0),
+        ("one_null_first", None, 1, 1),
+        ("one_null_second", 0, None, 1),
+        ("both_null", None, None, 0),
+        id_="iaaa",
+        argnames="col_a_value, col_b_value, expected_row_count_for_is",
+    )
+    def test_is_or_isnot_distinct_from(
+        self, col_a_value, col_b_value, expected_row_count_for_is, connection
+    ):
+        meta = self.metadata
+        tbl = Table(
+            "is_distinct_test",
+            meta,
+            Column("id", Integer, primary_key=True),
+            Column("col_a", Integer, nullable=True),
+            Column("col_b", Integer, nullable=True),
+        )
+        tbl.create(connection)
+        connection.execute(
+            tbl.insert(),
+            [{"id": 1, "col_a": col_a_value, "col_b": col_b_value}],
+        )
+
+        result = connection.execute(
+            tbl.select(tbl.c.col_a.is_distinct_from(tbl.c.col_b))
+        ).fetchall()
+        eq_(
+            len(result), expected_row_count_for_is,
+        )
+
+        expected_row_count_for_isnot = (
+            1 if expected_row_count_for_is == 0 else 0
+        )
+        result = connection.execute(
+            tbl.select(tbl.c.col_a.isnot_distinct_from(tbl.c.col_b))
+        ).fetchall()
+        eq_(
+            len(result), expected_row_count_for_isnot,
+        )