summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2016-02-02 10:15:40 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2016-02-02 10:20:54 -0500
commite0a580b3d055a600afae61840058a5a30ef5fe74 (patch)
tree592356800f2b452028bae1f91ffcffad8157aa87
parentc8b3d4ed3f2638599fc73486cf0f724fa033a638 (diff)
downloadsqlalchemy-e0a580b3d055a600afae61840058a5a30ef5fe74.tar.gz
- Fixed issue where inadvertent use of the Python ``__contains__``
override with a column expression (e.g. by using ``'x' in col``) would cause an endless loop in the case of an ARRAY type, as Python defers this to ``__getitem__`` access which never raises for this type. Overall, all use of ``__contains__`` now raises NotImplementedError. fixes #3642
-rw-r--r--doc/build/changelog/changelog_10.rst11
-rw-r--r--lib/sqlalchemy/sql/default_comparator.py1
-rw-r--r--lib/sqlalchemy/sql/operators.py5
-rw-r--r--test/dialect/postgresql/test_types.py9
-rw-r--r--test/sql/test_operators.py24
5 files changed, 48 insertions, 2 deletions
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst
index 781911d65..320cb7f2a 100644
--- a/doc/build/changelog/changelog_10.rst
+++ b/doc/build/changelog/changelog_10.rst
@@ -20,6 +20,17 @@
:released:
.. change::
+ :tags: bug, sql
+ :tickets: 3642
+
+ Fixed issue where inadvertent use of the Python ``__contains__``
+ override with a column expression (e.g. by using ``'x' in col``)
+ would cause an endless loop in the case of an ARRAY type, as Python
+ defers this to ``__getitem__`` access which never raises for this
+ type. Overall, all use of ``__contains__`` now raises
+ NotImplementedError.
+
+ .. change::
:tags: bug, engine, mysql
:tickets: 2696
diff --git a/lib/sqlalchemy/sql/default_comparator.py b/lib/sqlalchemy/sql/default_comparator.py
index 6c8e69dee..1bb1c344c 100644
--- a/lib/sqlalchemy/sql/default_comparator.py
+++ b/lib/sqlalchemy/sql/default_comparator.py
@@ -274,6 +274,7 @@ operator_lookup = {
"getitem": (_getitem_impl,),
"lshift": (_unsupported_impl,),
"rshift": (_unsupported_impl,),
+ "contains": (_unsupported_impl,),
}
diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py
index ba1d0f65e..80f08a97c 100644
--- a/lib/sqlalchemy/sql/operators.py
+++ b/lib/sqlalchemy/sql/operators.py
@@ -14,7 +14,7 @@ from .. import util
from operator import (
and_, or_, inv, add, mul, sub, mod, truediv, lt, le, ne, gt, ge, eq, neg,
- getitem, lshift, rshift
+ getitem, lshift, rshift, contains
)
if util.py2k:
@@ -335,6 +335,9 @@ class ColumnOperators(Operators):
"""
return self.operate(neg)
+ def __contains__(self, other):
+ return self.operate(contains, other)
+
def __getitem__(self, index):
"""Implement the [] operator.
diff --git a/test/dialect/postgresql/test_types.py b/test/dialect/postgresql/test_types.py
index 50b66f290..c53c67cef 100644
--- a/test/dialect/postgresql/test_types.py
+++ b/test/dialect/postgresql/test_types.py
@@ -772,6 +772,15 @@ class ArrayTest(AssertsCompiledSQL, fixtures.TestBase):
checkparams={'param_1': 4, 'param_3': 6, 'param_2': 5}
)
+ def test_contains_override_raises(self):
+ col = column('x', postgresql.ARRAY(Integer))
+
+ assert_raises_message(
+ NotImplementedError,
+ "Operator 'contains' is not supported on this expression",
+ lambda: 'foo' in col
+ )
+
def test_array_contained_by(self):
col = column('x', postgresql.ARRAY(Integer))
self.assert_compile(
diff --git a/test/sql/test_operators.py b/test/sql/test_operators.py
index 6a6c749a4..86286a9a3 100644
--- a/test/sql/test_operators.py
+++ b/test/sql/test_operators.py
@@ -15,7 +15,8 @@ from sqlalchemy.sql.elements import _literal_as_text
from sqlalchemy.schema import Column, Table, MetaData
from sqlalchemy.sql import compiler
from sqlalchemy.types import TypeEngine, TypeDecorator, UserDefinedType, \
- Boolean, NullType, MatchType, Indexable, Concatenable, ARRAY, JSON
+ Boolean, NullType, MatchType, Indexable, Concatenable, ARRAY, JSON, \
+ DateTime
from sqlalchemy.dialects import mysql, firebird, postgresql, oracle, \
sqlite, mssql
from sqlalchemy import util
@@ -265,6 +266,18 @@ class DefaultColumnComparatorTest(fixtures.TestBase):
expr.operator, operator.add
)
+ def test_contains_override_raises(self):
+ for col in [
+ Column('x', String),
+ Column('x', Integer),
+ Column('x', DateTime)
+ ]:
+ assert_raises_message(
+ NotImplementedError,
+ "Operator 'contains' is not supported on this expression",
+ lambda: 'foo' in col
+ )
+
class CustomUnaryOperatorTest(fixtures.TestBase, testing.AssertsCompiledSQL):
__dialect__ = 'default'
@@ -820,6 +833,15 @@ class ArrayIndexOpTest(fixtures.TestBase, testing.AssertsCompiledSQL):
checkparams={'x_1': 5}
)
+ def test_contains_override_raises(self):
+ col = Column('x', self.MyType())
+
+ assert_raises_message(
+ NotImplementedError,
+ "Operator 'contains' is not supported on this expression",
+ lambda: 'foo' in col
+ )
+
def test_getindex_sqlexpr(self):
col = Column('x', self.MyType())