summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorGord Thompson <gord@gordthompson.com>2020-04-02 11:51:11 -0600
committerGord Thompson <gord@gordthompson.com>2020-04-02 11:51:11 -0600
commit207e0b2fc0b36acca398b163c698412deec7077e (patch)
tree1bd3b739655cefd2d67b8a01bbaf961c1a708d57 /lib/sqlalchemy
parenta9b62055bfa61c11e9fe0b2984437e2c3e32bf0e (diff)
downloadsqlalchemy-207e0b2fc0b36acca398b163c698412deec7077e.tar.gz
Broaden is[not]_distinct_from support
Added support for .is[not]_distinct_from to SQL Server, MySQL, and Oracle. Fixes: #5137 Change-Id: I3b4d3b199821a55687f83c9a5b63a95d07a64cd5
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/dialects/mssql/base.py12
-rw-r--r--lib/sqlalchemy/dialects/mysql/base.py12
-rw-r--r--lib/sqlalchemy/dialects/oracle/base.py12
-rw-r--r--lib/sqlalchemy/dialects/sqlite/base.py3
-rw-r--r--lib/sqlalchemy/engine/default.py2
-rw-r--r--lib/sqlalchemy/testing/requirements.py16
-rw-r--r--lib/sqlalchemy/testing/suite/test_select.py49
7 files changed, 103 insertions, 3 deletions
diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py
index 526e6e8ab..69e6834d2 100644
--- a/lib/sqlalchemy/dialects/mssql/base.py
+++ b/lib/sqlalchemy/dialects/mssql/base.py
@@ -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):
diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py
index e193c1daa..26d751faa 100644
--- a/lib/sqlalchemy/dialects/mysql/base.py
+++ b/lib/sqlalchemy/dialects/mysql/base.py
@@ -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):
diff --git a/lib/sqlalchemy/dialects/oracle/base.py b/lib/sqlalchemy/dialects/oracle/base.py
index c0ee66ad7..ae869b921 100644
--- a/lib/sqlalchemy/dialects/oracle/base.py
+++ b/lib/sqlalchemy/dialects/oracle/base.py
@@ -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):
diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py
index 1a13f678f..d3105f268 100644
--- a/lib/sqlalchemy/dialects/sqlite/base.py
+++ b/lib/sqlalchemy/dialects/sqlite/base.py
@@ -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}),
diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py
index af61be034..a896dfc73 100644
--- a/lib/sqlalchemy/engine/default.py
+++ b/lib/sqlalchemy/engine/default.py
@@ -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
diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py
index 5ee0d67a2..644483b79 100644
--- a/lib/sqlalchemy/testing/requirements.py
+++ b/lib/sqlalchemy/testing/requirements.py
@@ -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",
+ )
diff --git a/lib/sqlalchemy/testing/suite/test_select.py b/lib/sqlalchemy/testing/suite/test_select.py
index 5a7fd28e1..f9363d702 100644
--- a/lib/sqlalchemy/testing/suite/test_select.py
+++ b/lib/sqlalchemy/testing/suite/test_select.py
@@ -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,
+ )