Author SHA1 Message Date
Claude b0e203e2c3 fix(rust) : connexion impossible sur une base dont la table sessions est ancienne
En production, l'ancien serveur gardait les sessions en mémoire : la table
« sessions » de la base n'avait jamais servi et n'avait pas la forme
attendue (sid, sess, expire). CREATE TABLE IF NOT EXISTS la laissait en
place, et toute connexion échouait : « column "sid" does not exist ».

Au démarrage, une table sessions sans ces trois colonnes est renommée
(sessions_ancienne_<date>, rien n'est supprimé), son index
IDX_session_expire aussi, puis la bonne table est créée. Sans effet sur
une base déjà correcte.

Vérifié sur une base init.sql dont la table sessions a une autre forme :
connexion et session ensuite valides, ancienne table conservée, rien ne
change au redémarrage suivant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ke2qyS8rgacP3ap5iezDbM
2026-10-15 18:45:02 +00:00
+37
View File
@@ -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<usize, sqlx::Error> {
let mut count = 0;