Repository: blaze/blaze

Issue:
isin not working correctly on a filtered table?
I'm on Windows, connecting to an Oracle DB with cxOracle. When trying to filter on a specific set of ids blaze isn't returning the rows even though they exist in the DB. It may be important that calling `bz.compute` on the result throws a `NotImplementedError`

``` python
In [129]: data
Out[129]: 
   cc_id    cc_reference
0  27410  FUTA6437S-Q316
1  27411  FUTA6437S-Q416
2  27412  FUTA6437S-Q117
3  27413  FUTA6437S-Q217
4  27414  FUTA7202S-Q118
5  27415       FUTA7203S
6  27428  FUTA7205S-Q218
7  27429  FUTA7205S-Q318
8  27430  FUTA7205S-Q418
9  27435       FUTA7207B

In [130]: data[data.cc_id == 27410]
Out[130]: 
   cc_id    cc_reference
0  27410  FUTA6437S-Q316

In [131]: data[data.cc_id.isin({27410})]

In [132]: type(data[data.cc_id.isin({27410})])
Out[132]: blaze.expr.expressions.Selection

In [133]: bz.compute(data[data.cc_id.isin({27410})])
Traceback (most recent call last):

  File "<ipython-input-133-8b20a8ca4c59>", line 1, in <module>
    bz.compute(data[data.cc_id.isin({27410})])

  File "C:\Python\lib\site-packages\multipledispatch\dispatcher.py", line 164, in __call__
    return func(*args, **kwargs)

  File "C:\Python\lib\site-packages\blaze\interactive.py", line 168, in compute
    return compute(expr, resources, **kwargs)

  File "C:\Python\lib\site-packages\multipledispatch\dispatcher.py", line 164, in __call__
    return func(*args, **kwargs)

  File "C:\Python\lib\site-packages\blaze\compute\core.py", line 406, in compute
    result = top_then_bottom_then_top_again_etc(expr3, d4, **kwargs)

  File "C:\Python\lib\site-packages\blaze\compute\core.py", line 198, in top_then_bottom_then_top_again_etc
    return top_then_bottom_then_top_again_etc(expr3, scope4, **kwargs)

  File "C:\Python\lib\site-packages\blaze\compute\core.py", line 198, in top_then_bottom_then_top_again_etc
    return top_then_bottom_then_top_again_etc(expr3, scope4, **kwargs)

  File "C:\Python\lib\site-packages\blaze\compute\core.py", line 198, in top_then_bottom_then_top_again_etc
    return top_then_bottom_then_top_again_etc(expr3, scope4, **kwargs)

  File "C:\Python\lib\site-packages\blaze\compute\core.py", line 198, in top_then_bottom_then_top_again_etc
    return top_then_bottom_then_top_again_etc(expr3, scope4, **kwargs)

  File "C:\Python\lib\site-packages\blaze\compute\core.py", line 198, in top_then_bottom_then_top_again_etc
    return top_then_bottom_then_top_again_etc(expr3, scope4, **kwargs)

  File "C:\Python\lib\site-packages\blaze\compute\core.py", line 198, in top_then_bottom_then_top_again_etc
    return top_then_bottom_then_top_again_etc(expr3, scope4, **kwargs)

  File "C:\Python\lib\site-packages\blaze\compute\core.py", line 196, in top_then_bottom_then_top_again_etc
    "data: %s" % (expr3, scope4))

NotImplementedError: Don't know how to compute:
expr: _[cc_id.isin(frozenset({27410}))]
data: {_: <sqlalchemy.sql.selectable.Select at 0xe8fdac8; Select object>, cc_id: <sqlalchemy.sql.selectable.Select at 0x1369c860; Select object>}

```



Discussion:
**Repro:**

``` python
import blaze as bz
from numpy.random import randn
import sqlalchemy as sa

engine = sa.create_engine('sqlite://')
metadata = sa.MetaData(bind=engine)

table = sa.Table(
    'Test', metadata,
    sa.Column('id', sa.Integer, primary_key=True),
    sa.Column('value', sa.Float(32), nullable=False),
)

records = [
    {'id': idx, 'value': value}
    for idx, value
    in enumerate(randn(10))
]

with engine.connect() as conn:
    conn.execute(table.insert(), records)


data = bz.Data(table)
data = data[data.id > 4]
assert data[data.id.isin({5,7,9})].nrows == 3
```

```
NotImplementedError: Don't know how to compute:
expr: (nelements(_2[id.isin(frozenset({9, 5, 7}))])) == 3
data: {_2: <sqlalchemy.sql.selectable.Select at 0xe990668; Select object>, id: <sqlalchemy.sql.selectable.Select at 0xe990978; Select object>}
```

I am also running into this error with a Postgres backend and an `isin` query for an array of strings. It doesn't seem to matter whether the data is filtered or not. 

Downgrading from 0.10.1 to 0.8.3 solved the problem. 

I bet we have a dispatch on `Select` instead of `Selectable`


Resolving patch:
diff --git a/blaze/compute/sql.py b/blaze/compute/sql.py
--- a/blaze/compute/sql.py
+++ b/blaze/compute/sql.py
@@ -459,6 +459,16 @@ def compute_up(expr, tbl, predicate, scope=None, **kwargs):
         return select([tbl]).where(predicate)
 
 
+@dispatch(Selection, Selectable, Selectable)
+def compute_up(expr, tbl, predicate, **kwargs):
+    col, = inner_columns(predicate)
+    return reconstruct_select(
+        inner_columns(tbl),
+        tbl,
+        whereclause=unify_wheres((tbl, predicate)),
+    ).where(col)
+
+
 def select(s):
     """ Permissive SQL select
 
@@ -1408,6 +1418,15 @@ def compute_up(expr, data, **kwargs):
     return data.in_(expr._keys)
 
 
+@dispatch(IsIn, Selectable)
+def compute_up(expr, data, **kwargs):
+    assert len(data.columns) == 1, (
+        'only 1 column is allowed in a Select in IsIn'
+    )
+    col, = inner_columns(data)
+    return reconstruct_select((col.in_(expr._keys),), data)
+
+
 @dispatch(Slice, (Select, Selectable, ColumnElement))
 def compute_up(expr, data, **kwargs):
     index = expr.index[0]  # [0] replace_slices returns tuple ((start, stop), )


Test patch:
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
@@ -810,3 +810,23 @@ def test_all(sql):
     assert compute(~(s.B == 1).all(), {s: sql}, return_type='core')
     assert compute(~(s.B == 2).all(), {s: sql}, return_type='core')
     assert compute(~(s.B == 3).all(), {s: sql}, return_type='core')
+
+
+def test_isin_selectable(sql):
+    s = symbol('s', discover(sql))
+
+    # wrap the resource in a select
+    assert compute(s.B.isin({1, 3}),
+                   sa.select(sql._resources()[sql].columns),
+                   return_type=list) == [(True,), (False,)]
+
+
+def test_selection_selectable(sql):
+    s = symbol('s', discover(sql))
+
+    # wrap the resource in a select
+    assert (compute(s[s.B.isin({1, 3})],
+                    sa.select(sql._resources()[sql].columns),
+                    return_type=pd.DataFrame) ==
+            pd.DataFrame([['a', 1]],
+                         columns=s.dshape.measure.names)).all().all()