Repository: mars-project/mars

Issue:
[BUG] `md.read_sql` failed when a sql is provided
<!--
Thank you for your contribution!

Please review https://github.com/mars-project/mars/blob/master/CONTRIBUTING.rst before opening an issue.
-->

**Describe the bug**

`md.read_sql` failed when a sql is provided.

**To Reproduce**
To help us reproducing this bug, please provide information below:
1. Your Python version
2. The version of Mars you use
3. Versions of crucial packages, such as numpy, scipy and protobuf
4. Full stack of the error.
5. Minimized code to reproduce the error.

The dataset can be downloaded from https://www.kaggle.com/wendykan/lending-club-loan-data?select=database.sqlite .

Code:

```python
In [1]: import mars.dataframe as md

In [7]: import sqlalchemy as sa                                                                                                                                                                                                                                                                                                                                             

In [9]: con = sa.create_engine('sqlite:///database.sqlite', echo=False)                                                                                                                                                                                                                                                                                                     

In [10]: df = md.read_sql('loan', con)                                                                                                                                                                                                                                                                                                                                      

In [11]: df.head().execute()                                                                                                                                                                                                                                                                                                                                                
Out[11]: 
  id member_id loan_amnt funded_amnt funded_amnt_inv        term int_rate installment grade sub_grade  ... hardship_payoff_balance_amount hardship_last_payment_amount disbursement_method debt_settlement_flag debt_settlement_flag_date settlement_status settlement_date settlement_amount settlement_percentage settlement_term
0                   1000        1000               0   36 months    10.71       32.61     B        B5  ...                                                                            Cash                    N                                                                                                                    
1                   1000        1000               0   36 months    16.08        35.2     F        F2  ...                                                                            Cash                    N                                                                                                                    
2                   1000        1000               0   36 months     9.45       32.01     B        B1  ...                                                                            Cash                    N                                                                                                                    
3                   1000        1000               0   36 months     9.64       32.11     B        B4  ...                                                                            Cash                    N                                                                                                                    
4                   1000        1000  0.004353680261   36 months    11.28       32.88     C        C1  ...                                                                            Cash                    N                                                                                                                    

[5 rows x 145 columns]

In [12]: df = md.read_sql("select * from loan where grade='A' and disbursement_method='cash'", con)                                                                                                                                                                                                                                                                         

In [13]: df.head().execute()                                                                                                                                                                                                                                                                                                                                                
/Users/xuyeqin/Workspace/mars/mars/dataframe/datasource/read_sql.py:277: RuntimeWarning: divide by zero encountered in double_scalars
  chunk_size = (int(options.chunk_store_limit / op.row_memory_usage), df.shape[1])
---------------------------------------------------------------------------
OverflowError                             Traceback (most recent call last)
<ipython-input-13-017f342e9a9d> in <module>
----> 1 df.head().execute()

~/Workspace/mars/mars/core.py in execute(self, session, **kw)
    560 
    561     def execute(self, session=None, **kw):
--> 562         self._data.execute(session, **kw)
    563         return self
    564 

~/Workspace/mars/mars/core.py in execute(self, session, **kw)
    369 
    370         # no more fetch, thus just fire run
--> 371         session.run(self, **kw)
    372         # return Tileable or ExecutableTuple itself
    373         return self

~/Workspace/mars/mars/session.py in run(self, *tileables, **kw)
    426         tileables = tuple(mt.tensor(t) if not isinstance(t, (Entity, Base)) else t
    427                           for t in tileables)
--> 428         result = self._sess.run(*tileables, **kw)
    429 
    430         for t in tileables:

~/Workspace/mars/mars/session.py in run(self, *tileables, **kw)
    103             # set number of running cores
    104             self.context.set_ncores(kw['n_parallel'])
--> 105             res = self._executor.execute_tileables(tileables, **kw)
    106             return res
    107 

~/Workspace/mars/mars/utils.py in _wrapped(*args, **kwargs)
    387                 _kernel_mode.eager = False
    388             _kernel_mode.eager_count = enter_eager_count + 1
--> 389             return func(*args, **kwargs)
    390         finally:
    391             _kernel_mode.eager_count -= 1

~/Workspace/mars/mars/utils.py in inner(*args, **kwargs)
    481     def inner(*args, **kwargs):
    482         with build_mode():
--> 483             return func(*args, **kwargs)
    484     return inner
    485 

~/Workspace/mars/mars/executor.py in execute_tileables(self, tileables, fetch, n_parallel, n_thread, print_progress, mock, compose, name)
    840                 # build chunk graph, tile will be done during building
    841                 chunk_graph = chunk_graph_builder.build(
--> 842                     tileables, tileable_graph=tileable_graph)
    843                 tileable_graph = chunk_graph_builder.prev_tileable_graph
    844                 temp_result_keys = set(result_keys)

~/Workspace/mars/mars/utils.py in _wrapped(*args, **kwargs)
    387                 _kernel_mode.eager = False
    388             _kernel_mode.eager_count = enter_eager_count + 1
--> 389             return func(*args, **kwargs)
    390         finally:
    391             _kernel_mode.eager_count -= 1

~/Workspace/mars/mars/utils.py in inner(*args, **kwargs)
    481     def inner(*args, **kwargs):
    482         with build_mode():
--> 483             return func(*args, **kwargs)
    484     return inner
    485 

~/Workspace/mars/mars/tiles.py in build(self, tileables, tileable_graph)
    348 
    349         chunk_graph = super().build(
--> 350             tileables, tileable_graph=tileable_graph)
    351         self._iterative_chunk_graphs.append(chunk_graph)
    352         if len(self._interrupted_ops) == 0:

~/Workspace/mars/mars/utils.py in _wrapped(*args, **kwargs)
    387                 _kernel_mode.eager = False
    388             _kernel_mode.eager_count = enter_eager_count + 1
--> 389             return func(*args, **kwargs)
    390         finally:
    391             _kernel_mode.eager_count -= 1

~/Workspace/mars/mars/utils.py in inner(*args, **kwargs)
    481     def inner(*args, **kwargs):
    482         with build_mode():
--> 483             return func(*args, **kwargs)
    484     return inner
    485 

~/Workspace/mars/mars/tiles.py in build(self, tileables, tileable_graph)
    261                     # for further execution
    262                     partial_tiled_chunks = \
--> 263                         self._on_tile_failure(tileable_data.op, exc_info)
    264                     if partial_tiled_chunks is not None and \
    265                             len(partial_tiled_chunks) > 0:

~/Workspace/mars/mars/tiles.py in inner(op, exc_info)
    300                     on_tile_failure(op, exc_info)
    301                 else:
--> 302                     raise exc_info[1].with_traceback(exc_info[2]) from None
    303         return inner
    304 

~/Workspace/mars/mars/tiles.py in build(self, tileables, tileable_graph)
    241                 continue
    242             try:
--> 243                 tiled = self._tile(tileable_data, tileable_graph)
    244                 tiled_op.add(tileable_data.op)
    245                 for t, td in zip(tileable_data.op.outputs, tiled):

~/Workspace/mars/mars/tiles.py in _tile(self, tileable_data, tileable_graph)
    336         if any(inp.op in self._interrupted_ops for inp in tileable_data.inputs):
    337             raise TilesError('Tile fail due to failure of inputs')
--> 338         return super()._tile(tileable_data, tileable_graph)
    339 
    340     @kernel_mode

~/Workspace/mars/mars/tiles.py in _tile(self, tileable_data, tileable_graph)
    199                 t._nsplits = o.nsplits
    200         elif on_tile is None:
--> 201             tds[0]._inplace_tile()
    202         else:
    203             tds = on_tile(tileable_data.op.outputs, tds)

~/Workspace/mars/mars/core.py in _inplace_tile(self)
    161 
    162     def _inplace_tile(self):
--> 163         return handler.inplace_tile(self)
    164 
    165     def __getattr__(self, attr):

~/Workspace/mars/mars/tiles.py in inplace_tile(self, to_tile)
    134         if not to_tile.is_coarse():
    135             return to_tile
--> 136         dispatched = self.dispatch(to_tile.op)
    137         self._assign_to([d.data for d in dispatched], to_tile.op.outputs)
    138         return to_tile

~/Workspace/mars/mars/utils.py in _wrapped(*args, **kwargs)
    387                 _kernel_mode.eager = False
    388             _kernel_mode.eager_count = enter_eager_count + 1
--> 389             return func(*args, **kwargs)
    390         finally:
    391             _kernel_mode.eager_count -= 1

~/Workspace/mars/mars/tiles.py in dispatch(self, op)
    117         else:
    118             try:
--> 119                 tiled = op_cls.tile(op)
    120             except NotImplementedError as ex:
    121                 cause = ex

~/Workspace/mars/mars/dataframe/datasource/read_sql.py in tile(cls, op)
    370     def tile(cls, op: 'DataFrameReadSQL'):
    371         if op.method == 'offset':
--> 372             return cls._tile_offset(op)
    373         else:
    374             return cls._tile_partition(op)

~/Workspace/mars/mars/dataframe/datasource/read_sql.py in _tile_offset(cls, op)
    275         chunk_size = df.extra_params.raw_chunk_size or options.chunk_size
    276         if chunk_size is None:
--> 277             chunk_size = (int(options.chunk_store_limit / op.row_memory_usage), df.shape[1])
    278         row_chunk_sizes = normalize_chunk_sizes(df.shape, chunk_size)[0]
    279         offsets = np.cumsum((0,) + row_chunk_sizes)

OverflowError: cannot convert float infinity to integer
```

And then I specify `chunk_size`, but another error raised.

```python
In [14]: df = md.read_sql("select * from loan where grade='A' and disbursement_method='cash'", con, chunk_size=10)                                                                                                                                                                                                                                                          

In [16]: df.head(10).execute()                                                                                                                                                                                                                                                                                                                                              
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-16-4299515d4e9d> in <module>
----> 1 df.head(10).execute()

~/Workspace/mars/mars/core.py in execute(self, session, **kw)
    560 
    561     def execute(self, session=None, **kw):
--> 562         self._data.execute(session, **kw)
    563         return self
    564 

~/Workspace/mars/mars/core.py in execute(self, session, **kw)
    369 
    370         # no more fetch, thus just fire run
--> 371         session.run(self, **kw)
    372         # return Tileable or ExecutableTuple itself
    373         return self

~/Workspace/mars/mars/session.py in run(self, *tileables, **kw)
    426         tileables = tuple(mt.tensor(t) if not isinstance(t, (Entity, Base)) else t
    427                           for t in tileables)
--> 428         result = self._sess.run(*tileables, **kw)
    429 
    430         for t in tileables:

~/Workspace/mars/mars/session.py in run(self, *tileables, **kw)
    103             # set number of running cores
    104             self.context.set_ncores(kw['n_parallel'])
--> 105             res = self._executor.execute_tileables(tileables, **kw)
    106             return res
    107 

~/Workspace/mars/mars/utils.py in _wrapped(*args, **kwargs)
    387                 _kernel_mode.eager = False
    388             _kernel_mode.eager_count = enter_eager_count + 1
--> 389             return func(*args, **kwargs)
    390         finally:
    391             _kernel_mode.eager_count -= 1

~/Workspace/mars/mars/utils.py in inner(*args, **kwargs)
    481     def inner(*args, **kwargs):
    482         with build_mode():
--> 483             return func(*args, **kwargs)
    484     return inner
    485 

~/Workspace/mars/mars/executor.py in execute_tileables(self, tileables, fetch, n_parallel, n_thread, print_progress, mock, compose, name)
    840                 # build chunk graph, tile will be done during building
    841                 chunk_graph = chunk_graph_builder.build(
--> 842                     tileables, tileable_graph=tileable_graph)
    843                 tileable_graph = chunk_graph_builder.prev_tileable_graph
    844                 temp_result_keys = set(result_keys)

~/Workspace/mars/mars/utils.py in _wrapped(*args, **kwargs)
    387                 _kernel_mode.eager = False
    388             _kernel_mode.eager_count = enter_eager_count + 1
--> 389             return func(*args, **kwargs)
    390         finally:
    391             _kernel_mode.eager_count -= 1

~/Workspace/mars/mars/utils.py in inner(*args, **kwargs)
    481     def inner(*args, **kwargs):
    482         with build_mode():
--> 483             return func(*args, **kwargs)
    484     return inner
    485 

~/Workspace/mars/mars/tiles.py in build(self, tileables, tileable_graph)
    348 
    349         chunk_graph = super().build(
--> 350             tileables, tileable_graph=tileable_graph)
    351         self._iterative_chunk_graphs.append(chunk_graph)
    352         if len(self._interrupted_ops) == 0:

~/Workspace/mars/mars/utils.py in _wrapped(*args, **kwargs)
    387                 _kernel_mode.eager = False
    388             _kernel_mode.eager_count = enter_eager_count + 1
--> 389             return func(*args, **kwargs)
    390         finally:
    391             _kernel_mode.eager_count -= 1

~/Workspace/mars/mars/utils.py in inner(*args, **kwargs)
    481     def inner(*args, **kwargs):
    482         with build_mode():
--> 483             return func(*args, **kwargs)
    484     return inner
    485 

~/Workspace/mars/mars/tiles.py in build(self, tileables, tileable_graph)
    261                     # for further execution
    262                     partial_tiled_chunks = \
--> 263                         self._on_tile_failure(tileable_data.op, exc_info)
    264                     if partial_tiled_chunks is not None and \
    265                             len(partial_tiled_chunks) > 0:

~/Workspace/mars/mars/tiles.py in inner(op, exc_info)
    300                     on_tile_failure(op, exc_info)
    301                 else:
--> 302                     raise exc_info[1].with_traceback(exc_info[2]) from None
    303         return inner
    304 

~/Workspace/mars/mars/tiles.py in build(self, tileables, tileable_graph)
    241                 continue
    242             try:
--> 243                 tiled = self._tile(tileable_data, tileable_graph)
    244                 tiled_op.add(tileable_data.op)
    245                 for t, td in zip(tileable_data.op.outputs, tiled):

~/Workspace/mars/mars/tiles.py in _tile(self, tileable_data, tileable_graph)
    336         if any(inp.op in self._interrupted_ops for inp in tileable_data.inputs):
    337             raise TilesError('Tile fail due to failure of inputs')
--> 338         return super()._tile(tileable_data, tileable_graph)
    339 
    340     @kernel_mode

~/Workspace/mars/mars/tiles.py in _tile(self, tileable_data, tileable_graph)
    199                 t._nsplits = o.nsplits
    200         elif on_tile is None:
--> 201             tds[0]._inplace_tile()
    202         else:
    203             tds = on_tile(tileable_data.op.outputs, tds)

~/Workspace/mars/mars/core.py in _inplace_tile(self)
    161 
    162     def _inplace_tile(self):
--> 163         return handler.inplace_tile(self)
    164 
    165     def __getattr__(self, attr):

~/Workspace/mars/mars/tiles.py in inplace_tile(self, to_tile)
    134         if not to_tile.is_coarse():
    135             return to_tile
--> 136         dispatched = self.dispatch(to_tile.op)
    137         self._assign_to([d.data for d in dispatched], to_tile.op.outputs)
    138         return to_tile

~/Workspace/mars/mars/utils.py in _wrapped(*args, **kwargs)
    387                 _kernel_mode.eager = False
    388             _kernel_mode.eager_count = enter_eager_count + 1
--> 389             return func(*args, **kwargs)
    390         finally:
    391             _kernel_mode.eager_count -= 1

~/Workspace/mars/mars/tiles.py in dispatch(self, op)
    117         else:
    118             try:
--> 119                 tiled = op_cls.tile(op)
    120             except NotImplementedError as ex:
    121                 cause = ex

~/Workspace/mars/mars/dataframe/indexing/iloc.py in tile(cls, op)
    338 
    339         handler = DataFrameIlocIndexesHandler()
--> 340         return [handler.handle(op)]
    341 
    342     @classmethod

~/Workspace/mars/mars/tensor/indexing/index_lib.py in handle(self, op, return_context)
    896 
    897         self._preprocess(context, index_infos)
--> 898         self._process(context, index_infos)
    899         self._postprocess(context, index_infos)
    900 

~/Workspace/mars/mars/tensor/indexing/index_lib.py in _process(cls, context, index_infos)
    923         index_to_shape = OrderedDict(sorted([(c.index, c.shape) for c in out_chunks],
    924                                             key=itemgetter(0)))
--> 925         context.out_nsplits = calc_nsplits(index_to_shape)
    926 
    927     @classmethod

~/Workspace/mars/mars/utils.py in calc_nsplits(chunk_idx_to_shape)
    658     :return: nsplits
    659     """
--> 660     ndim = len(next(iter(chunk_idx_to_shape)))
    661     tileable_nsplits = []
    662     # for each dimension, record chunk shape whose index is zero on other dimensions

StopIteration:
```



Discussion:

Resolving patch:
diff --git a/mars/dataframe/datasource/read_sql.py b/mars/dataframe/datasource/read_sql.py
--- a/mars/dataframe/datasource/read_sql.py
+++ b/mars/dataframe/datasource/read_sql.py
@@ -188,8 +188,11 @@ def _collect_info(self, engine_or_conn, selectable, columns, test_rows):
         test_df = pd.read_sql(query, engine_or_conn, index_col=self._index_col,
                               coerce_float=self._coerce_float,
                               parse_dates=self._parse_dates)
-        self._row_memory_usage = \
-            test_df.memory_usage(deep=True, index=True).sum() / test_rows
+        if len(test_df) == 0:
+            self._row_memory_usage = None
+        else:
+            self._row_memory_usage = \
+                test_df.memory_usage(deep=True, index=True).sum() / len(test_df)
 
         if self._method == 'offset':
             # fetch size
@@ -272,10 +275,15 @@ def __call__(self, test_rows, chunk_size):
     def _tile_offset(cls, op: 'DataFrameReadSQL'):
         df = op.outputs[0]
 
-        chunk_size = df.extra_params.raw_chunk_size or options.chunk_size
-        if chunk_size is None:
-            chunk_size = (int(options.chunk_store_limit / op.row_memory_usage), df.shape[1])
-        row_chunk_sizes = normalize_chunk_sizes(df.shape, chunk_size)[0]
+        if op.row_memory_usage is not None:
+            # Data selected
+            chunk_size = df.extra_params.raw_chunk_size or options.chunk_size
+            if chunk_size is None:
+                chunk_size = (int(options.chunk_store_limit / op.row_memory_usage), df.shape[1])
+            row_chunk_sizes = normalize_chunk_sizes(df.shape, chunk_size)[0]
+        else:
+            # No data selected
+            row_chunk_sizes = (0,)
         offsets = np.cumsum((0,) + row_chunk_sizes)
 
         out_chunks = []
diff --git a/mars/scheduler/graph.py b/mars/scheduler/graph.py
--- a/mars/scheduler/graph.py
+++ b/mars/scheduler/graph.py
@@ -753,6 +753,9 @@ def analyze_graph(self, **kwargs):
                 operand_infos[k]['optimize']['successor_size'] = succ_size
 
         worker_slots = self._get_worker_slots()
+        if not worker_slots:
+            raise RuntimeError('No worker attached for execution')
+
         self._assigned_workers = set(worker_slots)
         analyzer = GraphAnalyzer(chunk_graph, worker_slots)
 


Test patch:
diff --git a/mars/dataframe/datasource/tests/test_datasource_execution.py b/mars/dataframe/datasource/tests/test_datasource_execution.py
--- a/mars/dataframe/datasource/tests/test_datasource_execution.py
+++ b/mars/dataframe/datasource/tests/test_datasource_execution.py
@@ -505,6 +505,11 @@ def testReadSQLExecution(self):
             result = self.executor.execute_dataframe(r, concat=True)[0]
             pd.testing.assert_frame_equal(result, test_df[test_df.b > 's5'].set_index('d'))
 
+            # test SQL that return no result
+            r = md.read_sql_query('select * from test where a > 1000', uri)
+            result = self.executor.execute_dataframe(r, concat=True)[0]
+            pd.testing.assert_frame_equal(result, pd.DataFrame(columns=test_df.columns))
+
             engine = sa.create_engine(uri)
             m = sa.MetaData()
             try:
diff --git a/mars/scheduler/tests/integrated/test_normal_execution.py b/mars/scheduler/tests/integrated/test_normal_execution.py
--- a/mars/scheduler/tests/integrated/test_normal_execution.py
+++ b/mars/scheduler/tests/integrated/test_normal_execution.py
@@ -26,6 +26,8 @@
 
 from mars import tensor as mt
 from mars.actors.core import new_client
+from mars.errors import ExecutionFailed
+from mars.session import new_session
 from mars.scheduler.graph import GraphState
 from mars.scheduler.resource import ResourceActor
 from mars.scheduler.tests.integrated.base import SchedulerIntegratedTest
@@ -467,3 +469,18 @@ def f2(x, y, z=None):
         result = session_ref.fetch_result(graph_key, r3.key)
         expected = (raw1 + 1) * (raw2 + 1) * (raw1 + 1 + raw2 + 1)
         assert_allclose(loads(result), expected)
+
+    def testNoWorkerException(self):
+        self.start_processes(etcd=False, n_workers=0)
+
+        a = mt.ones((10, 10))
+        b = mt.ones((10, 10))
+        c = (a + b)
+
+        endpoint = self.scheduler_endpoints[0]
+        sess = new_session(endpoint)
+
+        try:
+            c.execute(session=sess)
+        except ExecutionFailed as e:
+            self.assertIsInstance(e.__cause__, RuntimeError)
diff --git a/mars/scheduler/tests/test_graph.py b/mars/scheduler/tests/test_graph.py
--- a/mars/scheduler/tests/test_graph.py
+++ b/mars/scheduler/tests/test_graph.py
@@ -27,6 +27,8 @@
 
 
 @patch_method(ResourceActor._broadcast_sessions)
+@patch_method(GraphActor._get_worker_slots,
+              new=lambda *_: {'localhost:12345': 1, 'localhost:23456': 1})
 class Test(unittest.TestCase):
     @contextlib.contextmanager
     def prepare_graph_in_pool(self, expr, clean_io_meta=True, compose=False):