Skip to content
This repository was archived by the owner on Oct 18, 2023. It is now read-only.

Commit 5fcc5ee

Browse files
Merge #328 #348
328: export sqlite3 dump r=MarinPostma a=MarinPostma This PR ports the dumping loginc from sqlite to sqld. It allows the dumping of sqld tables to a sqlite compatible format. sqld now exposes a CLI interface to export dumps, eg: - print the dump for the database at the default path (`data.sqld`) to stdout: ``` sqld dump ``` - print the dump for the database at the path specified by `--db-path`: ``` sqld --db-path some/path/to/database dump ``` - write the dump for the database at the path specified by `--db-path` at the path specified by path: ``` sqld --db-path some/path/to/database dump --path write/dump/here ``` `dump` is a subcommand, so any argument passed after the `dump` have dump as subject, and any argument passed before `dump` have `sqld` as subject. Any argument with `sqld` as subject other than `--db-path` is ignored. 348: whitelist specific pragmas r=MarinPostma a=MarinPostma whilelist `foreign_keys` and `writable_schema` pragmas Co-authored-by: ad hoc <[email protected]>
3 parents 2c8bf4d + e20bfae + 29626d5 commit 5fcc5ee

File tree

8 files changed

+578
-26
lines changed

8 files changed

+578
-26
lines changed

sqld/src/database/dump/exporter.rs

Lines changed: 490 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use anyhow::anyhow;
66
use sqld_libsql_bindings::wal_hook::WalHook;
77
use tokio::sync::{mpsc, oneshot};
88

9-
use super::libsql::open_db;
9+
use super::super::libsql::open_db;
1010

1111
type OpMsg = Box<dyn FnOnce(&rusqlite::Connection) + 'static + Send + Sync>;
1212

sqld/src/database/dump/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod exporter;
2+
pub mod loader;

sqld/src/database/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::query::{Params, Query, QueryResult};
55
use crate::query_analysis::{State, Statement};
66
use crate::Result;
77

8-
pub mod dump_loader;
8+
pub mod dump;
99
pub mod factory;
1010
pub mod libsql;
1111
pub mod write_proxy;

sqld/src/hrana/conn.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ async fn handle_msg(conn: &mut Conn, client_msg: tungstenite::Message) -> Result
110110
let client_msg: proto::ClientMsg = match serde_json::from_str(&client_msg) {
111111
Ok(client_msg) => client_msg,
112112
Err(err) => {
113-
let error_msg = format!("Invalid format of client message: {}", err);
113+
let error_msg = format!("Invalid format of client message: {err}");
114114
close(conn, CloseCode::Invalid, &error_msg).await;
115115
tracing::warn!("{}", error_msg);
116116
return Ok(false);

sqld/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::sync::Mutex;
66
use std::time::Duration;
77

88
use anyhow::Context as AnyhowContext;
9-
use database::dump_loader::DumpLoader;
9+
use database::dump::loader::DumpLoader;
1010
use database::factory::DbFactory;
1111
use database::libsql::LibSqlDb;
1212
use database::write_proxy::WriteProxyDbFactory;
@@ -30,7 +30,7 @@ use sha256::try_digest;
3030
pub use sqld_libsql_bindings as libsql;
3131

3232
mod auth;
33-
mod database;
33+
pub mod database;
3434
mod error;
3535
mod hrana;
3636
mod http;

sqld/src/main.rs

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
use std::{env, fs, net::SocketAddr, path::PathBuf, time::Duration};
1+
use std::{
2+
env,
3+
fs::{self, OpenOptions},
4+
io::{stdout, Write},
5+
net::SocketAddr,
6+
path::{Path, PathBuf},
7+
time::Duration,
8+
};
29

310
use anyhow::{bail, Context as _, Result};
411
use clap::Parser;
512
use mimalloc::MiMalloc;
6-
use sqld::Config;
13+
use sqld::{database::dump::exporter::export_dump, Config};
714
use tracing_subscriber::filter::LevelFilter;
815

916
#[global_allocator]
@@ -123,6 +130,18 @@ struct Cli {
123130
/// defaults to 200MB.
124131
#[clap(long, env = "SQLD_MAX_LOG_SIZE", default_value = "200")]
125132
max_log_size: u64,
133+
134+
#[clap(subcommand)]
135+
utils: Option<UtilsSubcommands>,
136+
}
137+
138+
#[derive(clap::Subcommand, Debug)]
139+
enum UtilsSubcommands {
140+
Dump {
141+
#[clap(long)]
142+
/// Path at which to write the dump
143+
path: Option<PathBuf>,
144+
},
126145
}
127146

128147
impl Cli {
@@ -162,7 +181,7 @@ impl Cli {
162181
};
163182
eprintln!("\t- database path: {}", self.db_path.display());
164183
let extensions_str = self.extensions_path.clone().map_or("<disabled>".to_string(), |x| x.display().to_string());
165-
eprintln!("\t- extensions path: {}", extensions_str);
184+
eprintln!("\t- extensions path: {extensions_str}");
166185
eprintln!("\t- listening for HTTP requests on: {}", self.http_listen_addr);
167186
if let Some(ref addr) = self.pg_listen_addr {
168187
eprintln!("\t- listening for PostgreSQL wire on: {addr}");
@@ -215,6 +234,25 @@ fn config_from_args(args: Cli) -> Result<Config> {
215234
})
216235
}
217236

237+
fn perform_dump(dump_path: Option<&Path>, db_path: &Path) -> anyhow::Result<()> {
238+
let out: Box<dyn Write> = match dump_path {
239+
Some(path) => {
240+
let f = OpenOptions::new()
241+
.create_new(true)
242+
.write(true)
243+
.open(path)
244+
.with_context(|| format!("file `{}` already exists", path.display()))?;
245+
Box::new(f)
246+
}
247+
None => Box::new(stdout()),
248+
};
249+
let conn = rusqlite::Connection::open(db_path.join("data"))?;
250+
251+
export_dump(conn, out)?;
252+
253+
Ok(())
254+
}
255+
218256
#[tokio::main]
219257
async fn main() -> Result<()> {
220258
tracing_subscriber::fmt()
@@ -226,24 +264,38 @@ async fn main() -> Result<()> {
226264
)
227265
.init();
228266
let args = Cli::parse();
229-
args.print_welcome_message();
230267

231-
#[cfg(feature = "mwal_backend")]
232-
match (&args.backend, args.mwal_addr.is_some()) {
233-
(sqld::Backend::Mwal, false) => {
234-
anyhow::bail!("--mwal-addr parameter must be present with mwal backend")
235-
}
236-
(backend, true) if backend != &sqld::Backend::Mwal => {
237-
anyhow::bail!(
238-
"--mwal-addr parameter conflicts with backend {:?}",
239-
args.backend
240-
)
268+
match args.utils {
269+
Some(UtilsSubcommands::Dump { path }) => {
270+
if let Some(ref path) = path {
271+
eprintln!(
272+
"Dumping database {} to {}",
273+
args.db_path.display(),
274+
path.display()
275+
);
276+
}
277+
perform_dump(path.as_deref(), &args.db_path)
241278
}
242-
_ => (),
243-
}
279+
None => {
280+
args.print_welcome_message();
281+
#[cfg(feature = "mwal_backend")]
282+
match (&args.backend, args.mwal_addr.is_some()) {
283+
(sqld::Backend::Mwal, false) => {
284+
anyhow::bail!("--mwal-addr parameter must be present with mwal backend")
285+
}
286+
(backend, true) if backend != &sqld::Backend::Mwal => {
287+
anyhow::bail!(
288+
"--mwal-addr parameter conflicts with backend {:?}",
289+
args.backend
290+
)
291+
}
292+
_ => (),
293+
}
244294

245-
let config = config_from_args(args)?;
246-
sqld::run_server(config).await?;
295+
let config = config_from_args(args)?;
296+
sqld::run_server(config).await?;
247297

248-
Ok(())
298+
Ok(())
299+
}
300+
}
249301
}

sqld/src/query_analysis.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use anyhow::Result;
22
use fallible_iterator::FallibleIterator;
33
use sqlite3_parser::{
4-
ast::{Cmd, Stmt},
4+
ast::{Cmd, PragmaBody, QualifiedName, Stmt},
55
lexer::sql::{Parser, ParserError},
66
};
77

@@ -56,12 +56,20 @@ impl StmtKind {
5656
| Stmt::CreateView { temporary: false, .. }
5757
) => Some(Self::Write),
5858
Cmd::Stmt(Stmt::Select { .. }) => Some(Self::Read),
59-
Cmd::Stmt(Stmt::Pragma { .. }) => Some(Self::Other),
59+
Cmd::Stmt(Stmt::Pragma(name, body)) if is_pragma_allowed(name, body.as_ref()) => Some(Self::Write),
6060
_ => None,
6161
}
6262
}
6363
}
6464

65+
fn is_pragma_allowed(name: &QualifiedName, _body: Option<&PragmaBody>) -> bool {
66+
matches!(name, QualifiedName {
67+
db_name: None,
68+
name,
69+
alias: None,
70+
} if name.0 == "writable_schema" || name.0 == "foreign_keys")
71+
}
72+
6573
/// The state of a transaction for a series of statement
6674
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
6775
pub enum State {

0 commit comments

Comments
 (0)