Skip to content

Commit

Permalink
bpo-41930: Add support for SQLite serialise/deserialise API (pythonGH…
Browse files Browse the repository at this point in the history
…-26728)

Co-authored-by: Jelle Zijlstra <[email protected]>
Co-authored-by: Kumar Aditya <[email protected]>
  • Loading branch information
3 people authored Apr 5, 2022
1 parent aa0f056 commit a755124
Show file tree
Hide file tree
Showing 10 changed files with 435 additions and 1 deletion.
38 changes: 38 additions & 0 deletions Doc/library/sqlite3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,44 @@ Connection Objects
.. versionadded:: 3.11


.. method:: serialize(*, name="main")

This method serializes a database into a :class:`bytes` object. For an
ordinary on-disk database file, the serialization is just a copy of the
disk file. For an in-memory database or a "temp" database, the
serialization is the same sequence of bytes which would be written to
disk if that database were backed up to disk.

*name* is the database to be serialized, and defaults to the main
database.

.. note::

This method is only available if the underlying SQLite library has the
serialize API.

.. versionadded:: 3.11


.. method:: deserialize(data, /, *, name="main")

This method causes the database connection to disconnect from database
*name*, and reopen *name* as an in-memory database based on the
serialization contained in *data*. Deserialization will raise
:exc:`OperationalError` if the database connection is currently involved
in a read transaction or a backup operation. :exc:`DataError` will be
raised if ``len(data)`` is larger than ``2**63 - 1``, and
:exc:`DatabaseError` will be raised if *data* does not contain a valid
SQLite database.

.. note::

This method is only available if the underlying SQLite library has the
deserialize API.

.. versionadded:: 3.11


.. _sqlite3-cursor-objects:

Cursor Objects
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,11 @@ sqlite3
Instead we leave it to the SQLite library to handle these cases.
(Contributed by Erlend E. Aasland in :issue:`44092`.)

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


sys
---
Expand Down
55 changes: 55 additions & 0 deletions Lib/test/test_sqlite3/test_dbapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

from test.support import (
SHORT_TIMEOUT,
bigmemtest,
check_disallow_instantiation,
threading_helper,
)
Expand Down Expand Up @@ -603,6 +604,56 @@ def test_uninit_operations(self):
func)


@unittest.skipUnless(hasattr(sqlite.Connection, "serialize"),
"Needs SQLite serialize API")
class SerializeTests(unittest.TestCase):
def test_serialize_deserialize(self):
with memory_database() as cx:
with cx:
cx.execute("create table t(t)")
data = cx.serialize()
self.assertEqual(len(data), 8192)

# Remove test table, verify that it was removed.
with cx:
cx.execute("drop table t")
regex = "no such table"
with self.assertRaisesRegex(sqlite.OperationalError, regex):
cx.execute("select t from t")

# Deserialize and verify that test table is restored.
cx.deserialize(data)
cx.execute("select t from t")

def test_deserialize_wrong_args(self):
dataset = (
(BufferError, memoryview(b"blob")[::2]),
(TypeError, []),
(TypeError, 1),
(TypeError, None),
)
for exc, arg in dataset:
with self.subTest(exc=exc, arg=arg):
with memory_database() as cx:
self.assertRaises(exc, cx.deserialize, arg)

def test_deserialize_corrupt_database(self):
with memory_database() as cx:
regex = "file is not a database"
with self.assertRaisesRegex(sqlite.DatabaseError, regex):
cx.deserialize(b"\0\1\3")
# SQLite does not generate an error until you try to query the
# deserialized database.
cx.execute("create table fail(f)")

@unittest.skipUnless(sys.maxsize > 2**32, 'requires 64bit platform')
@bigmemtest(size=2**63, memuse=3, dry_run=False)
def test_deserialize_too_much_data_64bit(self):
with memory_database() as cx:
with self.assertRaisesRegex(OverflowError, "'data' is too large"):
cx.deserialize(b"b" * size)


class OpenTests(unittest.TestCase):
_sql = "create table test(id integer)"

Expand Down Expand Up @@ -1030,6 +1081,10 @@ def test_check_connection_thread(self):
lambda: self.con.setlimit(sqlite.SQLITE_LIMIT_LENGTH, -1),
lambda: self.con.getlimit(sqlite.SQLITE_LIMIT_LENGTH),
]
if hasattr(sqlite.Connection, "serialize"):
fns.append(lambda: self.con.serialize())
fns.append(lambda: self.con.deserialize(b""))

for fn in fns:
with self.subTest(fn=fn):
self._run_test(fn)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add :meth:`~sqlite3.Connection.serialize` and
:meth:`~sqlite3.Connection.deserialize` support to :mod:`sqlite3`. Patch by
Erlend E. Aasland.
160 changes: 159 additions & 1 deletion Modules/_sqlite/clinic/connection.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a755124

Please sign in to comment.