Repository: mars-project/mars

Issue:
[BUG] Error `Tileable object must be executed first before being fetched` is wrongly raised
<!--
Thank you for your contribution!

Please review https://github.com/mars-project/mars/blob/master/CONTRIBUTING.rst before opening an issue.
-->

**Describe the bug**

Error `Tileable object must be executed first before being fetched` is wrongly raised.

**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.

```
In [20]: df = md.DataFrame(mt.random.rand(10, 3, chunk_size=3))                                                                                                                                             

In [21]: df.execute()                                                                                                                                                                                       
Out[21]: 
          0         1         2
0  0.996454  0.199555  0.058095
1  0.856396  0.477889  0.869464
2  0.578357  0.449761  0.313753
3  0.907721  0.887646  0.171193
4  0.727089  0.617502  0.210623
5  0.209806  0.070762  0.183754
6  0.389748  0.779089  0.468244
7  0.506215  0.026623  0.473943
8  0.120368  0.201167  0.367040
9  0.717196  0.199664  0.741672

In [22]: df.iloc[:4].fetch(batch_size=3)                                                                                                                                                                    
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-22-3025f16f5892> in <module>
----> 1 df.iloc[:4].fetch(batch_size=3)

~/Workspace/mars/mars/dataframe/core.py in fetch(self, session, **kw)
    449             raise TypeError(
    450                 f"'{next(iter(kw))}' is an invalid keyword argument for this function")
--> 451         batches = list(self._iter(batch_size=batch_size, session=session))
    452         return pd.concat(batches) if len(batches) > 1 else batches[0]
    453 

~/Workspace/mars/mars/dataframe/core.py in _iter(self, batch_size, session)
    435             for i in range(n_batch):
    436                 batch_data = iloc(self)[batch_size * i: batch_size * (i + 1)]
--> 437                 yield batch_data._fetch(session=session)
    438         else:
    439             yield self._fetch(session=session)

~/Workspace/mars/mars/core.py in _fetch(self, session, **kw)
    406         session = self._get_session(session)
    407         self._check_session(session, 'fetch')
--> 408         return session.fetch(self, **kw)
    409 
    410     def fetch(self, session=None, **kw):

~/Workspace/mars/mars/session.py in fetch(self, *tileables, **kw)
    532             ret_list = True
    533 
--> 534         result = self._sess.fetch(*tileables, **kw)
    535 
    536         ret = []

~/Workspace/mars/mars/session.py in fetch(self, n_parallel, *tileables, **kw)
    126         if n_parallel is None:
    127             kw['n_parallel'] = cpu_count()
--> 128         return self._executor.fetch_tileables(tileables, **kw)
    129 
    130     def fetch_log(self, tileables, offsets=None, sizes=None):  # pragma: no cover

~/Workspace/mars/mars/utils.py in _inner(*args, **kwargs)
    449         def _inner(*args, **kwargs):
    450             with self:
--> 451                 return func(*args, **kwargs)
    452 
    453         return _inner

~/Workspace/mars/mars/executor.py in fetch_tileables(self, tileables, **kw)
    973                 # check if the tileable is executed before
    974                 raise ValueError(
--> 975                     f'Tileable object {tileable.key} must be executed first before being fetched')
    976 
    977         # if chunk executed, fetch chunk mechanism will be triggered in execute_tileables

ValueError: Tileable object 781bdacb33bac80f7e5b8f92ae3923ba must be executed first before being fetched
```


Discussion:
This bug is introduced in #1853 .

Resolving patch:
diff --git a/mars/dataframe/core.py b/mars/dataframe/core.py
--- a/mars/dataframe/core.py
+++ b/mars/dataframe/core.py
@@ -445,12 +445,20 @@ def iterbatch(self, batch_size=1000, session=None, **kw):
         return self._iter(batch_size=batch_size, session=session)
 
     def fetch(self, session=None, **kw):
+        from .indexing.iloc import DataFrameIlocGetItem, SeriesIlocGetItem
+
         batch_size = kw.pop('batch_size', 1000)
         if len(kw) > 0:  # pragma: no cover
             raise TypeError(
                 f"'{next(iter(kw))}' is an invalid keyword argument for this function")
-        batches = list(self._iter(batch_size=batch_size, session=session))
-        return pd.concat(batches) if len(batches) > 1 else batches[0]
+
+        if isinstance(self.op, (DataFrameIlocGetItem, SeriesIlocGetItem)):
+            # see GH#1871
+            # already iloc, do not trigger batch fetch
+            return self._fetch(session=session, **kw)
+        else:
+            batches = list(self._iter(batch_size=batch_size, session=session))
+            return pd.concat(batches) if len(batches) > 1 else batches[0]
 
 
 class IndexData(HasShapeTileableData, _ToPandasMixin):


Test patch:
diff --git a/mars/tests/test_session.py b/mars/tests/test_session.py
--- a/mars/tests/test_session.py
+++ b/mars/tests/test_session.py
@@ -500,6 +500,13 @@ def testFetchDataFrameSlices(self):
         r4 = sess.fetch(df1.iloc[0, 2])
         self.assertEqual(r4, r1.iloc[0, 2])
 
+        arr2 = mt.random.rand(10, 3, chunk_size=3)
+        df2 = md.DataFrame(arr2)
+        r5 = sess.run(df2)
+
+        r6 = df2.iloc[:4].fetch(batch_size=3, session=sess)
+        pd.testing.assert_frame_equal(r5.iloc[:4], r6)
+
     def testMultiOutputsOp(self):
         sess = new_session()