Repository: kayak/pypika
Pull request #167 (2018-10-22 09:29:54)

Make single arg functions resolve as aggregate
Added an assumption to impl of Function that if the function accepts a single argument and that argument is aggregated, the return value of the function should also be marked as aggregated.


Patch:
diff --git a/pypika/__init__.py b/pypika/__init__.py
index 6e54187..a9df782 100644
--- a/pypika/__init__.py
+++ b/pypika/__init__.py
@@ -72,4 +72,4 @@ from .utils import (
 
 __author__ = "Timothy Heys"
 __email__ = "theys@kayak.com"
-__version__ = "0.15.6"
+__version__ = "0.15.7"
diff --git a/pypika/functions.py b/pypika/functions.py
index ddd7d80..1a2b9fd 100644
--- a/pypika/functions.py
+++ b/pypika/functions.py
@@ -77,6 +77,11 @@ class Abs(AggregateFunction):
         super(Abs, self).__init__('ABS', term, alias=alias)
 
 
+class Sqrt(Function):
+    def __init__(self, term, alias=None):
+        super(Sqrt, self).__init__('SQRT', term, alias=alias)
+
+
 class Coalesce(Function):
     def __init__(self, term, *default_values, **kwargs):
         super(Coalesce, self).__init__('COALESCE', term, *default_values, **kwargs)
diff --git a/pypika/terms.py b/pypika/terms.py
index d989c61..174de3d 100644
--- a/pypika/terms.py
+++ b/pypika/terms.py
@@ -741,6 +741,16 @@ class Function(Term):
                 for param in self.args
                 for table in param.tables_}
 
+    @property
+    def is_aggregate(self):
+        """
+        This is a shortcut thst assumes if a function has a single argument and that argument is aggregated, then this
+        function is also aggregated. A more sophisticated approach is needed, however it is unclear how that might work.
+        :returns:
+            True if the function accepts one argument and that argument is aggregate.
+        """
+        return len(self.args) == 1 and self.args[0].is_aggregate
+
     @builder
     def for_(self, table):
         """


Test patch:
diff --git a/pypika/tests/test_aggregate.py b/pypika/tests/test_aggregate.py
index 6420a74..b35cd73 100644
--- a/pypika/tests/test_aggregate.py
+++ b/pypika/tests/test_aggregate.py
@@ -4,6 +4,7 @@ import unittest
 from pypika import (
     Case,
     Field,
+    Table,
     functions as fn,
 )
 from pypika.terms import ValueWrapper
@@ -100,3 +101,31 @@ class IsAggregateTests(unittest.TestCase):
             .else_(3)
 
         self.assertIsNone(v.is_aggregate)
+
+    def test__non_aggregate_function_with_aggregated_arg(self):
+        t = Table('abc')
+        expr = fn.Sqrt(fn.Sum(t.a))
+
+        self.assertTrue(expr.is_aggregate)
+
+    def test_complicated(self):
+        t = Table('abc')
+        is_placebo = t.campaign_extra_info == 'placebo'
+
+        pixel_mobile_search = Case().when(is_placebo, t.action_fb_pixel_search + t.action_fb_mobile_search)
+        unique_impressions = Case().when(is_placebo, t.unique_impressions)
+
+        v = (fn.Sum(pixel_mobile_search)
+             / fn.Sum(unique_impressions)
+             - 1.96
+             * fn.Sqrt(
+                    1
+                    / fn.Sum(unique_impressions)
+                    * fn.Sum(pixel_mobile_search)
+                    / fn.Sum(unique_impressions)
+                    * (
+                          1
+                          - fn.Sum(pixel_mobile_search)
+                          / fn.Sum(unique_impressions))))
+
+        self.assertTrue(v.is_aggregate)