@javalabs/prisma-client 1.0.4 → 1.0.6
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.
- package/dist/scripts/data-migration/batch-migrator.d.ts +14 -19
- package/dist/scripts/data-migration/batch-migrator.js +98 -297
- package/dist/scripts/data-migration/batch-migrator.js.map +1 -1
- package/dist/scripts/data-migration/data-transformer.d.ts +16 -7
- package/dist/scripts/data-migration/data-transformer.js +169 -133
- package/dist/scripts/data-migration/data-transformer.js.map +1 -1
- package/dist/scripts/data-migration/db-connector.d.ts +6 -1
- package/dist/scripts/data-migration/db-connector.js +44 -8
- package/dist/scripts/data-migration/db-connector.js.map +1 -1
- package/dist/scripts/data-migration/dependency-resolver.d.ts +10 -10
- package/dist/scripts/data-migration/dependency-resolver.js +92 -211
- package/dist/scripts/data-migration/dependency-resolver.js.map +1 -1
- package/dist/scripts/data-migration/foreign-key-manager.d.ts +6 -5
- package/dist/scripts/data-migration/foreign-key-manager.js +108 -18
- package/dist/scripts/data-migration/foreign-key-manager.js.map +1 -1
- package/dist/scripts/data-migration/migration-config.json +63 -0
- package/dist/scripts/data-migration/migration-tool.d.ts +25 -6
- package/dist/scripts/data-migration/migration-tool.js +78 -38
- package/dist/scripts/data-migration/migration-tool.js.map +1 -1
- package/dist/scripts/data-migration/multi-source-migrator.d.ts +17 -0
- package/dist/scripts/data-migration/multi-source-migrator.js +130 -0
- package/dist/scripts/data-migration/multi-source-migrator.js.map +1 -0
- package/dist/scripts/data-migration/schema-utils.d.ts +3 -3
- package/dist/scripts/data-migration/schema-utils.js +62 -19
- package/dist/scripts/data-migration/schema-utils.js.map +1 -1
- package/dist/scripts/data-migration/tenant-migrator.js +9 -2
- package/dist/scripts/data-migration/tenant-migrator.js.map +1 -1
- package/dist/scripts/data-migration/typecast-manager.d.ts +7 -3
- package/dist/scripts/data-migration/typecast-manager.js +169 -25
- package/dist/scripts/data-migration/typecast-manager.js.map +1 -1
- package/dist/scripts/data-migration/types.d.ts +68 -2
- package/dist/scripts/fix-table-indexes.d.ts +26 -0
- package/dist/scripts/fix-table-indexes.js +460 -0
- package/dist/scripts/fix-table-indexes.js.map +1 -0
- package/dist/scripts/multi-db-migration.d.ts +1 -0
- package/dist/scripts/multi-db-migration.js +55 -0
- package/dist/scripts/multi-db-migration.js.map +1 -0
- package/dist/scripts/run-migration.js +41 -75
- package/dist/scripts/run-migration.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/migration-config.json +40 -72
- package/{migration-config-public.json → migration-config.json.bk} +14 -14
- package/package.json +6 -3
- package/src/scripts/data-migration/batch-migrator.ts +192 -513
- package/src/scripts/data-migration/data-transformer.ts +252 -203
- package/src/scripts/data-migration/db-connector.ts +66 -13
- package/src/scripts/data-migration/dependency-resolver.ts +121 -266
- package/src/scripts/data-migration/foreign-key-manager.ts +214 -32
- package/src/scripts/data-migration/migration-config.json +63 -0
- package/src/scripts/data-migration/migration-tool.ts +377 -225
- package/src/scripts/data-migration/schema-utils.ts +94 -32
- package/src/scripts/data-migration/tenant-migrator.ts +12 -5
- package/src/scripts/data-migration/typecast-manager.ts +186 -31
- package/src/scripts/data-migration/types.ts +78 -5
- package/src/scripts/dumps/source_dump_20250428_145606.sql +323 -0
- package/src/scripts/fix-table-indexes.ts +602 -0
- package/src/scripts/post-migration-validator.ts +206 -107
- package/src/scripts/run-migration.ts +87 -101
|
@@ -1,13 +1,29 @@
|
|
|
1
|
-
import * as pg from
|
|
2
|
-
import * as dotenv from
|
|
3
|
-
import { Logger } from
|
|
4
|
-
import * as fs from
|
|
5
|
-
import * as path from
|
|
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
6
|
|
|
7
7
|
dotenv.config();
|
|
8
8
|
|
|
9
|
+
interface ValidationIssue {
|
|
10
|
+
type: string;
|
|
11
|
+
tenantId?: string;
|
|
12
|
+
providerId?: number | null;
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ValidationResult {
|
|
17
|
+
success: boolean;
|
|
18
|
+
issueCount: number;
|
|
19
|
+
timestamp: string;
|
|
20
|
+
sourceDatabase: string;
|
|
21
|
+
targetDatabase: string;
|
|
22
|
+
issues: ValidationIssue[];
|
|
23
|
+
}
|
|
24
|
+
|
|
9
25
|
export class PostMigrationValidator {
|
|
10
|
-
private readonly logger = new Logger(
|
|
26
|
+
private readonly logger = new Logger("PostMigrationValidator");
|
|
11
27
|
private readonly sourcePool: pg.Pool;
|
|
12
28
|
private readonly targetPool: pg.Pool;
|
|
13
29
|
private readonly reportPath: string;
|
|
@@ -26,19 +42,45 @@ export class PostMigrationValidator {
|
|
|
26
42
|
});
|
|
27
43
|
|
|
28
44
|
// Crear directorio para reportes si no existe
|
|
29
|
-
const reportsDir = path.join(process.cwd(),
|
|
45
|
+
const reportsDir = path.join(process.cwd(), "migration-logs");
|
|
30
46
|
if (!fs.existsSync(reportsDir)) {
|
|
31
47
|
fs.mkdirSync(reportsDir, { recursive: true });
|
|
32
48
|
}
|
|
33
49
|
|
|
34
50
|
// Archivo para guardar el reporte
|
|
35
|
-
const timestamp = new Date()
|
|
36
|
-
|
|
51
|
+
const timestamp = new Date()
|
|
52
|
+
.toISOString()
|
|
53
|
+
.replace(/:/g, "-")
|
|
54
|
+
.replace(/\..+/, "");
|
|
55
|
+
this.reportPath = path.join(
|
|
56
|
+
reportsDir,
|
|
57
|
+
`post-migration-report-${timestamp}.json`
|
|
58
|
+
);
|
|
37
59
|
}
|
|
38
60
|
|
|
39
|
-
async validate(
|
|
61
|
+
async validate(options?: {
|
|
62
|
+
publicOnly?: boolean;
|
|
63
|
+
}): Promise<ValidationResult> {
|
|
64
|
+
const issues: ValidationIssue[] = [];
|
|
65
|
+
const timestamp = new Date().toISOString();
|
|
66
|
+
|
|
40
67
|
try {
|
|
41
|
-
|
|
68
|
+
// Si estamos en modo public-only, solo validamos el esquema público
|
|
69
|
+
if (options?.publicOnly) {
|
|
70
|
+
this.logger.log("Running validation in public-only mode");
|
|
71
|
+
// Aquí solo validaríamos las tablas públicas
|
|
72
|
+
return {
|
|
73
|
+
success: true,
|
|
74
|
+
issueCount: 0,
|
|
75
|
+
timestamp,
|
|
76
|
+
sourceDatabase: process.env.SOURCE_DATABASE_URL,
|
|
77
|
+
targetDatabase: process.env.DATABASE_URL,
|
|
78
|
+
issues: [],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Si no es public-only, procedemos con la validación normal de todos los schemas
|
|
83
|
+
this.logger.log("Starting post-migration validation");
|
|
42
84
|
|
|
43
85
|
// Obtener todos los API keys para determinar los schemas de tenant
|
|
44
86
|
const apiKeysResult = await this.sourcePool.query(`
|
|
@@ -46,7 +88,9 @@ export class PostMigrationValidator {
|
|
|
46
88
|
FROM api_keys
|
|
47
89
|
`);
|
|
48
90
|
|
|
49
|
-
this.logger.log(
|
|
91
|
+
this.logger.log(
|
|
92
|
+
`Found ${apiKeysResult.rows.length} API keys to validate`
|
|
93
|
+
);
|
|
50
94
|
|
|
51
95
|
// Para cada API key (tenant), validar los datos migrados
|
|
52
96
|
for (const apiKey of apiKeysResult.rows) {
|
|
@@ -60,37 +104,48 @@ export class PostMigrationValidator {
|
|
|
60
104
|
this.saveReport();
|
|
61
105
|
|
|
62
106
|
// Mostrar resumen
|
|
63
|
-
this.logger.log(
|
|
107
|
+
this.logger.log(
|
|
108
|
+
`Validation completed with ${this.issues.length} issues found`
|
|
109
|
+
);
|
|
64
110
|
if (this.issues.length > 0) {
|
|
65
111
|
this.logger.log(`Check the full report at: ${this.reportPath}`);
|
|
66
|
-
|
|
112
|
+
|
|
67
113
|
// Mostrar los primeros 5 problemas como ejemplo
|
|
68
|
-
this.logger.log(
|
|
114
|
+
this.logger.log("Sample issues:");
|
|
69
115
|
for (let i = 0; i < Math.min(5, this.issues.length); i++) {
|
|
70
116
|
const issue = this.issues[i];
|
|
71
117
|
this.logger.log(`- ${issue.type}: ${issue.message}`);
|
|
72
118
|
}
|
|
73
119
|
} else {
|
|
74
|
-
this.logger.log(
|
|
120
|
+
this.logger.log("No issues found. Migration was successful.");
|
|
75
121
|
}
|
|
76
122
|
|
|
77
123
|
return {
|
|
78
124
|
success: this.issues.length === 0,
|
|
79
125
|
issueCount: this.issues.length,
|
|
80
|
-
|
|
126
|
+
timestamp,
|
|
127
|
+
sourceDatabase: this.sourceUrl,
|
|
128
|
+
targetDatabase: this.targetUrl,
|
|
129
|
+
issues: this.issues,
|
|
81
130
|
};
|
|
82
131
|
} catch (error) {
|
|
83
|
-
this.logger.error(
|
|
132
|
+
this.logger.error(
|
|
133
|
+
`Error during validation: ${error.message}`,
|
|
134
|
+
error.stack
|
|
135
|
+
);
|
|
84
136
|
this.issues.push({
|
|
85
|
-
type:
|
|
137
|
+
type: "VALIDATION_ERROR",
|
|
86
138
|
message: `Validation process failed: ${error.message}`,
|
|
87
|
-
details: error.stack
|
|
139
|
+
details: error.stack,
|
|
88
140
|
});
|
|
89
141
|
this.saveReport();
|
|
90
142
|
return {
|
|
91
143
|
success: false,
|
|
92
144
|
issueCount: this.issues.length,
|
|
93
|
-
|
|
145
|
+
timestamp,
|
|
146
|
+
sourceDatabase: this.sourceUrl,
|
|
147
|
+
targetDatabase: this.targetUrl,
|
|
148
|
+
issues: this.issues,
|
|
94
149
|
};
|
|
95
150
|
} finally {
|
|
96
151
|
await this.cleanup();
|
|
@@ -98,28 +153,35 @@ export class PostMigrationValidator {
|
|
|
98
153
|
}
|
|
99
154
|
|
|
100
155
|
private async validateTenantData(tenantId: string, providerId: number) {
|
|
101
|
-
this.logger.log(
|
|
102
|
-
|
|
156
|
+
this.logger.log(
|
|
157
|
+
`Validating data for tenant: ${tenantId} (Provider ID: ${providerId})`
|
|
158
|
+
);
|
|
159
|
+
|
|
103
160
|
try {
|
|
104
161
|
// Verificar si el schema existe en la base de datos de destino
|
|
105
|
-
const schemaExistsResult = await this.targetPool.query(
|
|
162
|
+
const schemaExistsResult = await this.targetPool.query(
|
|
163
|
+
`
|
|
106
164
|
SELECT 1
|
|
107
165
|
FROM information_schema.schemata
|
|
108
166
|
WHERE schema_name = $1
|
|
109
167
|
LIMIT 1
|
|
110
|
-
`,
|
|
111
|
-
|
|
168
|
+
`,
|
|
169
|
+
[tenantId]
|
|
170
|
+
);
|
|
171
|
+
|
|
112
172
|
if (schemaExistsResult.rows.length === 0) {
|
|
113
|
-
this.logger.warn(
|
|
173
|
+
this.logger.warn(
|
|
174
|
+
`Schema ${tenantId} does not exist in target database`
|
|
175
|
+
);
|
|
114
176
|
this.issues.push({
|
|
115
|
-
type:
|
|
177
|
+
type: "MISSING_SCHEMA",
|
|
116
178
|
tenantId,
|
|
117
179
|
providerId,
|
|
118
|
-
message: `Schema ${tenantId} does not exist in target database. Migration failed for this tenant
|
|
180
|
+
message: `Schema ${tenantId} does not exist in target database. Migration failed for this tenant.`,
|
|
119
181
|
});
|
|
120
182
|
return;
|
|
121
183
|
}
|
|
122
|
-
|
|
184
|
+
|
|
123
185
|
// Obtener todas las tablas de la base de datos de origen
|
|
124
186
|
const tablesResult = await this.sourcePool.query(`
|
|
125
187
|
SELECT table_name
|
|
@@ -128,99 +190,122 @@ export class PostMigrationValidator {
|
|
|
128
190
|
AND table_type = 'BASE TABLE'
|
|
129
191
|
AND table_name NOT IN ('_prisma_migrations', 'api_keys')
|
|
130
192
|
`);
|
|
131
|
-
|
|
193
|
+
|
|
132
194
|
// Para cada tabla, comparar el número de registros
|
|
133
195
|
for (const tableRow of tablesResult.rows) {
|
|
134
196
|
const tableName = tableRow.table_name;
|
|
135
197
|
await this.validateTableData(tenantId, tableName, providerId);
|
|
136
198
|
}
|
|
137
199
|
} catch (error) {
|
|
138
|
-
this.logger.error(
|
|
200
|
+
this.logger.error(
|
|
201
|
+
`Error validating tenant ${tenantId}: ${error.message}`
|
|
202
|
+
);
|
|
139
203
|
this.issues.push({
|
|
140
|
-
type:
|
|
204
|
+
type: "TENANT_VALIDATION_ERROR",
|
|
141
205
|
tenantId,
|
|
142
206
|
providerId,
|
|
143
|
-
message: `Error validating tenant ${tenantId}: ${error.message}
|
|
207
|
+
message: `Error validating tenant ${tenantId}: ${error.message}`,
|
|
144
208
|
});
|
|
145
209
|
}
|
|
146
210
|
}
|
|
147
211
|
|
|
148
|
-
private async validateTableData(
|
|
212
|
+
private async validateTableData(
|
|
213
|
+
tenantId: string,
|
|
214
|
+
tableName: string,
|
|
215
|
+
providerId: number
|
|
216
|
+
) {
|
|
149
217
|
try {
|
|
150
218
|
// Contar registros en la base de datos de origen
|
|
151
219
|
let sourceCountQuery = `SELECT COUNT(*) as count FROM ${tableName}`;
|
|
152
|
-
|
|
220
|
+
|
|
153
221
|
// Añadir filtro por provider_id si la tabla tiene esa columna
|
|
154
|
-
const hasProviderIdResult = await this.sourcePool.query(
|
|
222
|
+
const hasProviderIdResult = await this.sourcePool.query(
|
|
223
|
+
`
|
|
155
224
|
SELECT 1
|
|
156
225
|
FROM information_schema.columns
|
|
157
226
|
WHERE table_schema = 'public'
|
|
158
227
|
AND table_name = $1
|
|
159
228
|
AND column_name = 'provider_id'
|
|
160
229
|
LIMIT 1
|
|
161
|
-
`,
|
|
162
|
-
|
|
230
|
+
`,
|
|
231
|
+
[tableName]
|
|
232
|
+
);
|
|
233
|
+
|
|
163
234
|
if (hasProviderIdResult.rows.length > 0) {
|
|
164
235
|
sourceCountQuery += ` WHERE provider_id = ${providerId}`;
|
|
165
236
|
}
|
|
166
|
-
|
|
237
|
+
|
|
167
238
|
const sourceCountResult = await this.sourcePool.query(sourceCountQuery);
|
|
168
239
|
const sourceCount = parseInt(sourceCountResult.rows[0].count);
|
|
169
|
-
|
|
240
|
+
|
|
170
241
|
// Contar registros en la base de datos de destino
|
|
171
242
|
const targetCountQuery = `SELECT COUNT(*) as count FROM "${tenantId}"."${tableName}"`;
|
|
172
243
|
const targetCountResult = await this.targetPool.query(targetCountQuery);
|
|
173
244
|
const targetCount = parseInt(targetCountResult.rows[0].count);
|
|
174
|
-
|
|
245
|
+
|
|
175
246
|
// Calcular el porcentaje de migración
|
|
176
|
-
const migrationPercentage =
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
247
|
+
const migrationPercentage =
|
|
248
|
+
sourceCount > 0 ? (targetCount / sourceCount) * 100 : 100;
|
|
249
|
+
|
|
250
|
+
this.logger.log(
|
|
251
|
+
`Table ${tableName} for tenant ${tenantId}: ${targetCount}/${sourceCount} records migrated (${migrationPercentage.toFixed(
|
|
252
|
+
2
|
|
253
|
+
)}%)`
|
|
254
|
+
);
|
|
255
|
+
|
|
180
256
|
// Si hay una discrepancia significativa, registrarla como un problema
|
|
181
257
|
if (migrationPercentage < 95) {
|
|
182
258
|
this.issues.push({
|
|
183
|
-
type:
|
|
259
|
+
type: "DATA_MISMATCH",
|
|
184
260
|
tenantId,
|
|
185
261
|
tableName,
|
|
186
262
|
sourceCount,
|
|
187
263
|
targetCount,
|
|
188
264
|
migrationPercentage: parseFloat(migrationPercentage.toFixed(2)),
|
|
189
|
-
message: `Table ${tableName} for tenant ${tenantId}: Only ${migrationPercentage.toFixed(
|
|
265
|
+
message: `Table ${tableName} for tenant ${tenantId}: Only ${migrationPercentage.toFixed(
|
|
266
|
+
2
|
|
267
|
+
)}% of records were migrated (${targetCount}/${sourceCount})`,
|
|
190
268
|
});
|
|
191
269
|
}
|
|
192
|
-
|
|
270
|
+
|
|
193
271
|
// Si hay más registros en el destino que en el origen, también es un problema
|
|
194
272
|
if (targetCount > sourceCount) {
|
|
195
273
|
this.issues.push({
|
|
196
|
-
type:
|
|
274
|
+
type: "EXCESS_DATA",
|
|
197
275
|
tenantId,
|
|
198
276
|
tableName,
|
|
199
277
|
sourceCount,
|
|
200
278
|
targetCount,
|
|
201
|
-
message: `Table ${tableName} for tenant ${tenantId}: Target has more records (${targetCount}) than source (${sourceCount})
|
|
279
|
+
message: `Table ${tableName} for tenant ${tenantId}: Target has more records (${targetCount}) than source (${sourceCount})`,
|
|
202
280
|
});
|
|
203
281
|
}
|
|
204
|
-
|
|
282
|
+
|
|
205
283
|
// Validar algunos registros aleatorios para verificar la integridad de los datos
|
|
206
284
|
if (sourceCount > 0 && targetCount > 0) {
|
|
207
285
|
await this.validateRandomRecords(tenantId, tableName, providerId);
|
|
208
286
|
}
|
|
209
287
|
} catch (error) {
|
|
210
|
-
this.logger.error(
|
|
288
|
+
this.logger.error(
|
|
289
|
+
`Error validating table ${tableName} for tenant ${tenantId}: ${error.message}`
|
|
290
|
+
);
|
|
211
291
|
this.issues.push({
|
|
212
|
-
type:
|
|
292
|
+
type: "TABLE_VALIDATION_ERROR",
|
|
213
293
|
tenantId,
|
|
214
294
|
tableName,
|
|
215
|
-
message: `Error validating table ${tableName} for tenant ${tenantId}: ${error.message}
|
|
295
|
+
message: `Error validating table ${tableName} for tenant ${tenantId}: ${error.message}`,
|
|
216
296
|
});
|
|
217
297
|
}
|
|
218
298
|
}
|
|
219
299
|
|
|
220
|
-
private async validateRandomRecords(
|
|
300
|
+
private async validateRandomRecords(
|
|
301
|
+
tenantId: string,
|
|
302
|
+
tableName: string,
|
|
303
|
+
providerId: number
|
|
304
|
+
) {
|
|
221
305
|
try {
|
|
222
306
|
// Obtener la clave primaria de la tabla
|
|
223
|
-
const primaryKeyResult = await this.sourcePool.query(
|
|
307
|
+
const primaryKeyResult = await this.sourcePool.query(
|
|
308
|
+
`
|
|
224
309
|
SELECT kcu.column_name
|
|
225
310
|
FROM information_schema.table_constraints tc
|
|
226
311
|
JOIN information_schema.key_column_usage kcu
|
|
@@ -230,111 +315,123 @@ export class PostMigrationValidator {
|
|
|
230
315
|
AND tc.table_schema = 'public'
|
|
231
316
|
AND tc.table_name = $1
|
|
232
317
|
LIMIT 1
|
|
233
|
-
`,
|
|
234
|
-
|
|
318
|
+
`,
|
|
319
|
+
[tableName]
|
|
320
|
+
);
|
|
321
|
+
|
|
235
322
|
if (primaryKeyResult.rows.length === 0) {
|
|
236
323
|
this.logger.warn(`No primary key found for table ${tableName}`);
|
|
237
324
|
return;
|
|
238
325
|
}
|
|
239
|
-
|
|
326
|
+
|
|
240
327
|
const primaryKeyColumn = primaryKeyResult.rows[0].column_name;
|
|
241
|
-
|
|
328
|
+
|
|
242
329
|
// Obtener hasta 5 registros aleatorios de la base de datos de origen
|
|
243
330
|
let randomRecordsQuery = `
|
|
244
331
|
SELECT *
|
|
245
332
|
FROM ${tableName}
|
|
246
333
|
`;
|
|
247
|
-
|
|
334
|
+
|
|
248
335
|
// Añadir filtro por provider_id si la tabla tiene esa columna
|
|
249
|
-
const hasProviderIdResult = await this.sourcePool.query(
|
|
336
|
+
const hasProviderIdResult = await this.sourcePool.query(
|
|
337
|
+
`
|
|
250
338
|
SELECT 1
|
|
251
339
|
FROM information_schema.columns
|
|
252
340
|
WHERE table_schema = 'public'
|
|
253
341
|
AND table_name = $1
|
|
254
342
|
AND column_name = 'provider_id'
|
|
255
343
|
LIMIT 1
|
|
256
|
-
`,
|
|
257
|
-
|
|
344
|
+
`,
|
|
345
|
+
[tableName]
|
|
346
|
+
);
|
|
347
|
+
|
|
258
348
|
if (hasProviderIdResult.rows.length > 0) {
|
|
259
349
|
randomRecordsQuery += ` WHERE provider_id = ${providerId}`;
|
|
260
350
|
}
|
|
261
|
-
|
|
351
|
+
|
|
262
352
|
randomRecordsQuery += `
|
|
263
353
|
ORDER BY RANDOM()
|
|
264
354
|
LIMIT 5
|
|
265
355
|
`;
|
|
266
|
-
|
|
267
|
-
const randomRecordsResult = await this.sourcePool.query(
|
|
268
|
-
|
|
356
|
+
|
|
357
|
+
const randomRecordsResult = await this.sourcePool.query(
|
|
358
|
+
randomRecordsQuery
|
|
359
|
+
);
|
|
360
|
+
|
|
269
361
|
// Para cada registro aleatorio, verificar si existe en la base de datos de destino
|
|
270
362
|
for (const sourceRecord of randomRecordsResult.rows) {
|
|
271
363
|
const primaryKeyValue = sourceRecord[primaryKeyColumn];
|
|
272
|
-
|
|
273
|
-
const targetRecordResult = await this.targetPool.query(
|
|
364
|
+
|
|
365
|
+
const targetRecordResult = await this.targetPool.query(
|
|
366
|
+
`
|
|
274
367
|
SELECT *
|
|
275
368
|
FROM "${tenantId}"."${tableName}"
|
|
276
369
|
WHERE "${primaryKeyColumn}" = $1
|
|
277
370
|
LIMIT 1
|
|
278
|
-
`,
|
|
279
|
-
|
|
371
|
+
`,
|
|
372
|
+
[primaryKeyValue]
|
|
373
|
+
);
|
|
374
|
+
|
|
280
375
|
if (targetRecordResult.rows.length === 0) {
|
|
281
376
|
this.issues.push({
|
|
282
|
-
type:
|
|
377
|
+
type: "MISSING_RECORD",
|
|
283
378
|
tenantId,
|
|
284
379
|
tableName,
|
|
285
380
|
recordId: primaryKeyValue,
|
|
286
|
-
message: `Record with ID ${primaryKeyValue} in table ${tableName} for tenant ${tenantId} was not migrated
|
|
381
|
+
message: `Record with ID ${primaryKeyValue} in table ${tableName} for tenant ${tenantId} was not migrated`,
|
|
287
382
|
});
|
|
288
383
|
continue;
|
|
289
384
|
}
|
|
290
|
-
|
|
385
|
+
|
|
291
386
|
const targetRecord = targetRecordResult.rows[0];
|
|
292
|
-
|
|
387
|
+
|
|
293
388
|
// Comparar los valores de las columnas
|
|
294
389
|
for (const columnName in sourceRecord) {
|
|
295
390
|
// Ignorar columnas específicas de la base de datos de origen
|
|
296
|
-
if (columnName ===
|
|
391
|
+
if (columnName === "provider_id") {
|
|
297
392
|
continue;
|
|
298
393
|
}
|
|
299
|
-
|
|
394
|
+
|
|
300
395
|
// Verificar si la columna existe en el registro de destino
|
|
301
396
|
if (!(columnName in targetRecord)) {
|
|
302
397
|
this.issues.push({
|
|
303
|
-
type:
|
|
398
|
+
type: "MISSING_COLUMN",
|
|
304
399
|
tenantId,
|
|
305
400
|
tableName,
|
|
306
401
|
recordId: primaryKeyValue,
|
|
307
402
|
columnName,
|
|
308
|
-
message: `Column ${columnName} is missing in record with ID ${primaryKeyValue} in table ${tableName} for tenant ${tenantId}
|
|
403
|
+
message: `Column ${columnName} is missing in record with ID ${primaryKeyValue} in table ${tableName} for tenant ${tenantId}`,
|
|
309
404
|
});
|
|
310
405
|
continue;
|
|
311
406
|
}
|
|
312
|
-
|
|
407
|
+
|
|
313
408
|
// Comparar los valores (con manejo especial para tipos de datos específicos)
|
|
314
409
|
const sourceValue = sourceRecord[columnName];
|
|
315
410
|
const targetValue = targetRecord[columnName];
|
|
316
|
-
|
|
411
|
+
|
|
317
412
|
if (this.areValuesDifferent(sourceValue, targetValue)) {
|
|
318
413
|
this.issues.push({
|
|
319
|
-
type:
|
|
414
|
+
type: "VALUE_MISMATCH",
|
|
320
415
|
tenantId,
|
|
321
416
|
tableName,
|
|
322
417
|
recordId: primaryKeyValue,
|
|
323
418
|
columnName,
|
|
324
419
|
sourceValue,
|
|
325
420
|
targetValue,
|
|
326
|
-
message: `Value mismatch for column ${columnName} in record with ID ${primaryKeyValue} in table ${tableName} for tenant ${tenantId}: ${sourceValue} (source) vs ${targetValue} (target)
|
|
421
|
+
message: `Value mismatch for column ${columnName} in record with ID ${primaryKeyValue} in table ${tableName} for tenant ${tenantId}: ${sourceValue} (source) vs ${targetValue} (target)`,
|
|
327
422
|
});
|
|
328
423
|
}
|
|
329
424
|
}
|
|
330
425
|
}
|
|
331
426
|
} catch (error) {
|
|
332
|
-
this.logger.error(
|
|
427
|
+
this.logger.error(
|
|
428
|
+
`Error validating random records for table ${tableName} in tenant ${tenantId}: ${error.message}`
|
|
429
|
+
);
|
|
333
430
|
this.issues.push({
|
|
334
|
-
type:
|
|
431
|
+
type: "RECORD_VALIDATION_ERROR",
|
|
335
432
|
tenantId,
|
|
336
433
|
tableName,
|
|
337
|
-
message: `Error validating random records for table ${tableName} in tenant ${tenantId}: ${error.message}
|
|
434
|
+
message: `Error validating random records for table ${tableName} in tenant ${tenantId}: ${error.message}`,
|
|
338
435
|
});
|
|
339
436
|
}
|
|
340
437
|
}
|
|
@@ -344,33 +441,33 @@ export class PostMigrationValidator {
|
|
|
344
441
|
if (sourceValue == null && targetValue == null) {
|
|
345
442
|
return false;
|
|
346
443
|
}
|
|
347
|
-
|
|
444
|
+
|
|
348
445
|
// Si uno es null y el otro no, son diferentes
|
|
349
446
|
if (sourceValue == null || targetValue == null) {
|
|
350
447
|
return true;
|
|
351
448
|
}
|
|
352
|
-
|
|
449
|
+
|
|
353
450
|
// Para fechas, comparar como strings ISO
|
|
354
451
|
if (sourceValue instanceof Date && targetValue instanceof Date) {
|
|
355
452
|
return sourceValue.toISOString() !== targetValue.toISOString();
|
|
356
453
|
}
|
|
357
|
-
|
|
454
|
+
|
|
358
455
|
// Para números, comparar con tolerancia para punto flotante
|
|
359
|
-
if (typeof sourceValue ===
|
|
456
|
+
if (typeof sourceValue === "number" && typeof targetValue === "number") {
|
|
360
457
|
const epsilon = 0.0001;
|
|
361
458
|
return Math.abs(sourceValue - targetValue) > epsilon;
|
|
362
459
|
}
|
|
363
|
-
|
|
460
|
+
|
|
364
461
|
// Para strings, comparar directamente
|
|
365
|
-
if (typeof sourceValue ===
|
|
462
|
+
if (typeof sourceValue === "string" && typeof targetValue === "string") {
|
|
366
463
|
return sourceValue !== targetValue;
|
|
367
464
|
}
|
|
368
|
-
|
|
465
|
+
|
|
369
466
|
// Para booleanos, comparar directamente
|
|
370
|
-
if (typeof sourceValue ===
|
|
467
|
+
if (typeof sourceValue === "boolean" && typeof targetValue === "boolean") {
|
|
371
468
|
return sourceValue !== targetValue;
|
|
372
469
|
}
|
|
373
|
-
|
|
470
|
+
|
|
374
471
|
// Para otros tipos, convertir a string y comparar
|
|
375
472
|
return String(sourceValue) !== String(targetValue);
|
|
376
473
|
}
|
|
@@ -382,13 +479,13 @@ export class PostMigrationValidator {
|
|
|
382
479
|
sourceDatabase: this.sourceUrl,
|
|
383
480
|
targetDatabase: this.targetUrl,
|
|
384
481
|
issueCount: this.issues.length,
|
|
385
|
-
issues: this.issues
|
|
482
|
+
issues: this.issues,
|
|
386
483
|
};
|
|
387
|
-
|
|
484
|
+
|
|
388
485
|
fs.writeFileSync(
|
|
389
486
|
this.reportPath,
|
|
390
487
|
JSON.stringify(report, null, 2),
|
|
391
|
-
|
|
488
|
+
"utf8"
|
|
392
489
|
);
|
|
393
490
|
} catch (error) {
|
|
394
491
|
this.logger.error(`Error saving validation report: ${error.message}`);
|
|
@@ -396,7 +493,7 @@ export class PostMigrationValidator {
|
|
|
396
493
|
}
|
|
397
494
|
|
|
398
495
|
private async cleanup() {
|
|
399
|
-
this.logger.log(
|
|
496
|
+
this.logger.log("Cleaning up database connections");
|
|
400
497
|
await this.sourcePool.end();
|
|
401
498
|
await this.targetPool.end();
|
|
402
499
|
}
|
|
@@ -408,20 +505,22 @@ if (require.main === module) {
|
|
|
408
505
|
try {
|
|
409
506
|
const validator = new PostMigrationValidator();
|
|
410
507
|
const result = await validator.validate();
|
|
411
|
-
|
|
508
|
+
|
|
412
509
|
if (result.success) {
|
|
413
|
-
console.log(
|
|
510
|
+
console.log("Post-migration validation successful! No issues found.");
|
|
414
511
|
process.exit(0);
|
|
415
512
|
} else {
|
|
416
|
-
console.log(
|
|
417
|
-
|
|
513
|
+
console.log(
|
|
514
|
+
`Post-migration validation completed with ${result.issueCount} issues found.`
|
|
515
|
+
);
|
|
516
|
+
console.log(JSON.stringify(result, null, 2));
|
|
418
517
|
process.exit(1);
|
|
419
518
|
}
|
|
420
519
|
} catch (error) {
|
|
421
|
-
console.error(
|
|
520
|
+
console.error("Error:", error.message);
|
|
422
521
|
process.exit(1);
|
|
423
522
|
}
|
|
424
523
|
};
|
|
425
524
|
|
|
426
525
|
run();
|
|
427
|
-
}
|
|
526
|
+
}
|