Pin critical auth indexes to COLLATE "C" (ICU-drift immune)

idx_users_email and idx_sessions_token_hash now use byte-order
collation so a future ICU library bump cannot silently corrupt the
indexes the way it did this week. The columns themselves retain
their default collation; only the index comparison is C, which is
safe for these because:

  - users.email is lowercased ASCII in practice
  - user_sessions.token_hash is SHA-256 hex (pure ASCII)

Both are used for equality lookups only, never ORDER BY. Migration
is idempotent, gated on app_settings.migration.text_indexes_c.

Slug indexes on learning_* tables left at default for now — those
are also ASCII in practice but under lighter load; the startup
drift check + auto-REINDEX covers them.
This commit is contained in:
Daniel 2026-04-14 03:55:09 +02:00
parent cf50d8577e
commit 4f51f1efb5

View file

@ -50,6 +50,37 @@ async function initDatabase() {
console.warn('⚠️ Collation check skipped:', e.message);
}
// Convert lookup-only text indexes to COLLATE "C" so a future ICU/glibc
// version bump cannot silently corrupt them. Idempotent — driven by a
// flag in app_settings so we only do the DROP/CREATE once.
try {
var done = await client.query(
"SELECT value FROM app_settings WHERE key = 'migration.text_indexes_c'"
).catch(function() { return { rows: [] }; });
if (!done.rows.length || done.rows[0].value !== 'true') {
console.log('Migrating text lookup indexes to COLLATE "C"...');
var indexMigrations = [
// users.email — primary auth lookup
'DROP INDEX IF EXISTS idx_users_email',
'CREATE INDEX idx_users_email ON users (email COLLATE "C")',
// user_sessions.token_hash — session auth
'DROP INDEX IF EXISTS idx_sessions_token_hash',
'CREATE INDEX idx_sessions_token_hash ON user_sessions (token_hash COLLATE "C")'
];
for (var i = 0; i < indexMigrations.length; i++) {
await client.query(indexMigrations[i]);
}
// Mark complete — create the setting row if the table exists.
await client.query(
"INSERT INTO app_settings (key, value) VALUES ('migration.text_indexes_c', 'true') " +
"ON CONFLICT (key) DO UPDATE SET value = 'true'"
).catch(function() {});
console.log('✅ Text indexes: migrated to COLLATE "C" (ICU-drift immune)');
}
} catch (e) {
console.warn('⚠️ Index migration skipped:', e.message);
}
await client.query(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,