diff --git a/jaydebeapiarrow/__init__.py b/jaydebeapiarrow/__init__.py index 9b1f8053..34cf24ba 100644 --- a/jaydebeapiarrow/__init__.py +++ b/jaydebeapiarrow/__init__.py @@ -660,7 +660,7 @@ def _get_iter(self): def fetchone(self): if not self._rs: - raise Error() + return None if self._buffer: return self._buffer.pop(0) @@ -677,7 +677,7 @@ def fetchone(self): def fetchmany(self, size=None): if not self._rs: - raise Error() + return [] if size is None: size = self.arraysize @@ -704,7 +704,7 @@ def fetchmany(self, size=None): def fetchall(self): if not self._rs: - raise Error() + return [] result = [] if self._buffer: diff --git a/mockdriver/src/main/java/org/jaydebeapi/mockdriver/MockConnection.java b/mockdriver/src/main/java/org/jaydebeapi/mockdriver/MockConnection.java index 2468344a..b189d8de 100644 --- a/mockdriver/src/main/java/org/jaydebeapi/mockdriver/MockConnection.java +++ b/mockdriver/src/main/java/org/jaydebeapi/mockdriver/MockConnection.java @@ -498,4 +498,12 @@ public final void mockMultiColumnResult(int[] sqlTypes, Object[] values) throws Mockito.when(this.prepareStatement(Mockito.any())).thenReturn(mockPreparedStatement); } + + /** Simulate a DDL/DML statement that returns no ResultSet (e.g. CREATE TABLE). */ + public final void mockDDLResult(int updateCount) throws SQLException { + PreparedStatement mockPreparedStatement = Mockito.mock(PreparedStatement.class); + Mockito.when(mockPreparedStatement.execute()).thenReturn(false); + Mockito.when(mockPreparedStatement.getUpdateCount()).thenReturn(updateCount); + Mockito.when(this.prepareStatement(Mockito.any())).thenReturn(mockPreparedStatement); + } } diff --git a/test/test_integration.py b/test/test_integration.py index c9242f0d..9e0bb269 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -999,6 +999,37 @@ def test_rollback_with_autocommit_disabled(self): self.conn.jconn.setAutoCommit(False) self.conn.rollback() + def test_fetchone_after_ddl_returns_none(self): + """fetchone() after a DDL statement (CREATE TABLE) should return None.""" + with self.conn.cursor() as cursor: + cursor.execute("DROP TABLE IF EXISTS ddl_test") + cursor.execute("CREATE TABLE ddl_test (id INTEGER)") + result = cursor.fetchone() + self.assertIsNone(result) + + def test_fetchall_after_ddl_returns_empty(self): + """fetchall() after a DDL statement (CREATE TABLE) should return [].""" + with self.conn.cursor() as cursor: + cursor.execute("DROP TABLE IF EXISTS ddl_test2") + cursor.execute("CREATE TABLE ddl_test2 (id INTEGER)") + result = cursor.fetchall() + self.assertEqual(result, []) + + def test_fetchmany_after_ddl_returns_empty(self): + """fetchmany() after a DDL statement (CREATE TABLE) should return [].""" + with self.conn.cursor() as cursor: + cursor.execute("DROP TABLE IF EXISTS ddl_test3") + cursor.execute("CREATE TABLE ddl_test3 (id INTEGER)") + result = cursor.fetchmany(5) + self.assertEqual(result, []) + + def test_description_after_ddl_is_none(self): + """cursor.description should be None after a DDL statement.""" + with self.conn.cursor() as cursor: + cursor.execute("DROP TABLE IF EXISTS ddl_test4") + cursor.execute("CREATE TABLE ddl_test4 (id INTEGER)") + self.assertIsNone(cursor.description) + class PostgresTest(IntegrationTestBase, unittest.TestCase): diff --git a/test/test_mock.py b/test/test_mock.py index c45843d4..40bebfeb 100644 --- a/test/test_mock.py +++ b/test/test_mock.py @@ -1248,7 +1248,6 @@ def test_rollback_called_when_autocommit_disabled(self): self.conn.jconn.mockAutoCommit(False) self.conn.rollback() - def test_lastrowid_exists_and_is_none(self): """PEP-249: lastrowid attribute must exist on cursor (fixes #84).""" with self.conn.cursor() as cursor: @@ -1273,6 +1272,46 @@ def test_lastrowid_none_after_executemany(self): """lastrowid should be None after executemany (mock driver limitation: skip).""" self.skipTest("Mock driver executeBatch returns None; covered by integration test") + # --- DDL statement tests (issue #76) --- + + def test_fetchone_after_ddl_returns_none(self): + """fetchone() after a DDL statement (no ResultSet) should return None, not raise.""" + self.conn.jconn.mockDDLResult(0) + with self.conn.cursor() as cursor: + cursor.execute("CREATE TABLE test (id INTEGER)") + result = cursor.fetchone() + self.assertIsNone(result) + + def test_fetchall_after_ddl_returns_empty(self): + """fetchall() after a DDL statement (no ResultSet) should return [], not raise.""" + self.conn.jconn.mockDDLResult(0) + with self.conn.cursor() as cursor: + cursor.execute("CREATE TABLE test (id INTEGER)") + result = cursor.fetchall() + self.assertEqual(result, []) + + def test_fetchmany_after_ddl_returns_empty(self): + """fetchmany() after a DDL statement (no ResultSet) should return [], not raise.""" + self.conn.jconn.mockDDLResult(0) + with self.conn.cursor() as cursor: + cursor.execute("CREATE TABLE test (id INTEGER)") + result = cursor.fetchmany(5) + self.assertEqual(result, []) + + def test_description_after_ddl_is_none(self): + """cursor.description should be None after a DDL statement.""" + self.conn.jconn.mockDDLResult(0) + with self.conn.cursor() as cursor: + cursor.execute("CREATE TABLE test (id INTEGER)") + self.assertIsNone(cursor.description) + + def test_rowcount_after_ddl(self): + """cursor.rowcount should reflect the update count after DDL.""" + self.conn.jconn.mockDDLResult(0) + with self.conn.cursor() as cursor: + cursor.execute("CREATE TABLE test (id INTEGER)") + self.assertEqual(cursor.rowcount, 0) + class JarPathSpacesTest(unittest.TestCase): """Tests for JAR file paths containing spaces (issue #86).