diff --git a/CHANGELOG.md b/CHANGELOG.md index f113e1d..3cc2a46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed +- `ipx copy-db`, the one-off move from SQLite to Postgres. Going back needs only the old `state.db`. - Starting up no longer looks for files downloaded twice before 0.6.0 read WordPress's player links correctly; that clean-up has run. diff --git a/docs/configuration.md b/docs/configuration.md index 49d8666..34fb3de 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -25,8 +25,7 @@ config.toml's default location is `$XDG_CONFIG_HOME/ipx/config.toml` `~` is expanded in paths. The database is SQLite in WAL mode unless `IPX_DATABASE_URL` names a Postgres database instead. Back SQLite up by copying `state.db` while the daemon is stopped, or -with `sqlite3 state.db .backup`; back Postgres up with `pg_dump`. `ipx copy-db ` copies a -SQLite database into the empty Postgres one `IPX_DATABASE_URL` names. +with `sqlite3 state.db .backup`; back Postgres up with `pg_dump`. ## `[general]` diff --git a/src/db.rs b/src/db.rs index 04bcce2..dd4d041 100644 --- a/src/db.rs +++ b/src/db.rs @@ -71,30 +71,6 @@ async fn create_missing(orm: &sea_orm::DatabaseConnection) -> Result<()> { Ok(()) } -/// One table's rows, a page at a time in primary-key order, from one database to another. -async fn copy_table(from: &sea_orm::DatabaseConnection, to: &impl ConnectionTrait) -> Result -where - E: EntityTrait, - E::Model: sea_orm::IntoActiveModel + Send + Sync, - E::ActiveModel: ActiveModelTrait + Send, -{ - use sea_orm::{IntoActiveModel, Iterable, PrimaryKeyToColumn}; - let mut query = E::find(); - for key in E::PrimaryKey::iter() { - query = query.order_by_asc(key.into_column()); - } - let mut pages = query.paginate(from, 1000); - let mut n = 0; - while let Some(rows) = pages.fetch_and_next().await? { - n += rows.len() as u64; - // reset_all: every column written, the primary key included, not just the changed ones. - E::insert_many(rows.into_iter().map(|m| m.into_active_model().reset_all())) - .exec_without_returning(to) - .await?; - } - Ok(n) -} - /// Where the database is: IPX_DATABASE_URL, a postgres:// URL, when it is set; otherwise the /// SQLite file in the data directory, as it has always been. pub fn location() -> String { @@ -1079,48 +1055,6 @@ impl Db { Ok(()) } - // ---- moving to another database ---- - - /// Copies every row of `from` into this database, which must be empty: the move from the - /// SQLite file to Postgres (issue #18). One transaction, so a copy that fails part-way leaves - /// nothing behind and can simply be run again. Returns each table's row count. - pub async fn copy_from(&self, from: &Db) -> Result> { - use crate::entity::*; - use sea_orm::TransactionTrait; - let held = users::Entity::find().count(&self.orm).await? - + feeds::Entity::find().count(&self.orm).await? - + entries::Entity::find().count(&self.orm).await?; - anyhow::ensure!(held == 0, "the database being copied into already holds rows; it has to be empty"); - let tx = self.orm.begin().await?; - // Users first: the tables that belong to a person refer to them. - let counts = vec![ - ("users", copy_table::(&from.orm, &tx).await?), - ("feeds", copy_table::(&from.orm, &tx).await?), - ("entries", copy_table::(&from.orm, &tx).await?), - ("enclosures", copy_table::(&from.orm, &tx).await?), - ("subscriptions", copy_table::(&from.orm, &tx).await?), - ("entry_state", copy_table::(&from.orm, &tx).await?), - ("sessions", copy_table::(&from.orm, &tx).await?), - ("catalogue", copy_table::(&from.orm, &tx).await?), - ("settings", copy_table::(&from.orm, &tx).await?), - ("blocklists", copy_table::(&from.orm, &tx).await?), - ("hidden", copy_table::(&from.orm, &tx).await?), - ]; - if self.orm.get_database_backend() == sea_orm::DbBackend::Postgres { - // The copied ids came with the rows; the counters that hand out new ones start past - // them, or the next account or file would collide with one copied. - for table in ["users", "enclosures"] { - tx.execute_unprepared(&format!( - "SELECT setval(pg_get_serial_sequence('{table}', 'id'), \ - coalesce((SELECT max(id) FROM {table}), 0) + 1, false)" - )) - .await?; - } - } - tx.commit().await?; - Ok(counts) - } - // ---- hand-written SQL ---- // // For what reads better as SQL than as a query builder: joins, sums, upserts. Written to diff --git a/src/main.rs b/src/main.rs index c0df746..97f15d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,12 +38,6 @@ 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 @@ -236,8 +230,7 @@ async fn main() -> Result<()> { | Command::Add { .. } | Command::Rm { .. } | Command::Import { .. } - | Command::Export { .. } - | Command::CopyDb { .. } => None, + | Command::Export { .. } => None, }; if let Some(cmd) = &wire_cmd && !cli.local @@ -246,13 +239,7 @@ async fn main() -> Result<()> { return ipc::proxy(&cfg.general.socket, cmd).await; } - // copy-db fills an empty database from another, configuration included; taking config.toml - // into it first would have the copy collide with it. - let cfg = if matches!(cli.command, Command::CopyDb { .. }) { - cfg - } else { - assemble_config(&db, cfg, &config_path).await? - }; + let cfg = assemble_config(&db, cfg, &config_path).await?; let is_daemon = matches!(cli.command, Command::Daemon { .. }); let (events, _) = broadcast::channel(1024); @@ -279,7 +266,6 @@ async fn main() -> Result<()> { Command::User { cmd } => user_cmd(&ctx, cmd).await, Command::Import { file } => import(&ctx, &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, }; // The batch exporter holds the last few seconds of spans; without this they are lost. @@ -871,15 +857,6 @@ async fn assemble_config(db: &db::Db, mut cfg: config::Config, path: &std::path: anyhow::bail!("the database says it holds the configuration and then that it does not") } -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 {