@javalabs/prisma-client 1.0.26 → 1.0.29

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 (170) hide show
  1. package/.github/CODEOWNERS +1 -1
  2. package/README.md +269 -269
  3. package/migration-config.json +63 -63
  4. package/migration-config.json.bk +95 -95
  5. package/migrations/add_reserved_amount.sql +7 -7
  6. package/package.json +44 -44
  7. package/prisma/migrations/add_uuid_to_transactions.sql +13 -13
  8. package/prisma/schema.prisma +609 -607
  9. package/src/index.ts +23 -23
  10. package/src/prisma-factory.service.ts +40 -40
  11. package/src/prisma.module.ts +9 -9
  12. package/src/prisma.service.ts +16 -16
  13. package/src/scripts/add-uuid-to-table.ts +138 -138
  14. package/src/scripts/create-tenant-schemas.ts +145 -145
  15. package/src/scripts/data-migration/batch-migrator.ts +248 -248
  16. package/src/scripts/data-migration/data-transformer.ts +426 -426
  17. package/src/scripts/data-migration/db-connector.ts +120 -120
  18. package/src/scripts/data-migration/dependency-resolver.ts +174 -174
  19. package/src/scripts/data-migration/entity-discovery.ts +196 -196
  20. package/src/scripts/data-migration/foreign-key-manager.ts +277 -277
  21. package/src/scripts/data-migration/migration-config.json +63 -63
  22. package/src/scripts/data-migration/migration-tool.ts +509 -509
  23. package/src/scripts/data-migration/schema-utils.ts +248 -248
  24. package/src/scripts/data-migration/tenant-migrator.ts +201 -201
  25. package/src/scripts/data-migration/typecast-manager.ts +193 -193
  26. package/src/scripts/data-migration/types.ts +113 -113
  27. package/src/scripts/database-initializer.ts +49 -49
  28. package/src/scripts/drop-database.ts +104 -104
  29. package/src/scripts/dump-source-db.sh +61 -61
  30. package/src/scripts/encrypt-user-passwords.ts +36 -36
  31. package/src/scripts/error-handler.ts +117 -117
  32. package/src/scripts/fix-data-types.ts +241 -241
  33. package/src/scripts/fix-enum-values.ts +357 -357
  34. package/src/scripts/fix-schema-discrepancies.ts +317 -317
  35. package/src/scripts/fix-table-indexes.ts +601 -601
  36. package/src/scripts/migrate-schema-structure.ts +90 -90
  37. package/src/scripts/migrate-uuid.ts +76 -76
  38. package/src/scripts/post-migration-validator.ts +526 -526
  39. package/src/scripts/pre-migration-validator.ts +610 -610
  40. package/src/scripts/reset-database.ts +263 -263
  41. package/src/scripts/retry-failed-migrations.ts +416 -416
  42. package/src/scripts/run-migration.ts +707 -707
  43. package/src/scripts/schema-sync.ts +128 -128
  44. package/src/scripts/sequence-sync-cli.ts +416 -416
  45. package/src/scripts/sequence-synchronizer.ts +127 -127
  46. package/src/scripts/sync-enum-types.ts +170 -170
  47. package/src/scripts/sync-enum-values.ts +563 -563
  48. package/src/scripts/truncate-database.ts +123 -123
  49. package/src/scripts/verify-migration-setup.ts +135 -135
  50. package/tsconfig.json +17 -17
  51. package/dist/index.d.ts +0 -7
  52. package/dist/index.js +0 -34
  53. package/dist/index.js.map +0 -1
  54. package/dist/prisma-factory.service.d.ts +0 -9
  55. package/dist/prisma-factory.service.js +0 -47
  56. package/dist/prisma-factory.service.js.map +0 -1
  57. package/dist/prisma.module.d.ts +0 -2
  58. package/dist/prisma.module.js +0 -23
  59. package/dist/prisma.module.js.map +0 -1
  60. package/dist/prisma.service.d.ts +0 -6
  61. package/dist/prisma.service.js +0 -27
  62. package/dist/prisma.service.js.map +0 -1
  63. package/dist/scripts/add-uuid-to-table.d.ts +0 -8
  64. package/dist/scripts/add-uuid-to-table.js +0 -98
  65. package/dist/scripts/add-uuid-to-table.js.map +0 -1
  66. package/dist/scripts/create-tenant-schemas.d.ts +0 -1
  67. package/dist/scripts/create-tenant-schemas.js +0 -117
  68. package/dist/scripts/create-tenant-schemas.js.map +0 -1
  69. package/dist/scripts/data-migration/batch-migrator.d.ts +0 -20
  70. package/dist/scripts/data-migration/batch-migrator.js +0 -134
  71. package/dist/scripts/data-migration/batch-migrator.js.map +0 -1
  72. package/dist/scripts/data-migration/data-transformer.d.ts +0 -26
  73. package/dist/scripts/data-migration/data-transformer.js +0 -278
  74. package/dist/scripts/data-migration/data-transformer.js.map +0 -1
  75. package/dist/scripts/data-migration/db-connector.d.ts +0 -12
  76. package/dist/scripts/data-migration/db-connector.js +0 -94
  77. package/dist/scripts/data-migration/db-connector.js.map +0 -1
  78. package/dist/scripts/data-migration/dependency-resolver.d.ts +0 -18
  79. package/dist/scripts/data-migration/dependency-resolver.js +0 -132
  80. package/dist/scripts/data-migration/dependency-resolver.js.map +0 -1
  81. package/dist/scripts/data-migration/entity-discovery.d.ts +0 -11
  82. package/dist/scripts/data-migration/entity-discovery.js +0 -152
  83. package/dist/scripts/data-migration/entity-discovery.js.map +0 -1
  84. package/dist/scripts/data-migration/foreign-key-manager.d.ts +0 -18
  85. package/dist/scripts/data-migration/foreign-key-manager.js +0 -160
  86. package/dist/scripts/data-migration/foreign-key-manager.js.map +0 -1
  87. package/dist/scripts/data-migration/migration-tool.d.ts +0 -48
  88. package/dist/scripts/data-migration/migration-tool.js +0 -290
  89. package/dist/scripts/data-migration/migration-tool.js.map +0 -1
  90. package/dist/scripts/data-migration/schema-utils.d.ts +0 -18
  91. package/dist/scripts/data-migration/schema-utils.js +0 -207
  92. package/dist/scripts/data-migration/schema-utils.js.map +0 -1
  93. package/dist/scripts/data-migration/tenant-migrator.d.ts +0 -15
  94. package/dist/scripts/data-migration/tenant-migrator.js +0 -117
  95. package/dist/scripts/data-migration/tenant-migrator.js.map +0 -1
  96. package/dist/scripts/data-migration/typecast-manager.d.ts +0 -9
  97. package/dist/scripts/data-migration/typecast-manager.js +0 -179
  98. package/dist/scripts/data-migration/typecast-manager.js.map +0 -1
  99. package/dist/scripts/data-migration/types.d.ts +0 -100
  100. package/dist/scripts/data-migration/types.js +0 -3
  101. package/dist/scripts/data-migration/types.js.map +0 -1
  102. package/dist/scripts/database-initializer.d.ts +0 -5
  103. package/dist/scripts/database-initializer.js +0 -45
  104. package/dist/scripts/database-initializer.js.map +0 -1
  105. package/dist/scripts/drop-database.d.ts +0 -10
  106. package/dist/scripts/drop-database.js +0 -81
  107. package/dist/scripts/drop-database.js.map +0 -1
  108. package/dist/scripts/encrypt-user-passwords.d.ts +0 -1
  109. package/dist/scripts/encrypt-user-passwords.js +0 -33
  110. package/dist/scripts/encrypt-user-passwords.js.map +0 -1
  111. package/dist/scripts/error-handler.d.ts +0 -12
  112. package/dist/scripts/error-handler.js +0 -82
  113. package/dist/scripts/error-handler.js.map +0 -1
  114. package/dist/scripts/fix-data-types.d.ts +0 -10
  115. package/dist/scripts/fix-data-types.js +0 -185
  116. package/dist/scripts/fix-data-types.js.map +0 -1
  117. package/dist/scripts/fix-enum-values.d.ts +0 -17
  118. package/dist/scripts/fix-enum-values.js +0 -234
  119. package/dist/scripts/fix-enum-values.js.map +0 -1
  120. package/dist/scripts/fix-schema-discrepancies.d.ts +0 -21
  121. package/dist/scripts/fix-schema-discrepancies.js +0 -240
  122. package/dist/scripts/fix-schema-discrepancies.js.map +0 -1
  123. package/dist/scripts/fix-table-indexes.d.ts +0 -26
  124. package/dist/scripts/fix-table-indexes.js +0 -460
  125. package/dist/scripts/fix-table-indexes.js.map +0 -1
  126. package/dist/scripts/migrate-schema-structure.d.ts +0 -1
  127. package/dist/scripts/migrate-schema-structure.js +0 -76
  128. package/dist/scripts/migrate-schema-structure.js.map +0 -1
  129. package/dist/scripts/migrate-uuid.d.ts +0 -2
  130. package/dist/scripts/migrate-uuid.js +0 -57
  131. package/dist/scripts/migrate-uuid.js.map +0 -1
  132. package/dist/scripts/post-migration-validator.d.ts +0 -34
  133. package/dist/scripts/post-migration-validator.js +0 -363
  134. package/dist/scripts/post-migration-validator.js.map +0 -1
  135. package/dist/scripts/pre-migration-validator.d.ts +0 -25
  136. package/dist/scripts/pre-migration-validator.js +0 -491
  137. package/dist/scripts/pre-migration-validator.js.map +0 -1
  138. package/dist/scripts/reset-database.d.ts +0 -17
  139. package/dist/scripts/reset-database.js +0 -202
  140. package/dist/scripts/reset-database.js.map +0 -1
  141. package/dist/scripts/retry-failed-migrations.d.ts +0 -14
  142. package/dist/scripts/retry-failed-migrations.js +0 -301
  143. package/dist/scripts/retry-failed-migrations.js.map +0 -1
  144. package/dist/scripts/run-migration.d.ts +0 -1
  145. package/dist/scripts/run-migration.js +0 -512
  146. package/dist/scripts/run-migration.js.map +0 -1
  147. package/dist/scripts/schema-sync.d.ts +0 -1
  148. package/dist/scripts/schema-sync.js +0 -85
  149. package/dist/scripts/schema-sync.js.map +0 -1
  150. package/dist/scripts/sequence-sync-cli.d.ts +0 -2
  151. package/dist/scripts/sequence-sync-cli.js +0 -287
  152. package/dist/scripts/sequence-sync-cli.js.map +0 -1
  153. package/dist/scripts/sequence-synchronizer.d.ts +0 -8
  154. package/dist/scripts/sequence-synchronizer.js +0 -88
  155. package/dist/scripts/sequence-synchronizer.js.map +0 -1
  156. package/dist/scripts/sync-enum-types.d.ts +0 -13
  157. package/dist/scripts/sync-enum-types.js +0 -139
  158. package/dist/scripts/sync-enum-types.js.map +0 -1
  159. package/dist/scripts/sync-enum-values.d.ts +0 -20
  160. package/dist/scripts/sync-enum-values.js +0 -336
  161. package/dist/scripts/sync-enum-values.js.map +0 -1
  162. package/dist/scripts/truncate-database.d.ts +0 -10
  163. package/dist/scripts/truncate-database.js +0 -100
  164. package/dist/scripts/truncate-database.js.map +0 -1
  165. package/dist/scripts/verify-migration-setup.d.ts +0 -11
  166. package/dist/scripts/verify-migration-setup.js +0 -120
  167. package/dist/scripts/verify-migration-setup.js.map +0 -1
  168. package/dist/tsconfig.tsbuildinfo +0 -1
  169. package/prisma/migrations/add_athena_match_fields.sql +0 -18
  170. package/prisma/migrations/add_bank_receipt_number.sql +0 -9
@@ -1,602 +1,602 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import * as pg from 'pg';
4
- import { Logger } from '@nestjs/common';
5
- import * as dotenv from 'dotenv';
6
-
7
- dotenv.config();
8
-
9
- interface IndexInfo {
10
- schema: string;
11
- table: string;
12
- index_name: string;
13
- column_names: string[];
14
- is_unique: boolean;
15
- is_primary: boolean;
16
- definition: string;
17
- }
18
-
19
- export class TableIndexFixer {
20
- private readonly logger = new Logger('TableIndexFixer');
21
- private readonly targetPool: pg.Pool;
22
- private readonly logDir: string;
23
- private readonly logPath: string;
24
- private fixedIndexes: any[] = [];
25
-
26
- constructor(
27
- private readonly targetUrl: string = process.env.DATABASE_URL
28
- ) {
29
- if (!this.targetUrl) {
30
- throw new Error('DATABASE_URL environment variable is required');
31
- }
32
-
33
- this.targetPool = new pg.Pool({
34
- connectionString: this.targetUrl,
35
- });
36
-
37
- // Crear directorio para logs si no existe
38
- this.logDir = path.join(process.cwd(), 'migration-logs');
39
- if (!fs.existsSync(this.logDir)) {
40
- fs.mkdirSync(this.logDir, { recursive: true });
41
- }
42
-
43
- // Crear archivo de log con timestamp
44
- const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
45
- this.logPath = path.join(this.logDir, `index-fixes-${timestamp}.json`);
46
- }
47
-
48
- async fixIndexes(): Promise<void> {
49
- try {
50
- this.logger.log('Iniciando proceso de corrección de índices');
51
-
52
- // Obtener todos los esquemas excepto los del sistema
53
- const schemas = await this.getSchemas();
54
- this.logger.log(`Encontrados ${schemas.length} esquemas para procesar`);
55
-
56
- // Para cada esquema, procesar sus índices
57
- for (const schema of schemas) {
58
- await this.fixIndexesForSchema(schema);
59
- }
60
-
61
- // Guardar el log de correcciones
62
- this.saveFixLog();
63
- this.logger.log(`Proceso de corrección de índices completado. Log guardado en: ${this.logPath}`);
64
- } catch (error) {
65
- this.logger.error(`Error durante la corrección de índices: ${error.message}`, error.stack);
66
- } finally {
67
- await this.cleanup();
68
- }
69
- }
70
-
71
- private async getSchemas(): Promise<string[]> {
72
- const result = await this.targetPool.query(`
73
- SELECT schema_name
74
- FROM information_schema.schemata
75
- WHERE schema_name NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
76
- AND schema_name NOT LIKE 'pg_%'
77
- `);
78
-
79
- return result.rows.map(row => row.schema_name);
80
- }
81
-
82
- private async fixIndexesForSchema(schema: string): Promise<void> {
83
- this.logger.log(`Procesando índices para el esquema: ${schema}`);
84
-
85
- try {
86
- // Obtener todas las tablas del esquema
87
- const tables = await this.getTablesForSchema(schema);
88
- this.logger.log(`Encontradas ${tables.length} tablas en el esquema ${schema}`);
89
-
90
- // Procesar cada tabla
91
- for (const table of tables) {
92
- await this.fixIndexesForTable(schema, table);
93
- }
94
- } catch (error) {
95
- this.logger.error(`Error procesando índices para el esquema ${schema}: ${error.message}`);
96
- }
97
- }
98
-
99
- private async getTablesForSchema(schema: string): Promise<string[]> {
100
- const result = await this.targetPool.query(`
101
- SELECT table_name
102
- FROM information_schema.tables
103
- WHERE table_schema = $1
104
- AND table_type = 'BASE TABLE'
105
- `, [schema]);
106
-
107
- return result.rows.map(row => row.table_name);
108
- }
109
-
110
- private async fixIndexesForTable(schema: string, table: string): Promise<void> {
111
- this.logger.log(`Procesando índices para la tabla ${schema}.${table}`);
112
-
113
- try {
114
- // 1. Obtener información de la tabla
115
- const tableInfo = await this.getTableInfo(schema, table);
116
-
117
- // 2. Obtener índices existentes
118
- const existingIndexes = await this.getTableIndexes(this.targetPool, schema, table);
119
- this.logger.log(`Encontrados ${existingIndexes.length} índices en la tabla ${schema}.${table}`);
120
-
121
- // 3. Verificar y corregir secuencias si es necesario
122
- if (tableInfo.has_identity_column) {
123
- await this.fixSequences(schema, table);
124
- }
125
-
126
- // 4. Verificar y corregir problemas comunes de índices
127
- await this.fixCommonIndexIssues(schema, table);
128
-
129
- // 5. Verificar y corregir índices faltantes para claves foráneas
130
- await this.fixForeignKeyIndexes(schema, table);
131
-
132
- // 6. Verificar y corregir índices duplicados o redundantes
133
- // Movido al final para evitar eliminar índices recién creados
134
- await this.removeRedundantIndexes(schema, table, await this.getTableIndexes(this.targetPool, schema, table));
135
-
136
- // 7. Verificación adicional para tablas críticas (usuarios)
137
- if (table === 'users' || table === 'user' || table.includes('user')) {
138
- await this.ensureCriticalUserTableIndexes(schema, table);
139
- }
140
-
141
- } catch (error) {
142
- this.logger.error(`Error procesando índices para la tabla ${schema}.${table}: ${error.message}`);
143
- }
144
- }
145
-
146
- // Nuevo método para asegurar índices críticos en tablas de usuarios
147
- private async ensureCriticalUserTableIndexes(schema: string, table: string): Promise<void> {
148
- try {
149
- const columns = await this.getTableColumns(schema, table);
150
- const existingIndexes = await this.getTableIndexes(this.targetPool, schema, table);
151
- const indexedColumns = new Set(existingIndexes.flatMap(idx => idx.column_names));
152
-
153
- // Columnas críticas que DEBEN tener índices en tablas de usuarios
154
- const criticalColumns = ['email', 'username', 'phone', 'id', 'uuid'];
155
-
156
- for (const criticalCol of criticalColumns) {
157
- const column = columns.find(col => col.column_name === criticalCol);
158
-
159
- if (column && !indexedColumns.has(column.column_name)) {
160
- // Determinar si debe ser un índice único
161
- const shouldBeUnique = ['email', 'username', 'phone', 'uuid'].includes(column.column_name);
162
-
163
- const indexInfo: IndexInfo = {
164
- schema,
165
- table,
166
- index_name: `idx_${table}_${column.column_name}`,
167
- column_names: [column.column_name],
168
- is_unique: shouldBeUnique,
169
- is_primary: false,
170
- definition: ''
171
- };
172
-
173
- this.logger.log(`Creando índice crítico para ${column.column_name} en tabla de usuarios ${schema}.${table}`);
174
- await this.createIndex(schema, table, indexInfo);
175
- }
176
- }
177
-
178
- // Verificar y corregir secuencias específicamente para tablas de usuarios
179
- await this.fixUserTableSequences(schema, table);
180
-
181
- } catch (error) {
182
- this.logger.error(`Error asegurando índices críticos para tabla de usuarios ${schema}.${table}: ${error.message}`);
183
- }
184
- }
185
-
186
- // Nuevo método para corregir secuencias en tablas de usuarios
187
- private async fixUserTableSequences(schema: string, table: string): Promise<void> {
188
- try {
189
- // Obtener todas las columnas de identidad de la tabla
190
- const columnsQuery = `
191
- SELECT column_name
192
- FROM information_schema.columns
193
- WHERE table_schema = $1
194
- AND table_name = $2
195
- AND (is_identity = 'YES' OR column_default LIKE 'nextval%')
196
- `;
197
-
198
- const columnsResult = await this.targetPool.query(columnsQuery, [schema, table]);
199
- const identityColumns = columnsResult.rows.map(row => row.column_name);
200
-
201
- for (const column of identityColumns) {
202
- // Obtener el nombre de la secuencia
203
- const sequenceQuery = `
204
- SELECT pg_get_serial_sequence($1, $2) AS sequence_name
205
- `;
206
-
207
- const sequenceResult = await this.targetPool.query(sequenceQuery, [`${schema}.${table}`, column]);
208
- const sequenceName = sequenceResult.rows[0]?.sequence_name;
209
-
210
- if (sequenceName) {
211
- // Restablecer la secuencia al valor máximo actual + 1 con is_called=true para asegurar que el próximo valor sea correcto
212
- const resetQuery = `
213
- SELECT setval($1, COALESCE((SELECT MAX(${column}) FROM ${schema}.${table}), 0) + 1, true)
214
- `;
215
-
216
- await this.targetPool.query(resetQuery, [sequenceName]);
217
-
218
- this.logger.log(`Restablecida secuencia ${sequenceName} para la columna ${column} en ${schema}.${table} (tabla de usuarios)`);
219
-
220
- // Registrar la corrección
221
- this.fixedIndexes.push({
222
- schema,
223
- table,
224
- column,
225
- sequence_name: sequenceName,
226
- action: 'reset_sequence_user_table',
227
- timestamp: new Date().toISOString()
228
- });
229
- }
230
- }
231
- } catch (error) {
232
- this.logger.error(`Error restableciendo secuencias para tabla de usuarios ${schema}.${table}: ${error.message}`);
233
- }
234
- }
235
-
236
- private async removeRedundantIndexes(schema: string, table: string, indexes: IndexInfo[]): Promise<void> {
237
- // Identificar índices redundantes (un índice es redundante si existe otro que cubre las mismas columnas)
238
- const redundantIndexes: IndexInfo[] = [];
239
-
240
- // Lista de nombres de índices críticos que nunca deben eliminarse
241
- const criticalIndexPatterns = [
242
- 'email', 'username', 'phone', 'uuid', 'pkey', 'primary',
243
- 'unique', 'user_id', 'auth', 'session', 'token'
244
- ];
245
-
246
- for (let i = 0; i < indexes.length; i++) {
247
- for (let j = 0; j < indexes.length; j++) {
248
- if (i !== j && !indexes[i].is_primary && !indexes[j].is_primary) {
249
- const index1 = indexes[i];
250
- const index2 = indexes[j];
251
-
252
- // Verificar que column_names sea un array en ambos índices
253
- if (!Array.isArray(index1.column_names) || !Array.isArray(index2.column_names)) {
254
- this.logger.warn(`Índice con formato incorrecto en ${schema}.${table}: ${index1.index_name} o ${index2.index_name}`);
255
- continue;
256
- }
257
-
258
- // Verificar si el índice es crítico (nunca debe eliminarse)
259
- const isIndex1Critical = criticalIndexPatterns.some(pattern =>
260
- index1.index_name.toLowerCase().includes(pattern)
261
- );
262
-
263
- if (isIndex1Critical) {
264
- // No eliminar índices críticos
265
- continue;
266
- }
267
-
268
- // Si todas las columnas de index1 están en index2 y index2 tiene más columnas, index1 es redundante
269
- if (index1.column_names.every(col => index2.column_names.includes(col)) &&
270
- index2.column_names.length > index1.column_names.length) {
271
- redundantIndexes.push(index1);
272
- break;
273
- }
274
- }
275
- }
276
- }
277
-
278
- // Eliminar índices redundantes
279
- for (const index of redundantIndexes) {
280
- try {
281
- const query = `DROP INDEX IF EXISTS "${schema}"."${index.index_name}"`;
282
- await this.targetPool.query(query);
283
-
284
- this.logger.log(`Eliminado índice redundante ${index.index_name} en ${schema}.${table}`);
285
-
286
- // Registrar la corrección
287
- this.fixedIndexes.push({
288
- schema,
289
- table,
290
- index_name: index.index_name,
291
- action: 'removed_redundant',
292
- timestamp: new Date().toISOString()
293
- });
294
- } catch (error) {
295
- this.logger.error(`Error eliminando índice redundante ${index.index_name} en ${schema}.${table}: ${error.message}`);
296
- }
297
- }
298
- }
299
-
300
- private async fixSequences(schema: string, table: string): Promise<void> {
301
- try {
302
- // 1. Obtener todas las columnas de identidad de la tabla
303
- const columnsQuery = `
304
- SELECT column_name
305
- FROM information_schema.columns
306
- WHERE table_schema = $1
307
- AND table_name = $2
308
- AND (is_identity = 'YES' OR column_default LIKE 'nextval%')
309
- `;
310
-
311
- const columnsResult = await this.targetPool.query(columnsQuery, [schema, table]);
312
- const identityColumns = columnsResult.rows.map(row => row.column_name);
313
-
314
- // 2. Para cada columna de identidad, restablecer la secuencia
315
- for (const column of identityColumns) {
316
- // Obtener el nombre de la secuencia
317
- const sequenceQuery = `
318
- SELECT pg_get_serial_sequence($1, $2) AS sequence_name
319
- `;
320
-
321
- const sequenceResult = await this.targetPool.query(sequenceQuery, [`${schema}.${table}`, column]);
322
- const sequenceName = sequenceResult.rows[0]?.sequence_name;
323
-
324
- if (sequenceName) {
325
- // Restablecer la secuencia al valor máximo actual + 1
326
- const resetQuery = `
327
- SELECT setval($1, COALESCE((SELECT MAX(${column}) FROM ${schema}.${table}), 0) + 1, true)
328
- `;
329
-
330
- await this.targetPool.query(resetQuery, [sequenceName]);
331
-
332
- this.logger.log(`Restablecida secuencia ${sequenceName} para la columna ${column} en ${schema}.${table}`);
333
-
334
- // Registrar la corrección
335
- this.fixedIndexes.push({
336
- schema,
337
- table,
338
- column,
339
- sequence_name: sequenceName,
340
- action: 'reset_sequence',
341
- timestamp: new Date().toISOString()
342
- });
343
- }
344
- }
345
- } catch (error) {
346
- this.logger.error(`Error restableciendo secuencias para ${schema}.${table}: ${error.message}`);
347
- }
348
- }
349
-
350
- private async getTableInfo(schema: string, table: string): Promise<any> {
351
- try {
352
- // Verificar si la tabla tiene columnas de identidad (serial, bigserial, etc.)
353
- const identityQuery = `
354
- SELECT EXISTS (
355
- SELECT 1
356
- FROM information_schema.columns
357
- WHERE table_schema = $1
358
- AND table_name = $2
359
- AND is_identity = 'YES'
360
- ) AS has_identity_column;
361
- `;
362
-
363
- const result = await this.targetPool.query(identityQuery, [schema, table]);
364
- return {
365
- has_identity_column: result.rows[0]?.has_identity_column || false
366
- };
367
- } catch (error) {
368
- this.logger.error(`Error obteniendo información de la tabla ${schema}.${table}: ${error.message}`);
369
- return { has_identity_column: false };
370
- }
371
- }
372
-
373
- private async getTableIndexes(pool: pg.Pool, schema: string, table: string): Promise<IndexInfo[]> {
374
- // Esta consulta obtiene información detallada sobre los índices de una tabla
375
- const query = `
376
- SELECT
377
- i.relname AS index_name,
378
- array_agg(a.attname) AS column_names,
379
- ix.indisunique AS is_unique,
380
- ix.indisprimary AS is_primary,
381
- pg_get_indexdef(ix.indexrelid) AS definition
382
- FROM
383
- pg_index ix
384
- JOIN pg_class i ON i.oid = ix.indexrelid
385
- JOIN pg_class t ON t.oid = ix.indrelid
386
- JOIN pg_namespace n ON n.oid = t.relnamespace
387
- JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
388
- WHERE
389
- n.nspname = $1
390
- AND t.relname = $2
391
- GROUP BY
392
- i.relname, ix.indisunique, ix.indisprimary, ix.indexrelid
393
- ORDER BY
394
- i.relname;
395
- `;
396
-
397
- try {
398
- const result = await pool.query(query, [schema, table]);
399
- return result.rows.map(row => ({
400
- schema,
401
- table,
402
- index_name: row.index_name,
403
- column_names: row.column_names,
404
- is_unique: row.is_unique,
405
- is_primary: row.is_primary,
406
- definition: row.definition
407
- }));
408
- } catch (error) {
409
- this.logger.error(`Error obteniendo índices para ${schema}.${table}: ${error.message}`);
410
- return [];
411
- }
412
- }
413
-
414
- private async createIndex(schema: string, table: string, indexInfo: IndexInfo): Promise<void> {
415
- try {
416
- // Generar un nombre único para el índice
417
- const columnList = indexInfo.column_names.join('_');
418
- const indexName = `idx_${table}_${columnList}`.substring(0, 63); // Limitar longitud a 63 caracteres
419
-
420
- // Construir la consulta SQL para crear el índice
421
- let query = `CREATE`;
422
- if (indexInfo.is_unique) {
423
- query += ` UNIQUE`;
424
- }
425
- query += ` INDEX IF NOT EXISTS "${indexName}" ON "${schema}"."${table}" (${indexInfo.column_names.map(col => `"${col}"`).join(', ')})`;
426
-
427
- // Ejecutar la consulta
428
- await this.targetPool.query(query);
429
-
430
- this.logger.log(`Creado índice ${indexName} en ${schema}.${table}`);
431
-
432
- // Registrar la corrección
433
- this.fixedIndexes.push({
434
- schema,
435
- table,
436
- index_name: indexName,
437
- columns: indexInfo.column_names,
438
- is_unique: indexInfo.is_unique,
439
- timestamp: new Date().toISOString()
440
- });
441
- } catch (error) {
442
- this.logger.error(`Error creando índice en ${schema}.${table}: ${error.message}`);
443
- }
444
- }
445
-
446
- private async fixCommonIndexIssues(schema: string, table: string): Promise<void> {
447
- try {
448
- // 1. Verificar columnas que deberían tener índices basados en convenciones de nombres
449
- const columns = await this.getTableColumns(schema, table);
450
-
451
- // Columnas que probablemente deberían tener índices
452
- const potentialIndexColumns = columns.filter(col =>
453
- col.column_name.endsWith('_id') || // Claves foráneas
454
- col.column_name === 'id' || // Clave primaria
455
- col.column_name === 'uuid' || // UUID
456
- col.column_name === 'slug' || // Slugs
457
- col.column_name === 'email' || // Emails
458
- col.column_name === 'username' || // Nombres de usuario
459
- col.column_name.includes('code') || // Códigos
460
- col.column_name === 'user_id' || // ID de usuario (común en muchas tablas)
461
- col.column_name === 'created_at' || // Campos de fecha (útiles para ordenar)
462
- col.column_name === 'updated_at' // Campos de fecha (útiles para ordenar)
463
- );
464
-
465
- // Obtener índices existentes
466
- const existingIndexes = await this.getTableIndexes(this.targetPool, schema, table);
467
- const indexedColumns = new Set(existingIndexes.flatMap(idx => idx.column_names));
468
-
469
- // Crear índices para columnas que deberían tenerlos pero no los tienen
470
- for (const column of potentialIndexColumns) {
471
- if (!indexedColumns.has(column.column_name)) {
472
- // Determinar si debería ser un índice único
473
- const shouldBeUnique = ['email', 'username', 'slug', 'uuid'].includes(column.column_name);
474
-
475
- // Crear un objeto IndexInfo para la columna
476
- const indexInfo: IndexInfo = {
477
- schema,
478
- table,
479
- index_name: `idx_${table}_${column.column_name}`,
480
- column_names: [column.column_name],
481
- is_unique: shouldBeUnique,
482
- is_primary: false,
483
- definition: ''
484
- };
485
-
486
- // Crear el índice
487
- await this.createIndex(schema, table, indexInfo);
488
- }
489
- }
490
-
491
- } catch (error) {
492
- this.logger.error(`Error corrigiendo problemas comunes de índices en ${schema}.${table}: ${error.message}`);
493
- }
494
- }
495
-
496
- private async getTableColumns(schema: string, table: string): Promise<any[]> {
497
- const query = `
498
- SELECT column_name, data_type, is_nullable
499
- FROM information_schema.columns
500
- WHERE table_schema = $1
501
- AND table_name = $2
502
- `;
503
-
504
- const result = await this.targetPool.query(query, [schema, table]);
505
- return result.rows;
506
- }
507
-
508
- private async fixForeignKeyIndexes(schema: string, table: string): Promise<void> {
509
- try {
510
- // Obtener todas las claves foráneas de la tabla
511
- const fkQuery = `
512
- SELECT
513
- kcu.column_name,
514
- ccu.table_schema AS foreign_table_schema,
515
- ccu.table_name AS foreign_table_name,
516
- ccu.column_name AS foreign_column_name
517
- FROM
518
- information_schema.table_constraints AS tc
519
- JOIN information_schema.key_column_usage AS kcu
520
- ON tc.constraint_name = kcu.constraint_name
521
- AND tc.table_schema = kcu.table_schema
522
- JOIN information_schema.constraint_column_usage AS ccu
523
- ON ccu.constraint_name = tc.constraint_name
524
- AND ccu.table_schema = tc.table_schema
525
- WHERE
526
- tc.constraint_type = 'FOREIGN KEY'
527
- AND tc.table_schema = $1
528
- AND tc.table_name = $2;
529
- `;
530
-
531
- const fkResult = await this.targetPool.query(fkQuery, [schema, table]);
532
-
533
- // Obtener índices existentes
534
- const existingIndexes = await this.getTableIndexes(this.targetPool, schema, table);
535
- const indexedColumns = new Set(existingIndexes.flatMap(idx => idx.column_names));
536
-
537
- // Crear índices para columnas de clave foránea que no tienen índice
538
- for (const fk of fkResult.rows) {
539
- if (!indexedColumns.has(fk.column_name)) {
540
- // Crear un objeto IndexInfo para la columna de clave foránea
541
- const indexInfo: IndexInfo = {
542
- schema,
543
- table,
544
- index_name: `idx_${table}_${fk.column_name}`,
545
- column_names: [fk.column_name],
546
- is_unique: false,
547
- is_primary: false,
548
- definition: ''
549
- };
550
-
551
- // Crear el índice
552
- await this.createIndex(schema, table, indexInfo);
553
- }
554
- }
555
- } catch (error) {
556
- this.logger.error(`Error creando índices para claves foráneas en ${schema}.${table}: ${error.message}`);
557
- }
558
- }
559
-
560
- private saveFixLog(): void {
561
- try {
562
- const logData = {
563
- timestamp: new Date().toISOString(),
564
- targetDatabase: this.targetUrl,
565
- fixCount: this.fixedIndexes.length,
566
- fixes: this.fixedIndexes
567
- };
568
-
569
- fs.writeFileSync(
570
- this.logPath,
571
- JSON.stringify(logData, null, 2),
572
- 'utf8'
573
- );
574
- } catch (error) {
575
- this.logger.error(`Error guardando log de correcciones: ${error.message}`);
576
- }
577
- }
578
-
579
- private async cleanup(): Promise<void> {
580
- try {
581
- await this.targetPool.end();
582
- } catch (error) {
583
- this.logger.error(`Error durante la limpieza: ${error.message}`);
584
- }
585
- }
586
- }
587
-
588
- // Script para ejecutar desde línea de comandos
589
- if (require.main === module) {
590
- const run = async () => {
591
- try {
592
- const indexFixer = new TableIndexFixer();
593
- await indexFixer.fixIndexes();
594
- process.exit(0);
595
- } catch (error) {
596
- console.error("Error:", error.message);
597
- process.exit(1);
598
- }
599
- };
600
-
601
- run();
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as pg from 'pg';
4
+ import { Logger } from '@nestjs/common';
5
+ import * as dotenv from 'dotenv';
6
+
7
+ dotenv.config();
8
+
9
+ interface IndexInfo {
10
+ schema: string;
11
+ table: string;
12
+ index_name: string;
13
+ column_names: string[];
14
+ is_unique: boolean;
15
+ is_primary: boolean;
16
+ definition: string;
17
+ }
18
+
19
+ export class TableIndexFixer {
20
+ private readonly logger = new Logger('TableIndexFixer');
21
+ private readonly targetPool: pg.Pool;
22
+ private readonly logDir: string;
23
+ private readonly logPath: string;
24
+ private fixedIndexes: any[] = [];
25
+
26
+ constructor(
27
+ private readonly targetUrl: string = process.env.DATABASE_URL
28
+ ) {
29
+ if (!this.targetUrl) {
30
+ throw new Error('DATABASE_URL environment variable is required');
31
+ }
32
+
33
+ this.targetPool = new pg.Pool({
34
+ connectionString: this.targetUrl,
35
+ });
36
+
37
+ // Crear directorio para logs si no existe
38
+ this.logDir = path.join(process.cwd(), 'migration-logs');
39
+ if (!fs.existsSync(this.logDir)) {
40
+ fs.mkdirSync(this.logDir, { recursive: true });
41
+ }
42
+
43
+ // Crear archivo de log con timestamp
44
+ const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
45
+ this.logPath = path.join(this.logDir, `index-fixes-${timestamp}.json`);
46
+ }
47
+
48
+ async fixIndexes(): Promise<void> {
49
+ try {
50
+ this.logger.log('Iniciando proceso de corrección de índices');
51
+
52
+ // Obtener todos los esquemas excepto los del sistema
53
+ const schemas = await this.getSchemas();
54
+ this.logger.log(`Encontrados ${schemas.length} esquemas para procesar`);
55
+
56
+ // Para cada esquema, procesar sus índices
57
+ for (const schema of schemas) {
58
+ await this.fixIndexesForSchema(schema);
59
+ }
60
+
61
+ // Guardar el log de correcciones
62
+ this.saveFixLog();
63
+ this.logger.log(`Proceso de corrección de índices completado. Log guardado en: ${this.logPath}`);
64
+ } catch (error) {
65
+ this.logger.error(`Error durante la corrección de índices: ${error.message}`, error.stack);
66
+ } finally {
67
+ await this.cleanup();
68
+ }
69
+ }
70
+
71
+ private async getSchemas(): Promise<string[]> {
72
+ const result = await this.targetPool.query(`
73
+ SELECT schema_name
74
+ FROM information_schema.schemata
75
+ WHERE schema_name NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
76
+ AND schema_name NOT LIKE 'pg_%'
77
+ `);
78
+
79
+ return result.rows.map(row => row.schema_name);
80
+ }
81
+
82
+ private async fixIndexesForSchema(schema: string): Promise<void> {
83
+ this.logger.log(`Procesando índices para el esquema: ${schema}`);
84
+
85
+ try {
86
+ // Obtener todas las tablas del esquema
87
+ const tables = await this.getTablesForSchema(schema);
88
+ this.logger.log(`Encontradas ${tables.length} tablas en el esquema ${schema}`);
89
+
90
+ // Procesar cada tabla
91
+ for (const table of tables) {
92
+ await this.fixIndexesForTable(schema, table);
93
+ }
94
+ } catch (error) {
95
+ this.logger.error(`Error procesando índices para el esquema ${schema}: ${error.message}`);
96
+ }
97
+ }
98
+
99
+ private async getTablesForSchema(schema: string): Promise<string[]> {
100
+ const result = await this.targetPool.query(`
101
+ SELECT table_name
102
+ FROM information_schema.tables
103
+ WHERE table_schema = $1
104
+ AND table_type = 'BASE TABLE'
105
+ `, [schema]);
106
+
107
+ return result.rows.map(row => row.table_name);
108
+ }
109
+
110
+ private async fixIndexesForTable(schema: string, table: string): Promise<void> {
111
+ this.logger.log(`Procesando índices para la tabla ${schema}.${table}`);
112
+
113
+ try {
114
+ // 1. Obtener información de la tabla
115
+ const tableInfo = await this.getTableInfo(schema, table);
116
+
117
+ // 2. Obtener índices existentes
118
+ const existingIndexes = await this.getTableIndexes(this.targetPool, schema, table);
119
+ this.logger.log(`Encontrados ${existingIndexes.length} índices en la tabla ${schema}.${table}`);
120
+
121
+ // 3. Verificar y corregir secuencias si es necesario
122
+ if (tableInfo.has_identity_column) {
123
+ await this.fixSequences(schema, table);
124
+ }
125
+
126
+ // 4. Verificar y corregir problemas comunes de índices
127
+ await this.fixCommonIndexIssues(schema, table);
128
+
129
+ // 5. Verificar y corregir índices faltantes para claves foráneas
130
+ await this.fixForeignKeyIndexes(schema, table);
131
+
132
+ // 6. Verificar y corregir índices duplicados o redundantes
133
+ // Movido al final para evitar eliminar índices recién creados
134
+ await this.removeRedundantIndexes(schema, table, await this.getTableIndexes(this.targetPool, schema, table));
135
+
136
+ // 7. Verificación adicional para tablas críticas (usuarios)
137
+ if (table === 'users' || table === 'user' || table.includes('user')) {
138
+ await this.ensureCriticalUserTableIndexes(schema, table);
139
+ }
140
+
141
+ } catch (error) {
142
+ this.logger.error(`Error procesando índices para la tabla ${schema}.${table}: ${error.message}`);
143
+ }
144
+ }
145
+
146
+ // Nuevo método para asegurar índices críticos en tablas de usuarios
147
+ private async ensureCriticalUserTableIndexes(schema: string, table: string): Promise<void> {
148
+ try {
149
+ const columns = await this.getTableColumns(schema, table);
150
+ const existingIndexes = await this.getTableIndexes(this.targetPool, schema, table);
151
+ const indexedColumns = new Set(existingIndexes.flatMap(idx => idx.column_names));
152
+
153
+ // Columnas críticas que DEBEN tener índices en tablas de usuarios
154
+ const criticalColumns = ['email', 'username', 'phone', 'id', 'uuid'];
155
+
156
+ for (const criticalCol of criticalColumns) {
157
+ const column = columns.find(col => col.column_name === criticalCol);
158
+
159
+ if (column && !indexedColumns.has(column.column_name)) {
160
+ // Determinar si debe ser un índice único
161
+ const shouldBeUnique = ['email', 'username', 'phone', 'uuid'].includes(column.column_name);
162
+
163
+ const indexInfo: IndexInfo = {
164
+ schema,
165
+ table,
166
+ index_name: `idx_${table}_${column.column_name}`,
167
+ column_names: [column.column_name],
168
+ is_unique: shouldBeUnique,
169
+ is_primary: false,
170
+ definition: ''
171
+ };
172
+
173
+ this.logger.log(`Creando índice crítico para ${column.column_name} en tabla de usuarios ${schema}.${table}`);
174
+ await this.createIndex(schema, table, indexInfo);
175
+ }
176
+ }
177
+
178
+ // Verificar y corregir secuencias específicamente para tablas de usuarios
179
+ await this.fixUserTableSequences(schema, table);
180
+
181
+ } catch (error) {
182
+ this.logger.error(`Error asegurando índices críticos para tabla de usuarios ${schema}.${table}: ${error.message}`);
183
+ }
184
+ }
185
+
186
+ // Nuevo método para corregir secuencias en tablas de usuarios
187
+ private async fixUserTableSequences(schema: string, table: string): Promise<void> {
188
+ try {
189
+ // Obtener todas las columnas de identidad de la tabla
190
+ const columnsQuery = `
191
+ SELECT column_name
192
+ FROM information_schema.columns
193
+ WHERE table_schema = $1
194
+ AND table_name = $2
195
+ AND (is_identity = 'YES' OR column_default LIKE 'nextval%')
196
+ `;
197
+
198
+ const columnsResult = await this.targetPool.query(columnsQuery, [schema, table]);
199
+ const identityColumns = columnsResult.rows.map(row => row.column_name);
200
+
201
+ for (const column of identityColumns) {
202
+ // Obtener el nombre de la secuencia
203
+ const sequenceQuery = `
204
+ SELECT pg_get_serial_sequence($1, $2) AS sequence_name
205
+ `;
206
+
207
+ const sequenceResult = await this.targetPool.query(sequenceQuery, [`${schema}.${table}`, column]);
208
+ const sequenceName = sequenceResult.rows[0]?.sequence_name;
209
+
210
+ if (sequenceName) {
211
+ // Restablecer la secuencia al valor máximo actual + 1 con is_called=true para asegurar que el próximo valor sea correcto
212
+ const resetQuery = `
213
+ SELECT setval($1, COALESCE((SELECT MAX(${column}) FROM ${schema}.${table}), 0) + 1, true)
214
+ `;
215
+
216
+ await this.targetPool.query(resetQuery, [sequenceName]);
217
+
218
+ this.logger.log(`Restablecida secuencia ${sequenceName} para la columna ${column} en ${schema}.${table} (tabla de usuarios)`);
219
+
220
+ // Registrar la corrección
221
+ this.fixedIndexes.push({
222
+ schema,
223
+ table,
224
+ column,
225
+ sequence_name: sequenceName,
226
+ action: 'reset_sequence_user_table',
227
+ timestamp: new Date().toISOString()
228
+ });
229
+ }
230
+ }
231
+ } catch (error) {
232
+ this.logger.error(`Error restableciendo secuencias para tabla de usuarios ${schema}.${table}: ${error.message}`);
233
+ }
234
+ }
235
+
236
+ private async removeRedundantIndexes(schema: string, table: string, indexes: IndexInfo[]): Promise<void> {
237
+ // Identificar índices redundantes (un índice es redundante si existe otro que cubre las mismas columnas)
238
+ const redundantIndexes: IndexInfo[] = [];
239
+
240
+ // Lista de nombres de índices críticos que nunca deben eliminarse
241
+ const criticalIndexPatterns = [
242
+ 'email', 'username', 'phone', 'uuid', 'pkey', 'primary',
243
+ 'unique', 'user_id', 'auth', 'session', 'token'
244
+ ];
245
+
246
+ for (let i = 0; i < indexes.length; i++) {
247
+ for (let j = 0; j < indexes.length; j++) {
248
+ if (i !== j && !indexes[i].is_primary && !indexes[j].is_primary) {
249
+ const index1 = indexes[i];
250
+ const index2 = indexes[j];
251
+
252
+ // Verificar que column_names sea un array en ambos índices
253
+ if (!Array.isArray(index1.column_names) || !Array.isArray(index2.column_names)) {
254
+ this.logger.warn(`Índice con formato incorrecto en ${schema}.${table}: ${index1.index_name} o ${index2.index_name}`);
255
+ continue;
256
+ }
257
+
258
+ // Verificar si el índice es crítico (nunca debe eliminarse)
259
+ const isIndex1Critical = criticalIndexPatterns.some(pattern =>
260
+ index1.index_name.toLowerCase().includes(pattern)
261
+ );
262
+
263
+ if (isIndex1Critical) {
264
+ // No eliminar índices críticos
265
+ continue;
266
+ }
267
+
268
+ // Si todas las columnas de index1 están en index2 y index2 tiene más columnas, index1 es redundante
269
+ if (index1.column_names.every(col => index2.column_names.includes(col)) &&
270
+ index2.column_names.length > index1.column_names.length) {
271
+ redundantIndexes.push(index1);
272
+ break;
273
+ }
274
+ }
275
+ }
276
+ }
277
+
278
+ // Eliminar índices redundantes
279
+ for (const index of redundantIndexes) {
280
+ try {
281
+ const query = `DROP INDEX IF EXISTS "${schema}"."${index.index_name}"`;
282
+ await this.targetPool.query(query);
283
+
284
+ this.logger.log(`Eliminado índice redundante ${index.index_name} en ${schema}.${table}`);
285
+
286
+ // Registrar la corrección
287
+ this.fixedIndexes.push({
288
+ schema,
289
+ table,
290
+ index_name: index.index_name,
291
+ action: 'removed_redundant',
292
+ timestamp: new Date().toISOString()
293
+ });
294
+ } catch (error) {
295
+ this.logger.error(`Error eliminando índice redundante ${index.index_name} en ${schema}.${table}: ${error.message}`);
296
+ }
297
+ }
298
+ }
299
+
300
+ private async fixSequences(schema: string, table: string): Promise<void> {
301
+ try {
302
+ // 1. Obtener todas las columnas de identidad de la tabla
303
+ const columnsQuery = `
304
+ SELECT column_name
305
+ FROM information_schema.columns
306
+ WHERE table_schema = $1
307
+ AND table_name = $2
308
+ AND (is_identity = 'YES' OR column_default LIKE 'nextval%')
309
+ `;
310
+
311
+ const columnsResult = await this.targetPool.query(columnsQuery, [schema, table]);
312
+ const identityColumns = columnsResult.rows.map(row => row.column_name);
313
+
314
+ // 2. Para cada columna de identidad, restablecer la secuencia
315
+ for (const column of identityColumns) {
316
+ // Obtener el nombre de la secuencia
317
+ const sequenceQuery = `
318
+ SELECT pg_get_serial_sequence($1, $2) AS sequence_name
319
+ `;
320
+
321
+ const sequenceResult = await this.targetPool.query(sequenceQuery, [`${schema}.${table}`, column]);
322
+ const sequenceName = sequenceResult.rows[0]?.sequence_name;
323
+
324
+ if (sequenceName) {
325
+ // Restablecer la secuencia al valor máximo actual + 1
326
+ const resetQuery = `
327
+ SELECT setval($1, COALESCE((SELECT MAX(${column}) FROM ${schema}.${table}), 0) + 1, true)
328
+ `;
329
+
330
+ await this.targetPool.query(resetQuery, [sequenceName]);
331
+
332
+ this.logger.log(`Restablecida secuencia ${sequenceName} para la columna ${column} en ${schema}.${table}`);
333
+
334
+ // Registrar la corrección
335
+ this.fixedIndexes.push({
336
+ schema,
337
+ table,
338
+ column,
339
+ sequence_name: sequenceName,
340
+ action: 'reset_sequence',
341
+ timestamp: new Date().toISOString()
342
+ });
343
+ }
344
+ }
345
+ } catch (error) {
346
+ this.logger.error(`Error restableciendo secuencias para ${schema}.${table}: ${error.message}`);
347
+ }
348
+ }
349
+
350
+ private async getTableInfo(schema: string, table: string): Promise<any> {
351
+ try {
352
+ // Verificar si la tabla tiene columnas de identidad (serial, bigserial, etc.)
353
+ const identityQuery = `
354
+ SELECT EXISTS (
355
+ SELECT 1
356
+ FROM information_schema.columns
357
+ WHERE table_schema = $1
358
+ AND table_name = $2
359
+ AND is_identity = 'YES'
360
+ ) AS has_identity_column;
361
+ `;
362
+
363
+ const result = await this.targetPool.query(identityQuery, [schema, table]);
364
+ return {
365
+ has_identity_column: result.rows[0]?.has_identity_column || false
366
+ };
367
+ } catch (error) {
368
+ this.logger.error(`Error obteniendo información de la tabla ${schema}.${table}: ${error.message}`);
369
+ return { has_identity_column: false };
370
+ }
371
+ }
372
+
373
+ private async getTableIndexes(pool: pg.Pool, schema: string, table: string): Promise<IndexInfo[]> {
374
+ // Esta consulta obtiene información detallada sobre los índices de una tabla
375
+ const query = `
376
+ SELECT
377
+ i.relname AS index_name,
378
+ array_agg(a.attname) AS column_names,
379
+ ix.indisunique AS is_unique,
380
+ ix.indisprimary AS is_primary,
381
+ pg_get_indexdef(ix.indexrelid) AS definition
382
+ FROM
383
+ pg_index ix
384
+ JOIN pg_class i ON i.oid = ix.indexrelid
385
+ JOIN pg_class t ON t.oid = ix.indrelid
386
+ JOIN pg_namespace n ON n.oid = t.relnamespace
387
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
388
+ WHERE
389
+ n.nspname = $1
390
+ AND t.relname = $2
391
+ GROUP BY
392
+ i.relname, ix.indisunique, ix.indisprimary, ix.indexrelid
393
+ ORDER BY
394
+ i.relname;
395
+ `;
396
+
397
+ try {
398
+ const result = await pool.query(query, [schema, table]);
399
+ return result.rows.map(row => ({
400
+ schema,
401
+ table,
402
+ index_name: row.index_name,
403
+ column_names: row.column_names,
404
+ is_unique: row.is_unique,
405
+ is_primary: row.is_primary,
406
+ definition: row.definition
407
+ }));
408
+ } catch (error) {
409
+ this.logger.error(`Error obteniendo índices para ${schema}.${table}: ${error.message}`);
410
+ return [];
411
+ }
412
+ }
413
+
414
+ private async createIndex(schema: string, table: string, indexInfo: IndexInfo): Promise<void> {
415
+ try {
416
+ // Generar un nombre único para el índice
417
+ const columnList = indexInfo.column_names.join('_');
418
+ const indexName = `idx_${table}_${columnList}`.substring(0, 63); // Limitar longitud a 63 caracteres
419
+
420
+ // Construir la consulta SQL para crear el índice
421
+ let query = `CREATE`;
422
+ if (indexInfo.is_unique) {
423
+ query += ` UNIQUE`;
424
+ }
425
+ query += ` INDEX IF NOT EXISTS "${indexName}" ON "${schema}"."${table}" (${indexInfo.column_names.map(col => `"${col}"`).join(', ')})`;
426
+
427
+ // Ejecutar la consulta
428
+ await this.targetPool.query(query);
429
+
430
+ this.logger.log(`Creado índice ${indexName} en ${schema}.${table}`);
431
+
432
+ // Registrar la corrección
433
+ this.fixedIndexes.push({
434
+ schema,
435
+ table,
436
+ index_name: indexName,
437
+ columns: indexInfo.column_names,
438
+ is_unique: indexInfo.is_unique,
439
+ timestamp: new Date().toISOString()
440
+ });
441
+ } catch (error) {
442
+ this.logger.error(`Error creando índice en ${schema}.${table}: ${error.message}`);
443
+ }
444
+ }
445
+
446
+ private async fixCommonIndexIssues(schema: string, table: string): Promise<void> {
447
+ try {
448
+ // 1. Verificar columnas que deberían tener índices basados en convenciones de nombres
449
+ const columns = await this.getTableColumns(schema, table);
450
+
451
+ // Columnas que probablemente deberían tener índices
452
+ const potentialIndexColumns = columns.filter(col =>
453
+ col.column_name.endsWith('_id') || // Claves foráneas
454
+ col.column_name === 'id' || // Clave primaria
455
+ col.column_name === 'uuid' || // UUID
456
+ col.column_name === 'slug' || // Slugs
457
+ col.column_name === 'email' || // Emails
458
+ col.column_name === 'username' || // Nombres de usuario
459
+ col.column_name.includes('code') || // Códigos
460
+ col.column_name === 'user_id' || // ID de usuario (común en muchas tablas)
461
+ col.column_name === 'created_at' || // Campos de fecha (útiles para ordenar)
462
+ col.column_name === 'updated_at' // Campos de fecha (útiles para ordenar)
463
+ );
464
+
465
+ // Obtener índices existentes
466
+ const existingIndexes = await this.getTableIndexes(this.targetPool, schema, table);
467
+ const indexedColumns = new Set(existingIndexes.flatMap(idx => idx.column_names));
468
+
469
+ // Crear índices para columnas que deberían tenerlos pero no los tienen
470
+ for (const column of potentialIndexColumns) {
471
+ if (!indexedColumns.has(column.column_name)) {
472
+ // Determinar si debería ser un índice único
473
+ const shouldBeUnique = ['email', 'username', 'slug', 'uuid'].includes(column.column_name);
474
+
475
+ // Crear un objeto IndexInfo para la columna
476
+ const indexInfo: IndexInfo = {
477
+ schema,
478
+ table,
479
+ index_name: `idx_${table}_${column.column_name}`,
480
+ column_names: [column.column_name],
481
+ is_unique: shouldBeUnique,
482
+ is_primary: false,
483
+ definition: ''
484
+ };
485
+
486
+ // Crear el índice
487
+ await this.createIndex(schema, table, indexInfo);
488
+ }
489
+ }
490
+
491
+ } catch (error) {
492
+ this.logger.error(`Error corrigiendo problemas comunes de índices en ${schema}.${table}: ${error.message}`);
493
+ }
494
+ }
495
+
496
+ private async getTableColumns(schema: string, table: string): Promise<any[]> {
497
+ const query = `
498
+ SELECT column_name, data_type, is_nullable
499
+ FROM information_schema.columns
500
+ WHERE table_schema = $1
501
+ AND table_name = $2
502
+ `;
503
+
504
+ const result = await this.targetPool.query(query, [schema, table]);
505
+ return result.rows;
506
+ }
507
+
508
+ private async fixForeignKeyIndexes(schema: string, table: string): Promise<void> {
509
+ try {
510
+ // Obtener todas las claves foráneas de la tabla
511
+ const fkQuery = `
512
+ SELECT
513
+ kcu.column_name,
514
+ ccu.table_schema AS foreign_table_schema,
515
+ ccu.table_name AS foreign_table_name,
516
+ ccu.column_name AS foreign_column_name
517
+ FROM
518
+ information_schema.table_constraints AS tc
519
+ JOIN information_schema.key_column_usage AS kcu
520
+ ON tc.constraint_name = kcu.constraint_name
521
+ AND tc.table_schema = kcu.table_schema
522
+ JOIN information_schema.constraint_column_usage AS ccu
523
+ ON ccu.constraint_name = tc.constraint_name
524
+ AND ccu.table_schema = tc.table_schema
525
+ WHERE
526
+ tc.constraint_type = 'FOREIGN KEY'
527
+ AND tc.table_schema = $1
528
+ AND tc.table_name = $2;
529
+ `;
530
+
531
+ const fkResult = await this.targetPool.query(fkQuery, [schema, table]);
532
+
533
+ // Obtener índices existentes
534
+ const existingIndexes = await this.getTableIndexes(this.targetPool, schema, table);
535
+ const indexedColumns = new Set(existingIndexes.flatMap(idx => idx.column_names));
536
+
537
+ // Crear índices para columnas de clave foránea que no tienen índice
538
+ for (const fk of fkResult.rows) {
539
+ if (!indexedColumns.has(fk.column_name)) {
540
+ // Crear un objeto IndexInfo para la columna de clave foránea
541
+ const indexInfo: IndexInfo = {
542
+ schema,
543
+ table,
544
+ index_name: `idx_${table}_${fk.column_name}`,
545
+ column_names: [fk.column_name],
546
+ is_unique: false,
547
+ is_primary: false,
548
+ definition: ''
549
+ };
550
+
551
+ // Crear el índice
552
+ await this.createIndex(schema, table, indexInfo);
553
+ }
554
+ }
555
+ } catch (error) {
556
+ this.logger.error(`Error creando índices para claves foráneas en ${schema}.${table}: ${error.message}`);
557
+ }
558
+ }
559
+
560
+ private saveFixLog(): void {
561
+ try {
562
+ const logData = {
563
+ timestamp: new Date().toISOString(),
564
+ targetDatabase: this.targetUrl,
565
+ fixCount: this.fixedIndexes.length,
566
+ fixes: this.fixedIndexes
567
+ };
568
+
569
+ fs.writeFileSync(
570
+ this.logPath,
571
+ JSON.stringify(logData, null, 2),
572
+ 'utf8'
573
+ );
574
+ } catch (error) {
575
+ this.logger.error(`Error guardando log de correcciones: ${error.message}`);
576
+ }
577
+ }
578
+
579
+ private async cleanup(): Promise<void> {
580
+ try {
581
+ await this.targetPool.end();
582
+ } catch (error) {
583
+ this.logger.error(`Error durante la limpieza: ${error.message}`);
584
+ }
585
+ }
586
+ }
587
+
588
+ // Script para ejecutar desde línea de comandos
589
+ if (require.main === module) {
590
+ const run = async () => {
591
+ try {
592
+ const indexFixer = new TableIndexFixer();
593
+ await indexFixer.fixIndexes();
594
+ process.exit(0);
595
+ } catch (error) {
596
+ console.error("Error:", error.message);
597
+ process.exit(1);
598
+ }
599
+ };
600
+
601
+ run();
602
602
  }