diff --git a/backend/utils/sequelize.js b/backend/utils/sequelize.js index c7243f6..0a21eea 100644 --- a/backend/utils/sequelize.js +++ b/backend/utils/sequelize.js @@ -60,14 +60,18 @@ const sequelize = new Sequelize(dbName, dbUser, dbPass, { }); // Helper: Query mit Timeout (muss nach sequelize Initialisierung definiert werden) -const queryWithTimeout = async (query, timeoutMs = 10000, description = 'Query') => { +const queryWithTimeout = async (query, timeoutMs = 10000, description = 'Query', replacements = null) => { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs); }); try { + const queryOptions = replacements + ? { replacements, type: sequelize.QueryTypes.SELECT } + : {}; + const result = await Promise.race([ - sequelize.query(query), + sequelize.query(query, queryOptions), timeoutPromise ]); return result; @@ -578,33 +582,56 @@ const syncModelsAlways = async (models) => { console.log(` 🔍 Checking for foreign keys in ${schema}.${tableName}...`); // Verwende queryWithTimeout für Foreign Key Queries - const foreignKeys = await queryWithTimeout(` - SELECT tc.constraint_name - FROM information_schema.table_constraints AS tc - WHERE tc.constraint_type = 'FOREIGN KEY' - AND tc.table_name = '${tableName.replace(/'/g, "''")}' - AND tc.table_schema = '${schema.replace(/'/g, "''")}' - `, 10000, `FK check for ${model.name}`); + let foreignKeys = []; + try { + // Verwende direkte SQL-String-Interpolation, da Parameter-Binding bei queryWithTimeout Probleme macht + const result = await queryWithTimeout(` + SELECT tc.constraint_name + FROM information_schema.table_constraints AS tc + WHERE tc.constraint_type = 'FOREIGN KEY' + AND tc.table_name = '${tableName.replace(/'/g, "''")}' + AND tc.table_schema = '${schema.replace(/'/g, "''")}' + `, 10000, `FK check for ${model.name}`); + // Result ist ein Array von Objekten + foreignKeys = Array.isArray(result) ? result : []; + } catch (fkCheckError) { + if (fkCheckError.message && fkCheckError.message.includes('Timeout')) { + console.warn(` ⚠️ Timeout beim Prüfen der Foreign Keys für ${model.name} - überspringe FK-Check...`); + foreignKeys = []; + } else { + // Bei anderen Fehlern auch überspringen, nicht kritisch + console.warn(` ⚠️ Fehler beim Prüfen der Foreign Keys für ${model.name}:`, fkCheckError.message?.substring(0, 100)); + foreignKeys = []; + } + } if (foreignKeys && foreignKeys.length > 0) { - console.log(` ⚠️ Found ${foreignKeys.length} existing foreign keys:`, foreignKeys.map(fk => fk.constraint_name).join(', ')); - console.log(` ⚠️ Removing ${foreignKeys.length} existing foreign keys from ${model.name} (schema: ${schema}) before sync`); - for (const fk of foreignKeys) { - try { - console.log(` 🗑️ Dropping constraint: ${fk.constraint_name}`); - await queryWithTimeout(` - ALTER TABLE "${schema}"."${tableName}" - DROP CONSTRAINT IF EXISTS "${fk.constraint_name}" CASCADE - `, 10000, `Drop FK ${fk.constraint_name}`); - } catch (dropError) { - if (dropError.message.includes('Timeout')) { - console.warn(` ⚠️ Timeout beim Entfernen von ${fk.constraint_name} - überspringe...`); - } else { - console.warn(` ⚠️ Konnte ${fk.constraint_name} nicht entfernen:`, dropError.message?.substring(0, 100)); + const constraintNames = foreignKeys + .map(fk => fk?.constraint_name) + .filter(name => name && name !== 'undefined' && name !== undefined); + + if (constraintNames.length > 0) { + console.log(` ⚠️ Found ${constraintNames.length} existing foreign keys:`, constraintNames.join(', ')); + console.log(` ⚠️ Removing ${constraintNames.length} existing foreign keys from ${model.name} (schema: ${schema}) before sync`); + for (const constraintName of constraintNames) { + try { + console.log(` 🗑️ Dropping constraint: ${constraintName}`); + await queryWithTimeout(` + ALTER TABLE "${schema}"."${tableName}" + DROP CONSTRAINT IF EXISTS "${constraintName.replace(/"/g, '""')}" CASCADE + `, 10000, `Drop FK ${constraintName}`); + } catch (dropError) { + if (dropError.message && dropError.message.includes('Timeout')) { + console.warn(` ⚠️ Timeout beim Entfernen von ${constraintName} - überspringe...`); + } else { + console.warn(` ⚠️ Konnte ${constraintName} nicht entfernen:`, dropError.message?.substring(0, 100)); + } } } + console.log(` ✅ Foreign key removal completed for ${model.name}`); + } else { + console.log(` ⚠️ Foreign keys gefunden, aber constraint_name ist undefined - überspringe FK-Entfernung`); } - console.log(` ✅ Foreign key removal completed for ${model.name}`); } else { console.log(` ✅ No foreign keys found for ${model.name}`); } @@ -619,39 +646,40 @@ const syncModelsAlways = async (models) => { console.log(` 🔄 Syncing model ${model.name} with constraints: false`); try { - // Versuche doppelte pg_description Einträge vor dem Sync zu bereinigen - // Hinweis: Benötigt Superuser-Rechte oder spezielle Berechtigungen - // Überspringe diese Query, da sie oft fehlschlägt und Verbindungen blockiert - // Die doppelten pg_description Einträge sind nicht kritisch für die Funktionalität + // Überspringe pg_description Cleanup komplett - benötigt Superuser-Rechte und blockiert oft + // Diese Query ist nicht kritisch für die Funktionalität + + // Prüfe ob Tabelle bereits existiert - wenn ja, überspringe Sync für große Tabellen + const tableName = model.tableName; + const schema = model.options?.schema || 'public'; + let tableExists = false; try { - const tableName = model.tableName; - const schema = model.options?.schema || 'public'; - // Verwende queryWithTimeout mit kurzem Timeout, da diese Query oft hängt - await queryWithTimeout(` - DELETE FROM pg_catalog.pg_description d1 - WHERE d1.objoid IN ( - SELECT c.oid - FROM pg_catalog.pg_class c - JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE c.relname = '${tableName.replace(/'/g, "''")}' - AND n.nspname = '${schema.replace(/'/g, "''")}' - ) - AND EXISTS ( - SELECT 1 - FROM pg_catalog.pg_description d2 - WHERE d2.objoid = d1.objoid - AND d2.objsubid = d1.objsubid - AND d2.ctid < d1.ctid - ) - `, 5000, `pg_description cleanup for ${model.name}`); - } catch (descError) { - // Ignoriere alle Fehler - diese Query ist optional und blockiert oft - if (descError.message && (descError.message.includes('Berechtigung') || descError.message.includes('timeout') || descError.message.includes('Timeout'))) { - // Stille Warnung - nicht kritisch - } else { - // Nur bei unerwarteten Fehlern warnen - console.warn(` ⚠️ Could not clean up duplicate pg_description entries for ${model.name}:`, descError.message?.substring(0, 100)); + const existsResult = await queryWithTimeout(` + SELECT EXISTS ( + SELECT 1 FROM information_schema.tables + WHERE table_schema = :schema + AND table_name = :tableName + ) as exists + `, 5000, `Table exists check for ${model.name}`, { schema, tableName }); + tableExists = existsResult && existsResult[0] && existsResult[0].exists; + } catch (checkError) { + // Bei Fehler annehmen, dass Tabelle nicht existiert und Sync versuchen + tableExists = false; + } + + // Für große Tabellen (FalukantUser, FalukantCharacter) - wenn Tabelle existiert, überspringe Sync + const largeTables = ['FalukantUser', 'FalukantCharacter', 'Notification', 'RegionData']; + if (largeTables.includes(model.name) && tableExists) { + console.log(` ℹ️ ${model.name} Tabelle existiert bereits - überspringe Sync (zu groß für alter)`); + // Restore associations before continuing + if (associationKeys.length > 0) { + model.associations = originalAssociations; } + // Restore virtual fields + for (const [key, attr] of Object.entries(virtualFields)) { + model.rawAttributes[key] = attr; + } + continue; } // Verwende syncModelWithTimeout für große Tabellen