diff options
-rw-r--r-- | CHANGES | 10 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/expression.py | 10 | ||||
-rw-r--r-- | test/sql/test_query.py | 3 | ||||
-rw-r--r-- | test/sql/test_select.py | 1 |
4 files changed, 22 insertions, 2 deletions
@@ -260,7 +260,15 @@ CHANGES named "tablename_id" - this is because the labeling logic is always applied to all columns so a naming conflict will never occur. - + + - calling expr.in_([]), i.e. with an empty list, emits a warning + before issuing the usual "expr != expr" clause. The + "expr != expr" can be very expensive, and it's preferred + that the user not issue in_() if the list is empty, + instead simply not querying, or modifying the criterion + as appropriate for more complex situations. + [ticket:1628] + - Deprecated or removed: * "scalar" flag on select() is removed, use select.as_scalar(). diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 2f0ac90af..acfb3d7c4 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -1498,7 +1498,15 @@ class _CompareMixin(ColumnOperators): args.append(o) if len(args) == 0: - # Special case handling for empty IN's, behave like comparison against zero row selectable + # Special case handling for empty IN's, behave like comparison + # against zero row selectable. We use != to build the + # contradiction as it handles NULL values appropriately, i.e. + # "not (x IN ())" should not return NULL values for x. + util.warn("The IN-predicate on \"%s\" was invoked with an empty sequence. " + "This results in a contradiction, which nonetheless can be " + "expensive to evaluate. Consider alternative strategies for " + "improved performance." % self) + return self != self return self.__compare(op, ClauseList(*args).self_group(against=op), negate=negate_op) diff --git a/test/sql/test_query.py b/test/sql/test_query.py index 0e1787e9e..c711819f9 100644 --- a/test/sql/test_query.py +++ b/test/sql/test_query.py @@ -721,6 +721,7 @@ class QueryTest(TestBase): finally: shadowed.drop(checkfirst=True) + @testing.emits_warning('.*empty sequence.*') def test_in_filtering(self): """test the behavior of the in_() function.""" @@ -747,6 +748,7 @@ class QueryTest(TestBase): # Null values are not outside any set assert len(r) == 0 + @testing.emits_warning('.*empty sequence.*') @testing.fails_on('firebird', "kinterbasdb doesn't send full type information") def test_bind_in(self): users.insert().execute(user_id = 7, user_name = 'jack') @@ -761,6 +763,7 @@ class QueryTest(TestBase): r = s.execute(search_key=None).fetchall() assert len(r) == 0 + @testing.emits_warning('.*empty sequence.*') @testing.fails_on('firebird', 'FIXME: unknown') @testing.fails_on('maxdb', 'FIXME: unknown') @testing.fails_on('oracle', 'FIXME: unknown') diff --git a/test/sql/test_select.py b/test/sql/test_select.py index 42998ee02..d063bd2d9 100644 --- a/test/sql/test_select.py +++ b/test/sql/test_select.py @@ -1265,6 +1265,7 @@ UNION SELECT mytable.myid FROM mytable" assert [str(c) for c in s.c] == ["id", "hoho"] + @testing.emits_warning('.*empty sequence.*') def test_in(self): self.assert_compile(select([table1], table1.c.myid.in_(['a'])), "SELECT mytable.myid, mytable.name, mytable.description FROM mytable WHERE mytable.myid IN (:myid_1)") |