Postgres: pick the database by URL, copy-db, tests on both
- IPX_DATABASE_URL (postgres://...) picks the database; unset, it is the SQLite file as before. Passwords are taken out of anything logged. - `ipx copy-db <state.db>` copies every table into the empty database the URL names, in one transaction, and moves the id counters past the copied ids. A copy of production went across in 14s with every count and column fingerprint identical. - With IPX_TEST_DATABASE_URL set, each test gets a Postgres schema of its own; all 79 pass on both databases. Fixtures write booleans as true/false. - Sorts say where an item with no value goes (NULLS FIRST going up, LAST going down): SQLite counts NULL as smallest, Postgres as largest, so "largest first" on Postgres led with every item that has no file. Tested on both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
21
src/main.rs
21
src/main.rs
@@ -37,6 +37,12 @@ struct Cli {
|
||||
enum Command {
|
||||
/// Show configured feeds and their state
|
||||
List,
|
||||
/// Copy everything from a SQLite state.db into the database IPX_DATABASE_URL names, which
|
||||
/// must be empty: the one-off move to Postgres
|
||||
CopyDb {
|
||||
/// The SQLite file to copy from
|
||||
from: PathBuf,
|
||||
},
|
||||
/// Scan feeds for new entries
|
||||
Fetch {
|
||||
/// Only this feed id
|
||||
@@ -180,7 +186,7 @@ async fn main() -> Result<()> {
|
||||
|
||||
let config_path = cli.config.clone().unwrap_or_else(config::config_path);
|
||||
let cfg = config::Config::load(&config_path)?;
|
||||
let db = db::Db::open(&config::data_dir().join("state.db")).await?;
|
||||
let db = db::Db::open(&db::location()).await?;
|
||||
|
||||
// A daemon owns the state; don't have two processes downloading the same thing.
|
||||
let wire_cmd = match &cli.command {
|
||||
@@ -195,7 +201,8 @@ async fn main() -> Result<()> {
|
||||
| Command::Add { .. }
|
||||
| Command::Rm { .. }
|
||||
| Command::Import { .. }
|
||||
| Command::Export { .. } => None,
|
||||
| Command::Export { .. }
|
||||
| Command::CopyDb { .. } => None,
|
||||
};
|
||||
if let Some(cmd) = &wire_cmd
|
||||
&& !cli.local
|
||||
@@ -229,6 +236,7 @@ async fn main() -> Result<()> {
|
||||
Command::User { cmd } => user_cmd(&ctx, cmd).await,
|
||||
Command::Import { file } => import(&ctx, &config_path, &file).await,
|
||||
Command::Export { file } => export(&ctx, &file).await,
|
||||
Command::CopyDb { from } => copy_db(&ctx, &from).await,
|
||||
_ => run(&ctx, wire_cmd.expect("only List and Daemon have no wire form")).await,
|
||||
}
|
||||
}
|
||||
@@ -770,6 +778,15 @@ pub fn collect_outlines(outlines: &[opml::Outline], out: &mut Vec<(String, Strin
|
||||
}
|
||||
}
|
||||
|
||||
async fn copy_db(ctx: &Ctx, from: &std::path::Path) -> Result<()> {
|
||||
anyhow::ensure!(from.exists(), "{} does not exist", from.display());
|
||||
let source = db::Db::open(&from.display().to_string()).await?;
|
||||
for (table, n) in ctx.db.copy_from(&source).await? {
|
||||
println!("{table:14} {n}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn export(ctx: &Ctx, file: &std::path::Path) -> Result<()> {
|
||||
let mut doc = opml::OPML::default();
|
||||
doc.head = Some(opml::Head {
|
||||
|
||||
Reference in New Issue
Block a user