]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Fixed issue where inadvertent use of the Python ``__contains__``
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 2 Feb 2016 15:15:40 +0000 (10:15 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 2 Feb 2016 15:20:54 +0000 (10:20 -0500)
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

doc/build/changelog/changelog_10.rst
lib/sqlalchemy/sql/default_comparator.py
lib/sqlalchemy/sql/operators.py
test/dialect/postgresql/test_types.py
test/sql/test_operators.py

index 781911d6505c9792162089f0fc2257b581eb9b38..320cb7f2af00938f595e2de81fa40b4c676aa133 100644 (file)
     :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
index 6c8e69dee39782ef8c74ca07e270058ee220c2f4..1bb1c344cc8643c4e0ca4ed1433f2c5858f0f4a7 100644 (file)
@@ -274,6 +274,7 @@ operator_lookup = {
     "getitem": (_getitem_impl,),
     "lshift": (_unsupported_impl,),
     "rshift": (_unsupported_impl,),
+    "contains": (_unsupported_impl,),
 }
 
 
index ba1d0f65e9779b47c81caa57fac578a622d6fbc5..80f08a97c920288912d7c994f247d5b898c6f213 100644 (file)
@@ -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.
 
index 50b66f290f33e90e2259a48aa1b662213ad14867..c53c67cefd2b6955cb22cc12bd14111e0143d093 100644 (file)
@@ -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(
index 6a6c749a40b5ea0711b8198082ca0484af7cbb5c..86286a9a3e878e5d36f3fc3528d54954b127a246 100644 (file)
@@ -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())