]> 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)
committerGord Thompson <gord@gordthompson.com>
Thu, 2 Apr 2020 17:51:11 +0000 (11:51 -0600)
Added support for .is[not]_distinct_from to SQL Server, MySQL, and Oracle.

Fixes: #5137
Change-Id: I3b4d3b199821a55687f83c9a5b63a95d07a64cd5

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 526e6e8ab2d027e5818fb4675e0025f7531f4590..69e6834d21a6fdc781a3e874f2a4c0d388708051 100644 (file)
@@ -1955,6 +1955,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 e193c1daa6731f314b956bbbf2ec6679c65b8a1a..26d751faaa8127a6f0f4146b20a4a83321c90fd2 100644 (file)
@@ -1572,6 +1572,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 c0ee66ad7477beed5696d2ddd357aecb9d9cbc8b..ae869b9214ec20311d9afec3b111c0371fafd62e 100644 (file)
@@ -1169,6 +1169,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 1a13f678ff48ec34429dc39ba53a4b95a11096d0..d3105f268b3238291127590c49501083e0612fb0 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 af61be034fc07e669400fd3a5270851c65fcc147..a896dfc73302759d8751ef30ffc60bcfec17f872 100644 (file)
@@ -124,6 +124,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 5ee0d67a2c56807795629f5c256393a6a7094584..644483b79dedbd7f3c1f05769ae536a48a6a1950 100644 (file)
@@ -1168,3 +1168,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 5a7fd28e12c7ae7e39c219f8b635f6181af85a89..f9363d70204de706da7ead5211012a9c892c0ff3 100644 (file)
@@ -1009,3 +1009,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,
+        )