diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index 12cd997418..bdab745de3 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -1322,8 +1322,6 @@ def test_blob_set_item_with_offset(self): expected = b"This blob data string is exactly fifty bytes long." self.assertEqual(self.blob.read(), expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_blob_set_slice_buffer_object(self): from array import array self.blob[0:5] = memoryview(b"12345") @@ -1351,22 +1349,16 @@ def test_blob_get_slice_negative_index(self): def test_blob_get_slice_with_skip(self): self.assertEqual(self.blob[0:10:2], b"ti lb") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_blob_set_slice(self): self.blob[0:5] = b"12345" expected = b"12345" + self.data[5:] actual = self.cx.execute("select b from test").fetchone()[0] self.assertEqual(actual, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_blob_set_empty_slice(self): self.blob[0:0] = b"" self.assertEqual(self.blob[:], self.data) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_blob_set_slice_with_skip(self): self.blob[0:10:2] = b"12345" actual = self.cx.execute("select b from test").fetchone()[0] @@ -1419,8 +1411,6 @@ def test_blob_set_item_error(self): with self.assertRaisesRegex(ValueError, "must be in range"): self.blob[0] = 2**65 - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_blob_set_slice_error(self): with self.assertRaisesRegex(IndexError, "wrong size"): self.blob[5:10] = b"a" diff --git a/stdlib/src/sqlite.rs b/stdlib/src/sqlite.rs index 8db90a7bba..cc146b3786 100644 --- a/stdlib/src/sqlite.rs +++ b/stdlib/src/sqlite.rs @@ -2238,11 +2238,12 @@ mod _sqlite { vm: &VirtualMachine, ) -> PyResult<()> { let Some(value) = value else { - return Err(vm.new_type_error("Blob doesn't support deletion")); + return Err(vm.new_type_error("Blob doesn't support slice deletion")); }; let inner = self.inner(vm)?; if let Some(index) = needle.try_index_opt(vm) { + // Handle single item assignment: blob[i] = b let Some(value) = value.downcast_ref::() else { return Err(vm.new_type_error(format!( "'{}' object cannot be interpreted as an integer", @@ -2255,11 +2256,61 @@ mod _sqlite { Self::expect_write(blob_len, 1, index, vm)?; let ret = inner.blob.write_single(value, index); self.check(ret, vm) - } else if let Some(_slice) = needle.downcast_ref::() { - Err(vm.new_not_implemented_error("Blob slice assignment is not implemented")) - // let blob_len = inner.blob.bytes(); - // let slice = slice.to_saturated(vm)?; - // let (range, step, length) = slice.adjust_indices(blob_len as usize); + } else if let Some(slice) = needle.downcast_ref::() { + // Handle slice assignment: blob[a:b:c] = b"..." + let value_buf = PyBuffer::try_from_borrowed_object(vm, &value)?; + + let buf = value_buf + .as_contiguous() + .ok_or_else(|| vm.new_buffer_error("underlying buffer is not C-contiguous"))?; + + let blob_len = inner.blob.bytes(); + let slice = slice.to_saturated(vm)?; + let (range, step, slice_len) = slice.adjust_indices(blob_len as usize); + + if step == 0 { + return Err(vm.new_value_error("slice step cannot be zero")); + } + + if buf.len() != slice_len { + return Err(vm.new_index_error("Blob slice assignment is wrong size")); + } + + if slice_len == 0 { + return Ok(()); + } + + if step == 1 { + let ret = inner.blob.write( + buf.as_ptr().cast(), + buf.len() as c_int, + range.start as c_int, + ); + self.check(ret, vm) + } else { + let span_len = range.end - range.start; + let mut temp_buf = vec![0u8; span_len]; + + let ret = inner.blob.read( + temp_buf.as_mut_ptr().cast(), + span_len as c_int, + range.start as c_int, + ); + self.check(ret, vm)?; + + let mut i_in_temp: usize = 0; + for i_in_src in 0..slice_len { + temp_buf[i_in_temp] = buf[i_in_src]; + i_in_temp += step as usize; + } + + let ret = inner.blob.write( + temp_buf.as_ptr().cast(), + span_len as c_int, + range.start as c_int, + ); + self.check(ret, vm) + } } else { Err(vm.new_type_error("Blob indices must be integers")) }