Skip to content

Commit a755124

Browse files
Erlend Egeberg AaslandJelleZijlstrakumaraditya303
authored
bpo-41930: Add support for SQLite serialise/deserialise API (GH-26728)
Co-authored-by: Jelle Zijlstra <[email protected]> Co-authored-by: Kumar Aditya <[email protected]>
1 parent aa0f056 commit a755124

File tree

10 files changed

+435
-1
lines changed

10 files changed

+435
-1
lines changed

Doc/library/sqlite3.rst

+38
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,44 @@ Connection Objects
748748
.. versionadded:: 3.11
749749

750750

751+
.. method:: serialize(*, name="main")
752+
753+
This method serializes a database into a :class:`bytes` object. For an
754+
ordinary on-disk database file, the serialization is just a copy of the
755+
disk file. For an in-memory database or a "temp" database, the
756+
serialization is the same sequence of bytes which would be written to
757+
disk if that database were backed up to disk.
758+
759+
*name* is the database to be serialized, and defaults to the main
760+
database.
761+
762+
.. note::
763+
764+
This method is only available if the underlying SQLite library has the
765+
serialize API.
766+
767+
.. versionadded:: 3.11
768+
769+
770+
.. method:: deserialize(data, /, *, name="main")
771+
772+
This method causes the database connection to disconnect from database
773+
*name*, and reopen *name* as an in-memory database based on the
774+
serialization contained in *data*. Deserialization will raise
775+
:exc:`OperationalError` if the database connection is currently involved
776+
in a read transaction or a backup operation. :exc:`DataError` will be
777+
raised if ``len(data)`` is larger than ``2**63 - 1``, and
778+
:exc:`DatabaseError` will be raised if *data* does not contain a valid
779+
SQLite database.
780+
781+
.. note::
782+
783+
This method is only available if the underlying SQLite library has the
784+
deserialize API.
785+
786+
.. versionadded:: 3.11
787+
788+
751789
.. _sqlite3-cursor-objects:
752790

753791
Cursor Objects

Doc/whatsnew/3.11.rst

+5
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,11 @@ sqlite3
366366
Instead we leave it to the SQLite library to handle these cases.
367367
(Contributed by Erlend E. Aasland in :issue:`44092`.)
368368

369+
* Add :meth:`~sqlite3.Connection.serialize` and
370+
:meth:`~sqlite3.Connection.deserialize` to :class:`sqlite3.Connection` for
371+
serializing and deserializing databases.
372+
(Contributed by Erlend E. Aasland in :issue:`41930`.)
373+
369374

370375
sys
371376
---

Lib/test/test_sqlite3/test_dbapi.py

+55
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
from test.support import (
3131
SHORT_TIMEOUT,
32+
bigmemtest,
3233
check_disallow_instantiation,
3334
threading_helper,
3435
)
@@ -603,6 +604,56 @@ def test_uninit_operations(self):
603604
func)
604605

605606

607+
@unittest.skipUnless(hasattr(sqlite.Connection, "serialize"),
608+
"Needs SQLite serialize API")
609+
class SerializeTests(unittest.TestCase):
610+
def test_serialize_deserialize(self):
611+
with memory_database() as cx:
612+
with cx:
613+
cx.execute("create table t(t)")
614+
data = cx.serialize()
615+
self.assertEqual(len(data), 8192)
616+
617+
# Remove test table, verify that it was removed.
618+
with cx:
619+
cx.execute("drop table t")
620+
regex = "no such table"
621+
with self.assertRaisesRegex(sqlite.OperationalError, regex):
622+
cx.execute("select t from t")
623+
624+
# Deserialize and verify that test table is restored.
625+
cx.deserialize(data)
626+
cx.execute("select t from t")
627+
628+
def test_deserialize_wrong_args(self):
629+
dataset = (
630+
(BufferError, memoryview(b"blob")[::2]),
631+
(TypeError, []),
632+
(TypeError, 1),
633+
(TypeError, None),
634+
)
635+
for exc, arg in dataset:
636+
with self.subTest(exc=exc, arg=arg):
637+
with memory_database() as cx:
638+
self.assertRaises(exc, cx.deserialize, arg)
639+
640+
def test_deserialize_corrupt_database(self):
641+
with memory_database() as cx:
642+
regex = "file is not a database"
643+
with self.assertRaisesRegex(sqlite.DatabaseError, regex):
644+
cx.deserialize(b"\0\1\3")
645+
# SQLite does not generate an error until you try to query the
646+
# deserialized database.
647+
cx.execute("create table fail(f)")
648+
649+
@unittest.skipUnless(sys.maxsize > 2**32, 'requires 64bit platform')
650+
@bigmemtest(size=2**63, memuse=3, dry_run=False)
651+
def test_deserialize_too_much_data_64bit(self):
652+
with memory_database() as cx:
653+
with self.assertRaisesRegex(OverflowError, "'data' is too large"):
654+
cx.deserialize(b"b" * size)
655+
656+
606657
class OpenTests(unittest.TestCase):
607658
_sql = "create table test(id integer)"
608659

@@ -1030,6 +1081,10 @@ def test_check_connection_thread(self):
10301081
lambda: self.con.setlimit(sqlite.SQLITE_LIMIT_LENGTH, -1),
10311082
lambda: self.con.getlimit(sqlite.SQLITE_LIMIT_LENGTH),
10321083
]
1084+
if hasattr(sqlite.Connection, "serialize"):
1085+
fns.append(lambda: self.con.serialize())
1086+
fns.append(lambda: self.con.deserialize(b""))
1087+
10331088
for fn in fns:
10341089
with self.subTest(fn=fn):
10351090
self._run_test(fn)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add :meth:`~sqlite3.Connection.serialize` and
2+
:meth:`~sqlite3.Connection.deserialize` support to :mod:`sqlite3`. Patch by
3+
Erlend E. Aasland.

Modules/_sqlite/clinic/connection.c.h

+159-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)