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_0_12~25 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=69f6a8d714c0b1852229bdac05186f2590c5181c;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 (cherry picked from commit e0a580b3d055a600afae61840058a5a30ef5fe74) --- 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 305f838938..d180dbc02e 100644 --- a/lib/sqlalchemy/sql/default_comparator.py +++ b/lib/sqlalchemy/sql/default_comparator.py @@ -263,6 +263,7 @@ operator_lookup = { "getitem": (_unsupported_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 05be71e9c2..5e2900d8c2 100644 --- a/lib/sqlalchemy/sql/operators.py +++ b/lib/sqlalchemy/sql/operators.py @@ -15,7 +15,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: @@ -333,6 +333,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 2ab0eee818..7e098019d4 100644 --- a/test/dialect/postgresql/test_types.py +++ b/test/dialect/postgresql/test_types.py @@ -707,7 +707,6 @@ class TimePrecisionTest(fixtures.TestBase, AssertsCompiledSQL): class ArrayTest(fixtures.TablesTest, AssertsExecutionResults): - __only_on__ = 'postgresql' __backend__ = True __unsupported_on__ = 'postgresql+pg8000', 'postgresql+zxjdbc' @@ -803,6 +802,15 @@ class ArrayTest(fixtures.TablesTest, AssertsExecutionResults): eq_(len(results), 1) eq_(results[0][0], 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_subtype_resultprocessor(self): arrtable = self.tables.arrtable arrtable.insert().execute(intarr=[4, 5, 6], diff --git a/test/sql/test_operators.py b/test/sql/test_operators.py index bb4cb1bf17..1a0554e507 100644 --- a/test/sql/test_operators.py +++ b/test/sql/test_operators.py @@ -13,7 +13,7 @@ from sqlalchemy.engine import default from sqlalchemy.sql.elements import _literal_as_text from sqlalchemy.schema import Column, Table, MetaData from sqlalchemy.types import TypeEngine, TypeDecorator, UserDefinedType, \ - Boolean, NullType, MatchType + Boolean, NullType, MatchType, DateTime from sqlalchemy.dialects import mysql, firebird, postgresql, oracle, \ sqlite, mssql from sqlalchemy import util @@ -210,6 +210,18 @@ class DefaultColumnComparatorTest(fixtures.TestBase): def test_concat(self): self._do_operate_test(operators.concat_op) + 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'