From af6a854400e2494f633e481f20352a6aadab7ef5 Mon Sep 17 00:00:00 2001 From: LogiFlow <101972532+R0m1k3@users.noreply.github.com> Date: Thu, 15 Oct 2026 20:46:16 +0200 Subject: [PATCH] fix(rust) : connexion impossible sur une base dont la table sessions est ancienne (#573) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'ancien serveur gardait les sessions en mémoire : la table sessions de production n'avait pas la forme attendue (sid, sess, expire) et la connexion échouait (« column "sid" does not exist »). Au démarrage, une telle table est renommée (sessions_ancienne_, rien n'est supprimé), son index aussi, puis la bonne table est créée. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ke2qyS8rgacP3ap5iezDbM --- server-rs/src/startup.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/server-rs/src/startup.rs b/server-rs/src/startup.rs index 73d5f16..a23ff2d 100644 --- a/server-rs/src/startup.rs +++ b/server-rs/src/startup.rs @@ -115,6 +115,9 @@ const SCHEMA_STATEMENTS: &[(&str, &str)] = &[ /// Applique les migrations de schéma, puis chiffre les secrets encore en clair pub async fn run_migrations(db: &PgPool, encryption_key: &str) { + if let Err(error) = set_aside_legacy_sessions_table(db).await { + tracing::error!(%error, "vérification de la table sessions en échec"); + } for (label, statement) in SCHEMA_STATEMENTS { if let Err(error) = sqlx::query(statement).execute(db).await { tracing::error!(%error, "migration « {label} » en échec"); @@ -128,6 +131,40 @@ pub async fn run_migrations(db: &PgPool, encryption_key: &str) { tracing::info!("migrations de démarrage terminées"); } +/// Une base peut porter une table « sessions » d'une autre forme, jamais utilisée +/// (l'ancien serveur gardait les sessions en mémoire) : CREATE TABLE IF NOT EXISTS +/// la laisserait en place et toute connexion échouerait (« column "sid" does not +/// exist »). Elle est renommée, sans rien supprimer, et la bonne table est créée +/// ensuite par SCHEMA_STATEMENTS. Les sessions ne sont que des connexions ouvertes. +async fn set_aside_legacy_sessions_table(db: &PgPool) -> Result<(), sqlx::Error> { + let (exists, expected_columns): (bool, i64) = sqlx::query_as( + "SELECT to_regclass('sessions') IS NOT NULL, + (SELECT count(*) FROM information_schema.columns + WHERE table_schema = current_schema() AND table_name = 'sessions' + AND column_name IN ('sid', 'sess', 'expire'))", + ) + .fetch_one(db) + .await?; + if !exists || expected_columns == 3 { + return Ok(()); + } + let suffix = chrono::Utc::now().format("%Y%m%d%H%M%S"); + let legacy = format!("sessions_ancienne_{suffix}"); + let mut transaction = db.begin().await?; + sqlx::query(&format!("ALTER TABLE sessions RENAME TO {legacy}")) + .execute(&mut *transaction) + .await?; + // Un index de ce nom sur l'ancienne table empêcherait de le créer sur la nouvelle + sqlx::query(&format!( + "ALTER INDEX IF EXISTS \"IDX_session_expire\" RENAME TO \"IDX_session_expire_ancienne_{suffix}\"" + )) + .execute(&mut *transaction) + .await?; + transaction.commit().await?; + tracing::warn!(table = %legacy, "table sessions d'une autre forme mise de côté"); + Ok(()) +} + /// Mots de passe SMTP des magasins et jetons NocoDB encore en clair → enc:v1 async fn encrypt_plain_secrets(db: &PgPool, key: &str) -> Result { let mut count = 0;