From d3f84f2d9e1e7e4113eb57a402afab9d0cd9ea77 Mon Sep 17 00:00:00 2001 From: Jiseok CHOI Date: Sat, 15 Aug 2026 14:54:58 +0900 Subject: [PATCH] sqlite3: validate narg before creating functions/aggregates CPython 3.14 added check_num_params() which raises ProgrammingError when narg/n_arg/num_params is out of range (-1..=SQLITE_LIMIT_FUNCTION_ARG). RustPython passed invalid values directly to SQLite, resulting in an OperationalError instead. Add check_num_params() helper and call it in create_function(), create_aggregate(), and create_window_function(). Assisted-by: GitHub Copilot:claude-sonnet-4-6 --- Lib/test/test_sqlite3/test_userfunctions.py | 3 --- crates/stdlib/src/_sqlite3.rs | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_sqlite3/test_userfunctions.py b/Lib/test/test_sqlite3/test_userfunctions.py index e7cecb85213..6da92d77616 100644 --- a/Lib/test/test_sqlite3/test_userfunctions.py +++ b/Lib/test/test_sqlite3/test_userfunctions.py @@ -170,7 +170,6 @@ def setUp(self): def tearDown(self): self.con.close() - @unittest.expectedFailure # TODO: RUSTPYTHON; error message differs for invalid num args def test_func_error_on_create(self): with self.assertRaisesRegex(sqlite.ProgrammingError, "not -100"): self.con.create_function("bla", -100, lambda x: 2*x) @@ -514,7 +513,6 @@ def test_win_sum_int(self): self.cur.execute(self.query % "sumint") self.assertEqual(self.cur.fetchall(), self.expected) - @unittest.expectedFailure # TODO: RUSTPYTHON; error message differs for invalid num args def test_win_error_on_create(self): with self.assertRaisesRegex(sqlite.ProgrammingError, "not -100"): self.con.create_window_function("shouldfail", -100, WindowSumInt) @@ -649,7 +647,6 @@ def setUp(self): def tearDown(self): self.con.close() - @unittest.expectedFailure # TODO: RUSTPYTHON; error message differs for invalid num args def test_aggr_error_on_create(self): with self.assertRaisesRegex(sqlite.ProgrammingError, "not -100"): self.con.create_function("bla", -100, AggrSum) diff --git a/crates/stdlib/src/_sqlite3.rs b/crates/stdlib/src/_sqlite3.rs index 5348cc1f5ec..5576b84629f 100644 --- a/crates/stdlib/src/_sqlite3.rs +++ b/crates/stdlib/src/_sqlite3.rs @@ -1279,6 +1279,7 @@ mod _sqlite3 { SQLITE_UTF8 }; let db = self.db_lock(vm)?; + check_num_params(&db, args.narg, "narg", vm)?; let Some(data) = CallbackData::new(args.func, vm) else { return db.create_function( name.as_ptr(), @@ -1310,6 +1311,7 @@ mod _sqlite3 { fn create_aggregate(&self, args: CreateAggregateArgs, vm: &VirtualMachine) -> PyResult<()> { let name = args.name.to_cstring(vm)?; let db = self.db_lock(vm)?; + check_num_params(&db, args.narg, "n_arg", vm)?; let Some(data) = CallbackData::new(args.aggregate_class, vm) else { return db.create_function( name.as_ptr(), @@ -1392,6 +1394,7 @@ mod _sqlite3 { ) -> PyResult<()> { let name = name.to_cstring(vm)?; let db = self.db_lock(vm)?; + check_num_params(&db, narg, "num_params", vm)?; let Some(data) = CallbackData::new(aggregate_class, vm) else { unsafe { sqlite3_create_window_function( @@ -3475,6 +3478,22 @@ mod _sqlite3 { Ok(obj) } + fn check_num_params( + db: &Sqlite, + n: c_int, + param_name: &str, + vm: &VirtualMachine, + ) -> PyResult<()> { + let limit = unsafe { sqlite3_limit(db.db, SQLITE_LIMIT_FUNCTION_ARG, -1) }; + if n < -1 || n > limit { + return Err(new_programming_error( + vm, + format!("'{param_name}' must be between -1 and {limit}, not {n}"), + )); + } + Ok(()) + } + fn ptr_to_str<'a>(p: *const libc::c_char, vm: &VirtualMachine) -> PyResult<&'a str> { if p.is_null() { return Err(vm.new_memory_error("string pointer is null"));