]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Implement placeholders for CUBE, ROLLUP, GROUPING SETS
authorSpitcyn <a.ch.clr@gmail.com>
Tue, 12 Sep 2017 14:21:57 +0000 (10:21 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 13 Sep 2017 19:18:37 +0000 (15:18 -0400)
Fixes: #3429
Co-authored-by: Mike Bayer <mike_mp@zzzcomputing.com>
Change-Id: I870ee7dc801d553c5309c291402ec468b671e9a9
Pull-request: https://github.com/zzzeek/sqlalchemy/pull/383

doc/build/changelog/migration_12.rst
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/elements.py
lib/sqlalchemy/sql/functions.py
test/sql/test_functions.py

index a670bb3948931e1478b9585dc4c24560fea625cc..e9deb75c49e0da24c2839ca71df880a9f88ea3aa 100644 (file)
@@ -812,6 +812,34 @@ if the application is working with plain floats.
 
 :ticket:`4020`
 
+
+.. change_3249:
+
+Support for GROUPING SETS, CUBE, ROLLUP
+---------------------------------------
+
+All three of GROUPING SETS, CUBE, ROLLUP are available via the
+:attr:`.func` namespace.  In the case of CUBE and ROLLUP, these functions
+already work in previous versions, however for GROUPING SETS, a placeholder
+is added to the compiler to allow for the space.  All three functions
+are named in the documentation now::
+
+    >>> from sqlalchemy import select, table, column, func, tuple_
+    >>> t = table('t',
+    ...           column('value'), column('x'),
+    ...           column('y'), column('z'), column('q'))
+    >>> stmt = select([func.sum(t.c.value)]).group_by(
+    ...     func.grouping_sets(
+    ...         tuple_(t.c.x, t.c.y),
+    ...         tuple_(t.c.z, t.c.q),
+    ...     )
+    ... )
+    >>> print(stmt)
+    SELECT sum(t.value) AS sum_1
+    FROM t GROUP BY GROUPING SETS((t.x, t.y), (t.z, t.q))
+
+:ticket:`3429`
+
 Key Behavioral Changes - ORM
 ============================
 
index 2dec3a5c39be8f6c2641624b62fb6cf4ea7ec70f..b0f0807d61c14ef84d421c7f7fd4ce12f69124c0 100644 (file)
@@ -121,7 +121,10 @@ FUNCTIONS = {
     functions.random: 'random%(expr)s',
     functions.sysdate: 'sysdate',
     functions.session_user: 'SESSION_USER',
-    functions.user: 'USER'
+    functions.user: 'USER',
+    functions.cube: 'CUBE%(expr)s',
+    functions.rollup: 'ROLLUP%(expr)s',
+    functions.grouping_sets: 'GROUPING SETS%(expr)s',
 }
 
 EXTRACT_MAP = {
index 36a6a655714ed5d1cc381ae1f5f35597cce49a1a..9213d616ce84a611dd73fac3ee17d2ff82711ec9 100644 (file)
@@ -3293,7 +3293,7 @@ class WithinGroup(ColumnElement):
     """Represent a WITHIN GROUP (ORDER BY) clause.
 
     This is a special operator against so-called
-    so-called "ordered set aggregate" and "hypothetical
+    "ordered set aggregate" and "hypothetical
     set aggregate" functions, including ``percentile_cont()``,
     ``rank()``, ``dense_rank()``, etc.
 
index 08f1d32a504798be7dd9506df998107de39b48b8..9a9396fec085889f35c2c4c5741bc291a5bffcf9 100644 (file)
@@ -811,3 +811,65 @@ class cume_dist(GenericFunction):
 
     """
     type = sqltypes.Numeric()
+
+
+class cube(GenericFunction):
+    r"""Implement the ``CUBE`` grouping operation.
+
+    This function is used as part of the GROUP BY of a statement,
+    e.g. :meth:`.Select.group_by`::
+
+        stmt = select(
+            [func.sum(table.c.value), table.c.col_1, table.c.col_2]
+            ).group_by(func.cube(table.c.col_1, table.c.col_2))
+
+    .. versionadded:: 1.2
+
+    """
+
+
+class rollup(GenericFunction):
+    r"""Implement the ``ROLLUP`` grouping operation.
+
+    This function is used as part of the GROUP BY of a statement,
+    e.g. :meth:`.Select.group_by`::
+
+        stmt = select(
+            [func.sum(table.c.value), table.c.col_1, table.c.col_2]
+        ).group_by(func.rollup(table.c.col_1, table.c.col_2))
+
+    .. versionadded:: 1.2
+
+    """
+
+
+class grouping_sets(GenericFunction):
+    r"""Implement the ``GROUPING SETS`` grouping operation.
+
+    This function is used as part of the GROUP BY of a statement,
+    e.g. :meth:`.Select.group_by`::
+
+        stmt = select(
+            [func.sum(table.c.value), table.c.col_1, table.c.col_2]
+        ).group_by(func.grouping_sets(table.c.col_1, table.c.col_2))
+
+    In order to group by multiple sets, use the :func:`.tuple_` construct::
+
+        from sqlalchemy import tuple_
+
+        stmt = select(
+            [
+                func.sum(table.c.value),
+                table.c.col_1, table.c.col_2,
+                table.c.col_3]
+        ).group_by(
+            func.grouping_sets(
+                tuple_(table.c.col_1, table.c.col_2),
+                tuple_(table.c.value, table.c.col_3),
+            )
+        )
+
+
+    .. versionadded:: 1.2
+
+    """
index 272bd876e17369abb62922dee457fa0fb3a3cad4..a7dfd2bebeb0f1ac06634dc942ba0932a90c72d6 100644 (file)
@@ -86,6 +86,42 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
         ]:
             self.assert_compile(func.random(), ret, dialect=dialect)
 
+    def test_cube_operators(self):
+
+        t = table('t', column('value'),
+                  column('x'), column('y'), column('z'), column('q'))
+
+        stmt = select([func.sum(t.c.value)])
+
+        self.assert_compile(
+            stmt.group_by(func.cube(t.c.x, t.c.y)),
+            "SELECT sum(t.value) AS sum_1 FROM t GROUP BY CUBE(t.x, t.y)"
+        )
+
+        self.assert_compile(
+            stmt.group_by(func.rollup(t.c.x, t.c.y)),
+            "SELECT sum(t.value) AS sum_1 FROM t GROUP BY ROLLUP(t.x, t.y)"
+        )
+
+        self.assert_compile(
+            stmt.group_by(
+                func.grouping_sets(t.c.x, t.c.y)
+            ),
+            "SELECT sum(t.value) AS sum_1 FROM t "
+            "GROUP BY GROUPING SETS(t.x, t.y)"
+        )
+
+        self.assert_compile(
+            stmt.group_by(
+                func.grouping_sets(
+                    sql.tuple_(t.c.x, t.c.y),
+                    sql.tuple_(t.c.z, t.c.q),
+                )
+            ),
+            "SELECT sum(t.value) AS sum_1 FROM t GROUP BY "
+            "GROUPING SETS((t.x, t.y), (t.z, t.q))"
+        )
+
     def test_generic_annotation(self):
         fn = func.coalesce('x', 'y')._annotate({"foo": "bar"})
         self.assert_compile(