Enhance query handling and foreign key management in sequelize.js
- Updated queryWithTimeout to support parameter replacements, improving query flexibility. - Enhanced foreign key checks in syncModelsAlways to handle timeouts and errors more gracefully, ensuring robust logging and skipping problematic checks. - Implemented a check for table existence before synchronization for large tables, preventing unnecessary sync operations and improving performance.
This commit is contained in:
@@ -60,14 +60,18 @@ const sequelize = new Sequelize(dbName, dbUser, dbPass, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Helper: Query mit Timeout (muss nach sequelize Initialisierung definiert werden)
|
// 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) => {
|
const timeoutPromise = new Promise((_, reject) => {
|
||||||
setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs);
|
setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs);
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const queryOptions = replacements
|
||||||
|
? { replacements, type: sequelize.QueryTypes.SELECT }
|
||||||
|
: {};
|
||||||
|
|
||||||
const result = await Promise.race([
|
const result = await Promise.race([
|
||||||
sequelize.query(query),
|
sequelize.query(query, queryOptions),
|
||||||
timeoutPromise
|
timeoutPromise
|
||||||
]);
|
]);
|
||||||
return result;
|
return result;
|
||||||
@@ -578,33 +582,56 @@ const syncModelsAlways = async (models) => {
|
|||||||
|
|
||||||
console.log(` 🔍 Checking for foreign keys in ${schema}.${tableName}...`);
|
console.log(` 🔍 Checking for foreign keys in ${schema}.${tableName}...`);
|
||||||
// Verwende queryWithTimeout für Foreign Key Queries
|
// Verwende queryWithTimeout für Foreign Key Queries
|
||||||
const foreignKeys = await queryWithTimeout(`
|
let foreignKeys = [];
|
||||||
SELECT tc.constraint_name
|
try {
|
||||||
FROM information_schema.table_constraints AS tc
|
// Verwende direkte SQL-String-Interpolation, da Parameter-Binding bei queryWithTimeout Probleme macht
|
||||||
WHERE tc.constraint_type = 'FOREIGN KEY'
|
const result = await queryWithTimeout(`
|
||||||
AND tc.table_name = '${tableName.replace(/'/g, "''")}'
|
SELECT tc.constraint_name
|
||||||
AND tc.table_schema = '${schema.replace(/'/g, "''")}'
|
FROM information_schema.table_constraints AS tc
|
||||||
`, 10000, `FK check for ${model.name}`);
|
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) {
|
if (foreignKeys && foreignKeys.length > 0) {
|
||||||
console.log(` ⚠️ Found ${foreignKeys.length} existing foreign keys:`, foreignKeys.map(fk => fk.constraint_name).join(', '));
|
const constraintNames = foreignKeys
|
||||||
console.log(` ⚠️ Removing ${foreignKeys.length} existing foreign keys from ${model.name} (schema: ${schema}) before sync`);
|
.map(fk => fk?.constraint_name)
|
||||||
for (const fk of foreignKeys) {
|
.filter(name => name && name !== 'undefined' && name !== undefined);
|
||||||
try {
|
|
||||||
console.log(` 🗑️ Dropping constraint: ${fk.constraint_name}`);
|
if (constraintNames.length > 0) {
|
||||||
await queryWithTimeout(`
|
console.log(` ⚠️ Found ${constraintNames.length} existing foreign keys:`, constraintNames.join(', '));
|
||||||
ALTER TABLE "${schema}"."${tableName}"
|
console.log(` ⚠️ Removing ${constraintNames.length} existing foreign keys from ${model.name} (schema: ${schema}) before sync`);
|
||||||
DROP CONSTRAINT IF EXISTS "${fk.constraint_name}" CASCADE
|
for (const constraintName of constraintNames) {
|
||||||
`, 10000, `Drop FK ${fk.constraint_name}`);
|
try {
|
||||||
} catch (dropError) {
|
console.log(` 🗑️ Dropping constraint: ${constraintName}`);
|
||||||
if (dropError.message.includes('Timeout')) {
|
await queryWithTimeout(`
|
||||||
console.warn(` ⚠️ Timeout beim Entfernen von ${fk.constraint_name} - überspringe...`);
|
ALTER TABLE "${schema}"."${tableName}"
|
||||||
} else {
|
DROP CONSTRAINT IF EXISTS "${constraintName.replace(/"/g, '""')}" CASCADE
|
||||||
console.warn(` ⚠️ Konnte ${fk.constraint_name} nicht entfernen:`, dropError.message?.substring(0, 100));
|
`, 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 {
|
} else {
|
||||||
console.log(` ✅ No foreign keys found for ${model.name}`);
|
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`);
|
console.log(` 🔄 Syncing model ${model.name} with constraints: false`);
|
||||||
try {
|
try {
|
||||||
// Versuche doppelte pg_description Einträge vor dem Sync zu bereinigen
|
// Überspringe pg_description Cleanup komplett - benötigt Superuser-Rechte und blockiert oft
|
||||||
// Hinweis: Benötigt Superuser-Rechte oder spezielle Berechtigungen
|
// Diese Query ist nicht kritisch für die Funktionalität
|
||||||
// Ü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
|
// 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 {
|
try {
|
||||||
const tableName = model.tableName;
|
const existsResult = await queryWithTimeout(`
|
||||||
const schema = model.options?.schema || 'public';
|
SELECT EXISTS (
|
||||||
// Verwende queryWithTimeout mit kurzem Timeout, da diese Query oft hängt
|
SELECT 1 FROM information_schema.tables
|
||||||
await queryWithTimeout(`
|
WHERE table_schema = :schema
|
||||||
DELETE FROM pg_catalog.pg_description d1
|
AND table_name = :tableName
|
||||||
WHERE d1.objoid IN (
|
) as exists
|
||||||
SELECT c.oid
|
`, 5000, `Table exists check for ${model.name}`, { schema, tableName });
|
||||||
FROM pg_catalog.pg_class c
|
tableExists = existsResult && existsResult[0] && existsResult[0].exists;
|
||||||
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
} catch (checkError) {
|
||||||
WHERE c.relname = '${tableName.replace(/'/g, "''")}'
|
// Bei Fehler annehmen, dass Tabelle nicht existiert und Sync versuchen
|
||||||
AND n.nspname = '${schema.replace(/'/g, "''")}'
|
tableExists = false;
|
||||||
)
|
}
|
||||||
AND EXISTS (
|
|
||||||
SELECT 1
|
// Für große Tabellen (FalukantUser, FalukantCharacter) - wenn Tabelle existiert, überspringe Sync
|
||||||
FROM pg_catalog.pg_description d2
|
const largeTables = ['FalukantUser', 'FalukantCharacter', 'Notification', 'RegionData'];
|
||||||
WHERE d2.objoid = d1.objoid
|
if (largeTables.includes(model.name) && tableExists) {
|
||||||
AND d2.objsubid = d1.objsubid
|
console.log(` ℹ️ ${model.name} Tabelle existiert bereits - überspringe Sync (zu groß für alter)`);
|
||||||
AND d2.ctid < d1.ctid
|
// Restore associations before continuing
|
||||||
)
|
if (associationKeys.length > 0) {
|
||||||
`, 5000, `pg_description cleanup for ${model.name}`);
|
model.associations = originalAssociations;
|
||||||
} 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));
|
|
||||||
}
|
}
|
||||||
|
// Restore virtual fields
|
||||||
|
for (const [key, attr] of Object.entries(virtualFields)) {
|
||||||
|
model.rawAttributes[key] = attr;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verwende syncModelWithTimeout für große Tabellen
|
// Verwende syncModelWithTimeout für große Tabellen
|
||||||
|
|||||||
Reference in New Issue
Block a user