Repository: treasure-data/td-client-python

Issue:
[Q] An error occurs when the record count of `read_sql` is 0
I executed the following Python script.

```
pandas.read_sql("SELECT * FROM td_table WHERE 1=0", td)
=> tdclient.errors.InternalError: row index out of bound (0 out of 0)
```

Is this in the specifications?

In the case of `mysql-connector-python`, the result is as follows.
```
pandas.read_sql("SELECT * FROM mysql_table WHERE 1=0", mysql)
=> Empty DataFrame
```

Discussion:

Patch:
diff --git a/tdclient/cursor.py b/tdclient/cursor.py
index ef4b159..83cc292 100644
--- a/tdclient/cursor.py
+++ b/tdclient/cursor.py
@@ -82,15 +82,22 @@ class Cursor(object):
         return [ (column[0], None, None, None, None, None, None) for column in result_schema ]
 
     def fetchone(self):
+        """
+        Fetch the next row of a query result set, returning a single sequence, or `None` when no more data is available.
+        """
         self._check_executed()
         if self._rownumber < self._rowcount:
             row = self._rows[self._rownumber]
             self._rownumber += 1
             return row
         else:
-            raise errors.InternalError("index out of bound (%d out of %d)" % (self._rownumber, self._rowcount))
+            return None
 
     def fetchmany(self, size=None):
+        """
+        Fetch the next set of rows of a query result, returning a sequence of sequences (e.g. a list of tuples).
+        An empty sequence is returned when no more rows are available.
+        """
         if size is None:
             return self.fetchall()
         else:
@@ -103,13 +110,17 @@ class Cursor(object):
                 raise errors.InternalError("index out of bound (%d out of %d)" % (self._rownumber, self._rowcount))
 
     def fetchall(self):
+        """
+        Fetch all (remaining) rows of a query result, returning them as a sequence of sequences (e.g. a list of tuples).
+        Note that the cursor's arraysize attribute can affect the performance of this operation.
+        """
         self._check_executed()
         if self._rownumber < self._rowcount:
             rows = self._rows[self._rownumber:]
             self._rownumber = self._rowcount
             return rows
         else:
-            raise errors.InternalError("row index out of bound (%d out of %d)" % (self._rownumber, self._rowcount))
+            return []
 
     def nextset(self):
         raise errors.NotSupportedError


Test patch:
diff --git a/tdclient/test/cursor_test.py b/tdclient/test/cursor_test.py
index 24780e7..0c0e368 100644
--- a/tdclient/test/cursor_test.py
+++ b/tdclient/test/cursor_test.py
@@ -133,8 +133,7 @@ def test_fetchone():
     assert td.fetchone() == ["foo", 1]
     assert td.fetchone() == ["bar", 1]
     assert td.fetchone() == ["baz", 2]
-    with pytest.raises(errors.InternalError) as error:
-        td.fetchone()
+    assert td.fetchone() == None
 
 def test_fetchmany():
     td = cursor.Cursor(mock.MagicMock())
@@ -144,8 +143,9 @@ def test_fetchmany():
     td._rowcount = len(td._rows)
     assert td.fetchmany(2) == [["foo", 1], ["bar", 1]]
     assert td.fetchmany() == [["baz", 2]]
+    assert td.fetchmany() == []
     with pytest.raises(errors.InternalError) as error:
-        td.fetchmany()
+        td.fetchmany(1)
 
 def test_fetchall():
     td = cursor.Cursor(mock.MagicMock())
@@ -154,8 +154,7 @@ def test_fetchall():
     td._rownumber = 0
     td._rowcount = len(td._rows)
     assert td.fetchall() == [["foo", 1], ["bar", 1], ["baz", 2]]
-    with pytest.raises(errors.InternalError) as error:
-        td.fetchall()
+    assert td.fetchall() == []
 
 def test_show_job():
     td = cursor.Cursor(mock.MagicMock())