@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,357 +1,357 @@
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 EnumFixer {
10
- private readonly logger = new Logger("EnumFixer");
11
- private readonly pool: pg.Pool;
12
- private enumValuesCache: Record<string, string[]> = {};
13
- // Fix the type definition to match how it's actually used
14
- private mappingsCache: Record<string, string | null> = {};
15
- private readonly mappingsFilePath: string;
16
-
17
- constructor(
18
- private readonly databaseUrl: string = process.env.DATABASE_URL,
19
- private readonly saveMapping: boolean = true
20
- ) {
21
- this.pool = new pg.Pool({
22
- connectionString: this.databaseUrl,
23
- });
24
-
25
- // Crear directorio para mappings si no existe
26
- const mappingsDir = path.join(process.cwd(), "migration-logs");
27
- if (!fs.existsSync(mappingsDir)) {
28
- fs.mkdirSync(mappingsDir, { recursive: true });
29
- }
30
-
31
- // Archivo para guardar los mappings
32
- const timestamp = new Date()
33
- .toISOString()
34
- .replace(/:/g, "-")
35
- .replace(/\..+/, "");
36
- this.mappingsFilePath = path.join(
37
- mappingsDir,
38
- `enum-mappings-${timestamp}.json`
39
- );
40
-
41
- // Inicializar archivo de mappings
42
- if (this.saveMapping) {
43
- this.saveMappings();
44
- }
45
- }
46
-
47
- async fixEnumValues() {
48
- try {
49
- this.logger.log("Starting enum values fixing process");
50
-
51
- // Obtener todos los schemas excepto los del sistema
52
- const schemas = await this.getSchemas();
53
- this.logger.log(`Found ${schemas.length} schemas to process`);
54
-
55
- // Para cada schema, procesar sus campos enum
56
- for (const schema of schemas) {
57
- await this.fixEnumValuesForSchema(schema);
58
- }
59
-
60
- // Guardar los mappings finales
61
- if (this.saveMapping) {
62
- this.saveMappings();
63
- this.logger.log(`Enum mappings saved to: ${this.mappingsFilePath}`);
64
- }
65
-
66
- this.logger.log("Enum values fixing completed successfully");
67
- } catch (error) {
68
- this.logger.error(
69
- `Error during enum values fixing: ${error.message}`,
70
- error.stack
71
- );
72
- } finally {
73
- await this.cleanup();
74
- }
75
- }
76
-
77
- private async getSchemas(): Promise<string[]> {
78
- const result = await this.pool.query(`
79
- SELECT schema_name
80
- FROM information_schema.schemata
81
- WHERE schema_name NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
82
- AND schema_name NOT LIKE 'pg_%'
83
- `);
84
-
85
- return result.rows.map((row) => row.schema_name);
86
- }
87
-
88
- private async fixEnumValuesForSchema(schema: string) {
89
- this.logger.log(`Processing enum fields for schema: ${schema}`);
90
-
91
- try {
92
- // Obtener todas las columnas enum en este schema
93
- const enumColumnsQuery = `
94
- SELECT
95
- c.table_name,
96
- c.column_name,
97
- c.udt_name
98
- FROM
99
- information_schema.columns c
100
- WHERE
101
- c.table_schema = $1
102
- AND c.data_type = 'USER-DEFINED'
103
- AND c.udt_name LIKE 'enum_%'
104
- `;
105
-
106
- const enumColumnsResult = await this.pool.query(enumColumnsQuery, [
107
- schema,
108
- ]);
109
-
110
- if (enumColumnsResult.rows.length === 0) {
111
- this.logger.log(`No enum fields found in schema: ${schema}`);
112
- return;
113
- }
114
-
115
- this.logger.log(
116
- `Found ${enumColumnsResult.rows.length} enum fields in schema: ${schema}`
117
- );
118
-
119
- // Procesar cada columna enum
120
- for (const row of enumColumnsResult.rows) {
121
- const tableName = row.table_name;
122
- const columnName = row.column_name;
123
- const enumTypeName = row.udt_name;
124
-
125
- await this.fixEnumColumn(schema, tableName, columnName, enumTypeName);
126
- }
127
-
128
- this.logger.log(`Completed processing enum fields for schema: ${schema}`);
129
- } catch (error) {
130
- this.logger.error(
131
- `Error processing enum fields for schema ${schema}: ${error.message}`,
132
- error.stack
133
- );
134
- }
135
- }
136
-
137
- private async fixEnumColumn(
138
- schema: string,
139
- tableName: string,
140
- columnName: string,
141
- enumTypeName: string
142
- ) {
143
- this.logger.log(
144
- `Fixing enum column ${columnName} (${enumTypeName}) in table ${schema}.${tableName}`
145
- );
146
-
147
- try {
148
- // Obtener los valores válidos para el enum
149
- let validEnumValues: string[];
150
- if (this.enumValuesCache[enumTypeName]) {
151
- validEnumValues = this.enumValuesCache[enumTypeName];
152
- } else {
153
- validEnumValues = await this.getEnumValues(enumTypeName);
154
- this.enumValuesCache[enumTypeName] = validEnumValues;
155
- }
156
-
157
- if (validEnumValues.length === 0) {
158
- this.logger.warn(
159
- `No enum values found for type ${enumTypeName}. Skipping.`
160
- );
161
- return;
162
- }
163
-
164
- this.logger.log(
165
- `Valid values for ${enumTypeName}: ${validEnumValues.join(", ")}`
166
- );
167
-
168
- // Obtener valores actuales en la columna
169
- const currentValuesQuery = `
170
- SELECT DISTINCT "${columnName}"
171
- FROM "${schema}"."${tableName}"
172
- WHERE "${columnName}" IS NOT NULL
173
- `;
174
-
175
- const currentValuesResult = await this.pool.query(currentValuesQuery);
176
- const currentValues = currentValuesResult.rows.map((r) => r[columnName]);
177
-
178
- // Encontrar valores inválidos
179
- const invalidValues = currentValues.filter(
180
- (val) => !validEnumValues.includes(val)
181
- );
182
-
183
- if (invalidValues.length === 0) {
184
- this.logger.log(
185
- `No invalid values found for enum column ${columnName} in table ${schema}.${tableName}`
186
- );
187
- return;
188
- }
189
-
190
- this.logger.log(
191
- `Found ${
192
- invalidValues.length
193
- } invalid values for enum ${enumTypeName} in ${schema}.${tableName}: ${invalidValues.join(
194
- ", "
195
- )}`
196
- );
197
-
198
- // Para cada valor inválido, intentar mapearlo a uno válido o establecerlo a NULL
199
- for (const invalidValue of invalidValues) {
200
- // Intentar encontrar un mapping existente
201
- const mappingKey = `${enumTypeName}:${invalidValue}`;
202
- let mappedValue: string | null = null;
203
-
204
- if (this.mappingsCache[mappingKey]) {
205
- mappedValue = this.mappingsCache[mappingKey];
206
- this.logger.log(
207
- `Using cached mapping for ${invalidValue} -> ${mappedValue}`
208
- );
209
- } else {
210
- // Intentar encontrar una coincidencia sin distinción de mayúsculas/minúsculas
211
- const matchingValidValue = validEnumValues.find(
212
- (v) => v.toLowerCase() === invalidValue.toLowerCase()
213
- );
214
-
215
- if (matchingValidValue) {
216
- mappedValue = matchingValidValue;
217
- this.logger.log(
218
- `Found case-insensitive match for ${invalidValue} -> ${mappedValue}`
219
- );
220
- } else {
221
- // Intentar encontrar una coincidencia parcial
222
- const similarValues = validEnumValues.filter(
223
- (v) =>
224
- v.toLowerCase().includes(invalidValue.toLowerCase()) ||
225
- invalidValue.toLowerCase().includes(v.toLowerCase())
226
- );
227
-
228
- if (similarValues.length === 1) {
229
- mappedValue = similarValues[0];
230
- this.logger.log(
231
- `Found partial match for ${invalidValue} -> ${mappedValue}`
232
- );
233
- } else if (similarValues.length > 1) {
234
- // Si hay múltiples coincidencias, usar la más cercana por longitud
235
- mappedValue = similarValues.reduce((prev, curr) =>
236
- Math.abs(curr.length - invalidValue.length) <
237
- Math.abs(prev.length - invalidValue.length)
238
- ? curr
239
- : prev
240
- );
241
- this.logger.log(
242
- `Found multiple partial matches for ${invalidValue}, using closest: ${mappedValue}`
243
- );
244
- } else {
245
- // Si no hay coincidencias, usar NULL
246
- mappedValue = null;
247
- this.logger.log(
248
- `No match found for ${invalidValue}, will set to NULL`
249
- );
250
- }
251
- }
252
-
253
- // Guardar el mapping para uso futuro
254
- this.mappingsCache[mappingKey] = mappedValue;
255
- if (this.saveMapping) {
256
- this.saveMappings();
257
- }
258
- }
259
-
260
- // Actualizar los registros con el valor mapeado usando CAST explícito
261
- const updateQuery =
262
- mappedValue !== null
263
- ? `
264
- UPDATE "${schema}"."${tableName}"
265
- SET "${columnName}" = CAST($1 AS "${enumTypeName}")
266
- WHERE "${columnName}" = $2
267
- `
268
- : `
269
- UPDATE "${schema}"."${tableName}"
270
- SET "${columnName}" = NULL
271
- WHERE "${columnName}" = $1
272
- `;
273
-
274
- const updateParams =
275
- mappedValue !== null ? [mappedValue, invalidValue] : [invalidValue];
276
-
277
- const updateResult = await this.pool.query(updateQuery, updateParams);
278
-
279
- this.logger.log(
280
- `Updated ${
281
- updateResult.rowCount
282
- } rows in ${schema}.${tableName} with ${invalidValue} -> ${
283
- mappedValue || "NULL"
284
- } with explicit CAST`
285
- );
286
- }
287
- } catch (error) {
288
- this.logger.error(
289
- `Error fixing enum column ${columnName} in table ${schema}.${tableName}: ${error.message}`,
290
- error.stack
291
- );
292
- }
293
- }
294
-
295
- private async getEnumValues(enumTypeName: string): Promise<string[]> {
296
- try {
297
- const enumQuery = `
298
- SELECT e.enumlabel
299
- FROM pg_enum e
300
- JOIN pg_type t ON e.enumtypid = t.oid
301
- WHERE t.typname = $1
302
- ORDER BY e.enumsortorder
303
- `;
304
-
305
- const result = await this.pool.query(enumQuery, [enumTypeName]);
306
- return result.rows.map((row) => row.enumlabel);
307
- } catch (error) {
308
- this.logger.error(
309
- `Error getting enum values for ${enumTypeName}: ${error.message}`
310
- );
311
- return [];
312
- }
313
- }
314
-
315
- private saveMappings(): void {
316
- try {
317
- fs.writeFileSync(
318
- this.mappingsFilePath,
319
- JSON.stringify(this.mappingsCache, null, 2),
320
- "utf8"
321
- );
322
- } catch (error) {
323
- this.logger.error(`Error saving enum mappings: ${error.message}`);
324
- }
325
- }
326
-
327
- private async cleanup() {
328
- this.logger.log("Cleaning up database connections");
329
- await this.pool.end();
330
- }
331
- }
332
-
333
- // Script para ejecutar desde línea de comandos
334
- if (require.main === module) {
335
- const run = async () => {
336
- try {
337
- // Obtener la URL de la base de datos desde los argumentos o .env
338
- const databaseUrl = process.argv[2] || process.env.DATABASE_URL;
339
-
340
- if (!databaseUrl) {
341
- console.error(
342
- "Error: No database URL provided. Please provide a database URL as an argument or set DATABASE_URL environment variable."
343
- );
344
- process.exit(1);
345
- }
346
-
347
- const enumFixer = new EnumFixer(databaseUrl);
348
- await enumFixer.fixEnumValues();
349
- process.exit(0);
350
- } catch (error) {
351
- console.error("Error:", error.message);
352
- process.exit(1);
353
- }
354
- };
355
-
356
- run();
357
- }
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 EnumFixer {
10
+ private readonly logger = new Logger("EnumFixer");
11
+ private readonly pool: pg.Pool;
12
+ private enumValuesCache: Record<string, string[]> = {};
13
+ // Fix the type definition to match how it's actually used
14
+ private mappingsCache: Record<string, string | null> = {};
15
+ private readonly mappingsFilePath: string;
16
+
17
+ constructor(
18
+ private readonly databaseUrl: string = process.env.DATABASE_URL,
19
+ private readonly saveMapping: boolean = true
20
+ ) {
21
+ this.pool = new pg.Pool({
22
+ connectionString: this.databaseUrl,
23
+ });
24
+
25
+ // Crear directorio para mappings si no existe
26
+ const mappingsDir = path.join(process.cwd(), "migration-logs");
27
+ if (!fs.existsSync(mappingsDir)) {
28
+ fs.mkdirSync(mappingsDir, { recursive: true });
29
+ }
30
+
31
+ // Archivo para guardar los mappings
32
+ const timestamp = new Date()
33
+ .toISOString()
34
+ .replace(/:/g, "-")
35
+ .replace(/\..+/, "");
36
+ this.mappingsFilePath = path.join(
37
+ mappingsDir,
38
+ `enum-mappings-${timestamp}.json`
39
+ );
40
+
41
+ // Inicializar archivo de mappings
42
+ if (this.saveMapping) {
43
+ this.saveMappings();
44
+ }
45
+ }
46
+
47
+ async fixEnumValues() {
48
+ try {
49
+ this.logger.log("Starting enum values fixing process");
50
+
51
+ // Obtener todos los schemas excepto los del sistema
52
+ const schemas = await this.getSchemas();
53
+ this.logger.log(`Found ${schemas.length} schemas to process`);
54
+
55
+ // Para cada schema, procesar sus campos enum
56
+ for (const schema of schemas) {
57
+ await this.fixEnumValuesForSchema(schema);
58
+ }
59
+
60
+ // Guardar los mappings finales
61
+ if (this.saveMapping) {
62
+ this.saveMappings();
63
+ this.logger.log(`Enum mappings saved to: ${this.mappingsFilePath}`);
64
+ }
65
+
66
+ this.logger.log("Enum values fixing completed successfully");
67
+ } catch (error) {
68
+ this.logger.error(
69
+ `Error during enum values fixing: ${error.message}`,
70
+ error.stack
71
+ );
72
+ } finally {
73
+ await this.cleanup();
74
+ }
75
+ }
76
+
77
+ private async getSchemas(): Promise<string[]> {
78
+ const result = await this.pool.query(`
79
+ SELECT schema_name
80
+ FROM information_schema.schemata
81
+ WHERE schema_name NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
82
+ AND schema_name NOT LIKE 'pg_%'
83
+ `);
84
+
85
+ return result.rows.map((row) => row.schema_name);
86
+ }
87
+
88
+ private async fixEnumValuesForSchema(schema: string) {
89
+ this.logger.log(`Processing enum fields for schema: ${schema}`);
90
+
91
+ try {
92
+ // Obtener todas las columnas enum en este schema
93
+ const enumColumnsQuery = `
94
+ SELECT
95
+ c.table_name,
96
+ c.column_name,
97
+ c.udt_name
98
+ FROM
99
+ information_schema.columns c
100
+ WHERE
101
+ c.table_schema = $1
102
+ AND c.data_type = 'USER-DEFINED'
103
+ AND c.udt_name LIKE 'enum_%'
104
+ `;
105
+
106
+ const enumColumnsResult = await this.pool.query(enumColumnsQuery, [
107
+ schema,
108
+ ]);
109
+
110
+ if (enumColumnsResult.rows.length === 0) {
111
+ this.logger.log(`No enum fields found in schema: ${schema}`);
112
+ return;
113
+ }
114
+
115
+ this.logger.log(
116
+ `Found ${enumColumnsResult.rows.length} enum fields in schema: ${schema}`
117
+ );
118
+
119
+ // Procesar cada columna enum
120
+ for (const row of enumColumnsResult.rows) {
121
+ const tableName = row.table_name;
122
+ const columnName = row.column_name;
123
+ const enumTypeName = row.udt_name;
124
+
125
+ await this.fixEnumColumn(schema, tableName, columnName, enumTypeName);
126
+ }
127
+
128
+ this.logger.log(`Completed processing enum fields for schema: ${schema}`);
129
+ } catch (error) {
130
+ this.logger.error(
131
+ `Error processing enum fields for schema ${schema}: ${error.message}`,
132
+ error.stack
133
+ );
134
+ }
135
+ }
136
+
137
+ private async fixEnumColumn(
138
+ schema: string,
139
+ tableName: string,
140
+ columnName: string,
141
+ enumTypeName: string
142
+ ) {
143
+ this.logger.log(
144
+ `Fixing enum column ${columnName} (${enumTypeName}) in table ${schema}.${tableName}`
145
+ );
146
+
147
+ try {
148
+ // Obtener los valores válidos para el enum
149
+ let validEnumValues: string[];
150
+ if (this.enumValuesCache[enumTypeName]) {
151
+ validEnumValues = this.enumValuesCache[enumTypeName];
152
+ } else {
153
+ validEnumValues = await this.getEnumValues(enumTypeName);
154
+ this.enumValuesCache[enumTypeName] = validEnumValues;
155
+ }
156
+
157
+ if (validEnumValues.length === 0) {
158
+ this.logger.warn(
159
+ `No enum values found for type ${enumTypeName}. Skipping.`
160
+ );
161
+ return;
162
+ }
163
+
164
+ this.logger.log(
165
+ `Valid values for ${enumTypeName}: ${validEnumValues.join(", ")}`
166
+ );
167
+
168
+ // Obtener valores actuales en la columna
169
+ const currentValuesQuery = `
170
+ SELECT DISTINCT "${columnName}"
171
+ FROM "${schema}"."${tableName}"
172
+ WHERE "${columnName}" IS NOT NULL
173
+ `;
174
+
175
+ const currentValuesResult = await this.pool.query(currentValuesQuery);
176
+ const currentValues = currentValuesResult.rows.map((r) => r[columnName]);
177
+
178
+ // Encontrar valores inválidos
179
+ const invalidValues = currentValues.filter(
180
+ (val) => !validEnumValues.includes(val)
181
+ );
182
+
183
+ if (invalidValues.length === 0) {
184
+ this.logger.log(
185
+ `No invalid values found for enum column ${columnName} in table ${schema}.${tableName}`
186
+ );
187
+ return;
188
+ }
189
+
190
+ this.logger.log(
191
+ `Found ${
192
+ invalidValues.length
193
+ } invalid values for enum ${enumTypeName} in ${schema}.${tableName}: ${invalidValues.join(
194
+ ", "
195
+ )}`
196
+ );
197
+
198
+ // Para cada valor inválido, intentar mapearlo a uno válido o establecerlo a NULL
199
+ for (const invalidValue of invalidValues) {
200
+ // Intentar encontrar un mapping existente
201
+ const mappingKey = `${enumTypeName}:${invalidValue}`;
202
+ let mappedValue: string | null = null;
203
+
204
+ if (this.mappingsCache[mappingKey]) {
205
+ mappedValue = this.mappingsCache[mappingKey];
206
+ this.logger.log(
207
+ `Using cached mapping for ${invalidValue} -> ${mappedValue}`
208
+ );
209
+ } else {
210
+ // Intentar encontrar una coincidencia sin distinción de mayúsculas/minúsculas
211
+ const matchingValidValue = validEnumValues.find(
212
+ (v) => v.toLowerCase() === invalidValue.toLowerCase()
213
+ );
214
+
215
+ if (matchingValidValue) {
216
+ mappedValue = matchingValidValue;
217
+ this.logger.log(
218
+ `Found case-insensitive match for ${invalidValue} -> ${mappedValue}`
219
+ );
220
+ } else {
221
+ // Intentar encontrar una coincidencia parcial
222
+ const similarValues = validEnumValues.filter(
223
+ (v) =>
224
+ v.toLowerCase().includes(invalidValue.toLowerCase()) ||
225
+ invalidValue.toLowerCase().includes(v.toLowerCase())
226
+ );
227
+
228
+ if (similarValues.length === 1) {
229
+ mappedValue = similarValues[0];
230
+ this.logger.log(
231
+ `Found partial match for ${invalidValue} -> ${mappedValue}`
232
+ );
233
+ } else if (similarValues.length > 1) {
234
+ // Si hay múltiples coincidencias, usar la más cercana por longitud
235
+ mappedValue = similarValues.reduce((prev, curr) =>
236
+ Math.abs(curr.length - invalidValue.length) <
237
+ Math.abs(prev.length - invalidValue.length)
238
+ ? curr
239
+ : prev
240
+ );
241
+ this.logger.log(
242
+ `Found multiple partial matches for ${invalidValue}, using closest: ${mappedValue}`
243
+ );
244
+ } else {
245
+ // Si no hay coincidencias, usar NULL
246
+ mappedValue = null;
247
+ this.logger.log(
248
+ `No match found for ${invalidValue}, will set to NULL`
249
+ );
250
+ }
251
+ }
252
+
253
+ // Guardar el mapping para uso futuro
254
+ this.mappingsCache[mappingKey] = mappedValue;
255
+ if (this.saveMapping) {
256
+ this.saveMappings();
257
+ }
258
+ }
259
+
260
+ // Actualizar los registros con el valor mapeado usando CAST explícito
261
+ const updateQuery =
262
+ mappedValue !== null
263
+ ? `
264
+ UPDATE "${schema}"."${tableName}"
265
+ SET "${columnName}" = CAST($1 AS "${enumTypeName}")
266
+ WHERE "${columnName}" = $2
267
+ `
268
+ : `
269
+ UPDATE "${schema}"."${tableName}"
270
+ SET "${columnName}" = NULL
271
+ WHERE "${columnName}" = $1
272
+ `;
273
+
274
+ const updateParams =
275
+ mappedValue !== null ? [mappedValue, invalidValue] : [invalidValue];
276
+
277
+ const updateResult = await this.pool.query(updateQuery, updateParams);
278
+
279
+ this.logger.log(
280
+ `Updated ${
281
+ updateResult.rowCount
282
+ } rows in ${schema}.${tableName} with ${invalidValue} -> ${
283
+ mappedValue || "NULL"
284
+ } with explicit CAST`
285
+ );
286
+ }
287
+ } catch (error) {
288
+ this.logger.error(
289
+ `Error fixing enum column ${columnName} in table ${schema}.${tableName}: ${error.message}`,
290
+ error.stack
291
+ );
292
+ }
293
+ }
294
+
295
+ private async getEnumValues(enumTypeName: string): Promise<string[]> {
296
+ try {
297
+ const enumQuery = `
298
+ SELECT e.enumlabel
299
+ FROM pg_enum e
300
+ JOIN pg_type t ON e.enumtypid = t.oid
301
+ WHERE t.typname = $1
302
+ ORDER BY e.enumsortorder
303
+ `;
304
+
305
+ const result = await this.pool.query(enumQuery, [enumTypeName]);
306
+ return result.rows.map((row) => row.enumlabel);
307
+ } catch (error) {
308
+ this.logger.error(
309
+ `Error getting enum values for ${enumTypeName}: ${error.message}`
310
+ );
311
+ return [];
312
+ }
313
+ }
314
+
315
+ private saveMappings(): void {
316
+ try {
317
+ fs.writeFileSync(
318
+ this.mappingsFilePath,
319
+ JSON.stringify(this.mappingsCache, null, 2),
320
+ "utf8"
321
+ );
322
+ } catch (error) {
323
+ this.logger.error(`Error saving enum mappings: ${error.message}`);
324
+ }
325
+ }
326
+
327
+ private async cleanup() {
328
+ this.logger.log("Cleaning up database connections");
329
+ await this.pool.end();
330
+ }
331
+ }
332
+
333
+ // Script para ejecutar desde línea de comandos
334
+ if (require.main === module) {
335
+ const run = async () => {
336
+ try {
337
+ // Obtener la URL de la base de datos desde los argumentos o .env
338
+ const databaseUrl = process.argv[2] || process.env.DATABASE_URL;
339
+
340
+ if (!databaseUrl) {
341
+ console.error(
342
+ "Error: No database URL provided. Please provide a database URL as an argument or set DATABASE_URL environment variable."
343
+ );
344
+ process.exit(1);
345
+ }
346
+
347
+ const enumFixer = new EnumFixer(databaseUrl);
348
+ await enumFixer.fixEnumValues();
349
+ process.exit(0);
350
+ } catch (error) {
351
+ console.error("Error:", error.message);
352
+ process.exit(1);
353
+ }
354
+ };
355
+
356
+ run();
357
+ }