This is an automated email from the ASF dual-hosted git repository.

isapego pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new bb51f3cc7c IGNITE-22741 DB API Driver 3: Implement Data Fetching 
(#4321)
bb51f3cc7c is described below

commit bb51f3cc7c3df8cc373e27d3d25c55a61a76b630
Author: Igor Sapego <[email protected]>
AuthorDate: Tue Sep 3 12:51:01 2024 +0400

    IGNITE-22741 DB API Driver 3: Implement Data Fetching (#4321)
---
 .../platforms/cpp/ignite/odbc/query/data_query.cpp |  11 +-
 .../platforms/cpp/ignite/odbc/query/data_query.h   |  14 ++
 modules/platforms/python/cpp_module/py_cursor.cpp  | 137 ++++++++++++++-
 modules/platforms/python/pyignite3/__init__.py     |  73 ++++++--
 modules/platforms/python/tests/test_execute.py     |   6 +-
 .../platforms/python/tests/test_fetch_constants.py | 188 ++++++++++++++++++++
 modules/platforms/python/tests/test_fetch_table.py | 193 +++++++++++++++++++++
 7 files changed, 600 insertions(+), 22 deletions(-)

diff --git a/modules/platforms/cpp/ignite/odbc/query/data_query.cpp 
b/modules/platforms/cpp/ignite/odbc/query/data_query.cpp
index 6f80cead4d..64d3fda363 100644
--- a/modules/platforms/cpp/ignite/odbc/query/data_query.cpp
+++ b/modules/platforms/cpp/ignite/odbc/query/data_query.cpp
@@ -138,7 +138,7 @@ const sql_parameter *data_query::get_sql_param(std::int16_t 
idx) {
     return nullptr;
 }
 
-sql_result data_query::fetch_next_row(column_binding_map &column_bindings) {
+sql_result data_query::fetch_next_row() {
     if (!m_executed) {
         m_diag.add_status_record(sql_state::SHY010_SEQUENCE_ERROR, "Query was 
not executed.");
 
@@ -167,6 +167,15 @@ sql_result data_query::fetch_next_row(column_binding_map 
&column_bindings) {
     if (!m_cursor->has_data())
         return sql_result::AI_NO_DATA;
 
+    return sql_result::AI_SUCCESS;
+}
+
+sql_result data_query::fetch_next_row(column_binding_map &column_bindings) {
+    auto res = fetch_next_row();
+    if (res != ignite::sql_result::AI_SUCCESS && res != 
ignite::sql_result::AI_SUCCESS_WITH_INFO) {
+        return res;
+    }
+
     auto row = m_cursor->get_row();
     assert(!row.empty());
 
diff --git a/modules/platforms/cpp/ignite/odbc/query/data_query.h 
b/modules/platforms/cpp/ignite/odbc/query/data_query.h
index 396b14bca7..fb12b7b463 100644
--- a/modules/platforms/cpp/ignite/odbc/query/data_query.h
+++ b/modules/platforms/cpp/ignite/odbc/query/data_query.h
@@ -77,6 +77,20 @@ public:
      */
     const column_meta_vector *get_meta() override;
 
+    /**
+     * Fetch next result row.
+     *
+     * @return Operation result.
+     */
+    sql_result fetch_next_row();
+
+    /**
+     * Get current row.
+     *
+     * @return  Row.
+     */
+    [[nodiscard]] const std::vector<primitive> &get_current_row() const { 
return m_cursor->get_row(); }
+
     /**
      * Fetch next result row to application buffers.
      *
diff --git a/modules/platforms/python/cpp_module/py_cursor.cpp 
b/modules/platforms/python/cpp_module/py_cursor.cpp
index 39a4961eb4..ca9fbca610 100644
--- a/modules/platforms/python/cpp_module/py_cursor.cpp
+++ b/modules/platforms/python/cpp_module/py_cursor.cpp
@@ -16,6 +16,7 @@
  */
 
 #include <ignite/odbc/sql_statement.h>
+#include <ignite/odbc/query/data_query.h>
 
 #include <ignite/common/detail/config.h>
 
@@ -70,7 +71,7 @@ static PyObject* py_cursor_execute(py_cursor* self, PyObject* 
args, PyObject* kw
     };
 
     const char* query = nullptr;
-    // TODO IGNITE-22741 Support parameters
+    // TODO IGNITE-23126 Support parameters
     PyObject *params = nullptr;
 
     int parsed = PyArg_ParseTupleAndKeywords(args, kwargs, "s|O", kwlist, 
&query, &params);
@@ -101,6 +102,137 @@ static PyObject* py_cursor_rowcount(py_cursor* self, 
PyObject*)
     return PyLong_FromLong(long(query->affected_rows()));
 }
 
+static PyObject* primitive_to_pyobject(ignite::primitive value) {
+    using ignite::ignite_type;
+
+    if (value.is_null()) {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+    switch (value.get_type()) {
+        case ignite_type::STRING: {
+            auto &str_val = value.get<std::string>();
+            return PyUnicode_FromStringAndSize(str_val.c_str(), 
str_val.size());
+        }
+
+        case ignite_type::INT8: {
+            auto &i8_val =  value.get<std::int8_t>();
+            return PyLong_FromLong(long(i8_val));
+        }
+
+        case ignite_type::INT16: {
+            auto &i16_val =  value.get<std::int16_t>();
+            return PyLong_FromLong(long(i16_val));
+        }
+
+        case ignite_type::INT32: {
+            auto &i32_val =  value.get<std::int32_t>();
+            return PyLong_FromLong(long(i32_val));
+        }
+
+        case ignite_type::INT64: {
+            auto &i64_val =  value.get<std::int64_t>();
+            return PyLong_FromLongLong(i64_val);
+        }
+
+        case ignite_type::FLOAT: {
+            auto &float_val =  value.get<float>();
+            return PyFloat_FromDouble(float_val);
+        }
+
+        case ignite_type::DOUBLE: {
+            auto &double_val =  value.get<double>();
+            return PyFloat_FromDouble(double_val);
+        }
+
+        case ignite_type::BOOLEAN: {
+            auto &bool_val =  value.get<bool>();
+            if (bool_val) {
+                Py_RETURN_TRUE;
+            } else {
+                Py_RETURN_FALSE;
+            }
+        }
+
+        case ignite_type::BYTE_ARRAY: {
+            auto &blob_val =  value.get<std::vector<std::byte>>();
+            return PyBytes_FromStringAndSize((const char*)blob_val.data(), 
blob_val.size());
+        }
+
+        case ignite_type::UUID:
+        case ignite_type::DATE:
+        case ignite_type::TIMESTAMP:
+        case ignite_type::TIME:
+        case ignite_type::DATETIME:
+        case ignite_type::BITMASK:
+        case ignite_type::DECIMAL:
+        case ignite_type::PERIOD:
+        case ignite_type::DURATION:
+        case ignite_type::NUMBER:
+        default: {
+            // TODO: IGNITE-22745 Provide wider data types support
+            auto err_msg = "The type is not supported yet: " + 
std::to_string(int(value.get_type()));
+            PyErr_SetString(PyExc_RuntimeError, err_msg.c_str());
+            return nullptr;
+        }
+    }
+}
+
+static PyObject* py_cursor_fetchone(py_cursor* self, PyObject*)
+{
+    if (!self->m_statement) {
+        PyErr_SetString(PyExc_RuntimeError, "Cursor is in invalid state 
(Already closed?)");
+        return nullptr;
+    }
+
+    auto query = self->m_statement->get_query();
+    if (!query) {
+        PyErr_SetString(PyExc_RuntimeError, "Query was not executed");
+        return nullptr;
+    }
+
+    if (query->get_type() != ignite::query_type::DATA) {
+        auto err_msg = "Unexpected query type: " + 
std::to_string(int(query->get_type()));
+        PyErr_SetString(PyExc_RuntimeError, err_msg.c_str());
+        return nullptr;
+    }
+
+    if (!query->is_data_available()) {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+    auto& query0 = static_cast<ignite::data_query&>(*query);
+    auto res = query0.fetch_next_row();
+    if (res == ignite::sql_result::AI_NO_DATA) {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+    if (!check_errors(*self->m_statement)) {
+        return nullptr;
+    }
+
+    auto row = query0.get_current_row();
+    auto res_list = PyTuple_New(row.size());
+    if (!res_list) {
+        PyErr_SetString(PyExc_RuntimeError, "Can not allocate a new list for 
the result set");
+        return nullptr;
+    }
+
+    for (std::size_t i = 0; i < row.size(); ++i) {
+        auto py_column = primitive_to_pyobject(row[i]);
+        if (!py_column) {
+            Py_DECREF(res_list);
+            return nullptr;
+        }
+        PyTuple_SetItem(res_list, i, py_column);
+    }
+
+    return res_list;
+}
+
 static PyObject* py_cursor_column_count(py_cursor* self, PyObject*)
 {
     if (!self->m_statement) {
@@ -273,9 +405,12 @@ static PyTypeObject py_cursor_type = {
 };
 
 static struct PyMethodDef py_cursor_methods[] = {
+    // Core methods
     {"close", (PyCFunction)py_cursor_close, METH_NOARGS, nullptr},
     {"execute", (PyCFunction)py_cursor_execute, METH_VARARGS | METH_KEYWORDS, 
nullptr},
     {"rowcount", (PyCFunction)py_cursor_rowcount, METH_NOARGS, nullptr},
+    {"fetchone", (PyCFunction)py_cursor_fetchone, METH_NOARGS, nullptr},
+    // Column metadata retrieval methods
     {"column_count", (PyCFunction)py_cursor_column_count, METH_NOARGS, 
nullptr},
     {"column_name", (PyCFunction)py_cursor_column_name, METH_VARARGS, nullptr},
     {"column_type_code", (PyCFunction)py_cursor_column_type_code, 
METH_VARARGS, nullptr},
diff --git a/modules/platforms/python/pyignite3/__init__.py 
b/modules/platforms/python/pyignite3/__init__.py
index e4a77113d8..baf9cf29fb 100644
--- a/modules/platforms/python/pyignite3/__init__.py
+++ b/modules/platforms/python/pyignite3/__init__.py
@@ -15,7 +15,7 @@
 import datetime
 import decimal
 import uuid
-from typing import Optional, List
+from typing import Optional, List, Any, Sequence
 
 from pyignite3 import _pyignite3_extension
 from pyignite3 import native_type_code
@@ -36,7 +36,7 @@ BOOLEAN = bool
 INT = int
 FLOAT = float
 STRING = str
-BINARY = memoryview
+BINARY = bytes
 NUMBER = decimal.Decimal
 DATE = datetime.date
 TIME = datetime.time
@@ -91,10 +91,10 @@ class Cursor:
     """
     Cursor class. Represents a single statement and holds the result of its 
execution.
     """
+
     def __init__(self, py_cursor):
         self._py_cursor = py_cursor
 
-        # TODO: IGNITE-22741 Implement data fetching
         self.arraysize = 1
         self._description = None
 
@@ -170,6 +170,7 @@ class Cursor:
 
     def _update_description(self):
         """
+        Internal method.
         Update column description for the current cursor. To be called after 
query execution.
         """
         self._description = []
@@ -191,26 +192,64 @@ class Cursor:
         # TODO: IGNITE-22742 Implement execution with a batch of parameters
         raise NotSupportedError('Operation is not supported')
 
-    def fetchone(self):
+    def fetchone(self) -> Optional[Sequence[Optional[Any]]]:
+        """
+        Fetch the next row of a query result set, returning a single sequence, 
or None when no more data is available.
+        An Error (or subclass) exception is raised if the previous call to 
.execute*() did not produce any result set
+        or no call was issued yet.
+        """
         if self._py_cursor is None:
             raise InterfaceError('Connection is already closed')
 
-        # TODO: IGNITE-22741 Implement data fetching
-        raise NotSupportedError('Operation is not supported')
+        return self._py_cursor.fetchone()
+
+    def fetchmany(self, size: Optional[int] = None) -> 
Optional[Sequence[Sequence[Optional[Any]]]]:
+        """
+        Fetch the next set of rows of a query result, returning a sequence of 
sequences. An empty sequence is returned
+        when no more rows are available.
 
-    def fetchmany(self):
+        The number of rows to fetch per call is specified by the parameter. If 
it is not given, the cursor’s arraysize
+        determines the number of rows to be fetched. The method tries to fetch 
as many rows as indicated by the size
+        parameter. If this is not possible due to the specified number of rows 
not being available, fewer rows will be
+        returned.
+
+        An Error (or subclass) exception is raised if the previous call to 
.execute*() did not produce any result set
+        or no call was issued yet.
+        """
         if self._py_cursor is None:
             raise InterfaceError('Connection is already closed')
 
-        # TODO: IGNITE-22741 Implement data fetching
-        raise NotSupportedError('Operation is not supported')
+        if size is None:
+            size = self.arraysize
+
+        if size <= 0:
+            raise InterfaceError(f'Size parameter should be positive 
[size={size}]')
+
+        res = []
+        for i in range(size):
+            row = self.fetchone()
+            if row is None:
+                break
+            res.append(row)
+
+        return None if not res else res
 
-    def fetchall(self):
+    def fetchall(self) -> Optional[Sequence[Sequence[Optional[Any]]]]:
+        """
+        Fetch all remaining rows of a query result, returning them as a 
sequence of sequences.
+        An Error (or subclass) exception is raised if the previous call to 
.execute*() did not produce any result set
+        or no call was issued yet.
+        """
         if self._py_cursor is None:
             raise InterfaceError('Connection is already closed')
 
-        # TODO: IGNITE-22741 Implement data fetching
-        raise NotSupportedError('Operation is not supported')
+        res = []
+        row = self.fetchone()
+        while row is not None:
+            res.append(row)
+            row = self.fetchone()
+
+        return None if not res else res
 
     def nextset(self):
         if self._py_cursor is None:
@@ -227,17 +266,17 @@ class Cursor:
         raise NotSupportedError('Operation is not supported')
 
     def setoutputsize(self, *args):
-        if self._py_cursor is None:
-            raise InterfaceError('Connection is already closed')
-
-        # TODO: IGNITE-22741 Implement data fetching
-        raise NotSupportedError('Operation is not supported')
+        """
+        This operation does nothing currently.
+        """
+        pass
 
 
 class Connection:
     """
     Connection class. Represents a single connection to the Ignite cluster.
     """
+
     def __init__(self):
         self._py_connection = None
 
diff --git a/modules/platforms/python/tests/test_execute.py 
b/modules/platforms/python/tests/test_execute.py
index 0efa936a43..510f7505d5 100644
--- a/modules/platforms/python/tests/test_execute.py
+++ b/modules/platforms/python/tests/test_execute.py
@@ -15,7 +15,7 @@
 import pytest
 
 import pyignite3
-from tests.util import start_cluster_gen, check_cluster_started, 
server_addresses_invalid, server_addresses_basic
+from tests.util import start_cluster_gen, check_cluster_started, 
server_addresses_basic
 
 
 @pytest.fixture(autouse=True)
@@ -79,7 +79,7 @@ def test_execute_sql_table_success():
                 assert cursor.description[2].precision == 3
 
             finally:
-                cursor.execute(f'drop table if exists {table_name}');
+                cursor.execute(f'drop table if exists {table_name}')
 
 
 def test_execute_update_rowcount():
@@ -96,4 +96,4 @@ def test_execute_update_rowcount():
                 assert cursor.rowcount == 6
 
             finally:
-                cursor.execute(f'drop table if exists {table_name}');
+                cursor.execute(f'drop table if exists {table_name}')
diff --git a/modules/platforms/python/tests/test_fetch_constants.py 
b/modules/platforms/python/tests/test_fetch_constants.py
new file mode 100644
index 0000000000..3fd7929e13
--- /dev/null
+++ b/modules/platforms/python/tests/test_fetch_constants.py
@@ -0,0 +1,188 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import math
+
+import pytest
+
+import pyignite3
+from tests.util import start_cluster_gen, check_cluster_started, 
server_addresses_basic
+
+
[email protected](autouse=True)
+def cluster():
+    if not check_cluster_started():
+        yield from start_cluster_gen()
+    else:
+        yield None
+
+
+def test_fetch_constant_string():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select 'Lorem ipsum'")
+            data = cursor.fetchone()
+            assert len(data) == 1
+            assert data[0] == 'Lorem ipsum'
+
+
+def test_fetch_constant_string_empty():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select ''")
+            data = cursor.fetchone()
+            assert len(data) == 1
+            assert data[0] == ''
+
+
+def test_fetch_constant_tinyint():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select CAST(42 AS TINYINT)")
+            data = cursor.fetchone()
+            assert len(data) == 1
+            assert data[0] == 42
+
+
+def test_fetch_constant_tinyint_negative():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select CAST(-18 AS TINYINT)")
+            data = cursor.fetchone()
+            assert len(data) == 1
+            assert data[0] == -18
+
+
+def test_fetch_constant_smallint():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select CAST(4242 AS SMALLINT)")
+            data = cursor.fetchone()
+            assert len(data) == 1
+            assert data[0] == 4242
+
+
+def test_fetch_constant_int():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select 987654321")
+            data = cursor.fetchone()
+            assert len(data) == 1
+            assert data[0] == 987654321
+
+
+def test_fetch_constant_bigint():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select CAST(1234567890987654321 AS BIGINT)")
+            data = cursor.fetchone()
+            assert len(data) == 1
+            assert data[0] == 1234567890987654321
+
+
+def test_fetch_constant_real():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select CAST(123.456 AS REAL)")
+            data = cursor.fetchone()
+            assert len(data) == 1
+            assert data[0] == pytest.approx(123.456)
+
+
+def test_fetch_constant_double():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select CAST(-123456789.987654321 AS DOUBLE)")
+            data = cursor.fetchone()
+            assert len(data) == 1
+            assert data[0] == pytest.approx(-123456789.987654321)
+
+
+def test_fetch_constant_double_nan():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select CAST('NaN' AS DOUBLE)")
+            data = cursor.fetchone()
+            assert len(data) == 1
+            assert math.isnan(data[0])
+
+
+def test_fetch_constant_bool_true():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select TRUE")
+            data = cursor.fetchone()
+            assert len(data) == 1
+            assert data[0] is True
+
+
+def test_fetch_constant_bool_false():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select FALSE")
+            data = cursor.fetchone()
+            assert len(data) == 1
+            assert data[0] is False
+
+
+def test_fetch_constant_binary():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select x'45F0AB'")
+            data = cursor.fetchone()
+            assert len(data) == 1
+            assert data[0] == b'\x45\xf0\xab'
+
+
+def test_fetch_constant_binary_empty():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select x''")
+            data = cursor.fetchone()
+            assert len(data) == 1
+            assert data[0] == b''
+
+
+def test_fetch_constant_null():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select NULL")
+            data = cursor.fetchone()
+            assert len(data) == 1
+            assert data[0] is None
+
+
+def test_fetch_constant_several_ints():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select 1,2,3")
+            data = cursor.fetchone()
+            assert len(data) == 3
+            assert data[0] == 1
+            assert data[1] == 2
+            assert data[2] == 3
+
+
+def test_fetch_constant_int_bool_string():
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("select 42, TRUE, 'Test string'")
+            data = cursor.fetchone()
+            assert len(data) == 3
+            assert data[0] == 42
+            assert data[1] is True
+            assert data[2] == 'Test string'
+
+            nothing = cursor.fetchone()
+            assert nothing is None
diff --git a/modules/platforms/python/tests/test_fetch_table.py 
b/modules/platforms/python/tests/test_fetch_table.py
new file mode 100644
index 0000000000..dd9b3d16ab
--- /dev/null
+++ b/modules/platforms/python/tests/test_fetch_table.py
@@ -0,0 +1,193 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import pytest
+
+import pyignite3
+from tests.util import start_cluster_gen, check_cluster_started, 
server_addresses_basic
+
+
[email protected](autouse=True)
+def cluster():
+    if not check_cluster_started():
+        yield from start_cluster_gen()
+    else:
+        yield None
+
+
+def create_and_populate_test_table(cursor, rows_num, table_name):
+    cursor.execute(f'drop table if exists {table_name}')
+    cursor.execute(f'create table {table_name}(id int primary key, data 
varchar, fl double)')
+    for i in range(rows_num):
+        cursor.execute(f"insert into {table_name} values ({i}, 'Value-{i * 
2}', {i / 2.0})")
+
+
+def check_row(i, row):
+    assert len(row) == 3
+    assert row[0] == i
+    assert row[1] == f'Value-{i * 2}'
+    assert row[2] == pytest.approx(i / 2.0)
+
+
+def test_fetchone_table_empty():
+    table_name = test_fetchone_table_empty.__name__
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            try:
+                cursor.execute(f'drop table if exists {table_name}')
+                cursor.execute(f'create table {table_name}(id int primary key, 
col1 varchar)')
+                cursor.execute(f"select col1, id from {table_name}")
+                end = cursor.fetchone()
+                assert end is None
+
+            finally:
+                cursor.execute(f'drop table if exists {table_name}')
+
+
+def test_fetchone_table_many_rows():
+    table_name = test_fetchone_table_many_rows.__name__
+    rows_num = 15
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            try:
+                create_and_populate_test_table(cursor, rows_num, table_name)
+
+                cursor.execute(f"select id, data, fl from {table_name} order 
by id")
+
+                for i in range(rows_num):
+                    row = cursor.fetchone()
+                    check_row(i, row)
+
+                end = cursor.fetchone()
+                assert end is None
+
+            finally:
+                cursor.execute(f'drop table if exists {table_name}')
+
+
+def test_fetchmany_table_empty():
+    table_name = test_fetchmany_table_empty.__name__
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            try:
+                cursor.execute(f'drop table if exists {table_name}')
+                cursor.execute(f'create table {table_name}(id int primary key, 
col1 varchar)')
+                cursor.execute(f"select col1, id from {table_name}")
+                end = cursor.fetchmany(size=10)
+                assert end is None
+
+            finally:
+                cursor.execute(f'drop table if exists {table_name}')
+
+
+def test_fetchmany_table_many_rows():
+    table_name = test_fetchmany_table_many_rows.__name__
+    rows_num = 15
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            try:
+                create_and_populate_test_table(cursor, rows_num, table_name)
+
+                cursor.arraysize = 5
+                cursor.execute(f"select id, data, fl from {table_name} order 
by id")
+
+                rows0_4 = cursor.fetchmany()
+                assert len(rows0_4) == 5
+                for i in range(5):
+                    check_row(i, rows0_4[i])
+
+                rows5_12 = cursor.fetchmany(size=8)
+                assert len(rows5_12) == 8
+                for i in range(8):
+                    check_row(i + 5, rows5_12[i])
+
+                rows13_14 = cursor.fetchmany()
+                assert len(rows13_14) == 2
+                for i in range(2):
+                    check_row(i + 13, rows13_14[i])
+
+                end = cursor.fetchone()
+                assert end is None
+
+            finally:
+                cursor.execute(f'drop table if exists {table_name}')
+
+
+def test_fetchall_table_empty():
+    table_name = test_fetchmany_table_empty.__name__
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            try:
+                cursor.execute(f'drop table if exists {table_name}')
+                cursor.execute(f'create table {table_name}(id int primary key, 
col1 varchar)')
+                cursor.execute(f"select col1, id from {table_name}")
+                end = cursor.fetchall()
+                assert end is None
+
+            finally:
+                cursor.execute(f'drop table if exists {table_name}')
+
+
+def test_fetchall_table_many_rows():
+    table_name = test_fetchmany_table_many_rows.__name__
+    rows_num = 15
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            try:
+                create_and_populate_test_table(cursor, rows_num, table_name)
+
+                cursor.arraysize = 5
+                cursor.execute(f"select id, data, fl from {table_name} order 
by id")
+
+                rows_all = cursor.fetchall()
+                assert len(rows_all) == rows_num
+                for i in range(rows_num):
+                    check_row(i, rows_all[i])
+
+                end = cursor.fetchone()
+                assert end is None
+
+            finally:
+                cursor.execute(f'drop table if exists {table_name}')
+
+
+def test_fetch_mixed_table_many_rows():
+    table_name = test_fetch_mixed_table_many_rows.__name__
+    rows_num = 15
+    with pyignite3.connect(address=server_addresses_basic[0]) as conn:
+        with conn.cursor() as cursor:
+            try:
+                create_and_populate_test_table(cursor, rows_num, table_name)
+
+                cursor.arraysize = 5
+                cursor.execute(f"select id, data, fl from {table_name} order 
by id")
+
+                rows0_4 = cursor.fetchmany()
+                assert len(rows0_4) == 5
+                for i in range(5):
+                    check_row(i, rows0_4[i])
+
+                row5 = cursor.fetchone()
+                check_row(5, row5)
+
+                rows_remaining = cursor.fetchall()
+                assert len(rows_remaining) == rows_num - 6
+                for i in range(rows_num - 6):
+                    check_row(i + 6, rows_remaining[i])
+
+                end = cursor.fetchone()
+                assert end is None
+
+            finally:
+                cursor.execute(f'drop table if exists {table_name}')

Reply via email to