From 38b2cdb16a5d588d770b936e2bb8b5616382b5ab Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 16 Apr 2022 09:07:41 +0200 Subject: [PATCH 1/9] gh-69093: Add mapping protocol support to sqlite3.Blob Authored-by: Aviv Palivoda Co-authored-by: Erlend E. Aasland --- Doc/library/sqlite3.rst | 7 +- Lib/test/test_sqlite3/test_dbapi.py | 102 +++++++++- ...2-04-14-01-00-31.gh-issue-69093.bmlMwI.rst | 2 + Modules/_sqlite/blob.c | 185 ++++++++++++++++++ 4 files changed, 289 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-04-14-01-00-31.gh-issue-69093.bmlMwI.rst diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 4838db01669e66..80b84a75df38bd 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1111,9 +1111,10 @@ Blob Objects .. class:: Blob - A :class:`Blob` instance is a :term:`file-like object` that can read and write - data in an SQLite :abbr:`BLOB (Binary Large OBject)`. Call ``len(blob)`` to - get the size (number of bytes) of the blob. + A :class:`Blob` instance is a :term:`file-like object` with + :term:`mapping` support, + that can read and write data in an SQLite :abbr:`BLOB (Binary Large OBject)`. + Call ``len(blob)`` to get the size (number of bytes) of the blob. Use the :class:`Blob` as a :term:`context manager` to ensure that the blob handle is closed after use. diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index 79dcb3ef8954a0..d225a321190dc6 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -33,7 +33,7 @@ check_disallow_instantiation, threading_helper, ) -from _testcapi import INT_MAX +from _testcapi import INT_MAX, ULLONG_MAX from os import SEEK_SET, SEEK_CUR, SEEK_END from test.support.os_helper import TESTFN, unlink, temp_dir @@ -1162,12 +1162,98 @@ def test_blob_open_error(self): with self.assertRaisesRegex(sqlite.OperationalError, regex): self.cx.blobopen(*args, **kwds) + 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"!") + + def test_blob_set_item(self): + self.blob[0] = b"b" + expected = b"b" + self.data[1:] + actual = self.cx.execute("select b from test").fetchone()[0] + self.assertEqual(actual, expected) + + def test_blob_set_item_negative_index(self): + self.blob[-1] = b"z" + self.assertEqual(self.blob[-1], b"z") + + def test_blob_get_slice(self): + self.assertEqual(self.blob[5:14], b"blob data") + + def test_blob_get_empty_slice(self): + self.assertEqual(self.blob[5:5], b"") + + def test_blob_get_slice_negative_index(self): + self.assertEqual(self.blob[5:-5], self.data[5:-5]) + + def test_blob_get_slice_with_skip(self): + self.assertEqual(self.blob[0:10:2], b"ti lb") + + 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) + + def test_blob_set_empty_slice(self): + self.blob[0:0] = b"" + self.assertEqual(self.blob[:], self.data) + + 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] + expected = b"1h2s3b4o5 " + self.data[10:] + self.assertEqual(actual, expected) + + def test_blob_mapping_invalid_index_type(self): + msg = "indices must be integers" + with self.assertRaisesRegex(TypeError, msg): + self.blob[5:5.5] + with self.assertRaisesRegex(TypeError, msg): + self.blob[1.5] + with self.assertRaisesRegex(TypeError, msg): + self.blob["a"] = b"b" + + def test_blob_get_item_error(self): + dataset = [len(self.blob), 105, -105] + for idx in dataset: + with self.subTest(idx=idx): + with self.assertRaisesRegex(IndexError, "index out of range"): + self.blob[idx] + with self.assertRaisesRegex(IndexError, "cannot fit 'int'"): + self.blob[ULLONG_MAX] + + def test_blob_set_item_error(self): + with self.assertRaisesRegex(ValueError, "must be a single byte"): + self.blob[0] = b"multiple" + 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" + + def test_blob_set_slice_error(self): + with self.assertRaisesRegex(IndexError, "wrong size"): + self.blob[5:10] = b"a" + with self.assertRaisesRegex(IndexError, "wrong size"): + self.blob[5:10] = b"a" * 1000 + with self.assertRaisesRegex(TypeError, "doesn't support.*deletion"): + del self.blob[5:10] + with self.assertRaisesRegex(ValueError, "step cannot be zero"): + self.blob[5:10:0] = b"12345" + with self.assertRaises(BufferError): + self.blob[5:10] = memoryview(b"abcde")[::2] + def test_blob_sequence_not_supported(self): - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, "unsupported operand"): self.blob + self.blob - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, "unsupported operand"): self.blob * 5 - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, "is not iterable"): b"a" in self.blob def test_blob_context_manager(self): @@ -1209,6 +1295,14 @@ def test_blob_closed(self): blob.__enter__() with self.assertRaisesRegex(sqlite.ProgrammingError, msg): blob.__exit__(None, None, None) + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + len(blob) + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + blob[0] + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + blob[0:1] + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + blob[0] = b"" def test_blob_closed_db_read(self): with memory_database() as cx: diff --git a/Misc/NEWS.d/next/Library/2022-04-14-01-00-31.gh-issue-69093.bmlMwI.rst b/Misc/NEWS.d/next/Library/2022-04-14-01-00-31.gh-issue-69093.bmlMwI.rst new file mode 100644 index 00000000000000..93e8196bd4a8e3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-04-14-01-00-31.gh-issue-69093.bmlMwI.rst @@ -0,0 +1,2 @@ +Add :term:`mapping` support to :class:`sqlite3.Blob`. Patch by Aviv Palivoda +and Erlend E. Aasland. diff --git a/Modules/_sqlite/blob.c b/Modules/_sqlite/blob.c index 3f766302d62517..a68d747f71aa89 100644 --- a/Modules/_sqlite/blob.c +++ b/Modules/_sqlite/blob.c @@ -347,6 +347,186 @@ blob_exit_impl(pysqlite_Blob *self, PyObject *type, PyObject *val, Py_RETURN_FALSE; } +static Py_ssize_t +blob_length(pysqlite_Blob *self) +{ + if (!check_blob(self)) { + return -1; + } + return sqlite3_blob_bytes(self->blob); +}; + +static int +get_subscript_index(pysqlite_Blob *self, PyObject *item) +{ + Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError); + if (i == -1 && PyErr_Occurred()) { + return -1; + } + int blob_len = sqlite3_blob_bytes(self->blob); + if (i < 0) { + i += blob_len; + } + if (i < 0 || i >= blob_len) { + PyErr_SetString(PyExc_IndexError, "Blob index out of range"); + return -1; + } + return i; +} + +static PyObject * +subscript_index(pysqlite_Blob *self, PyObject *item) +{ + int i = get_subscript_index(self, item); + if (i < 0) { + return NULL; + } + return inner_read(self, 1, i); +} + +static int +get_slice_info(pysqlite_Blob *self, PyObject *item, Py_ssize_t *start, + Py_ssize_t *stop, Py_ssize_t *step, Py_ssize_t *slicelen) +{ + if (PySlice_Unpack(item, start, stop, step) < 0) { + return -1; + } + int len = sqlite3_blob_bytes(self->blob); + *slicelen = PySlice_AdjustIndices(len, start, stop, *step); + return 0; +} + +static PyObject * +subscript_slice(pysqlite_Blob *self, PyObject *item) +{ + Py_ssize_t start, stop, step, len; + if (get_slice_info(self, item, &start, &stop, &step, &len) < 0) { + return NULL; + } + + if (step == 1) { + return inner_read(self, len, start); + } + PyObject *blob = inner_read(self, stop - start, start); + if (blob == NULL) { + return NULL; + } + PyObject *result = PyBytes_FromStringAndSize(NULL, len); + if (result != NULL) { + char *blob_buf = PyBytes_AS_STRING(blob); + char *res_buf = PyBytes_AS_STRING(result); + for (Py_ssize_t i = 0, j = 0; i < len; i++, j += step) { + res_buf[i] = blob_buf[j]; + } + Py_DECREF(blob); + } + return result; +} + +static PyObject * +blob_subscript(pysqlite_Blob *self, PyObject *item) +{ + if (!check_blob(self)) { + return NULL; + } + + if (PyIndex_Check(item)) { + return subscript_index(self, item); + } + if (PySlice_Check(item)) { + return subscript_slice(self, item); + } + + PyErr_SetString(PyExc_TypeError, "Blob indices must be integers"); + return NULL; +} + +static int +ass_subscript_index(pysqlite_Blob *self, PyObject *item, PyObject *value) +{ + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, + "Blob doesn't support item deletion"); + return -1; + } + if (!PyBytes_Check(value) || PyBytes_Size(value) != 1) { + PyErr_SetString(PyExc_ValueError, + "Blob assignment must be a single byte"); + return -1; + } + + int i = get_subscript_index(self, item); + if (i < 0) { + return -1; + } + const char *buf = PyBytes_AS_STRING(value); + return inner_write(self, buf, 1, i); +} + +static int +ass_subscript_slice(pysqlite_Blob *self, PyObject *item, PyObject *value) +{ + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, + "Blob doesn't support slice deletion"); + return -1; + } + + Py_ssize_t start, stop, step, len; + if (get_slice_info(self, item, &start, &stop, &step, &len) < 0) { + return -1; + } + + if (len == 0) { + return 0; + } + + Py_buffer vbuf; + if (PyObject_GetBuffer(value, &vbuf, PyBUF_SIMPLE) < 0) { + return -1; + } + + int rc = -1; + if (vbuf.len != len) { + PyErr_SetString(PyExc_IndexError, + "Blob slice assignment is wrong size"); + } + else if (step == 1) { + rc = inner_write(self, vbuf.buf, len, start); + } + else { + PyObject *blob_bytes = inner_read(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) { + blob_buf[j] = ((char *)vbuf.buf)[i]; + } + rc = inner_write(self, blob_buf, stop - start, start); + Py_DECREF(blob_bytes); + } + } + PyBuffer_Release(&vbuf); + return rc; +} + +static int +blob_ass_subscript(pysqlite_Blob *self, PyObject *item, PyObject *value) +{ + if (!check_blob(self)) { + return -1; + } + + if (PyIndex_Check(item)) { + return ass_subscript_index(self, item, value); + } + if (PySlice_Check(item)) { + return ass_subscript_slice(self, item, value); + } + + PyErr_SetString(PyExc_TypeError, "Blob indices must be integers"); + return -1; +} + static PyMethodDef blob_methods[] = { BLOB_CLOSE_METHODDEF @@ -370,6 +550,11 @@ static PyType_Slot blob_slots[] = { {Py_tp_clear, blob_clear}, {Py_tp_methods, blob_methods}, {Py_tp_members, blob_members}, + + // Mapping protocol + {Py_mp_length, blob_length}, + {Py_mp_subscript, blob_subscript}, + {Py_mp_ass_subscript, blob_ass_subscript}, {0, NULL}, }; From 86e324a49243b7789e5254335b8dab22842c831f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 16 Apr 2022 21:12:10 +0200 Subject: [PATCH 2/9] Cast --- Modules/_sqlite/blob.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Modules/_sqlite/blob.c b/Modules/_sqlite/blob.c index a68d747f71aa89..49d487fe6853a6 100644 --- a/Modules/_sqlite/blob.c +++ b/Modules/_sqlite/blob.c @@ -120,7 +120,7 @@ blob_seterror(pysqlite_Blob *self, int rc) } static PyObject * -inner_read(pysqlite_Blob *self, int length, int offset) +inner_read(pysqlite_Blob *self, Py_ssize_t length, Py_ssize_t offset) { PyObject *buffer = PyBytes_FromStringAndSize(NULL, length); if (buffer == NULL) { @@ -130,7 +130,7 @@ inner_read(pysqlite_Blob *self, int length, int offset) char *raw_buffer = PyBytes_AS_STRING(buffer); int rc; Py_BEGIN_ALLOW_THREADS - rc = sqlite3_blob_read(self->blob, raw_buffer, length, offset); + rc = sqlite3_blob_read(self->blob, raw_buffer, (int)length, (int)offset); Py_END_ALLOW_THREADS if (rc != SQLITE_OK) { @@ -181,9 +181,11 @@ blob_read_impl(pysqlite_Blob *self, int length) }; static int -inner_write(pysqlite_Blob *self, const void *buf, Py_ssize_t len, int offset) +inner_write(pysqlite_Blob *self, const void *buf, Py_ssize_t len, + Py_ssize_t offset) { - int remaining_len = sqlite3_blob_bytes(self->blob) - self->offset; + int blob_len = sqlite3_blob_bytes(self->blob); + int remaining_len = blob_len - self->offset; if (len > remaining_len) { PyErr_SetString(PyExc_ValueError, "data longer than blob length"); return -1; @@ -191,7 +193,7 @@ inner_write(pysqlite_Blob *self, const void *buf, Py_ssize_t len, int offset) int rc; Py_BEGIN_ALLOW_THREADS - rc = sqlite3_blob_write(self->blob, buf, (int)len, offset); + rc = sqlite3_blob_write(self->blob, buf, (int)len, (int)offset); Py_END_ALLOW_THREADS if (rc != SQLITE_OK) { @@ -356,7 +358,7 @@ blob_length(pysqlite_Blob *self) return sqlite3_blob_bytes(self->blob); }; -static int +static Py_ssize_t get_subscript_index(pysqlite_Blob *self, PyObject *item) { Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError); @@ -377,7 +379,7 @@ get_subscript_index(pysqlite_Blob *self, PyObject *item) static PyObject * subscript_index(pysqlite_Blob *self, PyObject *item) { - int i = get_subscript_index(self, item); + Py_ssize_t i = get_subscript_index(self, item); if (i < 0) { return NULL; } @@ -455,7 +457,7 @@ ass_subscript_index(pysqlite_Blob *self, PyObject *item, PyObject *value) return -1; } - int i = get_subscript_index(self, item); + Py_ssize_t i = get_subscript_index(self, item); if (i < 0) { return -1; } From 37a5c38c6ce5a922493e4cea7a8e5a9267263855 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 16 Apr 2022 21:47:40 +0200 Subject: [PATCH 3/9] Assert lenght and offset --- Modules/_sqlite/blob.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/_sqlite/blob.c b/Modules/_sqlite/blob.c index 49d487fe6853a6..a4ad8d05158b7a 100644 --- a/Modules/_sqlite/blob.c +++ b/Modules/_sqlite/blob.c @@ -122,6 +122,9 @@ blob_seterror(pysqlite_Blob *self, int rc) static PyObject * inner_read(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) - self->offset); + PyObject *buffer = PyBytes_FromStringAndSize(NULL, length); if (buffer == NULL) { return NULL; @@ -191,6 +194,7 @@ inner_write(pysqlite_Blob *self, const void *buf, Py_ssize_t len, return -1; } + assert(offset <= remaining_len); int rc; Py_BEGIN_ALLOW_THREADS rc = sqlite3_blob_write(self->blob, buf, (int)len, (int)offset); From a1797495a901bc708b2227e6f6262e0d1ae1a174 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 17 Apr 2022 00:49:08 +0200 Subject: [PATCH 4/9] Accept all buffer objects --- Lib/test/test_sqlite3/test_dbapi.py | 9 +++++++++ Modules/_sqlite/blob.c | 20 +++++++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index d225a321190dc6..bd1864535d2b6d 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -1178,6 +1178,15 @@ def test_blob_set_item(self): actual = self.cx.execute("select b from test").fetchone()[0] self.assertEqual(actual, expected) + def test_blob_set_buffer_object(self): + from array import array + self.blob[0] = memoryview(b"1") + self.blob[0] = bytearray(b"1") + self.blob[0] = array("b", [1]) + self.blob[0:5] = memoryview(b"12345") + self.blob[0:5] = bytearray(b"12345") + self.blob[0:5] = array("b", [1, 2, 3, 4, 5]) + def test_blob_set_item_negative_index(self): self.blob[-1] = b"z" self.assertEqual(self.blob[-1], b"z") diff --git a/Modules/_sqlite/blob.c b/Modules/_sqlite/blob.c index a4ad8d05158b7a..2cda9514063fda 100644 --- a/Modules/_sqlite/blob.c +++ b/Modules/_sqlite/blob.c @@ -455,18 +455,24 @@ ass_subscript_index(pysqlite_Blob *self, PyObject *item, PyObject *value) "Blob doesn't support item deletion"); return -1; } - if (!PyBytes_Check(value) || PyBytes_Size(value) != 1) { - PyErr_SetString(PyExc_ValueError, - "Blob assignment must be a single byte"); + Py_ssize_t i = get_subscript_index(self, item); + if (i < 0) { return -1; } - Py_ssize_t i = get_subscript_index(self, item); - if (i < 0) { + Py_buffer vbuf; + if (PyObject_GetBuffer(value, &vbuf, PyBUF_SIMPLE) < 0) { return -1; } - const char *buf = PyBytes_AS_STRING(value); - return inner_write(self, buf, 1, i); + 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); + } + PyBuffer_Release(&vbuf); + return rc; } static int From ed6a9b62da79b991b3376f6d88ff95baf1d4ef6b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 18 Apr 2022 23:57:35 +0200 Subject: [PATCH 5/9] Address review: reword docs and modify example --- Doc/includes/sqlite3/blob.py | 11 +++++++---- Doc/library/sqlite3.rst | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Doc/includes/sqlite3/blob.py b/Doc/includes/sqlite3/blob.py index b3694ad08af46b..d947059b3ae647 100644 --- a/Doc/includes/sqlite3/blob.py +++ b/Doc/includes/sqlite3/blob.py @@ -2,15 +2,18 @@ con = sqlite3.connect(":memory:") con.execute("create table test(blob_col blob)") -con.execute("insert into test(blob_col) values (zeroblob(10))") +con.execute("insert into test(blob_col) values (zeroblob(13))") # Write to our blob, using two write operations: with con.blobopen("test", "blob_col", 1) as blob: - blob.write(b"Hello") - blob.write(b"World") + blob.write(b"hello, ") + blob.write(b"world.") + # Modify the first and last bytes of our blob + blob[0] = b"H" + blob[-1] = b"!" # Read the contents of our blob with con.blobopen("test", "blob_col", 1) as blob: greeting = blob.read() -print(greeting) # outputs "b'HelloWorld'" +print(greeting) # outputs "b'Hello, world!'" diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 4f2915540a2343..77be92a53862d8 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1054,10 +1054,10 @@ Blob Objects .. class:: Blob - A :class:`Blob` instance is a :term:`file-like object` with - :term:`mapping` support, + A :class:`Blob` instance is a :term:`file-like object` that can read and write data in an SQLite :abbr:`BLOB (Binary Large OBject)`. - Call ``len(blob)`` to get the size (number of bytes) of the blob. + Call :func:`len(blob) ` to get the size (number of bytes) of the blob. + Use indices and :term:`slices ` for direct access to the blob data. Use the :class:`Blob` as a :term:`context manager` to ensure that the blob handle is closed after use. From 233f94074e9c31f5f260d3b890afc581b090d4c7 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 19 Apr 2022 00:04:25 +0200 Subject: [PATCH 6/9] bugfix: use correct offset in inner read/write --- Lib/test/test_sqlite3/test_dbapi.py | 16 ++++++++++++++++ Modules/_sqlite/blob.c | 6 +++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index bd1864535d2b6d..07ee663655516d 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -1138,6 +1138,13 @@ def test_blob_write_error_length(self): with self.assertRaisesRegex(ValueError, "data longer than blob"): self.blob.write(b"a" * 1000) + self.blob.seek(0, SEEK_SET) + n = len(self.blob) + self.blob.write(b"a" * (n-1)) + self.blob.write(b"a" * 1) + with self.assertRaisesRegex(ValueError, "data longer than blob"): + self.blob.write(b"a" * 1) + def test_blob_write_error_row_changed(self): self.cx.execute("update test set b='aaaa' where rowid=1") with self.assertRaises(sqlite.OperationalError): @@ -1178,6 +1185,15 @@ def test_blob_set_item(self): actual = self.cx.execute("select b from test").fetchone()[0] self.assertEqual(actual, expected) + 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.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): from array import array self.blob[0] = memoryview(b"1") diff --git a/Modules/_sqlite/blob.c b/Modules/_sqlite/blob.c index 2cda9514063fda..ea12053a862c23 100644 --- a/Modules/_sqlite/blob.c +++ b/Modules/_sqlite/blob.c @@ -123,7 +123,7 @@ static PyObject * inner_read(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) - self->offset); + assert(offset <= sqlite3_blob_bytes(self->blob)); PyObject *buffer = PyBytes_FromStringAndSize(NULL, length); if (buffer == NULL) { @@ -188,13 +188,13 @@ inner_write(pysqlite_Blob *self, const void *buf, Py_ssize_t len, Py_ssize_t offset) { int blob_len = sqlite3_blob_bytes(self->blob); - int remaining_len = blob_len - self->offset; + int remaining_len = blob_len - offset; if (len > remaining_len) { PyErr_SetString(PyExc_ValueError, "data longer than blob length"); return -1; } - assert(offset <= remaining_len); + assert(offset <= blob_len); int rc; Py_BEGIN_ALLOW_THREADS rc = sqlite3_blob_write(self->blob, buf, (int)len, (int)offset); From 9cbc33979006bc8a3c73d3d5fe63c57bcea07aa3 Mon Sep 17 00:00:00 2001 From: Erlend Egeberg Aasland Date: Tue, 19 Apr 2022 08:10:08 +0200 Subject: [PATCH 7/9] Update Misc/NEWS.d/next/Library/2022-04-14-01-00-31.gh-issue-69093.bmlMwI.rst Co-authored-by: Jelle Zijlstra --- .../next/Library/2022-04-14-01-00-31.gh-issue-69093.bmlMwI.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2022-04-14-01-00-31.gh-issue-69093.bmlMwI.rst b/Misc/NEWS.d/next/Library/2022-04-14-01-00-31.gh-issue-69093.bmlMwI.rst index 93e8196bd4a8e3..4bb8531beeacd8 100644 --- a/Misc/NEWS.d/next/Library/2022-04-14-01-00-31.gh-issue-69093.bmlMwI.rst +++ b/Misc/NEWS.d/next/Library/2022-04-14-01-00-31.gh-issue-69093.bmlMwI.rst @@ -1,2 +1,2 @@ -Add :term:`mapping` support to :class:`sqlite3.Blob`. Patch by Aviv Palivoda +Add indexing and slicing support to :class:`sqlite3.Blob`. Patch by Aviv Palivoda and Erlend E. Aasland. From 7df4b9d394de8febbe3c5056baee4eccd623900a Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 19 Apr 2022 22:25:49 +0200 Subject: [PATCH 8/9] Address review: verify writes --- Lib/test/test_sqlite3/test_dbapi.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index 07ee663655516d..8bfdce2bbe92e4 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -1141,9 +1141,9 @@ def test_blob_write_error_length(self): self.blob.seek(0, SEEK_SET) n = len(self.blob) self.blob.write(b"a" * (n-1)) - self.blob.write(b"a" * 1) + self.blob.write(b"a") with self.assertRaisesRegex(ValueError, "data longer than blob"): - self.blob.write(b"a" * 1) + self.blob.write(b"a") def test_blob_write_error_row_changed(self): self.cx.execute("update test set b='aaaa' where rowid=1") @@ -1197,11 +1197,22 @@ def test_blob_set_item_with_offset(self): def test_blob_set_buffer_object(self): from array import array self.blob[0] = memoryview(b"1") - self.blob[0] = bytearray(b"1") - self.blob[0] = array("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.blob[0:5] = bytearray(b"12345") + self.assertEqual(self.blob[0:5], b"12345") + + self.blob[0:5] = bytearray(b"23456") + self.assertEqual(self.blob[0:5], b"23456") + self.blob[0:5] = array("b", [1, 2, 3, 4, 5]) + 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" From cb2619e4943fcdc718f20dbf82e37fdb10cd1cfd Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 19 Apr 2022 22:27:26 +0200 Subject: [PATCH 9/9] Address review: Py_ssize_t iso. int --- Modules/_sqlite/blob.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_sqlite/blob.c b/Modules/_sqlite/blob.c index ea12053a862c23..0c57ff8ca4252b 100644 --- a/Modules/_sqlite/blob.c +++ b/Modules/_sqlite/blob.c @@ -187,8 +187,8 @@ static int inner_write(pysqlite_Blob *self, const void *buf, Py_ssize_t len, Py_ssize_t offset) { - int blob_len = sqlite3_blob_bytes(self->blob); - int remaining_len = blob_len - offset; + Py_ssize_t blob_len = sqlite3_blob_bytes(self->blob); + Py_ssize_t remaining_len = blob_len - offset; if (len > remaining_len) { PyErr_SetString(PyExc_ValueError, "data longer than blob length"); return -1; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy