From 5f7a823a97cc0195de6db8db615495dafeb5ccae Mon Sep 17 00:00:00 2001 From: Jiseok CHOI Date: Mon, 20 Oct 2025 16:27:30 +0900 Subject: [PATCH] Fix sqlite3 Cursor initialization check Add proper __init__ validation for sqlite3.Cursor to ensure base class __init__ is called before using cursor methods. This fixes the test_cursor_constructor_call_check test case. Changes: - Modified Cursor to initialize with inner=None in py_new - Added explicit __init__ method that sets up CursorInner - Updated close() method to check for uninitialized state - Changed error message to match CPython: 'Base Cursor.__init__ not called.' This ensures CPython compatibility where attempting to use a Cursor instance without calling the base __init__ raises ProgrammingError. --- Lib/test/test_sqlite3/test_regression.py | 2 - stdlib/src/sqlite.rs | 67 ++++++++++++++++++++---- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_sqlite3/test_regression.py b/Lib/test/test_sqlite3/test_regression.py index a658ff1f3ce..56be8b50e2a 100644 --- a/Lib/test/test_sqlite3/test_regression.py +++ b/Lib/test/test_sqlite3/test_regression.py @@ -195,8 +195,6 @@ def __del__(self): con.isolation_level = value self.assertEqual(con.isolation_level, "DEFERRED") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_cursor_constructor_call_check(self): """ Verifies that cursor methods check whether base class __init__ was diff --git a/stdlib/src/sqlite.rs b/stdlib/src/sqlite.rs index aba410c42bb..52d658e6e91 100644 --- a/stdlib/src/sqlite.rs +++ b/stdlib/src/sqlite.rs @@ -1459,6 +1459,8 @@ mod _sqlite { #[pytraverse(skip)] rowcount: i64, statement: Option>, + #[pytraverse(skip)] + closed: bool, } #[derive(FromArgs)] @@ -1484,20 +1486,54 @@ mod _sqlite { lastrowid: -1, rowcount: -1, statement: None, + closed: false, })), } } + fn new_uninitialized(connection: PyRef, _vm: &VirtualMachine) -> Self { + Self { + connection, + arraysize: Radium::new(1), + row_factory: PyAtomicRef::from(None), + inner: PyMutex::from(None), + } + } + + #[pymethod] + fn __init__(&self, _connection: PyRef, _vm: &VirtualMachine) -> PyResult<()> { + let mut guard = self.inner.lock(); + if guard.is_some() { + // Already initialized (e.g., from a call to super().__init__) + return Ok(()); + } + *guard = Some(CursorInner { + description: None, + row_cast_map: vec![], + lastrowid: -1, + rowcount: -1, + statement: None, + closed: false, + }); + Ok(()) + } + fn inner(&self, vm: &VirtualMachine) -> PyResult> { let guard = self.inner.lock(); if guard.is_some() { - Ok(PyMutexGuard::map(guard, |x| unsafe { - x.as_mut().unwrap_unchecked() - })) + let inner_guard = + PyMutexGuard::map(guard, |x| unsafe { x.as_mut().unwrap_unchecked() }); + if inner_guard.closed { + return Err(new_programming_error( + vm, + "Cannot operate on a closed cursor.".to_owned(), + )); + } + Ok(inner_guard) } else { Err(new_programming_error( vm, - "Cannot operate on a closed cursor.".to_owned(), + "Base Cursor.__init__ not called.".to_owned(), )) } } @@ -1717,12 +1753,23 @@ mod _sqlite { } #[pymethod] - fn close(&self) { - if let Some(inner) = self.inner.lock().take() - && let Some(stmt) = inner.statement - { - stmt.lock().reset(); + fn close(&self, vm: &VirtualMachine) -> PyResult<()> { + // Check if __init__ was called + let mut guard = self.inner.lock(); + if guard.is_none() { + return Err(new_programming_error( + vm, + "Base Cursor.__init__ not called.".to_owned(), + )); } + + if let Some(inner) = guard.as_mut() { + if let Some(stmt) = &inner.statement { + stmt.lock().reset(); + } + inner.closed = true; + } + Ok(()) } #[pymethod] @@ -1809,7 +1856,7 @@ mod _sqlite { type Args = (PyRef,); fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - Self::new(args.0, None, vm) + Self::new_uninitialized(args.0, vm) .into_ref_with_type(vm, cls) .map(Into::into) }