|
| 1 | +/// Demonstrates serialize/deserialize by embedding a SQLite database inside a custom |
| 2 | +/// binary container format. |
| 3 | +/// |
| 4 | +/// The container prepends a magic header to the raw SQLite bytes, making it impossible |
| 5 | +/// to open directly with `SqliteConnectOptions::filename()`. This is the whole point: |
| 6 | +/// `sqlite3_serialize` / `sqlite3_deserialize` let you treat a database as an opaque |
| 7 | +/// byte slice that can live inside any format you control. |
| 8 | +/// |
| 9 | +/// Container layout: |
| 10 | +/// [4 bytes] magic: b"SQLX" |
| 11 | +/// [n bytes] SQLite database bytes |
| 12 | +use sqlx::sqlite::SqliteOwnedBuf; |
| 13 | +use sqlx::{Connection, SqliteConnection}; |
| 14 | +use std::io::{self, Write}; |
| 15 | +use std::path::Path; |
| 16 | + |
| 17 | +const MAGIC: &[u8; 4] = b"SQLX"; |
| 18 | + |
| 19 | +fn write_container(path: &Path, db_bytes: &[u8]) -> io::Result<()> { |
| 20 | + let mut file = std::fs::File::create(path)?; |
| 21 | + file.write_all(MAGIC)?; |
| 22 | + file.write_all(db_bytes)?; |
| 23 | + Ok(()) |
| 24 | +} |
| 25 | + |
| 26 | +fn read_container(path: &Path) -> io::Result<Vec<u8>> { |
| 27 | + let raw = std::fs::read(path)?; |
| 28 | + assert_eq!(&raw[..4], MAGIC, "not a valid container file"); |
| 29 | + Ok(raw[4..].to_vec()) |
| 30 | +} |
| 31 | + |
| 32 | +#[tokio::main(flavor = "current_thread")] |
| 33 | +async fn main() -> anyhow::Result<()> { |
| 34 | + let container_path = Path::new("notes.sqlx"); |
| 35 | + |
| 36 | + let mut conn = SqliteConnection::connect("sqlite::memory:").await?; |
| 37 | + |
| 38 | + sqlx::raw_sql( |
| 39 | + "create table notes(id integer primary key, body text not null); |
| 40 | + insert into notes(body) values ('hello'), ('world');", |
| 41 | + ) |
| 42 | + .execute(&mut conn) |
| 43 | + .await?; |
| 44 | + |
| 45 | + // serialize and persist inside the custom container |
| 46 | + let snapshot: SqliteOwnedBuf = conn.serialize(None).await?; |
| 47 | + write_container(container_path, snapshot.as_ref())?; |
| 48 | + conn.close().await?; |
| 49 | + |
| 50 | + // restore into a fresh in-memory connection |
| 51 | + let db_bytes = read_container(container_path)?; |
| 52 | + let owned = SqliteOwnedBuf::try_from(db_bytes.as_slice())?; |
| 53 | + let mut restored = SqliteConnection::connect("sqlite::memory:").await?; |
| 54 | + restored.deserialize(None, owned, false).await?; |
| 55 | + |
| 56 | + let rows = sqlx::query_as::<_, (i64, String)>("select id, body from notes order by id") |
| 57 | + .fetch_all(&mut restored) |
| 58 | + .await?; |
| 59 | + assert_eq!(rows.len(), 2); |
| 60 | + |
| 61 | + sqlx::query("insert into notes(body) values ('from restored connection')") |
| 62 | + .execute(&mut restored) |
| 63 | + .await?; |
| 64 | + |
| 65 | + // serialize the updated database back into the container |
| 66 | + let updated: SqliteOwnedBuf = restored.serialize(None).await?; |
| 67 | + write_container(container_path, updated.as_ref())?; |
| 68 | + |
| 69 | + std::fs::remove_file(container_path)?; |
| 70 | + |
| 71 | + Ok(()) |
| 72 | +} |
0 commit comments