Skip to content

feat(stdlib/sqlite): Implement slice assignment for Blob #6039

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions Lib/test/test_sqlite3/test_dbapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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"
Expand Down
63 changes: 57 additions & 6 deletions stdlib/src/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<PyInt>() else {
return Err(vm.new_type_error(format!(
"'{}' object cannot be interpreted as an integer",
Expand All @@ -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::<PySlice>() {
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::<PySlice>() {
// 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"))
}
Expand Down
Loading