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
: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
----------------------------------------------------------------------
.. autodata:: func
+.. autofunction:: funcfilter
+
.. autofunction:: label
.. autofunction:: literal
.. autoclass:: sqlalchemy.sql.elements.False_
:members:
+.. autoclass:: FunctionFilter
+ :members:
+
.. autoclass:: Label
:members:
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'
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)
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:
"(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,
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
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'
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):