sqlx_sqlite/connection/serialize.rs
1use super::ConnectionState;
2use crate::{error::Error, SqliteConnection, SqliteError};
3use libsqlite3_sys::{
4 sqlite3_deserialize, sqlite3_free, sqlite3_malloc64, sqlite3_serialize,
5 SQLITE_DESERIALIZE_FREEONCLOSE, SQLITE_DESERIALIZE_READONLY, SQLITE_DESERIALIZE_RESIZEABLE,
6 SQLITE_NOMEM, SQLITE_OK,
7};
8use std::ffi::c_char;
9use std::fmt::Debug;
10use std::{
11 ops::{Deref, DerefMut},
12 ptr,
13 ptr::NonNull,
14};
15
16impl SqliteConnection {
17 /// Serialize the given SQLite database schema using [`sqlite3_serialize()`].
18 ///
19 /// The returned buffer is a SQLite managed allocation containing the equivalent data
20 /// as writing the database to disk. It is freed on-drop.
21 ///
22 /// To serialize the primary, unqualified schema (`main`), pass `None` for the schema name.
23 ///
24 /// # Errors
25 /// * [`Error::InvalidArgument`] if the schema name contains a zero/NUL byte (`\0`).
26 /// * [`Error::Database`] if the schema does not exist or another error occurs.
27 ///
28 /// [`sqlite3_serialize()`]: https://sqlite.org/c3ref/serialize.html
29 pub async fn serialize(&mut self, schema: Option<&str>) -> Result<SqliteOwnedBuf, Error> {
30 let schema = schema.map(SchemaName::try_from).transpose()?;
31
32 self.worker.serialize(schema).await
33 }
34
35 /// Deserialize a SQLite database from a buffer into the specified schema using [`sqlite3_deserialize()`].
36 ///
37 /// The given schema will be disconnected and re-connected as an in-memory database
38 /// backed by `data`, which should be the serialized form of a database previously returned
39 /// by a call to [`Self::serialize()`], documented as being equivalent to
40 /// the contents of the database file on disk.
41 ///
42 /// An error will be returned if a schema with the given name is not already attached.
43 /// You can use `ATTACH ':memory' as "<schema name>"` to create an empty schema first.
44 ///
45 /// Pass `None` to deserialize to the primary, unqualified schema (`main`).
46 ///
47 /// The SQLite connection will take ownership of `data` and will free it when the connection
48 /// is closed or the schema is detached ([`SQLITE_DESERIALIZE_FREEONCLOSE`][deserialize-flags]).
49 ///
50 /// If `read_only` is `true`, the schema is opened as read-only ([`SQLITE_DESERIALIZE_READONLY`][deserialize-flags]).
51 /// If `false`, the schema is marked as resizable ([`SQLITE_DESERIALIZE_RESIZABLE`][deserialize-flags]).
52 ///
53 /// If the database is in WAL mode, an error is returned.
54 /// See [`sqlite3_deserialize()`] for details.
55 ///
56 /// # Errors
57 /// * [`Error::InvalidArgument`] if the schema name contains a zero/NUL byte (`\0`).
58 /// * [`Error::Database`] if an error occurs during deserialization.
59 ///
60 /// [`sqlite3_deserialize()`]: https://sqlite.org/c3ref/deserialize.html
61 /// [deserialize-flags]: https://sqlite.org/c3ref/c_deserialize_freeonclose.html
62 pub async fn deserialize(
63 &mut self,
64 schema: Option<&str>,
65 data: SqliteOwnedBuf,
66 read_only: bool,
67 ) -> Result<(), Error> {
68 let schema = schema.map(SchemaName::try_from).transpose()?;
69
70 self.worker.deserialize(schema, data, read_only).await
71 }
72}
73
74pub(crate) fn serialize(
75 conn: &mut ConnectionState,
76 schema: Option<SchemaName>,
77) -> Result<SqliteOwnedBuf, Error> {
78 let mut size = 0;
79
80 let buf = unsafe {
81 let ptr = sqlite3_serialize(
82 conn.handle.as_ptr(),
83 schema.as_ref().map_or(ptr::null(), SchemaName::as_ptr),
84 &mut size,
85 0,
86 );
87
88 // looking at the source, `sqlite3_serialize` actually sets `size = -1` on error:
89 // https://github.com/sqlite/sqlite/blob/da5f81387843f92652128087a8f8ecef0b79461d/src/memdb.c#L776
90 usize::try_from(size)
91 .ok()
92 .and_then(|size| SqliteOwnedBuf::from_raw(ptr, size))
93 };
94
95 if let Some(buf) = buf {
96 return Ok(buf);
97 }
98
99 if let Some(error) = conn.handle.last_error() {
100 return Err(error.into());
101 }
102
103 if size > 0 {
104 // If `size` is positive but `sqlite3_serialize` still returned NULL,
105 // the most likely culprit is an out-of-memory condition.
106 return Err(SqliteError::from_code(SQLITE_NOMEM).into());
107 }
108
109 // Otherwise, the schema was probably not found.
110 // We return the equivalent error as when you try to execute `PRAGMA <schema>.page_count`
111 // against a non-existent schema.
112 Err(SqliteError::generic(format!(
113 "database {} does not exist",
114 schema.as_ref().map_or("main", SchemaName::as_str)
115 ))
116 .into())
117}
118
119pub(crate) fn deserialize(
120 conn: &mut ConnectionState,
121 schema: Option<SchemaName>,
122 data: SqliteOwnedBuf,
123 read_only: bool,
124) -> Result<(), Error> {
125 // SQLITE_DESERIALIZE_FREEONCLOSE causes SQLite to take ownership of the buffer
126 let mut flags = SQLITE_DESERIALIZE_FREEONCLOSE;
127 if read_only {
128 flags |= SQLITE_DESERIALIZE_READONLY;
129 } else {
130 flags |= SQLITE_DESERIALIZE_RESIZEABLE;
131 }
132
133 let (buf, size) = data.into_raw();
134
135 let rc = unsafe {
136 sqlite3_deserialize(
137 conn.handle.as_ptr(),
138 schema.as_ref().map_or(ptr::null(), SchemaName::as_ptr),
139 buf,
140 i64::try_from(size).unwrap(),
141 i64::try_from(size).unwrap(),
142 flags,
143 )
144 };
145
146 match rc {
147 SQLITE_OK => Ok(()),
148 SQLITE_NOMEM => Err(SqliteError::from_code(SQLITE_NOMEM).into()),
149 // SQLite unfortunately doesn't set any specific message for deserialization errors.
150 _ => Err(SqliteError::generic("an error occurred during deserialization").into()),
151 }
152}
153
154/// Memory buffer owned and allocated by SQLite. Freed on drop.
155///
156/// Intended primarily for use with [`SqliteConnection::serialize()`] and [`SqliteConnection::deserialize()`].
157///
158/// Can be created from `&[u8]` using the `TryFrom` impl. The slice must not be empty.
159#[derive(Debug)]
160pub struct SqliteOwnedBuf {
161 ptr: NonNull<u8>,
162 size: usize,
163}
164
165unsafe impl Send for SqliteOwnedBuf {}
166unsafe impl Sync for SqliteOwnedBuf {}
167
168impl Drop for SqliteOwnedBuf {
169 fn drop(&mut self) {
170 unsafe {
171 sqlite3_free(self.ptr.as_ptr().cast());
172 }
173 }
174}
175
176impl SqliteOwnedBuf {
177 /// Uses `sqlite3_malloc` to allocate a buffer and returns a pointer to it.
178 ///
179 /// # Safety
180 /// The allocated buffer is uninitialized.
181 unsafe fn with_capacity(size: usize) -> Option<SqliteOwnedBuf> {
182 let ptr = sqlite3_malloc64(u64::try_from(size).unwrap()).cast::<u8>();
183 Self::from_raw(ptr, size)
184 }
185
186 /// Creates a new mem buffer from a pointer that has been created with sqlite_malloc
187 ///
188 /// # Safety:
189 /// * The pointer must point to a valid allocation created by `sqlite3_malloc()`, or `NULL`.
190 unsafe fn from_raw(ptr: *mut u8, size: usize) -> Option<Self> {
191 Some(Self {
192 ptr: NonNull::new(ptr)?,
193 size,
194 })
195 }
196
197 fn into_raw(self) -> (*mut u8, usize) {
198 let raw = (self.ptr.as_ptr(), self.size);
199 // this is used in sqlite_deserialize and
200 // underlying buffer must not be freed
201 std::mem::forget(self);
202 raw
203 }
204}
205
206/// # Errors
207/// Returns [`Error::InvalidArgument`] if the slice is empty.
208impl TryFrom<&[u8]> for SqliteOwnedBuf {
209 type Error = Error;
210
211 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
212 unsafe {
213 // SAFETY: `buf` is not initialized until `ptr::copy_nonoverlapping` completes.
214 let mut buf = Self::with_capacity(bytes.len()).ok_or_else(|| {
215 Error::InvalidArgument("SQLite owned buffer cannot be empty".to_string())
216 })?;
217 ptr::copy_nonoverlapping(bytes.as_ptr(), buf.ptr.as_mut(), buf.size);
218 Ok(buf)
219 }
220 }
221}
222
223impl Deref for SqliteOwnedBuf {
224 type Target = [u8];
225
226 fn deref(&self) -> &Self::Target {
227 unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.size) }
228 }
229}
230
231impl DerefMut for SqliteOwnedBuf {
232 fn deref_mut(&mut self) -> &mut Self::Target {
233 unsafe { std::slice::from_raw_parts_mut(self.ptr.as_mut(), self.size) }
234 }
235}
236
237impl AsRef<[u8]> for SqliteOwnedBuf {
238 fn as_ref(&self) -> &[u8] {
239 self.deref()
240 }
241}
242
243impl AsMut<[u8]> for SqliteOwnedBuf {
244 fn as_mut(&mut self) -> &mut [u8] {
245 self.deref_mut()
246 }
247}
248
249/// Checked schema name to pass to SQLite.
250///
251/// # Safety:
252/// * Valid UTF-8 (not guaranteed by `CString`)
253/// * No internal zero bytes (`\0`) (not guaranteed by `String`)
254/// * Terminated with a zero byte (`\0`) (not guaranteed by `String`)
255#[derive(Debug)]
256pub(crate) struct SchemaName(Box<str>);
257
258impl SchemaName {
259 /// Get the schema name as a string without the zero byte terminator.
260 pub fn as_str(&self) -> &str {
261 &self.0[..self.0.len() - 1]
262 }
263
264 /// Get a pointer to the string data, suitable for passing as C's `*const char`.
265 ///
266 /// # Safety
267 /// The string data is guaranteed to be terminated with a zero byte.
268 pub fn as_ptr(&self) -> *const c_char {
269 self.0.as_ptr() as *const c_char
270 }
271}
272
273impl<'a> TryFrom<&'a str> for SchemaName {
274 type Error = Error;
275
276 fn try_from(name: &'a str) -> Result<Self, Self::Error> {
277 // SAFETY: we must ensure that the string does not contain an internal NULL byte
278 if let Some(pos) = name.as_bytes().iter().position(|&b| b == 0) {
279 return Err(Error::InvalidArgument(format!(
280 "schema name {name:?} contains a zero byte at index {pos}"
281 )));
282 }
283
284 let capacity = name.len().checked_add(1).unwrap();
285
286 let mut s = String::new();
287 // `String::with_capacity()` does not guarantee that it will not overallocate,
288 // which might mean an unnecessary reallocation to make `capacity == len`
289 // in the conversion to `Box<str>`.
290 s.reserve_exact(capacity);
291
292 s.push_str(name);
293 s.push('\0');
294
295 Ok(SchemaName(s.into()))
296 }
297}