mirror of
				https://github.com/isar/rusqlite.git
				synced 2025-10-31 13:58:55 +08:00 
			
		
		
		
	Merge pull request #1341 from gwenn/serialize
Serialize and deserialize database
This commit is contained in:
		| @@ -77,6 +77,8 @@ column_decltype = [] | |||||||
| wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"] | wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"] | ||||||
| # Note: doesn't support 32-bit. | # Note: doesn't support 32-bit. | ||||||
| winsqlite3 = ["libsqlite3-sys/winsqlite3"] | winsqlite3 = ["libsqlite3-sys/winsqlite3"] | ||||||
|  | # 3.23.0 | ||||||
|  | serialize = ["modern_sqlite"] | ||||||
|  |  | ||||||
| # Helper feature for enabling most non-build-related optional features | # Helper feature for enabling most non-build-related optional features | ||||||
| # or dependencies (except `session`). This is useful for running tests / clippy | # or dependencies (except `session`). This is useful for running tests / clippy | ||||||
|   | |||||||
| @@ -386,7 +386,6 @@ impl Error { | |||||||
|  |  | ||||||
| #[cold] | #[cold] | ||||||
| pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error { | pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error { | ||||||
|     // TODO sqlite3_error_offset // 3.38.0, #1130 |  | ||||||
|     Error::SqliteFailure(ffi::Error::new(code), message) |     Error::SqliteFailure(ffi::Error::new(code), message) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -119,6 +119,9 @@ mod params; | |||||||
| mod pragma; | mod pragma; | ||||||
| mod raw_statement; | mod raw_statement; | ||||||
| mod row; | mod row; | ||||||
|  | #[cfg(feature = "serialize")] | ||||||
|  | #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] | ||||||
|  | pub mod serialize; | ||||||
| #[cfg(feature = "session")] | #[cfg(feature = "session")] | ||||||
| #[cfg_attr(docsrs, doc(cfg(feature = "session")))] | #[cfg_attr(docsrs, doc(cfg(feature = "session")))] | ||||||
| pub mod session; | pub mod session; | ||||||
|   | |||||||
							
								
								
									
										162
									
								
								src/serialize.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								src/serialize.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,162 @@ | |||||||
|  | //! Serialize a database. | ||||||
|  | use std::convert::TryInto; | ||||||
|  | use std::marker::PhantomData; | ||||||
|  | use std::ops::Deref; | ||||||
|  | use std::ptr::NonNull; | ||||||
|  |  | ||||||
|  | use crate::error::error_from_handle; | ||||||
|  | use crate::ffi; | ||||||
|  | use crate::{Connection, DatabaseName, Result}; | ||||||
|  |  | ||||||
|  | /// Shared (SQLITE_SERIALIZE_NOCOPY) serialized database | ||||||
|  | pub struct SharedData<'conn> { | ||||||
|  |     phantom: PhantomData<&'conn Connection>, | ||||||
|  |     ptr: NonNull<u8>, | ||||||
|  |     sz: usize, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Owned serialized database | ||||||
|  | pub struct OwnedData { | ||||||
|  |     ptr: NonNull<u8>, | ||||||
|  |     sz: usize, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl OwnedData { | ||||||
|  |     /// SAFETY: Caller must be certain that `ptr` is allocated by | ||||||
|  |     /// `sqlite3_malloc`. | ||||||
|  |     pub unsafe fn from_raw_nonnull(ptr: NonNull<u8>, sz: usize) -> Self { | ||||||
|  |         Self { ptr, sz } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn into_raw(self) -> (*mut u8, usize) { | ||||||
|  |         let raw = (self.ptr.as_ptr(), self.sz); | ||||||
|  |         std::mem::forget(self); | ||||||
|  |         raw | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Drop for OwnedData { | ||||||
|  |     fn drop(&mut self) { | ||||||
|  |         unsafe { | ||||||
|  |             ffi::sqlite3_free(self.ptr.as_ptr().cast()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Serialized database | ||||||
|  | pub enum Data<'conn> { | ||||||
|  |     /// Shared (SQLITE_SERIALIZE_NOCOPY) serialized database | ||||||
|  |     Shared(SharedData<'conn>), | ||||||
|  |     /// Owned serialized database | ||||||
|  |     Owned(OwnedData), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'conn> Deref for Data<'conn> { | ||||||
|  |     type Target = [u8]; | ||||||
|  |  | ||||||
|  |     fn deref(&self) -> &[u8] { | ||||||
|  |         let (ptr, sz) = match self { | ||||||
|  |             Data::Owned(OwnedData { ptr, sz }) => (ptr.as_ptr(), *sz), | ||||||
|  |             Data::Shared(SharedData { ptr, sz, .. }) => (ptr.as_ptr(), *sz), | ||||||
|  |         }; | ||||||
|  |         unsafe { std::slice::from_raw_parts(ptr, sz) } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Connection { | ||||||
|  |     /// Serialize a database. | ||||||
|  |     pub fn serialize<'conn>(&'conn self, schema: DatabaseName<'_>) -> Result<Data<'conn>> { | ||||||
|  |         let schema = schema.as_cstring()?; | ||||||
|  |         let mut sz = 0; | ||||||
|  |         let mut ptr: *mut u8 = unsafe { | ||||||
|  |             ffi::sqlite3_serialize( | ||||||
|  |                 self.handle(), | ||||||
|  |                 schema.as_ptr(), | ||||||
|  |                 &mut sz, | ||||||
|  |                 ffi::SQLITE_SERIALIZE_NOCOPY, | ||||||
|  |             ) | ||||||
|  |         }; | ||||||
|  |         Ok(if ptr.is_null() { | ||||||
|  |             ptr = unsafe { ffi::sqlite3_serialize(self.handle(), schema.as_ptr(), &mut sz, 0) }; | ||||||
|  |             if ptr.is_null() { | ||||||
|  |                 return Err(unsafe { error_from_handle(self.handle(), ffi::SQLITE_NOMEM) }); | ||||||
|  |             } | ||||||
|  |             Data::Owned(OwnedData { | ||||||
|  |                 ptr: NonNull::new(ptr).unwrap(), | ||||||
|  |                 sz: sz.try_into().unwrap(), | ||||||
|  |             }) | ||||||
|  |         } else { | ||||||
|  |             // shared buffer | ||||||
|  |             Data::Shared(SharedData { | ||||||
|  |                 ptr: NonNull::new(ptr).unwrap(), | ||||||
|  |                 sz: sz.try_into().unwrap(), | ||||||
|  |                 phantom: PhantomData, | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Deserialize a database. | ||||||
|  |     pub fn deserialize( | ||||||
|  |         &mut self, | ||||||
|  |         schema: DatabaseName<'_>, | ||||||
|  |         data: OwnedData, | ||||||
|  |         read_only: bool, | ||||||
|  |     ) -> Result<()> { | ||||||
|  |         let schema = schema.as_cstring()?; | ||||||
|  |         let (data, sz) = data.into_raw(); | ||||||
|  |         let sz = sz.try_into().unwrap(); | ||||||
|  |         let flags = if read_only { | ||||||
|  |             ffi::SQLITE_DESERIALIZE_FREEONCLOSE | ffi::SQLITE_DESERIALIZE_READONLY | ||||||
|  |         } else { | ||||||
|  |             ffi::SQLITE_DESERIALIZE_FREEONCLOSE | ffi::SQLITE_DESERIALIZE_RESIZEABLE | ||||||
|  |         }; | ||||||
|  |         let rc = unsafe { | ||||||
|  |             ffi::sqlite3_deserialize(self.handle(), schema.as_ptr(), data, sz, sz, flags) | ||||||
|  |         }; | ||||||
|  |         if rc != ffi::SQLITE_OK { | ||||||
|  |             // TODO sqlite3_free(data) ? | ||||||
|  |             return Err(unsafe { error_from_handle(self.handle(), rc) }); | ||||||
|  |         } | ||||||
|  |         /* TODO | ||||||
|  |         if let Some(mxSize) = mxSize { | ||||||
|  |             unsafe { | ||||||
|  |                 ffi::sqlite3_file_control( | ||||||
|  |                     self.handle(), | ||||||
|  |                     schema.as_ptr(), | ||||||
|  |                     ffi::SQLITE_FCNTL_SIZE_LIMIT, | ||||||
|  |                     &mut mxSize, | ||||||
|  |                 ) | ||||||
|  |             }; | ||||||
|  |         }*/ | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | mod test { | ||||||
|  |     use super::*; | ||||||
|  |     use crate::{Connection, DatabaseName, Result}; | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn serialize() -> Result<()> { | ||||||
|  |         let db = Connection::open_in_memory()?; | ||||||
|  |         db.execute_batch("CREATE TABLE x AS SELECT 'data'")?; | ||||||
|  |         let data = db.serialize(DatabaseName::Main)?; | ||||||
|  |         let Data::Owned(data) = data else { panic!("expected OwnedData")}; | ||||||
|  |         assert!(data.sz > 0); | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn deserialize() -> Result<()> { | ||||||
|  |         let src = Connection::open_in_memory()?; | ||||||
|  |         src.execute_batch("CREATE TABLE x AS SELECT 'data'")?; | ||||||
|  |         let data = src.serialize(DatabaseName::Main)?; | ||||||
|  |         let Data::Owned(data) = data else { panic!("expected OwnedData")}; | ||||||
|  |  | ||||||
|  |         let mut dst = Connection::open_in_memory()?; | ||||||
|  |         dst.deserialize(DatabaseName::Main, data, false)?; | ||||||
|  |         dst.execute("DELETE FROM x", [])?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user