From 29d33d2bb9978546760563dcffae1fdbe117b38e Mon Sep 17 00:00:00 2001 From: smavros Date: Tue, 3 Mar 2026 12:41:59 +0100 Subject: [PATCH 1/4] Adds empty result set in the result sets' set for DDL and DML operations --- pymonetdb/sql/cursors.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pymonetdb/sql/cursors.py b/pymonetdb/sql/cursors.py index 46f6261..40909da 100644 --- a/pymonetdb/sql/cursors.py +++ b/pymonetdb/sql/cursors.py @@ -525,6 +525,11 @@ def _store_result(self, block, *, update_existing: bool): # noqa: C901 self._rows = [] self.description = None self.rowcount = -1 + self._query_id = None + if not update_existing: + self._next_result_sets.append( + (self._query_id, self.rowcount, self.description, self._rows) + ) elif line.startswith(mapi.MSG_QUPDATE): (affected, identity) = line[2:].split()[:2] @@ -534,6 +539,10 @@ def _store_result(self, block, *, update_existing: bool): # noqa: C901 self.rowcount = int(affected) self.lastrowid = int(identity) self._query_id = None + if not update_existing: + self._next_result_sets.append( + (self._query_id, self.rowcount, self.description, self._rows) + ) elif line.startswith(mapi.MSG_QTRANS): self._offset = 0 From 20968f67b89e9374a96cf7b6aa1a28c8c3713538 Mon Sep 17 00:00:00 2001 From: smavros Date: Tue, 3 Mar 2026 13:49:15 +0100 Subject: [PATCH 2/4] Adds test for the new behavior of nextset() --- tests/test_resultset.py | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/test_resultset.py b/tests/test_resultset.py index 91ff386..1aabde0 100644 --- a/tests/test_resultset.py +++ b/tests/test_resultset.py @@ -402,6 +402,48 @@ def access_pattern(self, n): self.assertFalse(self.cursor.nextset()) + def test_multiple_with_dml_dql(self): + self.do_connect() + + queries = 'SELECT 1;' + queries += 'CREATE TABLE IF NOT EXISTS bar (n int);' + queries += 'SELECT 10 UNION SELECT 20;' + queries += 'INSERT INTO bar values (10), (20);' + queries += "SELECT 'alice', 'bob';" + self.cursor.execute(queries) + + self.assertEqual(self.cursor.rowcount, 1) + self.assertEqual(self.cursor.fetchall(), [(1,)]) + self.assertTrue(self.cursor.description is not None) + self.assertTrue(self.cursor.nextset()) + + self.assertEqual(self.cursor.rowcount, -1) + try: + self.cursor.fetchall() + except Exception as e: + self.assertTrue(isinstance(e, pymonetdb.ProgrammingError)) + self.assertTrue(self.cursor.description is None) + self.assertTrue(self.cursor.nextset()) + + self.assertEqual(self.cursor.rowcount, 2) + self.assertEqual(self.cursor.fetchall(), [(10,), (20,)]) + self.assertTrue(self.cursor.description is not None) + self.assertTrue(self.cursor.nextset()) + + self.assertEqual(self.cursor.rowcount, 2) + try: + self.cursor.fetchall() + except Exception as e: + self.assertTrue(isinstance(e, pymonetdb.ProgrammingError)) + self.assertTrue(self.cursor.description is None) + self.assertTrue(self.cursor.nextset()) + + self.assertEqual(self.cursor.rowcount, 1) + self.assertEqual(self.cursor.fetchall(), [('alice', 'bob')]) + self.assertTrue(self.cursor.description is not None) + + self.assertFalse(self.cursor.nextset()) + def test_huge(self): self.skip_unless_have_sqltype('hugeint') max_value = (1 << 127) - 1 From 2a1e598ceabb6e5ef58bd672ae213806eae33de2 Mon Sep 17 00:00:00 2001 From: smavros Date: Tue, 3 Mar 2026 14:30:41 +0100 Subject: [PATCH 3/4] Makes Description list optional in the _next_result_sets type For DDL/DML we could simply have an empty list of descriptions but it seems that this does not adheres to the PEP-0249 which requires description attribute to be `None` in case of an operations which does not return any rows --- pymonetdb/sql/cursors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymonetdb/sql/cursors.py b/pymonetdb/sql/cursors.py index 40909da..7b5bed4 100644 --- a/pymonetdb/sql/cursors.py +++ b/pymonetdb/sql/cursors.py @@ -49,7 +49,7 @@ class Cursor(object): lastrowid: Optional[int] _unpack_int64: str - _next_result_sets: List[Tuple[str, int, List[Description], List[Tuple]]] + _next_result_sets: List[Tuple[str, int, Optional[List[Description]], List[Tuple]]] def __init__(self, connection: 'pymonetdb.sql.connections.Connection'): """This read-only attribute return a reference to the Connection From 366c18e0aaafd0481c3d0a7e0757611d34fd1951 Mon Sep 17 00:00:00 2001 From: smavros Date: Tue, 3 Mar 2026 14:43:41 +0100 Subject: [PATCH 4/4] Makes query_id str parameter optional DDL/DML mapi messages do not include a query id. If we want to have an empty result set in the result sets set then we need to support `None` query_ids --- pymonetdb/sql/cursors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymonetdb/sql/cursors.py b/pymonetdb/sql/cursors.py index 7b5bed4..72e4243 100644 --- a/pymonetdb/sql/cursors.py +++ b/pymonetdb/sql/cursors.py @@ -49,7 +49,7 @@ class Cursor(object): lastrowid: Optional[int] _unpack_int64: str - _next_result_sets: List[Tuple[str, int, Optional[List[Description]], List[Tuple]]] + _next_result_sets: List[Tuple[Optional[str], int, Optional[List[Description]], List[Tuple]]] def __init__(self, connection: 'pymonetdb.sql.connections.Connection'): """This read-only attribute return a reference to the Connection