Remove ipx copy-db (#85)
It was the one-off copy from SQLite to Postgres (#18), run once on 2026-09-18. Production has run on Postgres since; rolling back needs only the old state.db, which is kept, not this command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
66
src/db.rs
66
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<E>(from: &sea_orm::DatabaseConnection, to: &impl ConnectionTrait) -> Result<u64>
|
||||
where
|
||||
E: EntityTrait,
|
||||
E::Model: sea_orm::IntoActiveModel<E::ActiveModel> + Send + Sync,
|
||||
E::ActiveModel: ActiveModelTrait<Entity = E> + 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<Vec<(&'static str, u64)>> {
|
||||
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::<users::Entity>(&from.orm, &tx).await?),
|
||||
("feeds", copy_table::<feeds::Entity>(&from.orm, &tx).await?),
|
||||
("entries", copy_table::<entries::Entity>(&from.orm, &tx).await?),
|
||||
("enclosures", copy_table::<enclosures::Entity>(&from.orm, &tx).await?),
|
||||
("subscriptions", copy_table::<subscriptions::Entity>(&from.orm, &tx).await?),
|
||||
("entry_state", copy_table::<entry_state::Entity>(&from.orm, &tx).await?),
|
||||
("sessions", copy_table::<sessions::Entity>(&from.orm, &tx).await?),
|
||||
("catalogue", copy_table::<catalogue::Entity>(&from.orm, &tx).await?),
|
||||
("settings", copy_table::<settings::Entity>(&from.orm, &tx).await?),
|
||||
("blocklists", copy_table::<blocklists::Entity>(&from.orm, &tx).await?),
|
||||
("hidden", copy_table::<hidden::Entity>(&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
|
||||
|
||||
27
src/main.rs
27
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 {
|
||||
|
||||
Reference in New Issue
Block a user