]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- changelog, migration for pr github:134
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 4 Oct 2014 16:18:20 +0000 (12:18 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 4 Oct 2014 16:18:20 +0000 (12:18 -0400)
doc/build/changelog/changelog_10.rst
doc/build/changelog/migration_10.rst
doc/build/core/sqlelement.rst
lib/sqlalchemy/sql/elements.py
lib/sqlalchemy/sql/functions.py
test/sql/test_compiler.py
test/sql/test_functions.py

index 4d5ab1f06d9fd857e93751e00b18ff814844ae7b..a746abeac2604e507e4b719000e8c23ab7fe5f8e 100644 (file)
     series as well.  For changes that are specific to 1.0 with an emphasis
     on compatibility concerns, see :doc:`/changelog/migration_10`.
 
+    .. change::
+        :tags: feature, postgresql
+        :pullreq: github:134
+
+        Added support for the FILTER keyword as applied to aggregate
+        functions, supported by Postgresql 9.4.   Pull request
+        courtesy Ilja Everilä.
+
+        .. seealso::
+
+            :ref:`feature_gh134`
+
     .. change::
         :tags: bug, sql, engine
         :tickets: 3215
index 0e9dd8d7bbbee273b3ed36495d7b58d8416712b6..b0ac868ec181f56283f4891ea3867d454702f036 100644 (file)
@@ -1026,6 +1026,22 @@ running 0.9 in production.
 
 :ticket:`2891`
 
+.. _feature_gh134:
+
+Postgresql FILTER keyword
+-------------------------
+
+The SQL standard FILTER keyword for aggregate functions is now supported
+by Postgresql as of 9.4.  SQLAlchemy allows this using
+:meth:`.FunctionElement.filter`::
+
+    func.count(1).filter(True)
+
+.. seealso::
+
+    :meth:`.FunctionElement.filter`
+
+    :class:`.FunctionFilter`
 
 MySQL internal "no such table" exceptions not passed to event handlers
 ----------------------------------------------------------------------
index 61600e9274b3dc3a0634ae4cbb35668e817d37e9..44a969dbb39cbe5bc59ecb0c96001bb40284975f 100644 (file)
@@ -35,6 +35,8 @@ used to construct any kind of typed SQL expression.
 
 .. autodata:: func
 
+.. autofunction:: funcfilter
+
 .. autofunction:: label
 
 .. autofunction:: literal
@@ -109,6 +111,9 @@ used to construct any kind of typed SQL expression.
 .. autoclass:: sqlalchemy.sql.elements.False_
    :members:
 
+.. autoclass:: FunctionFilter
+   :members:
+
 .. autoclass:: Label
    :members:
 
index 53838358d1779105973a212b71149a21e875db29..db14031d203cc059ca5c8511dd6dedbbf21fdaa9 100644 (file)
@@ -2895,6 +2895,17 @@ class FunctionFilter(ColumnElement):
     which controls which rows are passed to it.
     It's supported only by certain database backends.
 
+    Invocation of :class:`.FunctionFilter` is via
+    :meth:`.FunctionElement.filter`::
+
+        func.count(1).filter(True)
+
+    .. versionadded:: 1.0.0
+
+    .. seealso::
+
+        :meth:`.FunctionElement.filter`
+
     """
     __visit_name__ = 'funcfilter'
 
@@ -2916,11 +2927,29 @@ class FunctionFilter(ColumnElement):
         This function is also available from the :data:`~.expression.func`
         construct itself via the :meth:`.FunctionElement.filter` method.
 
+        .. versionadded:: 1.0.0
+
+        .. seealso::
+
+            :meth:`.FunctionElement.filter`
+
+
         """
         self.func = func
         self.filter(*criterion)
 
     def filter(self, *criterion):
+        """Produce an additional FILTER against the function.
+
+        This method adds additional criteria to the initial criteria
+        set up by :meth:`.FunctionElement.filter`.
+
+        Multiple criteria are joined together at SQL render time
+        via ``AND``.
+
+
+        """
+
         for criterion in list(criterion):
             criterion = _expression_literal_as_text(criterion)
 
index a07eca8c68e3e850ea8b0a206f9065af6d18d0fb..9280c7d6032fc745f95674e944d87593c9bb2feb 100644 (file)
@@ -131,7 +131,14 @@ class FunctionElement(Executable, ColumnElement, FromClause):
             from sqlalchemy import funcfilter
             funcfilter(func.count(1), True)
 
-        See :func:`~.expression.funcfilter` for a full description.
+        .. versionadded:: 1.0.0
+
+        .. seealso::
+
+            :class:`.FunctionFilter`
+
+            :func:`.funcfilter`
+
 
         """
         if not criterion:
index ed13e8455c463149cd7e9f1558797db498038cb4..3e6b873519c69823268e387223c351dfc06e2e54 100644 (file)
@@ -2190,97 +2190,6 @@ class SelectTest(fixtures.TestBase, AssertsCompiledSQL):
             "(ORDER BY mytable.myid + :myid_1) AS anon_1 FROM mytable"
         )
 
-    def test_funcfilter(self):
-        self.assert_compile(
-            func.count(1).filter(),
-            "count(:param_1)"
-        )
-        self.assert_compile(
-            func.count(1).filter(
-                table1.c.name != None
-            ),
-            "count(:param_1) FILTER (WHERE mytable.name IS NOT NULL)"
-        )
-        self.assert_compile(
-            func.count(1).filter(
-                table1.c.name == None,
-                table1.c.myid > 0
-            ),
-            "count(:param_1) FILTER (WHERE mytable.name IS NULL AND "
-            "mytable.myid > :myid_1)"
-        )
-
-        self.assert_compile(
-            select([func.count(1).filter(
-                table1.c.description != None
-            ).label('foo')]),
-            "SELECT count(:param_1) FILTER (WHERE mytable.description "
-            "IS NOT NULL) AS foo FROM mytable"
-        )
-
-        # test from_obj generation.
-        # from func:
-        self.assert_compile(
-            select([
-                func.max(table1.c.name).filter(
-                    literal_column('description') != None
-                )
-            ]),
-            "SELECT max(mytable.name) FILTER (WHERE description "
-            "IS NOT NULL) AS anon_1 FROM mytable"
-        )
-        # from criterion:
-        self.assert_compile(
-            select([
-                func.count(1).filter(
-                    table1.c.name == 'name'
-                )
-            ]),
-            "SELECT count(:param_1) FILTER (WHERE mytable.name = :name_1) "
-            "AS anon_1 FROM mytable"
-        )
-
-        # test chaining:
-        self.assert_compile(
-            select([
-                func.count(1).filter(
-                    table1.c.name == 'name'
-                ).filter(
-                    table1.c.description == 'description'
-                )
-            ]),
-            "SELECT count(:param_1) FILTER (WHERE "
-            "mytable.name = :name_1 AND mytable.description = :description_1) "
-            "AS anon_1 FROM mytable"
-        )
-
-        # test filtered windowing:
-        self.assert_compile(
-            select([
-                func.rank().filter(
-                    table1.c.name > 'foo'
-                ).over(
-                    order_by=table1.c.name
-                )
-            ]),
-            "SELECT rank() FILTER (WHERE mytable.name > :name_1) "
-            "OVER (ORDER BY mytable.name) AS anon_1 FROM mytable"
-        )
-
-        self.assert_compile(
-            select([
-                func.rank().filter(
-                    table1.c.name > 'foo'
-                ).over(
-                    order_by=table1.c.name,
-                    partition_by=['description']
-                )
-            ]),
-            "SELECT rank() FILTER (WHERE mytable.name > :name_1) "
-            "OVER (PARTITION BY mytable.description ORDER BY mytable.name) "
-            "AS anon_1 FROM mytable"
-        )
-
     def test_date_between(self):
         import datetime
         table = Table('dt', metadata,
index 9b7649e6394db4ae97995a0ef9c710b9ca42a738..ec8d9b5c0d4f77cf930b6c63284bdec346139816 100644 (file)
@@ -1,7 +1,8 @@
 from sqlalchemy.testing import eq_
 import datetime
 from sqlalchemy import func, select, Integer, literal, DateTime, Table, \
-    Column, Sequence, MetaData, extract, Date, String, bindparam
+    Column, Sequence, MetaData, extract, Date, String, bindparam, \
+    literal_column
 from sqlalchemy.sql import table, column
 from sqlalchemy import sql, util
 from sqlalchemy.sql.compiler import BIND_TEMPLATES
@@ -15,6 +16,13 @@ from sqlalchemy.testing import fixtures, AssertsCompiledSQL, engines
 from sqlalchemy.dialects import sqlite, postgresql, mysql, oracle
 
 
+table1 = table('mytable',
+               column('myid', Integer),
+               column('name', String),
+               column('description', String),
+               )
+
+
 class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
     __dialect__ = 'default'
 
@@ -367,6 +375,108 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
         expr = func.rows("foo").alias('bar')
         assert len(expr.c)
 
+    def test_funcfilter_empty(self):
+        self.assert_compile(
+            func.count(1).filter(),
+            "count(:param_1)"
+        )
+
+    def test_funcfilter_criterion(self):
+        self.assert_compile(
+            func.count(1).filter(
+                table1.c.name != None
+            ),
+            "count(:param_1) FILTER (WHERE mytable.name IS NOT NULL)"
+        )
+
+    def test_funcfilter_compound_criterion(self):
+        self.assert_compile(
+            func.count(1).filter(
+                table1.c.name == None,
+                table1.c.myid > 0
+            ),
+            "count(:param_1) FILTER (WHERE mytable.name IS NULL AND "
+            "mytable.myid > :myid_1)"
+        )
+
+    def test_funcfilter_label(self):
+        self.assert_compile(
+            select([func.count(1).filter(
+                table1.c.description != None
+            ).label('foo')]),
+            "SELECT count(:param_1) FILTER (WHERE mytable.description "
+            "IS NOT NULL) AS foo FROM mytable"
+        )
+
+    def test_funcfilter_fromobj_fromfunc(self):
+        # test from_obj generation.
+        # from func:
+        self.assert_compile(
+            select([
+                func.max(table1.c.name).filter(
+                    literal_column('description') != None
+                )
+            ]),
+            "SELECT max(mytable.name) FILTER (WHERE description "
+            "IS NOT NULL) AS anon_1 FROM mytable"
+        )
+
+    def test_funcfilter_fromobj_fromcriterion(self):
+        # from criterion:
+        self.assert_compile(
+            select([
+                func.count(1).filter(
+                    table1.c.name == 'name'
+                )
+            ]),
+            "SELECT count(:param_1) FILTER (WHERE mytable.name = :name_1) "
+            "AS anon_1 FROM mytable"
+        )
+
+    def test_funcfilter_chaining(self):
+        # test chaining:
+        self.assert_compile(
+            select([
+                func.count(1).filter(
+                    table1.c.name == 'name'
+                ).filter(
+                    table1.c.description == 'description'
+                )
+            ]),
+            "SELECT count(:param_1) FILTER (WHERE "
+            "mytable.name = :name_1 AND mytable.description = :description_1) "
+            "AS anon_1 FROM mytable"
+        )
+
+    def test_funcfilter_windowing_orderby(self):
+        # test filtered windowing:
+        self.assert_compile(
+            select([
+                func.rank().filter(
+                    table1.c.name > 'foo'
+                ).over(
+                    order_by=table1.c.name
+                )
+            ]),
+            "SELECT rank() FILTER (WHERE mytable.name > :name_1) "
+            "OVER (ORDER BY mytable.name) AS anon_1 FROM mytable"
+        )
+
+    def test_funcfilter_windowing_orderby_partitionby(self):
+        self.assert_compile(
+            select([
+                func.rank().filter(
+                    table1.c.name > 'foo'
+                ).over(
+                    order_by=table1.c.name,
+                    partition_by=['description']
+                )
+            ]),
+            "SELECT rank() FILTER (WHERE mytable.name > :name_1) "
+            "OVER (PARTITION BY mytable.description ORDER BY mytable.name) "
+            "AS anon_1 FROM mytable"
+        )
+
 
 class ExecuteTest(fixtures.TestBase):