[Python-checkins] gh-92019: Make sqlite3.Blob indexing conform with the norm (#92020)

JelleZijlstra webhook-mailer at python.org
Sat Apr 30 11:02:05 EDT 2022


https://github.com/python/cpython/commit/9ea9078ec715ffc92c10c61321f3b1184fa3cac9
commit: 9ea9078ec715ffc92c10c61321f3b1184fa3cac9
branch: main
author: Erlend Egeberg Aasland <erlend.aasland at protonmail.com>
committer: JelleZijlstra <jelle.zijlstra at gmail.com>
date: 2022-04-30T09:01:37-06:00
summary:

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

files:
M Doc/includes/sqlite3/blob.py
M Lib/test/test_sqlite3/test_dbapi.py
M Modules/_sqlite/blob.c

diff --git a/Doc/includes/sqlite3/blob.py b/Doc/includes/sqlite3/blob.py
index d947059b3ae64..ff58d6c352b65 100644
--- a/Doc/includes/sqlite3/blob.py
+++ b/Doc/includes/sqlite3/blob.py
@@ -9,8 +9,8 @@
     blob.write(b"hello, ")
     blob.write(b"world.")
     # Modify the first and last bytes of our blob
-    blob[0] = b"H"
-    blob[-1] = b"!"
+    blob[0] = ord("H")
+    blob[-1] = ord("!")
 
 # Read the contents of our blob
 with con.blobopen("test", "blob_col", 1) as blob:
diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py
index 8bfdce2bbe92e..7e675a91b5d1f 100644
--- a/Lib/test/test_sqlite3/test_dbapi.py
+++ b/Lib/test/test_sqlite3/test_dbapi.py
@@ -1173,14 +1173,14 @@ def test_blob_length(self):
         self.assertEqual(len(self.blob), 50)
 
     def test_blob_get_item(self):
-        self.assertEqual(self.blob[5], b"b")
-        self.assertEqual(self.blob[6], b"l")
-        self.assertEqual(self.blob[7], b"o")
-        self.assertEqual(self.blob[8], b"b")
-        self.assertEqual(self.blob[-1], b"!")
+        self.assertEqual(self.blob[5], ord("b"))
+        self.assertEqual(self.blob[6], ord("l"))
+        self.assertEqual(self.blob[7], ord("o"))
+        self.assertEqual(self.blob[8], ord("b"))
+        self.assertEqual(self.blob[-1], ord("!"))
 
     def test_blob_set_item(self):
-        self.blob[0] = b"b"
+        self.blob[0] = ord("b")
         expected = b"b" + self.data[1:]
         actual = self.cx.execute("select b from test").fetchone()[0]
         self.assertEqual(actual, expected)
@@ -1188,23 +1188,14 @@ def test_blob_set_item(self):
     def test_blob_set_item_with_offset(self):
         self.blob.seek(0, SEEK_END)
         self.assertEqual(self.blob.read(), b"")  # verify that we're at EOB
-        self.blob[0] = b"T"
-        self.blob[-1] = b"."
+        self.blob[0] = ord("T")
+        self.blob[-1] = ord(".")
         self.blob.seek(0, SEEK_SET)
         expected = b"This blob data string is exactly fifty bytes long."
         self.assertEqual(self.blob.read(), expected)
 
-    def test_blob_set_buffer_object(self):
+    def test_blob_set_slice_buffer_object(self):
         from array import array
-        self.blob[0] = memoryview(b"1")
-        self.assertEqual(self.blob[0], b"1")
-
-        self.blob[1] = bytearray(b"2")
-        self.assertEqual(self.blob[1], b"2")
-
-        self.blob[2] = array("b", [4])
-        self.assertEqual(self.blob[2], b"\x04")
-
         self.blob[0:5] = memoryview(b"12345")
         self.assertEqual(self.blob[0:5], b"12345")
 
@@ -1215,8 +1206,8 @@ def test_blob_set_buffer_object(self):
         self.assertEqual(self.blob[0:5], b"\x01\x02\x03\x04\x05")
 
     def test_blob_set_item_negative_index(self):
-        self.blob[-1] = b"z"
-        self.assertEqual(self.blob[-1], b"z")
+        self.blob[-1] = 255
+        self.assertEqual(self.blob[-1], 255)
 
     def test_blob_get_slice(self):
         self.assertEqual(self.blob[5:14], b"blob data")
@@ -1264,13 +1255,29 @@ def test_blob_get_item_error(self):
         with self.assertRaisesRegex(IndexError, "cannot fit 'int'"):
             self.blob[ULLONG_MAX]
 
+        # Provoke read error
+        self.cx.execute("update test set b='aaaa' where rowid=1")
+        with self.assertRaises(sqlite.OperationalError):
+            self.blob[0]
+
     def test_blob_set_item_error(self):
-        with self.assertRaisesRegex(ValueError, "must be a single byte"):
+        with self.assertRaisesRegex(TypeError, "cannot be interpreted"):
             self.blob[0] = b"multiple"
+        with self.assertRaisesRegex(TypeError, "cannot be interpreted"):
+            self.blob[0] = b"1"
+        with self.assertRaisesRegex(TypeError, "cannot be interpreted"):
+            self.blob[0] = bytearray(b"1")
         with self.assertRaisesRegex(TypeError, "doesn't support.*deletion"):
             del self.blob[0]
         with self.assertRaisesRegex(IndexError, "Blob index out of range"):
-            self.blob[1000] = b"a"
+            self.blob[1000] = 0
+        with self.assertRaisesRegex(ValueError, "must be in range"):
+            self.blob[0] = -1
+        with self.assertRaisesRegex(ValueError, "must be in range"):
+            self.blob[0] = 256
+        # Overflow errors are overridden with ValueError
+        with self.assertRaisesRegex(ValueError, "must be in range"):
+            self.blob[0] = 2**65
 
     def test_blob_set_slice_error(self):
         with self.assertRaisesRegex(IndexError, "wrong size"):
diff --git a/Modules/_sqlite/blob.c b/Modules/_sqlite/blob.c
index 0c57ff8ca4252..76d261baf00f3 100644
--- a/Modules/_sqlite/blob.c
+++ b/Modules/_sqlite/blob.c
@@ -120,10 +120,26 @@ blob_seterror(pysqlite_Blob *self, int rc)
 }
 
 static PyObject *
-inner_read(pysqlite_Blob *self, Py_ssize_t length, Py_ssize_t offset)
+read_single(pysqlite_Blob *self, Py_ssize_t offset)
+{
+    unsigned char buf = 0;
+    int rc;
+    Py_BEGIN_ALLOW_THREADS
+    rc = sqlite3_blob_read(self->blob, (void *)&buf, 1, (int)offset);
+    Py_END_ALLOW_THREADS
+
+    if (rc != SQLITE_OK) {
+        blob_seterror(self, rc);
+        return NULL;
+    }
+    return PyLong_FromUnsignedLong((unsigned long)buf);
+}
+
+static PyObject *
+read_multiple(pysqlite_Blob *self, Py_ssize_t length, Py_ssize_t offset)
 {
     assert(length <= sqlite3_blob_bytes(self->blob));
-    assert(offset <= sqlite3_blob_bytes(self->blob));
+    assert(offset < sqlite3_blob_bytes(self->blob));
 
     PyObject *buffer = PyBytes_FromStringAndSize(NULL, length);
     if (buffer == NULL) {
@@ -175,7 +191,12 @@ blob_read_impl(pysqlite_Blob *self, int length)
         length = max_read_len;
     }
 
-    PyObject *buffer = inner_read(self, length, self->offset);
+    assert(length >= 0);
+    if (length == 0) {
+        return PyBytes_FromStringAndSize(NULL, 0);
+    }
+
+    PyObject *buffer = read_multiple(self, length, self->offset);
     if (buffer == NULL) {
         return NULL;
     }
@@ -387,7 +408,7 @@ subscript_index(pysqlite_Blob *self, PyObject *item)
     if (i < 0) {
         return NULL;
     }
-    return inner_read(self, 1, i);
+    return read_single(self, i);
 }
 
 static int
@@ -411,9 +432,9 @@ subscript_slice(pysqlite_Blob *self, PyObject *item)
     }
 
     if (step == 1) {
-        return inner_read(self, len, start);
+        return read_multiple(self, len, start);
     }
-    PyObject *blob = inner_read(self, stop - start, start);
+    PyObject *blob = read_multiple(self, stop - start, start);
     if (blob == NULL) {
         return NULL;
     }
@@ -455,24 +476,29 @@ ass_subscript_index(pysqlite_Blob *self, PyObject *item, PyObject *value)
                         "Blob doesn't support item deletion");
         return -1;
     }
+    if (!PyLong_Check(value)) {
+        PyErr_Format(PyExc_TypeError,
+                     "'%s' object cannot be interpreted as an integer",
+                     Py_TYPE(value)->tp_name);
+        return -1;
+    }
     Py_ssize_t i = get_subscript_index(self, item);
     if (i < 0) {
         return -1;
     }
 
-    Py_buffer vbuf;
-    if (PyObject_GetBuffer(value, &vbuf, PyBUF_SIMPLE) < 0) {
-        return -1;
+    long val = PyLong_AsLong(value);
+    if (val == -1 && PyErr_Occurred()) {
+        PyErr_Clear();
+        val = -1;
     }
-    int rc = -1;
-    if (vbuf.len != 1) {
-        PyErr_SetString(PyExc_ValueError, "Blob assignment must be a single byte");
-    }
-    else {
-        rc = inner_write(self, (const char *)vbuf.buf, 1, i);
+    if (val < 0 || val > 255) {
+        PyErr_SetString(PyExc_ValueError, "byte must be in range(0, 256)");
+        return -1;
     }
-    PyBuffer_Release(&vbuf);
-    return rc;
+    // Downcast to avoid endianness problems.
+    unsigned char byte = (unsigned char)val;
+    return inner_write(self, (const void *)&byte, 1, i);
 }
 
 static int
@@ -507,7 +533,7 @@ ass_subscript_slice(pysqlite_Blob *self, PyObject *item, PyObject *value)
         rc = inner_write(self, vbuf.buf, len, start);
     }
     else {
-        PyObject *blob_bytes = inner_read(self, stop - start, start);
+        PyObject *blob_bytes = read_multiple(self, stop - start, start);
         if (blob_bytes != NULL) {
             char *blob_buf = PyBytes_AS_STRING(blob_bytes);
             for (Py_ssize_t i = 0, j = 0; i < len; i++, j += step) {



More information about the Python-checkins mailing list