Mike Bayer avatar Mike Bayer committed a55d5f3 Merge

merge Audrius HSTORE commits from bitbucket

Comments (0)

Files changed (4)

doc/build/dialects/postgresql.rst

 .. autoclass:: array
 
 .. autoclass:: ARRAY
-    :members: __init__
+    :members: __init__, Comparator
     :show-inheritance:
 
 .. autoclass:: BIT

lib/sqlalchemy/dialects/postgresql/base.py

     as well as UPDATE statements when the :meth:`.Update.values` method
     is used::
 
-        mytable.update().values({mytable.c.data[5]:7,
-                        mytable.c.data[2:7]:[1,2,3]})
+        mytable.update().values({
+            mytable.c.data[5]: 7,
+            mytable.c.data[2:7]: [1, 2, 3]
+        })
+
+    :class:`.ARRAY` provides special methods for containment operations,
+    e.g.::
+
+        mytable.c.data.contains([1, 2])
+
+    For a full list of special methods see :class:`.ARRAY.Comparator`.
 
     .. versionadded:: 0.8 Added support for index and slice operations
        to the :class:`.ARRAY` type, including support for UPDATE
-       statements.
+       statements, and special array containment operations.
 
     The :class:`.ARRAY` type may not be supported on all DBAPIs.
     It is known to work on psycopg2 and not pg8000.
     __visit_name__ = 'ARRAY'
 
     class Comparator(sqltypes.Concatenable.Comparator):
+        """Define comparison operations for :class:`.ARRAY`."""
+
         def __getitem__(self, index):
             if isinstance(index, slice):
                 index = _Slice(index, self)
             return self._binary_operate(self.expr, operators.getitem, index,
                             result_type=return_type)
 
+        def contains(self, other, **kwargs):
+            """Boolean expression.  Test if elements are a superset of the
+            elements of the argument array expression.
+            """
+            return self.expr.op('@>')(other)
+
+        def contained_by(self, other):
+            """Boolean expression.  Test if elements are a proper subset of the
+            elements of the argument array expression.
+            """
+            return self.expr.op('<@')(other)
+
+        def overlap(self, other):
+            """Boolean expression.  Test if array has elements in common with
+            an argument array expression.
+            """
+            return self.expr.op('&&')(other)
+
+        def _adapt_expression(self, op, other_comparator):
+            if isinstance(op, operators.custom_op):
+                if op.opstring in ['@>', '<@', '&&']:
+                    return op, sqltypes.Boolean
+            return sqltypes.Concatenable.Comparator.\
+                _adapt_expression(self, op, other_comparator)
+
     comparator_factory = Comparator
 
     def __init__(self, item_type, as_tuple=False, dimensions=None):

lib/sqlalchemy/dialects/postgresql/hstore.py

         residual = residual[:-1] + '[...]'
 
     return "After %r, could not parse residual at position %d: %r" % (
-                    parsed_tail, pos, residual)
+        parsed_tail, pos, residual)
 
 
 def _parse_hstore(hstore_str):
 
     __visit_name__ = 'HSTORE'
 
-    class comparator_factory(sqltypes.TypeEngine.Comparator):
+    class comparator_factory(sqltypes.Concatenable.Comparator):
         """Define comparison operations for :class:`.HSTORE`."""
 
         def has_key(self, other):
             """
             return self.expr.op('->', precedence=5)(other)
 
-        def __add__(self, other):
-            """HStore expression.  Merge the left and right hstore expressions,
-            with duplicate keys taking the value from the right expression.
-            """
-            return self.expr.concat(other)
-
         def delete(self, key):
             """HStore expression.  Returns the contents of this hstore with the
             given key deleted.  Note that the key may be a SQLA expression.
                     return op, sqltypes.Boolean
                 elif op.opstring == '->':
                     return op, sqltypes.Text
-            return op, other_comparator.type
+            return sqltypes.Concatenable.Comparator.\
+                _adapt_expression(self, op, other_comparator)
 
     def bind_processor(self, dialect):
         def process(value):

test/dialect/test_postgresql.py

             "CAST(x AS INTEGER[])"
         )
         self.assert_compile(
-                c[5],
-                "x[%(x_1)s]",
-                checkparams={'x_1': 5}
+            c[5],
+            "x[%(x_1)s]",
+            checkparams={'x_1': 5}
         )
 
         self.assert_compile(
-                c[5:7],
-                "x[%(x_1)s:%(x_2)s]",
-                checkparams={'x_2': 7, 'x_1': 5}
+            c[5:7],
+            "x[%(x_1)s:%(x_2)s]",
+            checkparams={'x_2': 7, 'x_1': 5}
         )
         self.assert_compile(
-                c[5:7][2:3],
-                "x[%(x_1)s:%(x_2)s][%(param_1)s:%(param_2)s]",
-                checkparams={'x_2': 7, 'x_1': 5, 'param_1':2, 'param_2':3}
+            c[5:7][2:3],
+            "x[%(x_1)s:%(x_2)s][%(param_1)s:%(param_2)s]",
+            checkparams={'x_2': 7, 'x_1': 5, 'param_1':2, 'param_2':3}
         )
         self.assert_compile(
-                c[5:7][3],
-                "x[%(x_1)s:%(x_2)s][%(param_1)s]",
-                checkparams={'x_2': 7, 'x_1': 5, 'param_1':3}
+            c[5:7][3],
+            "x[%(x_1)s:%(x_2)s][%(param_1)s]",
+            checkparams={'x_2': 7, 'x_1': 5, 'param_1':3}
+        )
+
+        self.assert_compile(
+            c.contains([1]),
+            'x @> %(x_1)s',
+            checkparams={'x_1': [1]}
+        )
+        self.assert_compile(
+            c.contained_by([2]),
+            'x <@ %(x_1)s',
+            checkparams={'x_1': [2]}
+        )
+        self.assert_compile(
+            c.overlap([3]),
+            'x && %(x_1)s',
+            checkparams={'x_1': [3]}
         )
 
     def test_array_literal_type(self):
                 [7, 8]
             )
 
+    def test_array_contains_exec(self):
+        with testing.db.connect() as conn:
+            conn.execute(
+                arrtable.insert(),
+                intarr=[4, 5, 6]
+            )
+            eq_(
+                conn.scalar(
+                    select([arrtable.c.intarr]).
+                        where(arrtable.c.intarr.contains([4, 5]))
+                ),
+                [4, 5, 6]
+            )
+
+    def test_array_contained_by_exec(self):
+        with testing.db.connect() as conn:
+            conn.execute(
+                arrtable.insert(),
+                intarr=[6, 5, 4]
+            )
+            eq_(
+                conn.scalar(
+                    select([arrtable.c.intarr.contained_by([4, 5, 6, 7])])
+                ),
+                True
+            )
+
+    def test_array_overlap_exec(self):
+        with testing.db.connect() as conn:
+            conn.execute(
+                arrtable.insert(),
+                intarr=[4, 5, 6]
+            )
+            eq_(
+                conn.scalar(
+                    select([arrtable.c.intarr]).
+                        where(arrtable.c.intarr.overlap([7, 6]))
+                ),
+                [4, 5, 6]
+            )
+
     @testing.provide_metadata
     def test_tuple_flag(self):
         metadata = self.metadata
             '"key2"=>"value2", "key1"=>"value1", '
                         'crapcrapcrap, "key3"=>"value3"'
         )
+
     def test_result_deserialize_default(self):
         from sqlalchemy.engine import default
 
 
     def test_cols_concat_op(self):
         self._test_cols(
-            self.hashcol + self.hashcol,
-            "test_table.hash || test_table.hash AS anon_1",
+            hstore('foo', 'bar') + self.hashcol,
+            "hstore(%(param_1)s, %(param_2)s) || test_table.hash AS anon_1",
             True
         )
 
             True
         )
 
+
 class HStoreRoundTripTest(fixtures.TablesTest):
     __requires__ = 'hstore',
     __dialect__ = 'postgresql'
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.