From: Mike Bayer Date: Tue, 2 Feb 2016 15:15:40 +0000 (-0500) Subject: - Fixed issue where inadvertent use of the Python ``__contains__`` X-Git-Tag: rel_1_1_0b1~98^2~58 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e0a580b3d055a600afae61840058a5a30ef5fe74;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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 --- diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index 781911d650..320cb7f2af 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -19,6 +19,17 @@ :version: 1.0.12 :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 6c8e69dee3..1bb1c344cc 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 ba1d0f65e9..80f08a97c9 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 50b66f290f..c53c67cefd 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 6a6c749a40..86286a9a3e 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())