Skip to content

Commit 9ea9078

Browse files
gh-92019: Make sqlite3.Blob indexing conform with the norm (#92020)
- get index now returns an int - set index now requires an int in range(0, 256) Resolves #92019
1 parent e91dee8 commit 9ea9078

File tree

3 files changed

+75
-42
lines changed

3 files changed

+75
-42
lines changed

Doc/includes/sqlite3/blob.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
blob.write(b"hello, ")
1010
blob.write(b"world.")
1111
# Modify the first and last bytes of our blob
12-
blob[0] = b"H"
13-
blob[-1] = b"!"
12+
blob[0] = ord("H")
13+
blob[-1] = ord("!")
1414

1515
# Read the contents of our blob
1616
with con.blobopen("test", "blob_col", 1) as blob:

Lib/test/test_sqlite3/test_dbapi.py

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,38 +1173,29 @@ def test_blob_length(self):
11731173
self.assertEqual(len(self.blob), 50)
11741174

11751175
def test_blob_get_item(self):
1176-
self.assertEqual(self.blob[5], b"b")
1177-
self.assertEqual(self.blob[6], b"l")
1178-
self.assertEqual(self.blob[7], b"o")
1179-
self.assertEqual(self.blob[8], b"b")
1180-
self.assertEqual(self.blob[-1], b"!")
1176+
self.assertEqual(self.blob[5], ord("b"))
1177+
self.assertEqual(self.blob[6], ord("l"))
1178+
self.assertEqual(self.blob[7], ord("o"))
1179+
self.assertEqual(self.blob[8], ord("b"))
1180+
self.assertEqual(self.blob[-1], ord("!"))
11811181

11821182
def test_blob_set_item(self):
1183-
self.blob[0] = b"b"
1183+
self.blob[0] = ord("b")
11841184
expected = b"b" + self.data[1:]
11851185
actual = self.cx.execute("select b from test").fetchone()[0]
11861186
self.assertEqual(actual, expected)
11871187

11881188
def test_blob_set_item_with_offset(self):
11891189
self.blob.seek(0, SEEK_END)
11901190
self.assertEqual(self.blob.read(), b"") # verify that we're at EOB
1191-
self.blob[0] = b"T"
1192-
self.blob[-1] = b"."
1191+
self.blob[0] = ord("T")
1192+
self.blob[-1] = ord(".")
11931193
self.blob.seek(0, SEEK_SET)
11941194
expected = b"This blob data string is exactly fifty bytes long."
11951195
self.assertEqual(self.blob.read(), expected)
11961196

1197-
def test_blob_set_buffer_object(self):
1197+
def test_blob_set_slice_buffer_object(self):
11981198
from array import array
1199-
self.blob[0] = memoryview(b"1")
1200-
self.assertEqual(self.blob[0], b"1")
1201-
1202-
self.blob[1] = bytearray(b"2")
1203-
self.assertEqual(self.blob[1], b"2")
1204-
1205-
self.blob[2] = array("b", [4])
1206-
self.assertEqual(self.blob[2], b"\x04")
1207-
12081199
self.blob[0:5] = memoryview(b"12345")
12091200
self.assertEqual(self.blob[0:5], b"12345")
12101201

@@ -1215,8 +1206,8 @@ def test_blob_set_buffer_object(self):
12151206
self.assertEqual(self.blob[0:5], b"\x01\x02\x03\x04\x05")
12161207

12171208
def test_blob_set_item_negative_index(self):
1218-
self.blob[-1] = b"z"
1219-
self.assertEqual(self.blob[-1], b"z")
1209+
self.blob[-1] = 255
1210+
self.assertEqual(self.blob[-1], 255)
12201211

12211212
def test_blob_get_slice(self):
12221213
self.assertEqual(self.blob[5:14], b"blob data")
@@ -1264,13 +1255,29 @@ def test_blob_get_item_error(self):
12641255
with self.assertRaisesRegex(IndexError, "cannot fit 'int'"):
12651256
self.blob[ULLONG_MAX]
12661257

1258+
# Provoke read error
1259+
self.cx.execute("update test set b='aaaa' where rowid=1")
1260+
with self.assertRaises(sqlite.OperationalError):
1261+
self.blob[0]
1262+
12671263
def test_blob_set_item_error(self):
1268-
with self.assertRaisesRegex(ValueError, "must be a single byte"):
1264+
with self.assertRaisesRegex(TypeError, "cannot be interpreted"):
12691265
self.blob[0] = b"multiple"
1266+
with self.assertRaisesRegex(TypeError, "cannot be interpreted"):
1267+
self.blob[0] = b"1"
1268+
with self.assertRaisesRegex(TypeError, "cannot be interpreted"):
1269+
self.blob[0] = bytearray(b"1")
12701270
with self.assertRaisesRegex(TypeError, "doesn't support.*deletion"):
12711271
del self.blob[0]
12721272
with self.assertRaisesRegex(IndexError, "Blob index out of range"):
1273-
self.blob[1000] = b"a"
1273+
self.blob[1000] = 0
1274+
with self.assertRaisesRegex(ValueError, "must be in range"):
1275+
self.blob[0] = -1
1276+
with self.assertRaisesRegex(ValueError, "must be in range"):
1277+
self.blob[0] = 256
1278+
# Overflow errors are overridden with ValueError
1279+
with self.assertRaisesRegex(ValueError, "must be in range"):
1280+
self.blob[0] = 2**65
12741281

12751282
def test_blob_set_slice_error(self):
12761283
with self.assertRaisesRegex(IndexError, "wrong size"):

Modules/_sqlite/blob.c

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,26 @@ blob_seterror(pysqlite_Blob *self, int rc)
120120
}
121121

122122
static PyObject *
123-
inner_read(pysqlite_Blob *self, Py_ssize_t length, Py_ssize_t offset)
123+
read_single(pysqlite_Blob *self, Py_ssize_t offset)
124+
{
125+
unsigned char buf = 0;
126+
int rc;
127+
Py_BEGIN_ALLOW_THREADS
128+
rc = sqlite3_blob_read(self->blob, (void *)&buf, 1, (int)offset);
129+
Py_END_ALLOW_THREADS
130+
131+
if (rc != SQLITE_OK) {
132+
blob_seterror(self, rc);
133+
return NULL;
134+
}
135+
return PyLong_FromUnsignedLong((unsigned long)buf);
136+
}
137+
138+
static PyObject *
139+
read_multiple(pysqlite_Blob *self, Py_ssize_t length, Py_ssize_t offset)
124140
{
125141
assert(length <= sqlite3_blob_bytes(self->blob));
126-
assert(offset <= sqlite3_blob_bytes(self->blob));
142+
assert(offset < sqlite3_blob_bytes(self->blob));
127143

128144
PyObject *buffer = PyBytes_FromStringAndSize(NULL, length);
129145
if (buffer == NULL) {
@@ -175,7 +191,12 @@ blob_read_impl(pysqlite_Blob *self, int length)
175191
length = max_read_len;
176192
}
177193

178-
PyObject *buffer = inner_read(self, length, self->offset);
194+
assert(length >= 0);
195+
if (length == 0) {
196+
return PyBytes_FromStringAndSize(NULL, 0);
197+
}
198+
199+
PyObject *buffer = read_multiple(self, length, self->offset);
179200
if (buffer == NULL) {
180201
return NULL;
181202
}
@@ -387,7 +408,7 @@ subscript_index(pysqlite_Blob *self, PyObject *item)
387408
if (i < 0) {
388409
return NULL;
389410
}
390-
return inner_read(self, 1, i);
411+
return read_single(self, i);
391412
}
392413

393414
static int
@@ -411,9 +432,9 @@ subscript_slice(pysqlite_Blob *self, PyObject *item)
411432
}
412433

413434
if (step == 1) {
414-
return inner_read(self, len, start);
435+
return read_multiple(self, len, start);
415436
}
416-
PyObject *blob = inner_read(self, stop - start, start);
437+
PyObject *blob = read_multiple(self, stop - start, start);
417438
if (blob == NULL) {
418439
return NULL;
419440
}
@@ -455,24 +476,29 @@ ass_subscript_index(pysqlite_Blob *self, PyObject *item, PyObject *value)
455476
"Blob doesn't support item deletion");
456477
return -1;
457478
}
479+
if (!PyLong_Check(value)) {
480+
PyErr_Format(PyExc_TypeError,
481+
"'%s' object cannot be interpreted as an integer",
482+
Py_TYPE(value)->tp_name);
483+
return -1;
484+
}
458485
Py_ssize_t i = get_subscript_index(self, item);
459486
if (i < 0) {
460487
return -1;
461488
}
462489

463-
Py_buffer vbuf;
464-
if (PyObject_GetBuffer(value, &vbuf, PyBUF_SIMPLE) < 0) {
465-
return -1;
490+
long val = PyLong_AsLong(value);
491+
if (val == -1 && PyErr_Occurred()) {
492+
PyErr_Clear();
493+
val = -1;
466494
}
467-
int rc = -1;
468-
if (vbuf.len != 1) {
469-
PyErr_SetString(PyExc_ValueError, "Blob assignment must be a single byte");
470-
}
471-
else {
472-
rc = inner_write(self, (const char *)vbuf.buf, 1, i);
495+
if (val < 0 || val > 255) {
496+
PyErr_SetString(PyExc_ValueError, "byte must be in range(0, 256)");
497+
return -1;
473498
}
474-
PyBuffer_Release(&vbuf);
475-
return rc;
499+
// Downcast to avoid endianness problems.
500+
unsigned char byte = (unsigned char)val;
501+
return inner_write(self, (const void *)&byte, 1, i);
476502
}
477503

478504
static int
@@ -507,7 +533,7 @@ ass_subscript_slice(pysqlite_Blob *self, PyObject *item, PyObject *value)
507533
rc = inner_write(self, vbuf.buf, len, start);
508534
}
509535
else {
510-
PyObject *blob_bytes = inner_read(self, stop - start, start);
536+
PyObject *blob_bytes = read_multiple(self, stop - start, start);
511537
if (blob_bytes != NULL) {
512538
char *blob_buf = PyBytes_AS_STRING(blob_bytes);
513539
for (Py_ssize_t i = 0, j = 0; i < len; i++, j += step) {

0 commit comments

Comments
 (0)