@javalabs/prisma-client 1.0.18 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/.github/CODEOWNERS +1 -0
  2. package/README.md +269 -269
  3. package/dist/index.d.ts +1 -1
  4. package/dist/prisma.service.d.ts +1 -1
  5. package/dist/scripts/add-uuid-to-table.js +32 -32
  6. package/dist/scripts/data-migration/batch-migrator.js +12 -12
  7. package/dist/scripts/data-migration/data-transformer.js +14 -14
  8. package/dist/scripts/data-migration/dependency-resolver.js +23 -23
  9. package/dist/scripts/data-migration/entity-discovery.js +68 -68
  10. package/dist/scripts/data-migration/foreign-key-manager.js +23 -23
  11. package/dist/scripts/data-migration/migration-tool.js +5 -5
  12. package/dist/scripts/data-migration/schema-utils.js +74 -74
  13. package/dist/scripts/data-migration/typecast-manager.js +4 -4
  14. package/dist/scripts/database-initializer.js +5 -5
  15. package/dist/scripts/drop-database.js +5 -5
  16. package/dist/scripts/fix-data-types.js +53 -53
  17. package/dist/scripts/fix-enum-values.js +34 -34
  18. package/dist/scripts/fix-schema-discrepancies.js +40 -40
  19. package/dist/scripts/fix-table-indexes.js +81 -81
  20. package/dist/scripts/migrate-schema-structure.js +4 -4
  21. package/dist/scripts/migrate-uuid.js +19 -19
  22. package/dist/scripts/post-migration-validator.js +49 -49
  23. package/dist/scripts/pre-migration-validator.js +107 -107
  24. package/dist/scripts/reset-database.js +21 -21
  25. package/dist/scripts/retry-failed-migrations.js +28 -28
  26. package/dist/scripts/run-migration.js +5 -5
  27. package/dist/scripts/schema-sync.js +18 -18
  28. package/dist/scripts/sequence-sync-cli.js +55 -55
  29. package/dist/scripts/sequence-synchronizer.js +20 -20
  30. package/dist/scripts/sync-enum-types.js +30 -30
  31. package/dist/scripts/sync-enum-values.js +52 -52
  32. package/dist/scripts/truncate-database.js +10 -10
  33. package/dist/scripts/verify-migration-setup.js +10 -10
  34. package/dist/tsconfig.tsbuildinfo +1 -1
  35. package/migration-config.json +63 -63
  36. package/migration-config.json.bk +95 -95
  37. package/migrations/add_reserved_amount.sql +8 -0
  38. package/package.json +44 -44
  39. package/prisma/migrations/add_uuid_to_transactions.sql +13 -13
  40. package/prisma/schema.prisma +601 -554
  41. package/src/index.ts +23 -23
  42. package/src/prisma-factory.service.ts +40 -40
  43. package/src/prisma.module.ts +9 -9
  44. package/src/prisma.service.ts +16 -16
  45. package/src/scripts/add-uuid-to-table.ts +138 -138
  46. package/src/scripts/create-tenant-schemas.ts +145 -145
  47. package/src/scripts/data-migration/batch-migrator.ts +248 -248
  48. package/src/scripts/data-migration/data-transformer.ts +426 -426
  49. package/src/scripts/data-migration/db-connector.ts +120 -120
  50. package/src/scripts/data-migration/dependency-resolver.ts +174 -174
  51. package/src/scripts/data-migration/entity-discovery.ts +196 -196
  52. package/src/scripts/data-migration/foreign-key-manager.ts +277 -277
  53. package/src/scripts/data-migration/migration-config.json +63 -63
  54. package/src/scripts/data-migration/migration-tool.ts +509 -509
  55. package/src/scripts/data-migration/schema-utils.ts +248 -248
  56. package/src/scripts/data-migration/tenant-migrator.ts +201 -201
  57. package/src/scripts/data-migration/typecast-manager.ts +193 -193
  58. package/src/scripts/data-migration/types.ts +113 -113
  59. package/src/scripts/database-initializer.ts +49 -49
  60. package/src/scripts/drop-database.ts +104 -104
  61. package/src/scripts/dump-source-db.sh +61 -61
  62. package/src/scripts/encrypt-user-passwords.ts +36 -36
  63. package/src/scripts/error-handler.ts +117 -117
  64. package/src/scripts/fix-data-types.ts +241 -241
  65. package/src/scripts/fix-enum-values.ts +357 -357
  66. package/src/scripts/fix-schema-discrepancies.ts +317 -317
  67. package/src/scripts/fix-table-indexes.ts +601 -601
  68. package/src/scripts/migrate-schema-structure.ts +90 -90
  69. package/src/scripts/migrate-uuid.ts +76 -76
  70. package/src/scripts/post-migration-validator.ts +526 -526
  71. package/src/scripts/pre-migration-validator.ts +610 -610
  72. package/src/scripts/reset-database.ts +263 -263
  73. package/src/scripts/retry-failed-migrations.ts +416 -416
  74. package/src/scripts/run-migration.ts +707 -707
  75. package/src/scripts/schema-sync.ts +128 -128
  76. package/src/scripts/sequence-sync-cli.ts +416 -416
  77. package/src/scripts/sequence-synchronizer.ts +127 -127
  78. package/src/scripts/sync-enum-types.ts +170 -170
  79. package/src/scripts/sync-enum-values.ts +563 -563
  80. package/src/scripts/truncate-database.ts +123 -123
  81. package/src/scripts/verify-migration-setup.ts +135 -135
  82. package/tsconfig.json +17 -17
  83. package/dist/scripts/data-migration/dependency-manager.d.ts +0 -9
  84. package/dist/scripts/data-migration/dependency-manager.js +0 -86
  85. package/dist/scripts/data-migration/dependency-manager.js.map +0 -1
  86. package/dist/scripts/data-migration/migration-config.json +0 -63
  87. package/dist/scripts/data-migration/migration-phases.d.ts +0 -5
  88. package/dist/scripts/data-migration/migration-phases.js +0 -55
  89. package/dist/scripts/data-migration/migration-phases.js.map +0 -1
  90. package/dist/scripts/data-migration/multi-source-migrator.d.ts +0 -17
  91. package/dist/scripts/data-migration/multi-source-migrator.js +0 -130
  92. package/dist/scripts/data-migration/multi-source-migrator.js.map +0 -1
  93. package/dist/scripts/data-migration/phase-generator.d.ts +0 -15
  94. package/dist/scripts/data-migration/phase-generator.js +0 -187
  95. package/dist/scripts/data-migration/phase-generator.js.map +0 -1
  96. package/dist/scripts/data-migration.d.ts +0 -22
  97. package/dist/scripts/data-migration.js +0 -593
  98. package/dist/scripts/data-migration.js.map +0 -1
  99. package/dist/scripts/multi-db-migration.d.ts +0 -1
  100. package/dist/scripts/multi-db-migration.js +0 -55
  101. package/dist/scripts/multi-db-migration.js.map +0 -1
@@ -1,611 +1,611 @@
1
- import * as pg from 'pg';
2
- import * as dotenv from 'dotenv';
3
- import { Logger } from '@nestjs/common';
4
- import * as fs from 'fs';
5
- import * as path from 'path';
6
-
7
- dotenv.config();
8
-
9
- export class PreMigrationValidator {
10
- private readonly logger = new Logger('PreMigrationValidator');
11
- private readonly sourcePool: pg.Pool;
12
- private readonly targetPool: pg.Pool;
13
- private readonly reportPath: string;
14
- private issues: any[] = [];
15
-
16
- constructor(
17
- private readonly sourceUrl: string = process.env.SOURCE_DATABASE_URL,
18
- private readonly targetUrl: string = process.env.DATABASE_URL
19
- ) {
20
- this.sourcePool = new pg.Pool({
21
- connectionString: this.sourceUrl,
22
- });
23
-
24
- this.targetPool = new pg.Pool({
25
- connectionString: this.targetUrl,
26
- });
27
-
28
- // Crear directorio para reportes si no existe
29
- const reportsDir = path.join(process.cwd(), 'migration-logs');
30
- if (!fs.existsSync(reportsDir)) {
31
- fs.mkdirSync(reportsDir, { recursive: true });
32
- }
33
-
34
- // Archivo para guardar el reporte
35
- const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
36
- this.reportPath = path.join(reportsDir, `pre-migration-report-${timestamp}.json`);
37
- }
38
-
39
- async validate() {
40
- try {
41
- this.logger.log('Starting pre-migration validation');
42
-
43
- // 1. Verificar conexiones a las bases de datos
44
- await this.validateDatabaseConnections();
45
-
46
- // 2. Verificar API keys en la base de datos de origen
47
- await this.validateApiKeys();
48
-
49
- // 3. Verificar compatibilidad de tipos de datos
50
- await this.validateDataTypeCompatibility();
51
-
52
- // 4. Verificar tamaños de columnas
53
- await this.validateColumnSizes();
54
-
55
- // 5. Verificar valores de enum
56
- await this.validateEnumValues();
57
-
58
- // 6. Verificar restricciones de clave foránea
59
- await this.validateForeignKeyConstraints();
60
-
61
- // 7. Verificar índices únicos
62
- await this.validateUniqueConstraints();
63
-
64
- // Guardar el reporte
65
- this.saveReport();
66
-
67
- // Mostrar resumen
68
- this.logger.log(`Validation completed with ${this.issues.length} issues found`);
69
- if (this.issues.length > 0) {
70
- this.logger.log(`Check the full report at: ${this.reportPath}`);
71
-
72
- // Mostrar los primeros 5 problemas como ejemplo
73
- this.logger.log('Sample issues:');
74
- for (let i = 0; i < Math.min(5, this.issues.length); i++) {
75
- const issue = this.issues[i];
76
- this.logger.log(`- ${issue.type}: ${issue.message}`);
77
- }
78
- } else {
79
- this.logger.log('No issues found. Migration should proceed smoothly.');
80
- }
81
-
82
- return {
83
- success: this.issues.length === 0,
84
- issueCount: this.issues.length,
85
- reportPath: this.reportPath
86
- };
87
- } catch (error) {
88
- this.logger.error(`Error during validation: ${error.message}`, error.stack);
89
- this.issues.push({
90
- type: 'VALIDATION_ERROR',
91
- message: `Validation process failed: ${error.message}`,
92
- details: error.stack
93
- });
94
- this.saveReport();
95
- return {
96
- success: false,
97
- issueCount: this.issues.length,
98
- reportPath: this.reportPath
99
- };
100
- } finally {
101
- await this.cleanup();
102
- }
103
- }
104
-
105
- private async validateDatabaseConnections() {
106
- this.logger.log('Validating database connections');
107
-
108
- try {
109
- // Verificar conexión a la base de datos de origen
110
- await this.sourcePool.query('SELECT 1');
111
- this.logger.log('Source database connection successful');
112
- } catch (error) {
113
- this.logger.error(`Source database connection failed: ${error.message}`);
114
- this.issues.push({
115
- type: 'CONNECTION_ERROR',
116
- source: 'source',
117
- message: `Cannot connect to source database: ${error.message}`
118
- });
119
- }
120
-
121
- try {
122
- // Verificar conexión a la base de datos de destino
123
- await this.targetPool.query('SELECT 1');
124
- this.logger.log('Target database connection successful');
125
- } catch (error) {
126
- this.logger.error(`Target database connection failed: ${error.message}`);
127
- this.issues.push({
128
- type: 'CONNECTION_ERROR',
129
- source: 'target',
130
- message: `Cannot connect to target database: ${error.message}`
131
- });
132
- }
133
- }
134
-
135
- private async validateApiKeys() {
136
- this.logger.log('Validating API keys');
137
-
138
- try {
139
- // Verificar que existan API keys en la base de datos de origen
140
- const result = await this.sourcePool.query(`
141
- SELECT COUNT(*) as count FROM api_keys
142
- `);
143
-
144
- const count = parseInt(result.rows[0].count);
145
-
146
- if (count === 0) {
147
- this.logger.warn('No API keys found in source database');
148
- this.issues.push({
149
- type: 'DATA_ERROR',
150
- message: 'No API keys found in source database. Migration will not create any tenant schemas.'
151
- });
152
- } else {
153
- this.logger.log(`Found ${count} API keys in source database`);
154
- }
155
- } catch (error) {
156
- this.logger.error(`Error validating API keys: ${error.message}`);
157
- this.issues.push({
158
- type: 'VALIDATION_ERROR',
159
- message: `Error validating API keys: ${error.message}`
160
- });
161
- }
162
- }
163
-
164
- private async validateDataTypeCompatibility() {
165
- this.logger.log('Validating data type compatibility');
166
-
167
- try {
168
- // Obtener todas las tablas de la base de datos de origen
169
- const tablesResult = await this.sourcePool.query(`
170
- SELECT table_name
171
- FROM information_schema.tables
172
- WHERE table_schema = 'public'
173
- AND table_type = 'BASE TABLE'
174
- `);
175
-
176
- for (const tableRow of tablesResult.rows) {
177
- const tableName = tableRow.table_name;
178
-
179
- // Obtener columnas de la tabla en la base de datos de origen
180
- const sourceColumnsResult = await this.sourcePool.query(`
181
- SELECT column_name, data_type, udt_name
182
- FROM information_schema.columns
183
- WHERE table_schema = 'public'
184
- AND table_name = $1
185
- `, [tableName]);
186
-
187
- // Verificar si la tabla existe en la base de datos de destino (schema public)
188
- const targetTableExists = await this.targetPool.query(`
189
- SELECT 1
190
- FROM information_schema.tables
191
- WHERE table_schema = 'public'
192
- AND table_name = $1
193
- LIMIT 1
194
- `, [tableName]);
195
-
196
- if (targetTableExists.rows.length === 0) {
197
- // Si la tabla no existe en el destino, no hay problema de compatibilidad
198
- continue;
199
- }
200
-
201
- // Obtener columnas de la tabla en la base de datos de destino
202
- const targetColumnsResult = await this.targetPool.query(`
203
- SELECT column_name, data_type, udt_name
204
- FROM information_schema.columns
205
- WHERE table_schema = 'public'
206
- AND table_name = $1
207
- `, [tableName]);
208
-
209
- // Crear mapas para facilitar la comparación
210
- const sourceColumns = sourceColumnsResult.rows.reduce((map, col) => {
211
- map[col.column_name] = col;
212
- return map;
213
- }, {});
214
-
215
- const targetColumns = targetColumnsResult.rows.reduce((map, col) => {
216
- map[col.column_name] = col;
217
- return map;
218
- }, {});
219
-
220
- // Comparar tipos de datos
221
- for (const columnName in sourceColumns) {
222
- if (targetColumns[columnName]) {
223
- const sourceType = sourceColumns[columnName].data_type;
224
- const targetType = targetColumns[columnName].data_type;
225
-
226
- // Verificar si los tipos son compatibles
227
- if (sourceType !== targetType) {
228
- // Algunos tipos pueden ser compatibles aunque tengan nombres diferentes
229
- const isCompatible = this.areTypesCompatible(sourceType, targetType);
230
-
231
- if (!isCompatible) {
232
- this.logger.warn(`Data type mismatch for ${tableName}.${columnName}: ${sourceType} (source) vs ${targetType} (target)`);
233
- this.issues.push({
234
- type: 'TYPE_MISMATCH',
235
- table: tableName,
236
- column: columnName,
237
- sourceType,
238
- targetType,
239
- message: `Data type mismatch for ${tableName}.${columnName}: ${sourceType} (source) vs ${targetType} (target)`
240
- });
241
- }
242
- }
243
- }
244
- }
245
- }
246
- } catch (error) {
247
- this.logger.error(`Error validating data type compatibility: ${error.message}`);
248
- this.issues.push({
249
- type: 'VALIDATION_ERROR',
250
- message: `Error validating data type compatibility: ${error.message}`
251
- });
252
- }
253
- }
254
-
255
- private areTypesCompatible(sourceType: string, targetType: string): boolean {
256
- // Definir pares de tipos compatibles
257
- const compatibleTypes = [
258
- ['character varying', 'varchar'],
259
- ['character', 'char'],
260
- ['integer', 'int'],
261
- ['boolean', 'bool'],
262
- ['double precision', 'float8'],
263
- ['real', 'float4'],
264
- ['timestamp without time zone', 'timestamp'],
265
- ['timestamp with time zone', 'timestamptz']
266
- ];
267
-
268
- // Verificar si los tipos son iguales
269
- if (sourceType === targetType) {
270
- return true;
271
- }
272
-
273
- // Verificar si los tipos son compatibles
274
- return compatibleTypes.some(pair =>
275
- (pair[0] === sourceType && pair[1] === targetType) ||
276
- (pair[0] === targetType && pair[1] === sourceType)
277
- );
278
- }
279
-
280
- private async validateColumnSizes() {
281
- this.logger.log('Validating column sizes');
282
-
283
- try {
284
- // Obtener todas las tablas de la base de datos de origen
285
- const tablesResult = await this.sourcePool.query(`
286
- SELECT table_name
287
- FROM information_schema.tables
288
- WHERE table_schema = 'public'
289
- AND table_type = 'BASE TABLE'
290
- `);
291
-
292
- for (const tableRow of tablesResult.rows) {
293
- const tableName = tableRow.table_name;
294
-
295
- // Obtener columnas de tipo character con longitud máxima
296
- const sourceColumnsResult = await this.sourcePool.query(`
297
- SELECT column_name, data_type, character_maximum_length
298
- FROM information_schema.columns
299
- WHERE table_schema = 'public'
300
- AND table_name = $1
301
- AND data_type IN ('character varying', 'character', 'varchar', 'char')
302
- AND character_maximum_length IS NOT NULL
303
- `, [tableName]);
304
-
305
- // Verificar si la tabla existe en la base de datos de destino (schema public)
306
- const targetTableExists = await this.targetPool.query(`
307
- SELECT 1
308
- FROM information_schema.tables
309
- WHERE table_schema = 'public'
310
- AND table_name = $1
311
- LIMIT 1
312
- `, [tableName]);
313
-
314
- if (targetTableExists.rows.length === 0) {
315
- // Si la tabla no existe en el destino, no hay problema de tamaño
316
- continue;
317
- }
318
-
319
- // Para cada columna, verificar si el tamaño en el destino es suficiente
320
- for (const sourceColumn of sourceColumnsResult.rows) {
321
- const columnName = sourceColumn.column_name;
322
- const sourceMaxLength = sourceColumn.character_maximum_length;
323
-
324
- // Obtener la columna correspondiente en el destino
325
- const targetColumnResult = await this.targetPool.query(`
326
- SELECT character_maximum_length
327
- FROM information_schema.columns
328
- WHERE table_schema = 'public'
329
- AND table_name = $1
330
- AND column_name = $2
331
- AND data_type IN ('character varying', 'character', 'varchar', 'char')
332
- `, [tableName, columnName]);
333
-
334
- if (targetColumnResult.rows.length > 0) {
335
- const targetMaxLength = targetColumnResult.rows[0].character_maximum_length;
336
-
337
- // Si el tamaño en el destino es menor, podría haber truncamiento
338
- if (targetMaxLength < sourceMaxLength) {
339
- this.logger.warn(`Column size mismatch for ${tableName}.${columnName}: ${sourceMaxLength} (source) vs ${targetMaxLength} (target)`);
340
- this.issues.push({
341
- type: 'SIZE_MISMATCH',
342
- table: tableName,
343
- column: columnName,
344
- sourceSize: sourceMaxLength,
345
- targetSize: targetMaxLength,
346
- message: `Column size mismatch for ${tableName}.${columnName}: ${sourceMaxLength} (source) vs ${targetMaxLength} (target). Data may be truncated.`
347
- });
348
- }
349
- }
350
- }
351
- }
352
- } catch (error) {
353
- this.logger.error(`Error validating column sizes: ${error.message}`);
354
- this.issues.push({
355
- type: 'VALIDATION_ERROR',
356
- message: `Error validating column sizes: ${error.message}`
357
- });
358
- }
359
- }
360
-
361
- private async validateEnumValues() {
362
- this.logger.log('Validating enum values');
363
-
364
- try {
365
- // Obtener todos los tipos enum en la base de datos de origen
366
- const sourceEnumsResult = await this.sourcePool.query(`
367
- SELECT t.typname AS enum_name
368
- FROM pg_type t
369
- JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
370
- WHERE t.typtype = 'e'
371
- AND n.nspname = 'public'
372
- `);
373
-
374
- // Para cada tipo enum, obtener sus valores
375
- for (const enumRow of sourceEnumsResult.rows) {
376
- const enumName = enumRow.enum_name;
377
-
378
- // Obtener valores del enum en la base de datos de origen
379
- const sourceEnumValuesResult = await this.sourcePool.query(`
380
- SELECT e.enumlabel
381
- FROM pg_enum e
382
- JOIN pg_type t ON e.enumtypid = t.oid
383
- WHERE t.typname = $1
384
- ORDER BY e.enumsortorder
385
- `, [enumName]);
386
-
387
- const sourceEnumValues = sourceEnumValuesResult.rows.map(row => row.enumlabel);
388
-
389
- // Verificar si el enum existe en la base de datos de destino
390
- const targetEnumExistsResult = await this.targetPool.query(`
391
- SELECT 1
392
- FROM pg_type t
393
- JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
394
- WHERE t.typtype = 'e'
395
- AND t.typname = $1
396
- AND n.nspname = 'public'
397
- LIMIT 1
398
- `, [enumName]);
399
-
400
- if (targetEnumExistsResult.rows.length === 0) {
401
- // Si el enum no existe en el destino, no hay problema de compatibilidad
402
- continue;
403
- }
404
-
405
- // Obtener valores del enum en la base de datos de destino
406
- const targetEnumValuesResult = await this.targetPool.query(`
407
- SELECT e.enumlabel
408
- FROM pg_enum e
409
- JOIN pg_type t ON e.enumtypid = t.oid
410
- WHERE t.typname = $1
411
- ORDER BY e.enumsortorder
412
- `, [enumName]);
413
-
414
- const targetEnumValues = targetEnumValuesResult.rows.map(row => row.enumlabel);
415
-
416
- // Verificar si todos los valores del origen existen en el destino
417
- for (const sourceValue of sourceEnumValues) {
418
- if (!targetEnumValues.includes(sourceValue)) {
419
- this.logger.warn(`Enum value '${sourceValue}' for type ${enumName} exists in source but not in target`);
420
- this.issues.push({
421
- type: 'ENUM_VALUE_MISSING',
422
- enumType: enumName,
423
- value: sourceValue,
424
- message: `Enum value '${sourceValue}' for type ${enumName} exists in source but not in target. Data migration will fail for records with this value.`
425
- });
426
- }
427
- }
428
- }
429
- } catch (error) {
430
- this.logger.error(`Error validating enum values: ${error.message}`);
431
- this.issues.push({
432
- type: 'VALIDATION_ERROR',
433
- message: `Error validating enum values: ${error.message}`
434
- });
435
- }
436
- }
437
-
438
- private async validateForeignKeyConstraints() {
439
- this.logger.log('Validating foreign key constraints');
440
-
441
- // Esta validación es más compleja y depende de la estructura específica
442
- // Por ahora, solo verificamos si hay tablas con claves foráneas en el origen
443
- try {
444
- const foreignKeysResult = await this.sourcePool.query(`
445
- SELECT
446
- tc.table_name,
447
- kcu.column_name,
448
- ccu.table_name AS foreign_table_name,
449
- ccu.column_name AS foreign_column_name
450
- FROM
451
- information_schema.table_constraints AS tc
452
- JOIN information_schema.key_column_usage AS kcu
453
- ON tc.constraint_name = kcu.constraint_name
454
- AND tc.table_schema = kcu.table_schema
455
- JOIN information_schema.constraint_column_usage AS ccu
456
- ON ccu.constraint_name = tc.constraint_name
457
- AND ccu.table_schema = tc.table_schema
458
- WHERE tc.constraint_type = 'FOREIGN KEY'
459
- AND tc.table_schema = 'public'
460
- `);
461
-
462
- this.logger.log(`Found ${foreignKeysResult.rows.length} foreign key constraints in source database`);
463
-
464
- // Aquí podríamos hacer validaciones más específicas si es necesario
465
- } catch (error) {
466
- this.logger.error(`Error validating foreign key constraints: ${error.message}`);
467
- this.issues.push({
468
- type: 'VALIDATION_ERROR',
469
- message: `Error validating foreign key constraints: ${error.message}`
470
- });
471
- }
472
- }
473
-
474
- private async validateUniqueConstraints() {
475
- this.logger.log('Validating unique constraints...');
476
-
477
- try {
478
- // Obtener restricciones únicas de la base de datos de origen
479
- const sourceConstraintsQuery = `
480
- SELECT
481
- tc.table_name,
482
- kcu.column_name
483
- FROM
484
- information_schema.table_constraints tc
485
- JOIN information_schema.key_column_usage kcu
486
- ON tc.constraint_name = kcu.constraint_name
487
- WHERE
488
- tc.constraint_type = 'UNIQUE'
489
- AND tc.table_schema = 'public'
490
- `;
491
-
492
- const sourceConstraints = await this.sourcePool.query(sourceConstraintsQuery);
493
-
494
- // Para cada restricción, verificar si existe en la base de datos de destino
495
- for (const constraint of sourceConstraints.rows) {
496
- const { table_name, column_name } = constraint;
497
-
498
- try {
499
- // Verificar si la columna existe en la tabla de destino
500
- const columnExistsQuery = `
501
- SELECT column_name
502
- FROM information_schema.columns
503
- WHERE table_name = $1
504
- AND column_name = $2
505
- AND table_schema = 'public'
506
- `;
507
-
508
- const columnExists = await this.targetPool.query(columnExistsQuery, [table_name, column_name]);
509
-
510
- if (columnExists.rows.length === 0) {
511
- // La columna no existe, registrar como advertencia pero no como error crítico
512
- this.issues.push({
513
- type: 'COLUMN_MISMATCH',
514
- message: `Column "${column_name}" in table "${table_name}" exists in source but not in target`,
515
- severity: 'WARNING',
516
- details: {
517
- table: table_name,
518
- column: column_name,
519
- constraint_type: 'UNIQUE'
520
- }
521
- });
522
- continue; // Saltar a la siguiente restricción
523
- }
524
-
525
- // Resto del código para validar restricciones únicas...
526
- } catch (error) {
527
- // Capturar errores específicos de columnas que no existen
528
- if (error.message.includes('does not exist')) {
529
- this.issues.push({
530
- type: 'COLUMN_MISMATCH',
531
- message: `Error checking column "${column_name}" in table "${table_name}": ${error.message}`,
532
- severity: 'WARNING',
533
- details: {
534
- table: table_name,
535
- column: column_name,
536
- error: error.message
537
- }
538
- });
539
- } else {
540
- throw error; // Re-lanzar otros errores
541
- }
542
- }
543
- }
544
-
545
- this.logger.log('Unique constraints validation completed');
546
- } catch (error) {
547
- // Convertir errores críticos en advertencias para permitir que la migración continúe
548
- this.issues.push({
549
- type: 'VALIDATION_ERROR',
550
- message: `Error validating unique constraints: ${error.message}`,
551
- severity: 'WARNING',
552
- details: {
553
- error: error.message,
554
- stack: error.stack
555
- }
556
- });
557
-
558
- this.logger.warn(`Error during unique constraints validation: ${error.message}`);
559
- }
560
- }
561
-
562
- private saveReport() {
563
- try {
564
- const report = {
565
- timestamp: new Date().toISOString(),
566
- sourceDatabase: this.sourceUrl,
567
- targetDatabase: this.targetUrl,
568
- issueCount: this.issues.length,
569
- issues: this.issues
570
- };
571
-
572
- fs.writeFileSync(
573
- this.reportPath,
574
- JSON.stringify(report, null, 2),
575
- 'utf8'
576
- );
577
- } catch (error) {
578
- this.logger.error(`Error saving validation report: ${error.message}`);
579
- }
580
- }
581
-
582
- private async cleanup() {
583
- this.logger.log('Cleaning up database connections');
584
- await this.sourcePool.end();
585
- await this.targetPool.end();
586
- }
587
- }
588
-
589
- // Script para ejecutar desde línea de comandos
590
- if (require.main === module) {
591
- const run = async () => {
592
- try {
593
- const validator = new PreMigrationValidator();
594
- const result = await validator.validate();
595
-
596
- if (result.success) {
597
- console.log('Validation successful! No issues found.');
598
- process.exit(0);
599
- } else {
600
- console.log(`Validation completed with ${result.issueCount} issues found.`);
601
- console.log(`Check the full report at: ${result.reportPath}`);
602
- process.exit(1);
603
- }
604
- } catch (error) {
605
- console.error('Error:', error.message);
606
- process.exit(1);
607
- }
608
- };
609
-
610
- run();
1
+ import * as pg from 'pg';
2
+ import * as dotenv from 'dotenv';
3
+ import { Logger } from '@nestjs/common';
4
+ import * as fs from 'fs';
5
+ import * as path from 'path';
6
+
7
+ dotenv.config();
8
+
9
+ export class PreMigrationValidator {
10
+ private readonly logger = new Logger('PreMigrationValidator');
11
+ private readonly sourcePool: pg.Pool;
12
+ private readonly targetPool: pg.Pool;
13
+ private readonly reportPath: string;
14
+ private issues: any[] = [];
15
+
16
+ constructor(
17
+ private readonly sourceUrl: string = process.env.SOURCE_DATABASE_URL,
18
+ private readonly targetUrl: string = process.env.DATABASE_URL
19
+ ) {
20
+ this.sourcePool = new pg.Pool({
21
+ connectionString: this.sourceUrl,
22
+ });
23
+
24
+ this.targetPool = new pg.Pool({
25
+ connectionString: this.targetUrl,
26
+ });
27
+
28
+ // Crear directorio para reportes si no existe
29
+ const reportsDir = path.join(process.cwd(), 'migration-logs');
30
+ if (!fs.existsSync(reportsDir)) {
31
+ fs.mkdirSync(reportsDir, { recursive: true });
32
+ }
33
+
34
+ // Archivo para guardar el reporte
35
+ const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
36
+ this.reportPath = path.join(reportsDir, `pre-migration-report-${timestamp}.json`);
37
+ }
38
+
39
+ async validate() {
40
+ try {
41
+ this.logger.log('Starting pre-migration validation');
42
+
43
+ // 1. Verificar conexiones a las bases de datos
44
+ await this.validateDatabaseConnections();
45
+
46
+ // 2. Verificar API keys en la base de datos de origen
47
+ await this.validateApiKeys();
48
+
49
+ // 3. Verificar compatibilidad de tipos de datos
50
+ await this.validateDataTypeCompatibility();
51
+
52
+ // 4. Verificar tamaños de columnas
53
+ await this.validateColumnSizes();
54
+
55
+ // 5. Verificar valores de enum
56
+ await this.validateEnumValues();
57
+
58
+ // 6. Verificar restricciones de clave foránea
59
+ await this.validateForeignKeyConstraints();
60
+
61
+ // 7. Verificar índices únicos
62
+ await this.validateUniqueConstraints();
63
+
64
+ // Guardar el reporte
65
+ this.saveReport();
66
+
67
+ // Mostrar resumen
68
+ this.logger.log(`Validation completed with ${this.issues.length} issues found`);
69
+ if (this.issues.length > 0) {
70
+ this.logger.log(`Check the full report at: ${this.reportPath}`);
71
+
72
+ // Mostrar los primeros 5 problemas como ejemplo
73
+ this.logger.log('Sample issues:');
74
+ for (let i = 0; i < Math.min(5, this.issues.length); i++) {
75
+ const issue = this.issues[i];
76
+ this.logger.log(`- ${issue.type}: ${issue.message}`);
77
+ }
78
+ } else {
79
+ this.logger.log('No issues found. Migration should proceed smoothly.');
80
+ }
81
+
82
+ return {
83
+ success: this.issues.length === 0,
84
+ issueCount: this.issues.length,
85
+ reportPath: this.reportPath
86
+ };
87
+ } catch (error) {
88
+ this.logger.error(`Error during validation: ${error.message}`, error.stack);
89
+ this.issues.push({
90
+ type: 'VALIDATION_ERROR',
91
+ message: `Validation process failed: ${error.message}`,
92
+ details: error.stack
93
+ });
94
+ this.saveReport();
95
+ return {
96
+ success: false,
97
+ issueCount: this.issues.length,
98
+ reportPath: this.reportPath
99
+ };
100
+ } finally {
101
+ await this.cleanup();
102
+ }
103
+ }
104
+
105
+ private async validateDatabaseConnections() {
106
+ this.logger.log('Validating database connections');
107
+
108
+ try {
109
+ // Verificar conexión a la base de datos de origen
110
+ await this.sourcePool.query('SELECT 1');
111
+ this.logger.log('Source database connection successful');
112
+ } catch (error) {
113
+ this.logger.error(`Source database connection failed: ${error.message}`);
114
+ this.issues.push({
115
+ type: 'CONNECTION_ERROR',
116
+ source: 'source',
117
+ message: `Cannot connect to source database: ${error.message}`
118
+ });
119
+ }
120
+
121
+ try {
122
+ // Verificar conexión a la base de datos de destino
123
+ await this.targetPool.query('SELECT 1');
124
+ this.logger.log('Target database connection successful');
125
+ } catch (error) {
126
+ this.logger.error(`Target database connection failed: ${error.message}`);
127
+ this.issues.push({
128
+ type: 'CONNECTION_ERROR',
129
+ source: 'target',
130
+ message: `Cannot connect to target database: ${error.message}`
131
+ });
132
+ }
133
+ }
134
+
135
+ private async validateApiKeys() {
136
+ this.logger.log('Validating API keys');
137
+
138
+ try {
139
+ // Verificar que existan API keys en la base de datos de origen
140
+ const result = await this.sourcePool.query(`
141
+ SELECT COUNT(*) as count FROM api_keys
142
+ `);
143
+
144
+ const count = parseInt(result.rows[0].count);
145
+
146
+ if (count === 0) {
147
+ this.logger.warn('No API keys found in source database');
148
+ this.issues.push({
149
+ type: 'DATA_ERROR',
150
+ message: 'No API keys found in source database. Migration will not create any tenant schemas.'
151
+ });
152
+ } else {
153
+ this.logger.log(`Found ${count} API keys in source database`);
154
+ }
155
+ } catch (error) {
156
+ this.logger.error(`Error validating API keys: ${error.message}`);
157
+ this.issues.push({
158
+ type: 'VALIDATION_ERROR',
159
+ message: `Error validating API keys: ${error.message}`
160
+ });
161
+ }
162
+ }
163
+
164
+ private async validateDataTypeCompatibility() {
165
+ this.logger.log('Validating data type compatibility');
166
+
167
+ try {
168
+ // Obtener todas las tablas de la base de datos de origen
169
+ const tablesResult = await this.sourcePool.query(`
170
+ SELECT table_name
171
+ FROM information_schema.tables
172
+ WHERE table_schema = 'public'
173
+ AND table_type = 'BASE TABLE'
174
+ `);
175
+
176
+ for (const tableRow of tablesResult.rows) {
177
+ const tableName = tableRow.table_name;
178
+
179
+ // Obtener columnas de la tabla en la base de datos de origen
180
+ const sourceColumnsResult = await this.sourcePool.query(`
181
+ SELECT column_name, data_type, udt_name
182
+ FROM information_schema.columns
183
+ WHERE table_schema = 'public'
184
+ AND table_name = $1
185
+ `, [tableName]);
186
+
187
+ // Verificar si la tabla existe en la base de datos de destino (schema public)
188
+ const targetTableExists = await this.targetPool.query(`
189
+ SELECT 1
190
+ FROM information_schema.tables
191
+ WHERE table_schema = 'public'
192
+ AND table_name = $1
193
+ LIMIT 1
194
+ `, [tableName]);
195
+
196
+ if (targetTableExists.rows.length === 0) {
197
+ // Si la tabla no existe en el destino, no hay problema de compatibilidad
198
+ continue;
199
+ }
200
+
201
+ // Obtener columnas de la tabla en la base de datos de destino
202
+ const targetColumnsResult = await this.targetPool.query(`
203
+ SELECT column_name, data_type, udt_name
204
+ FROM information_schema.columns
205
+ WHERE table_schema = 'public'
206
+ AND table_name = $1
207
+ `, [tableName]);
208
+
209
+ // Crear mapas para facilitar la comparación
210
+ const sourceColumns = sourceColumnsResult.rows.reduce((map, col) => {
211
+ map[col.column_name] = col;
212
+ return map;
213
+ }, {});
214
+
215
+ const targetColumns = targetColumnsResult.rows.reduce((map, col) => {
216
+ map[col.column_name] = col;
217
+ return map;
218
+ }, {});
219
+
220
+ // Comparar tipos de datos
221
+ for (const columnName in sourceColumns) {
222
+ if (targetColumns[columnName]) {
223
+ const sourceType = sourceColumns[columnName].data_type;
224
+ const targetType = targetColumns[columnName].data_type;
225
+
226
+ // Verificar si los tipos son compatibles
227
+ if (sourceType !== targetType) {
228
+ // Algunos tipos pueden ser compatibles aunque tengan nombres diferentes
229
+ const isCompatible = this.areTypesCompatible(sourceType, targetType);
230
+
231
+ if (!isCompatible) {
232
+ this.logger.warn(`Data type mismatch for ${tableName}.${columnName}: ${sourceType} (source) vs ${targetType} (target)`);
233
+ this.issues.push({
234
+ type: 'TYPE_MISMATCH',
235
+ table: tableName,
236
+ column: columnName,
237
+ sourceType,
238
+ targetType,
239
+ message: `Data type mismatch for ${tableName}.${columnName}: ${sourceType} (source) vs ${targetType} (target)`
240
+ });
241
+ }
242
+ }
243
+ }
244
+ }
245
+ }
246
+ } catch (error) {
247
+ this.logger.error(`Error validating data type compatibility: ${error.message}`);
248
+ this.issues.push({
249
+ type: 'VALIDATION_ERROR',
250
+ message: `Error validating data type compatibility: ${error.message}`
251
+ });
252
+ }
253
+ }
254
+
255
+ private areTypesCompatible(sourceType: string, targetType: string): boolean {
256
+ // Definir pares de tipos compatibles
257
+ const compatibleTypes = [
258
+ ['character varying', 'varchar'],
259
+ ['character', 'char'],
260
+ ['integer', 'int'],
261
+ ['boolean', 'bool'],
262
+ ['double precision', 'float8'],
263
+ ['real', 'float4'],
264
+ ['timestamp without time zone', 'timestamp'],
265
+ ['timestamp with time zone', 'timestamptz']
266
+ ];
267
+
268
+ // Verificar si los tipos son iguales
269
+ if (sourceType === targetType) {
270
+ return true;
271
+ }
272
+
273
+ // Verificar si los tipos son compatibles
274
+ return compatibleTypes.some(pair =>
275
+ (pair[0] === sourceType && pair[1] === targetType) ||
276
+ (pair[0] === targetType && pair[1] === sourceType)
277
+ );
278
+ }
279
+
280
+ private async validateColumnSizes() {
281
+ this.logger.log('Validating column sizes');
282
+
283
+ try {
284
+ // Obtener todas las tablas de la base de datos de origen
285
+ const tablesResult = await this.sourcePool.query(`
286
+ SELECT table_name
287
+ FROM information_schema.tables
288
+ WHERE table_schema = 'public'
289
+ AND table_type = 'BASE TABLE'
290
+ `);
291
+
292
+ for (const tableRow of tablesResult.rows) {
293
+ const tableName = tableRow.table_name;
294
+
295
+ // Obtener columnas de tipo character con longitud máxima
296
+ const sourceColumnsResult = await this.sourcePool.query(`
297
+ SELECT column_name, data_type, character_maximum_length
298
+ FROM information_schema.columns
299
+ WHERE table_schema = 'public'
300
+ AND table_name = $1
301
+ AND data_type IN ('character varying', 'character', 'varchar', 'char')
302
+ AND character_maximum_length IS NOT NULL
303
+ `, [tableName]);
304
+
305
+ // Verificar si la tabla existe en la base de datos de destino (schema public)
306
+ const targetTableExists = await this.targetPool.query(`
307
+ SELECT 1
308
+ FROM information_schema.tables
309
+ WHERE table_schema = 'public'
310
+ AND table_name = $1
311
+ LIMIT 1
312
+ `, [tableName]);
313
+
314
+ if (targetTableExists.rows.length === 0) {
315
+ // Si la tabla no existe en el destino, no hay problema de tamaño
316
+ continue;
317
+ }
318
+
319
+ // Para cada columna, verificar si el tamaño en el destino es suficiente
320
+ for (const sourceColumn of sourceColumnsResult.rows) {
321
+ const columnName = sourceColumn.column_name;
322
+ const sourceMaxLength = sourceColumn.character_maximum_length;
323
+
324
+ // Obtener la columna correspondiente en el destino
325
+ const targetColumnResult = await this.targetPool.query(`
326
+ SELECT character_maximum_length
327
+ FROM information_schema.columns
328
+ WHERE table_schema = 'public'
329
+ AND table_name = $1
330
+ AND column_name = $2
331
+ AND data_type IN ('character varying', 'character', 'varchar', 'char')
332
+ `, [tableName, columnName]);
333
+
334
+ if (targetColumnResult.rows.length > 0) {
335
+ const targetMaxLength = targetColumnResult.rows[0].character_maximum_length;
336
+
337
+ // Si el tamaño en el destino es menor, podría haber truncamiento
338
+ if (targetMaxLength < sourceMaxLength) {
339
+ this.logger.warn(`Column size mismatch for ${tableName}.${columnName}: ${sourceMaxLength} (source) vs ${targetMaxLength} (target)`);
340
+ this.issues.push({
341
+ type: 'SIZE_MISMATCH',
342
+ table: tableName,
343
+ column: columnName,
344
+ sourceSize: sourceMaxLength,
345
+ targetSize: targetMaxLength,
346
+ message: `Column size mismatch for ${tableName}.${columnName}: ${sourceMaxLength} (source) vs ${targetMaxLength} (target). Data may be truncated.`
347
+ });
348
+ }
349
+ }
350
+ }
351
+ }
352
+ } catch (error) {
353
+ this.logger.error(`Error validating column sizes: ${error.message}`);
354
+ this.issues.push({
355
+ type: 'VALIDATION_ERROR',
356
+ message: `Error validating column sizes: ${error.message}`
357
+ });
358
+ }
359
+ }
360
+
361
+ private async validateEnumValues() {
362
+ this.logger.log('Validating enum values');
363
+
364
+ try {
365
+ // Obtener todos los tipos enum en la base de datos de origen
366
+ const sourceEnumsResult = await this.sourcePool.query(`
367
+ SELECT t.typname AS enum_name
368
+ FROM pg_type t
369
+ JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
370
+ WHERE t.typtype = 'e'
371
+ AND n.nspname = 'public'
372
+ `);
373
+
374
+ // Para cada tipo enum, obtener sus valores
375
+ for (const enumRow of sourceEnumsResult.rows) {
376
+ const enumName = enumRow.enum_name;
377
+
378
+ // Obtener valores del enum en la base de datos de origen
379
+ const sourceEnumValuesResult = await this.sourcePool.query(`
380
+ SELECT e.enumlabel
381
+ FROM pg_enum e
382
+ JOIN pg_type t ON e.enumtypid = t.oid
383
+ WHERE t.typname = $1
384
+ ORDER BY e.enumsortorder
385
+ `, [enumName]);
386
+
387
+ const sourceEnumValues = sourceEnumValuesResult.rows.map(row => row.enumlabel);
388
+
389
+ // Verificar si el enum existe en la base de datos de destino
390
+ const targetEnumExistsResult = await this.targetPool.query(`
391
+ SELECT 1
392
+ FROM pg_type t
393
+ JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
394
+ WHERE t.typtype = 'e'
395
+ AND t.typname = $1
396
+ AND n.nspname = 'public'
397
+ LIMIT 1
398
+ `, [enumName]);
399
+
400
+ if (targetEnumExistsResult.rows.length === 0) {
401
+ // Si el enum no existe en el destino, no hay problema de compatibilidad
402
+ continue;
403
+ }
404
+
405
+ // Obtener valores del enum en la base de datos de destino
406
+ const targetEnumValuesResult = await this.targetPool.query(`
407
+ SELECT e.enumlabel
408
+ FROM pg_enum e
409
+ JOIN pg_type t ON e.enumtypid = t.oid
410
+ WHERE t.typname = $1
411
+ ORDER BY e.enumsortorder
412
+ `, [enumName]);
413
+
414
+ const targetEnumValues = targetEnumValuesResult.rows.map(row => row.enumlabel);
415
+
416
+ // Verificar si todos los valores del origen existen en el destino
417
+ for (const sourceValue of sourceEnumValues) {
418
+ if (!targetEnumValues.includes(sourceValue)) {
419
+ this.logger.warn(`Enum value '${sourceValue}' for type ${enumName} exists in source but not in target`);
420
+ this.issues.push({
421
+ type: 'ENUM_VALUE_MISSING',
422
+ enumType: enumName,
423
+ value: sourceValue,
424
+ message: `Enum value '${sourceValue}' for type ${enumName} exists in source but not in target. Data migration will fail for records with this value.`
425
+ });
426
+ }
427
+ }
428
+ }
429
+ } catch (error) {
430
+ this.logger.error(`Error validating enum values: ${error.message}`);
431
+ this.issues.push({
432
+ type: 'VALIDATION_ERROR',
433
+ message: `Error validating enum values: ${error.message}`
434
+ });
435
+ }
436
+ }
437
+
438
+ private async validateForeignKeyConstraints() {
439
+ this.logger.log('Validating foreign key constraints');
440
+
441
+ // Esta validación es más compleja y depende de la estructura específica
442
+ // Por ahora, solo verificamos si hay tablas con claves foráneas en el origen
443
+ try {
444
+ const foreignKeysResult = await this.sourcePool.query(`
445
+ SELECT
446
+ tc.table_name,
447
+ kcu.column_name,
448
+ ccu.table_name AS foreign_table_name,
449
+ ccu.column_name AS foreign_column_name
450
+ FROM
451
+ information_schema.table_constraints AS tc
452
+ JOIN information_schema.key_column_usage AS kcu
453
+ ON tc.constraint_name = kcu.constraint_name
454
+ AND tc.table_schema = kcu.table_schema
455
+ JOIN information_schema.constraint_column_usage AS ccu
456
+ ON ccu.constraint_name = tc.constraint_name
457
+ AND ccu.table_schema = tc.table_schema
458
+ WHERE tc.constraint_type = 'FOREIGN KEY'
459
+ AND tc.table_schema = 'public'
460
+ `);
461
+
462
+ this.logger.log(`Found ${foreignKeysResult.rows.length} foreign key constraints in source database`);
463
+
464
+ // Aquí podríamos hacer validaciones más específicas si es necesario
465
+ } catch (error) {
466
+ this.logger.error(`Error validating foreign key constraints: ${error.message}`);
467
+ this.issues.push({
468
+ type: 'VALIDATION_ERROR',
469
+ message: `Error validating foreign key constraints: ${error.message}`
470
+ });
471
+ }
472
+ }
473
+
474
+ private async validateUniqueConstraints() {
475
+ this.logger.log('Validating unique constraints...');
476
+
477
+ try {
478
+ // Obtener restricciones únicas de la base de datos de origen
479
+ const sourceConstraintsQuery = `
480
+ SELECT
481
+ tc.table_name,
482
+ kcu.column_name
483
+ FROM
484
+ information_schema.table_constraints tc
485
+ JOIN information_schema.key_column_usage kcu
486
+ ON tc.constraint_name = kcu.constraint_name
487
+ WHERE
488
+ tc.constraint_type = 'UNIQUE'
489
+ AND tc.table_schema = 'public'
490
+ `;
491
+
492
+ const sourceConstraints = await this.sourcePool.query(sourceConstraintsQuery);
493
+
494
+ // Para cada restricción, verificar si existe en la base de datos de destino
495
+ for (const constraint of sourceConstraints.rows) {
496
+ const { table_name, column_name } = constraint;
497
+
498
+ try {
499
+ // Verificar si la columna existe en la tabla de destino
500
+ const columnExistsQuery = `
501
+ SELECT column_name
502
+ FROM information_schema.columns
503
+ WHERE table_name = $1
504
+ AND column_name = $2
505
+ AND table_schema = 'public'
506
+ `;
507
+
508
+ const columnExists = await this.targetPool.query(columnExistsQuery, [table_name, column_name]);
509
+
510
+ if (columnExists.rows.length === 0) {
511
+ // La columna no existe, registrar como advertencia pero no como error crítico
512
+ this.issues.push({
513
+ type: 'COLUMN_MISMATCH',
514
+ message: `Column "${column_name}" in table "${table_name}" exists in source but not in target`,
515
+ severity: 'WARNING',
516
+ details: {
517
+ table: table_name,
518
+ column: column_name,
519
+ constraint_type: 'UNIQUE'
520
+ }
521
+ });
522
+ continue; // Saltar a la siguiente restricción
523
+ }
524
+
525
+ // Resto del código para validar restricciones únicas...
526
+ } catch (error) {
527
+ // Capturar errores específicos de columnas que no existen
528
+ if (error.message.includes('does not exist')) {
529
+ this.issues.push({
530
+ type: 'COLUMN_MISMATCH',
531
+ message: `Error checking column "${column_name}" in table "${table_name}": ${error.message}`,
532
+ severity: 'WARNING',
533
+ details: {
534
+ table: table_name,
535
+ column: column_name,
536
+ error: error.message
537
+ }
538
+ });
539
+ } else {
540
+ throw error; // Re-lanzar otros errores
541
+ }
542
+ }
543
+ }
544
+
545
+ this.logger.log('Unique constraints validation completed');
546
+ } catch (error) {
547
+ // Convertir errores críticos en advertencias para permitir que la migración continúe
548
+ this.issues.push({
549
+ type: 'VALIDATION_ERROR',
550
+ message: `Error validating unique constraints: ${error.message}`,
551
+ severity: 'WARNING',
552
+ details: {
553
+ error: error.message,
554
+ stack: error.stack
555
+ }
556
+ });
557
+
558
+ this.logger.warn(`Error during unique constraints validation: ${error.message}`);
559
+ }
560
+ }
561
+
562
+ private saveReport() {
563
+ try {
564
+ const report = {
565
+ timestamp: new Date().toISOString(),
566
+ sourceDatabase: this.sourceUrl,
567
+ targetDatabase: this.targetUrl,
568
+ issueCount: this.issues.length,
569
+ issues: this.issues
570
+ };
571
+
572
+ fs.writeFileSync(
573
+ this.reportPath,
574
+ JSON.stringify(report, null, 2),
575
+ 'utf8'
576
+ );
577
+ } catch (error) {
578
+ this.logger.error(`Error saving validation report: ${error.message}`);
579
+ }
580
+ }
581
+
582
+ private async cleanup() {
583
+ this.logger.log('Cleaning up database connections');
584
+ await this.sourcePool.end();
585
+ await this.targetPool.end();
586
+ }
587
+ }
588
+
589
+ // Script para ejecutar desde línea de comandos
590
+ if (require.main === module) {
591
+ const run = async () => {
592
+ try {
593
+ const validator = new PreMigrationValidator();
594
+ const result = await validator.validate();
595
+
596
+ if (result.success) {
597
+ console.log('Validation successful! No issues found.');
598
+ process.exit(0);
599
+ } else {
600
+ console.log(`Validation completed with ${result.issueCount} issues found.`);
601
+ console.log(`Check the full report at: ${result.reportPath}`);
602
+ process.exit(1);
603
+ }
604
+ } catch (error) {
605
+ console.error('Error:', error.message);
606
+ process.exit(1);
607
+ }
608
+ };
609
+
610
+ run();
611
611
  }