Repository: blaze/blaze

Issue:
"WITH alias as (...) SELECT" statements are generated for Mysql while mysql doesn't support them
Example

``` python
db = blaze.Data('mysql://...')
tb = db['my_table'][['field1', 'time', 'column']]

sel = tb[tb.field1 == 661]

sel.time.min()
```

Generated SQL:

``` sql
WITH alias_25 AS 
(SELECT my_table.time AS time 
FROM my_table
WHERE my_table.field1 = %s)
 SELECT min(alias_25.time) AS time_min 
FROM alias_25
```

Error:

```
ProgrammingError: (_mysql_exceptions.ProgrammingError) (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'alias_25 AS \n(SELECT my_table.time AS time \nFROM my_table' at line 1") [SQL: u'WITH alias_25 AS \n(SELECT my_table.time AS time \nFROM my_table \nWHERE my_table.field1 = %s)\n SELECT min(alias_25.time) AS time_min \nFROM alias_25'] [parameters: (661,)]
```



Discussion:

Resolving patch:
diff --git a/blaze/compute/sql.py b/blaze/compute/sql.py
--- a/blaze/compute/sql.py
+++ b/blaze/compute/sql.py
@@ -457,7 +457,7 @@ def reconstruct_select(columns, original, **kwargs):
 def compute_up(expr, data, **kwargs):
     if expr.axis != (0,):
         raise ValueError('axis not equal to 0 not defined for SQL reductions')
-    data = data.cte(name=next(aliases))
+    data = data.alias(name=next(aliases))
     cols = list(inner_columns(data))
     d = dict((expr._child[c], cols[i])
              for i, c in enumerate(expr._child.fields))
diff --git a/blaze/utils.py b/blaze/utils.py
--- a/blaze/utils.py
+++ b/blaze/utils.py
@@ -2,6 +2,7 @@
 
 import os
 import datetime
+import re
 
 try:
     from cytoolz import nth
@@ -231,3 +232,9 @@ def object_hook(obj):
 @object_hook.register('timedelta')
 def _read_timedelta(ds):
     return datetime.timedelta(seconds=ds)
+
+
+def normalize(s):
+    s = ' '.join(s.strip().split()).lower()
+    s = re.sub(r'(alias)_?\d*', r'\1', s)
+    return re.sub(r'__([A-Za-z_][A-Za-z_0-9]*)', r'\1', s)


Test patch:
diff --git a/blaze/compute/tests/test_mysql_compute.py b/blaze/compute/tests/test_mysql_compute.py
new file mode 100644
--- /dev/null
+++ b/blaze/compute/tests/test_mysql_compute.py
@@ -0,0 +1,61 @@
+from __future__ import absolute_import, print_function, division
+
+from getpass import getuser
+
+import pytest
+
+sa = pytest.importorskip('sqlalchemy')
+pytest.importorskip('pymysql')
+
+from odo import odo, drop, discover
+
+import pandas as pd
+
+from blaze import symbol, compute
+from blaze.utils import example, normalize
+
+
+@pytest.yield_fixture(scope='module')
+def data():
+    try:
+        t = odo(
+            example('nyc.csv'),
+            'mysql+pymysql://%s@localhost/test::nyc' % getuser()
+        )
+    except sa.exc.OperationalError as e:
+        pytest.skip(str(e))
+    else:
+        try:
+            yield t.bind
+        finally:
+            drop(t)
+
+
+@pytest.fixture
+def db(data):
+    return symbol('test', discover(data))
+
+
+def test_agg_sql(db, data):
+    subset = db.nyc[['pickup_datetime', 'dropoff_datetime', 'passenger_count']]
+    expr = subset[subset.passenger_count < 4].passenger_count.min()
+    result = compute(expr, data)
+    expected = """
+    select
+        min(alias.passenger_count) as passenger_count_min
+    from
+        (select
+            nyc.passenger_count as passenger_count
+         from
+            nyc
+         where nyc.passenger_count < %s) as alias
+    """
+    assert normalize(str(result)) == normalize(expected)
+
+
+def test_agg_compute(db, data):
+    subset = db.nyc[['pickup_datetime', 'dropoff_datetime', 'passenger_count']]
+    expr = subset[subset.passenger_count < 4].passenger_count.min()
+    result = compute(expr, data)
+    passenger_count = odo(compute(db.nyc.passenger_count, {db: data}), pd.Series)
+    assert passenger_count[passenger_count < 4].min() == result.scalar()
diff --git a/blaze/compute/tests/test_postgresql_compute.py b/blaze/compute/tests/test_postgresql_compute.py
--- a/blaze/compute/tests/test_postgresql_compute.py
+++ b/blaze/compute/tests/test_postgresql_compute.py
@@ -1,6 +1,5 @@
 from datetime import timedelta
 import itertools
-import re
 
 import pytest
 
@@ -15,18 +14,12 @@
 from odo import odo, resource, drop, discover
 from blaze import symbol, compute, concat, join, sin, cos, radians, atan2
 from blaze import sqrt, transform, Data
-from blaze.utils import example
+from blaze.utils import example, normalize
 
 
 names = ('tbl%d' % i for i in itertools.count())
 
 
-def normalize(s):
-    s = ' '.join(s.strip().split()).lower()
-    s = re.sub(r'(alias)_?\d*', r'\1', s)
-    return re.sub(r'__([A-Za-z_][A-Za-z_0-9]*)', r'\1', s)
-
-
 @pytest.fixture
 def url():
     return 'postgresql://postgres@localhost/test::%s'
diff --git a/blaze/compute/tests/test_sql_compute.py b/blaze/compute/tests/test_sql_compute.py
--- a/blaze/compute/tests/test_sql_compute.py
+++ b/blaze/compute/tests/test_sql_compute.py
@@ -5,7 +5,6 @@
 sa = pytest.importorskip('sqlalchemy')
 
 import itertools
-import re
 from distutils.version import LooseVersion
 
 
@@ -20,7 +19,7 @@
     floor, cos, merge, nunique, mean, sum, count, exp
 )
 from blaze.compatibility import xfail
-from blaze.utils import tmpfile, example
+from blaze.utils import tmpfile, example, normalize
 
 
 def computefull(t, s):
@@ -97,12 +96,6 @@ def data():
                 sa.Column('id', sa.Integer, primary_key=True))
 
 
-def normalize(s):
-    s = ' '.join(s.strip().split()).lower()
-    s = re.sub(r'(alias)_?\d*', r'\1', s)
-    return re.sub(r'__([A-Za-z_][A-Za-z_0-9]*)', r'\1', s)
-
-
 def test_table():
     result = str(computefull(t, s))
     expected = """
@@ -156,7 +149,6 @@ def test_arithmetic():
         str(sa.select([s.c.amount + s.c.id * 2]))
 
 
-
 def test_join():
     metadata = sa.MetaData()
     lhs = sa.Table('amounts', metadata,
@@ -207,6 +199,7 @@ def test_join():
     order by
         anon_1.amount asc""")
 
+
 def test_clean_complex_join():
     metadata = sa.MetaData()
     lhs = sa.Table('amounts', metadata,
@@ -759,15 +752,19 @@ def test_columnwise_on_complex_selection():
 
 
 def test_reductions_on_complex_selections():
-    assert normalize(str(select(compute(t[t.amount > 0].id.sum(), s)))) == \
-        normalize("""
-    with alias as
-        (select accounts.id as id
-         from
-            accounts
-         where
-            accounts.amount > :amount_1)
-    select sum(alias.id) as id_sum from alias""")
+    assert (
+        normalize(str(select(compute(t[t.amount > 0].id.sum(), s)))) ==
+        normalize(
+            """
+            select
+                sum(alias.id) as id_sum
+            from (select
+                    accounts.id as id
+                  from accounts
+                  where accounts.amount > :amount_1) as alias
+            """
+        )
+    )
 
 
 def test_clean_summary_by_where():
@@ -948,16 +945,16 @@ def test_join_on_same_table():
     result = compute(expr, {t: T})
 
     assert normalize(str(result)) == normalize("""
-    with alias as
-    (select
-         tab_left.b as b_left
-     from
-         tab as tab_left
-     join
-         tab as tab_right
-     on
-         tab_left.a = tab_right.a)
-    select sum(alias.b_left) as b_left_sum from alias""")
+    select sum(alias.b_left) as b_left_sum from
+        (select
+            tab_left.b as b_left
+        from
+            tab as tab_left
+        join
+            tab as tab_right
+        on
+            tab_left.a = tab_right.a) as
+        alias""")
 
     expr = join(t, t, 'a')
     expr = summary(total=expr.a.sum(), smallest=expr.b_right.min())
@@ -1586,11 +1583,11 @@ def test_transform_then_project():
 def test_reduce_does_not_compose():
     expr = by(t.name, counts=t.count()).counts.max()
     result = str(compute(expr, s))
-    expected = """WITH alias AS
-(SELECT count(accounts.id) AS counts
-FROM accounts GROUP BY accounts.name)
- SELECT max(alias.counts) AS counts_max
-FROM alias"""
+    expected = """
+    SELECT max(alias.counts) AS counts_max
+    FROM
+    (SELECT count(accounts.id) AS counts
+    FROM accounts GROUP BY accounts.name) as alias"""
     assert normalize(result) == normalize(expected)