@javalabs/prisma-client 1.0.0
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/README.md +220 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/prisma-factory.service.d.ts +9 -0
- package/dist/prisma-factory.service.js +47 -0
- package/dist/prisma-factory.service.js.map +1 -0
- package/dist/prisma.module.d.ts +2 -0
- package/dist/prisma.module.js +23 -0
- package/dist/prisma.module.js.map +1 -0
- package/dist/prisma.service.d.ts +6 -0
- package/dist/prisma.service.js +27 -0
- package/dist/prisma.service.js.map +1 -0
- package/dist/scripts/create-tenant-schemas.d.ts +1 -0
- package/dist/scripts/create-tenant-schemas.js +117 -0
- package/dist/scripts/create-tenant-schemas.js.map +1 -0
- package/dist/scripts/data-migration/batch-migrator.d.ts +25 -0
- package/dist/scripts/data-migration/batch-migrator.js +333 -0
- package/dist/scripts/data-migration/batch-migrator.js.map +1 -0
- package/dist/scripts/data-migration/data-transformer.d.ts +17 -0
- package/dist/scripts/data-migration/data-transformer.js +242 -0
- package/dist/scripts/data-migration/data-transformer.js.map +1 -0
- package/dist/scripts/data-migration/db-connector.d.ts +7 -0
- package/dist/scripts/data-migration/db-connector.js +58 -0
- package/dist/scripts/data-migration/db-connector.js.map +1 -0
- package/dist/scripts/data-migration/dependency-manager.d.ts +9 -0
- package/dist/scripts/data-migration/dependency-manager.js +86 -0
- package/dist/scripts/data-migration/dependency-manager.js.map +1 -0
- package/dist/scripts/data-migration/dependency-resolver.d.ts +18 -0
- package/dist/scripts/data-migration/dependency-resolver.js +251 -0
- package/dist/scripts/data-migration/dependency-resolver.js.map +1 -0
- package/dist/scripts/data-migration/entity-discovery.d.ts +11 -0
- package/dist/scripts/data-migration/entity-discovery.js +152 -0
- package/dist/scripts/data-migration/entity-discovery.js.map +1 -0
- package/dist/scripts/data-migration/foreign-key-manager.d.ts +17 -0
- package/dist/scripts/data-migration/foreign-key-manager.js +70 -0
- package/dist/scripts/data-migration/foreign-key-manager.js.map +1 -0
- package/dist/scripts/data-migration/migration-phases.d.ts +5 -0
- package/dist/scripts/data-migration/migration-phases.js +55 -0
- package/dist/scripts/data-migration/migration-phases.js.map +1 -0
- package/dist/scripts/data-migration/migration-tool.d.ts +29 -0
- package/dist/scripts/data-migration/migration-tool.js +250 -0
- package/dist/scripts/data-migration/migration-tool.js.map +1 -0
- package/dist/scripts/data-migration/phase-generator.d.ts +15 -0
- package/dist/scripts/data-migration/phase-generator.js +187 -0
- package/dist/scripts/data-migration/phase-generator.js.map +1 -0
- package/dist/scripts/data-migration/schema-utils.d.ts +18 -0
- package/dist/scripts/data-migration/schema-utils.js +164 -0
- package/dist/scripts/data-migration/schema-utils.js.map +1 -0
- package/dist/scripts/data-migration/tenant-migrator.d.ts +15 -0
- package/dist/scripts/data-migration/tenant-migrator.js +110 -0
- package/dist/scripts/data-migration/tenant-migrator.js.map +1 -0
- package/dist/scripts/data-migration/typecast-manager.d.ts +5 -0
- package/dist/scripts/data-migration/typecast-manager.js +35 -0
- package/dist/scripts/data-migration/typecast-manager.js.map +1 -0
- package/dist/scripts/data-migration/types.d.ts +34 -0
- package/dist/scripts/data-migration/types.js +3 -0
- package/dist/scripts/data-migration/types.js.map +1 -0
- package/dist/scripts/data-migration.d.ts +22 -0
- package/dist/scripts/data-migration.js +593 -0
- package/dist/scripts/data-migration.js.map +1 -0
- package/dist/scripts/drop-database.d.ts +10 -0
- package/dist/scripts/drop-database.js +81 -0
- package/dist/scripts/drop-database.js.map +1 -0
- package/dist/scripts/error-handler.d.ts +12 -0
- package/dist/scripts/error-handler.js +82 -0
- package/dist/scripts/error-handler.js.map +1 -0
- package/dist/scripts/fix-data-types.d.ts +10 -0
- package/dist/scripts/fix-data-types.js +185 -0
- package/dist/scripts/fix-data-types.js.map +1 -0
- package/dist/scripts/fix-enum-values.d.ts +17 -0
- package/dist/scripts/fix-enum-values.js +234 -0
- package/dist/scripts/fix-enum-values.js.map +1 -0
- package/dist/scripts/fix-schema-discrepancies.d.ts +21 -0
- package/dist/scripts/fix-schema-discrepancies.js +240 -0
- package/dist/scripts/fix-schema-discrepancies.js.map +1 -0
- package/dist/scripts/migrate-schema-structure.d.ts +1 -0
- package/dist/scripts/migrate-schema-structure.js +76 -0
- package/dist/scripts/migrate-schema-structure.js.map +1 -0
- package/dist/scripts/post-migration-validator.d.ts +21 -0
- package/dist/scripts/post-migration-validator.js +341 -0
- package/dist/scripts/post-migration-validator.js.map +1 -0
- package/dist/scripts/pre-migration-validator.d.ts +25 -0
- package/dist/scripts/pre-migration-validator.js +491 -0
- package/dist/scripts/pre-migration-validator.js.map +1 -0
- package/dist/scripts/reset-database.d.ts +17 -0
- package/dist/scripts/reset-database.js +202 -0
- package/dist/scripts/reset-database.js.map +1 -0
- package/dist/scripts/retry-failed-migrations.d.ts +14 -0
- package/dist/scripts/retry-failed-migrations.js +301 -0
- package/dist/scripts/retry-failed-migrations.js.map +1 -0
- package/dist/scripts/run-migration.d.ts +1 -0
- package/dist/scripts/run-migration.js +525 -0
- package/dist/scripts/run-migration.js.map +1 -0
- package/dist/scripts/schema-sync.d.ts +1 -0
- package/dist/scripts/schema-sync.js +85 -0
- package/dist/scripts/schema-sync.js.map +1 -0
- package/dist/scripts/sync-enum-types.d.ts +13 -0
- package/dist/scripts/sync-enum-types.js +139 -0
- package/dist/scripts/sync-enum-types.js.map +1 -0
- package/dist/scripts/sync-enum-values.d.ts +20 -0
- package/dist/scripts/sync-enum-values.js +336 -0
- package/dist/scripts/sync-enum-values.js.map +1 -0
- package/dist/scripts/truncate-database.d.ts +10 -0
- package/dist/scripts/truncate-database.js +100 -0
- package/dist/scripts/truncate-database.js.map +1 -0
- package/dist/scripts/verify-migration-setup.d.ts +11 -0
- package/dist/scripts/verify-migration-setup.js +120 -0
- package/dist/scripts/verify-migration-setup.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/migration-config-public.json +95 -0
- package/migration-config.json +95 -0
- package/package.json +33 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +360 -0
- package/src/index.ts +23 -0
- package/src/prisma-factory.service.ts +41 -0
- package/src/prisma.module.ts +10 -0
- package/src/prisma.service.ts +17 -0
- package/src/scripts/create-tenant-schemas.ts +146 -0
- package/src/scripts/data-migration/batch-migrator.ts +569 -0
- package/src/scripts/data-migration/data-transformer.ts +377 -0
- package/src/scripts/data-migration/db-connector.ts +67 -0
- package/src/scripts/data-migration/dependency-resolver.ts +319 -0
- package/src/scripts/data-migration/entity-discovery.ts +197 -0
- package/src/scripts/data-migration/foreign-key-manager.ts +95 -0
- package/src/scripts/data-migration/migration-tool.ts +357 -0
- package/src/scripts/data-migration/schema-utils.ts +186 -0
- package/src/scripts/data-migration/tenant-migrator.ts +194 -0
- package/src/scripts/data-migration/typecast-manager.ts +38 -0
- package/src/scripts/data-migration/types.ts +40 -0
- package/src/scripts/drop-database.ts +105 -0
- package/src/scripts/dump-source-db.sh +62 -0
- package/src/scripts/dumps/source_dump_20250413_112626.sql +1527 -0
- package/src/scripts/error-handler.ts +118 -0
- package/src/scripts/fix-data-types.ts +242 -0
- package/src/scripts/fix-enum-values.ts +357 -0
- package/src/scripts/fix-schema-discrepancies.ts +318 -0
- package/src/scripts/migrate-schema-structure.ts +90 -0
- package/src/scripts/post-migration-validator.ts +427 -0
- package/src/scripts/pre-migration-validator.ts +611 -0
- package/src/scripts/reset-database.ts +264 -0
- package/src/scripts/retry-failed-migrations.ts +416 -0
- package/src/scripts/run-migration.ts +691 -0
- package/src/scripts/schema-sync.ts +129 -0
- package/src/scripts/sync-enum-types.ts +171 -0
- package/src/scripts/sync-enum-values.ts +563 -0
- package/src/scripts/truncate-database.ts +124 -0
- package/src/scripts/verify-migration-setup.ts +136 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import { Logger } from "@nestjs/common";
|
|
2
|
+
import { Pool } from "pg";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as dotenv from "dotenv";
|
|
6
|
+
|
|
7
|
+
dotenv.config();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Clase para sincronizar valores entre enums específicos y enums consolidados
|
|
11
|
+
* Esta herramienta asegura que los valores en los enums específicos (deprecados)
|
|
12
|
+
* sean compatibles con los enums consolidados durante la migración.
|
|
13
|
+
*/
|
|
14
|
+
export class EnumValueSynchronizer {
|
|
15
|
+
private readonly logger = new Logger("EnumValueSynchronizer");
|
|
16
|
+
private readonly pool: Pool;
|
|
17
|
+
|
|
18
|
+
// Mapeo de enums específicos a enums consolidados
|
|
19
|
+
private readonly enumMappings: Record<string, string> = {
|
|
20
|
+
enum_credit_requests_status: "enum_transaction_status",
|
|
21
|
+
enum_global_user_transactions_status: "enum_transaction_status",
|
|
22
|
+
enum_global_user_transactions_transaction_type: "enum_transaction_type",
|
|
23
|
+
enum_invoices_status: "enum_transaction_status",
|
|
24
|
+
enum_payin_status: "enum_transaction_status",
|
|
25
|
+
enum_payout_status: "enum_transaction_status",
|
|
26
|
+
enum_pending_references_status: "enum_transaction_status",
|
|
27
|
+
enum_toku_status: "enum_transaction_status",
|
|
28
|
+
enum_transaction_updates_new_status: "enum_transaction_status",
|
|
29
|
+
enum_transactions_account_type: "enum_account_type",
|
|
30
|
+
enum_transactions_status: "enum_transaction_status",
|
|
31
|
+
enum_transactions_transaction_type: "enum_transaction_type",
|
|
32
|
+
enum_transactions_typetransaction: "enum_transaction_type",
|
|
33
|
+
enum_transactions_user_type_account: "enum_account_type",
|
|
34
|
+
enum_transactions_usertypeaccount: "enum_account_type",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Mapeo de valores de texto a valores de enum válidos
|
|
38
|
+
private readonly valueNormalizationMap: Record<
|
|
39
|
+
string,
|
|
40
|
+
Record<string, string>
|
|
41
|
+
> = {
|
|
42
|
+
// Mapeos para enum_transaction_status
|
|
43
|
+
enum_transaction_status: {
|
|
44
|
+
PENDING: "PENDING",
|
|
45
|
+
pending: "PENDING",
|
|
46
|
+
COMPLETED: "COMPLETED",
|
|
47
|
+
completed: "COMPLETED",
|
|
48
|
+
FAILED: "FAILED",
|
|
49
|
+
failed: "FAILED",
|
|
50
|
+
PROCESSING: "PROCESSING",
|
|
51
|
+
processing: "PROCESSING",
|
|
52
|
+
CANCELLED: "CANCELLED",
|
|
53
|
+
cancelled: "CANCELLED",
|
|
54
|
+
canceled: "CANCELLED",
|
|
55
|
+
CANCELED: "CANCELLED",
|
|
56
|
+
REJECTED: "REJECTED",
|
|
57
|
+
rejected: "REJECTED",
|
|
58
|
+
APPROVED: "APPROVED",
|
|
59
|
+
approved: "APPROVED",
|
|
60
|
+
EXPIRED: "EXPIRED",
|
|
61
|
+
expired: "EXPIRED",
|
|
62
|
+
REFUNDED: "REFUNDED",
|
|
63
|
+
refunded: "REFUNDED",
|
|
64
|
+
PARTIAL_REFUND: "PARTIAL_REFUND",
|
|
65
|
+
partial_refund: "PARTIAL_REFUND",
|
|
66
|
+
},
|
|
67
|
+
// Mapeos para enum_transaction_type
|
|
68
|
+
enum_transaction_type: {
|
|
69
|
+
DEPOSIT: "DEPOSIT",
|
|
70
|
+
deposit: "DEPOSIT",
|
|
71
|
+
WITHDRAWAL: "WITHDRAWAL",
|
|
72
|
+
withdrawal: "WITHDRAWAL",
|
|
73
|
+
TRANSFER: "TRANSFER",
|
|
74
|
+
transfer: "TRANSFER",
|
|
75
|
+
PAYMENT: "PAYMENT",
|
|
76
|
+
payment: "PAYMENT",
|
|
77
|
+
REFUND: "REFUND",
|
|
78
|
+
refund: "REFUND",
|
|
79
|
+
FEE: "FEE",
|
|
80
|
+
fee: "FEE",
|
|
81
|
+
ADJUSTMENT: "ADJUSTMENT",
|
|
82
|
+
adjustment: "ADJUSTMENT",
|
|
83
|
+
},
|
|
84
|
+
// Mapeos para enum_account_type
|
|
85
|
+
enum_account_type: {
|
|
86
|
+
PERSONAL: "PERSONAL",
|
|
87
|
+
personal: "PERSONAL",
|
|
88
|
+
BUSINESS: "BUSINESS",
|
|
89
|
+
business: "BUSINESS",
|
|
90
|
+
MERCHANT: "MERCHANT",
|
|
91
|
+
merchant: "MERCHANT",
|
|
92
|
+
PLATFORM: "PLATFORM",
|
|
93
|
+
platform: "PLATFORM",
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Caché para valores de enum
|
|
98
|
+
private enumValuesCache: Record<string, string[]> = {};
|
|
99
|
+
|
|
100
|
+
constructor(
|
|
101
|
+
private readonly sourceUrl: string = process.env.SOURCE_DATABASE_URL,
|
|
102
|
+
private readonly targetUrl: string = process.env.DATABASE_URL
|
|
103
|
+
) {
|
|
104
|
+
this.pool = new Pool({
|
|
105
|
+
connectionString: this.targetUrl,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Sincroniza los valores entre enums específicos y enums consolidados
|
|
111
|
+
* y normaliza los valores de texto a valores de enum válidos
|
|
112
|
+
*/
|
|
113
|
+
async synchronizeEnumValues() {
|
|
114
|
+
try {
|
|
115
|
+
this.logger.log("Iniciando sincronización de valores de enum");
|
|
116
|
+
|
|
117
|
+
// Crear directorio para logs si no existe
|
|
118
|
+
const logsDir = path.join(process.cwd(), "migration-logs");
|
|
119
|
+
if (!fs.existsSync(logsDir)) {
|
|
120
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Obtener todos los tipos enum en la base de datos
|
|
124
|
+
const enumsResult = await this.pool.query(`
|
|
125
|
+
SELECT t.typname AS enum_name
|
|
126
|
+
FROM pg_type t
|
|
127
|
+
JOIN pg_namespace n ON t.typnamespace = n.oid
|
|
128
|
+
WHERE t.typtype = 'e' -- enum types
|
|
129
|
+
AND n.nspname = 'public'
|
|
130
|
+
ORDER BY t.typname
|
|
131
|
+
`);
|
|
132
|
+
|
|
133
|
+
this.logger.log(
|
|
134
|
+
`Encontrados ${enumsResult.rows.length} tipos enum en la base de datos`
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Procesar cada enum específico y su correspondiente enum consolidado
|
|
138
|
+
for (const [specificEnum, consolidatedEnum] of Object.entries(
|
|
139
|
+
this.enumMappings
|
|
140
|
+
)) {
|
|
141
|
+
await this.synchronizeSpecificEnum(specificEnum, consolidatedEnum);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Después de sincronizar los enums, normalizar los valores en las tablas
|
|
145
|
+
await this.normalizeEnumValuesInTables();
|
|
146
|
+
|
|
147
|
+
this.logger.log("Sincronización de valores de enum completada con éxito");
|
|
148
|
+
} catch (error) {
|
|
149
|
+
this.logger.error(
|
|
150
|
+
`Error durante la sincronización de valores de enum: ${error.message}`,
|
|
151
|
+
error.stack
|
|
152
|
+
);
|
|
153
|
+
throw error;
|
|
154
|
+
} finally {
|
|
155
|
+
await this.pool.end();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Sincroniza los valores entre un enum específico y su enum consolidado
|
|
161
|
+
*/
|
|
162
|
+
private async synchronizeSpecificEnum(
|
|
163
|
+
specificEnum: string,
|
|
164
|
+
consolidatedEnum: string
|
|
165
|
+
) {
|
|
166
|
+
try {
|
|
167
|
+
this.logger.log(
|
|
168
|
+
`Sincronizando valores entre ${specificEnum} y ${consolidatedEnum}`
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Verificar si ambos enums existen
|
|
172
|
+
const specificEnumExists = await this.enumExists(specificEnum);
|
|
173
|
+
const consolidatedEnumExists = await this.enumExists(consolidatedEnum);
|
|
174
|
+
|
|
175
|
+
if (!specificEnumExists || !consolidatedEnumExists) {
|
|
176
|
+
this.logger.warn(
|
|
177
|
+
`No se puede sincronizar: ${
|
|
178
|
+
!specificEnumExists ? specificEnum : consolidatedEnum
|
|
179
|
+
} no existe`
|
|
180
|
+
);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Obtener valores de ambos enums
|
|
185
|
+
const specificValues = await this.getEnumValues(specificEnum);
|
|
186
|
+
const consolidatedValues = await this.getEnumValues(consolidatedEnum);
|
|
187
|
+
|
|
188
|
+
this.logger.log(
|
|
189
|
+
`Valores en ${specificEnum}: ${specificValues.join(", ")}\n` +
|
|
190
|
+
`Valores en ${consolidatedEnum}: ${consolidatedValues.join(", ")}`
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Verificar valores que están en el enum específico pero no en el consolidado
|
|
194
|
+
const missingValues = specificValues.filter(
|
|
195
|
+
(value) => !consolidatedValues.includes(value)
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if (missingValues.length === 0) {
|
|
199
|
+
this.logger.log(
|
|
200
|
+
`No hay valores faltantes entre ${specificEnum} y ${consolidatedEnum}`
|
|
201
|
+
);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.logger.log(
|
|
206
|
+
`Encontrados ${
|
|
207
|
+
missingValues.length
|
|
208
|
+
} valores en ${specificEnum} que faltan en ${consolidatedEnum}: ${missingValues.join(
|
|
209
|
+
", "
|
|
210
|
+
)}`
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Agregar los valores faltantes al enum consolidado
|
|
214
|
+
for (const value of missingValues) {
|
|
215
|
+
await this.addValueToEnum(consolidatedEnum, value);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
this.logger.log(
|
|
219
|
+
`Sincronización entre ${specificEnum} y ${consolidatedEnum} completada`
|
|
220
|
+
);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
this.logger.error(
|
|
223
|
+
`Error sincronizando ${specificEnum} con ${consolidatedEnum}: ${error.message}`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Verifica si un enum existe en la base de datos
|
|
230
|
+
*/
|
|
231
|
+
private async enumExists(enumName: string): Promise<boolean> {
|
|
232
|
+
const result = await this.pool.query(
|
|
233
|
+
`
|
|
234
|
+
SELECT 1
|
|
235
|
+
FROM pg_type t
|
|
236
|
+
JOIN pg_namespace n ON t.typnamespace = n.oid
|
|
237
|
+
WHERE t.typname = $1
|
|
238
|
+
AND t.typtype = 'e' -- enum types
|
|
239
|
+
AND n.nspname = 'public'
|
|
240
|
+
`,
|
|
241
|
+
[enumName]
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
return result.rows.length > 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Obtiene los valores de un enum
|
|
249
|
+
*/
|
|
250
|
+
private async getEnumValues(enumName: string): Promise<string[]> {
|
|
251
|
+
const result = await this.pool.query(
|
|
252
|
+
`
|
|
253
|
+
SELECT e.enumlabel
|
|
254
|
+
FROM pg_enum e
|
|
255
|
+
JOIN pg_type t ON e.enumtypid = t.oid
|
|
256
|
+
JOIN pg_namespace n ON t.typnamespace = n.oid
|
|
257
|
+
WHERE t.typname = $1
|
|
258
|
+
AND n.nspname = 'public'
|
|
259
|
+
ORDER BY e.enumsortorder
|
|
260
|
+
`,
|
|
261
|
+
[enumName]
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
return result.rows.map((row) => row.enumlabel);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Agrega un valor a un enum existente
|
|
269
|
+
*/
|
|
270
|
+
private async addValueToEnum(enumName: string, value: string) {
|
|
271
|
+
try {
|
|
272
|
+
// Escapar comillas simples en el valor
|
|
273
|
+
const escapedValue = value.replace(/'/g, "''");
|
|
274
|
+
|
|
275
|
+
// Agregar el valor al enum
|
|
276
|
+
await this.pool.query(`
|
|
277
|
+
ALTER TYPE ${enumName} ADD VALUE IF NOT EXISTS '${escapedValue}'
|
|
278
|
+
`);
|
|
279
|
+
|
|
280
|
+
// Actualizar la caché de valores de enum
|
|
281
|
+
if (this.enumValuesCache[enumName]) {
|
|
282
|
+
if (!this.enumValuesCache[enumName].includes(value)) {
|
|
283
|
+
this.enumValuesCache[enumName].push(value);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
this.logger.log(`Valor '${value}' agregado al enum ${enumName}`);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
this.logger.error(
|
|
290
|
+
`Error agregando valor '${value}' al enum ${enumName}: ${error.message}`
|
|
291
|
+
);
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Normaliza los valores de enum en todas las tablas
|
|
298
|
+
*/
|
|
299
|
+
private async normalizeEnumValuesInTables() {
|
|
300
|
+
this.logger.log("Iniciando normalización de valores de enum en tablas");
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
// Obtener todos los schemas excepto los del sistema
|
|
304
|
+
const schemas = await this.getSchemas();
|
|
305
|
+
this.logger.log(`Encontrados ${schemas.length} schemas para procesar`);
|
|
306
|
+
|
|
307
|
+
// Para cada schema, normalizar los valores de enum en sus tablas
|
|
308
|
+
for (const schema of schemas) {
|
|
309
|
+
await this.normalizeEnumValuesInSchema(schema);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
this.logger.log("Normalización de valores de enum en tablas completada");
|
|
313
|
+
} catch (error) {
|
|
314
|
+
this.logger.error(
|
|
315
|
+
`Error durante la normalización de valores de enum: ${error.message}`,
|
|
316
|
+
error.stack
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Obtiene todos los schemas de la base de datos excepto los del sistema
|
|
323
|
+
*/
|
|
324
|
+
private async getSchemas(): Promise<string[]> {
|
|
325
|
+
const result = await this.pool.query(`
|
|
326
|
+
SELECT schema_name
|
|
327
|
+
FROM information_schema.schemata
|
|
328
|
+
WHERE schema_name NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
|
|
329
|
+
AND schema_name NOT LIKE 'pg_%'
|
|
330
|
+
`);
|
|
331
|
+
|
|
332
|
+
return result.rows.map((row) => row.schema_name);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Normaliza los valores de enum en un schema específico
|
|
337
|
+
*/
|
|
338
|
+
private async normalizeEnumValuesInSchema(schema: string) {
|
|
339
|
+
this.logger.log(`Procesando valores de enum en schema: ${schema}`);
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
// Obtener todas las columnas enum en este schema
|
|
343
|
+
const enumColumnsQuery = `
|
|
344
|
+
SELECT
|
|
345
|
+
c.table_name,
|
|
346
|
+
c.column_name,
|
|
347
|
+
c.udt_name
|
|
348
|
+
FROM
|
|
349
|
+
information_schema.columns c
|
|
350
|
+
WHERE
|
|
351
|
+
c.table_schema = $1
|
|
352
|
+
AND c.data_type = 'USER-DEFINED'
|
|
353
|
+
AND c.udt_name LIKE 'enum_%'
|
|
354
|
+
`;
|
|
355
|
+
|
|
356
|
+
const enumColumnsResult = await this.pool.query(enumColumnsQuery, [
|
|
357
|
+
schema,
|
|
358
|
+
]);
|
|
359
|
+
|
|
360
|
+
if (enumColumnsResult.rows.length === 0) {
|
|
361
|
+
this.logger.log(`No se encontraron columnas enum en schema: ${schema}`);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
this.logger.log(
|
|
366
|
+
`Encontradas ${enumColumnsResult.rows.length} columnas enum en schema: ${schema}`
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
// Procesar cada columna enum
|
|
370
|
+
for (const row of enumColumnsResult.rows) {
|
|
371
|
+
const tableName = row.table_name;
|
|
372
|
+
const columnName = row.column_name;
|
|
373
|
+
const enumTypeName = row.udt_name;
|
|
374
|
+
|
|
375
|
+
await this.normalizeEnumColumn(
|
|
376
|
+
schema,
|
|
377
|
+
tableName,
|
|
378
|
+
columnName,
|
|
379
|
+
enumTypeName
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
this.logger.log(
|
|
384
|
+
`Completado procesamiento de valores enum para schema: ${schema}`
|
|
385
|
+
);
|
|
386
|
+
} catch (error) {
|
|
387
|
+
this.logger.error(
|
|
388
|
+
`Error procesando valores enum para schema ${schema}: ${error.message}`,
|
|
389
|
+
error.stack
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Normaliza los valores en una columna enum específica
|
|
396
|
+
*/
|
|
397
|
+
private async normalizeEnumColumn(
|
|
398
|
+
schema: string,
|
|
399
|
+
tableName: string,
|
|
400
|
+
columnName: string,
|
|
401
|
+
enumTypeName: string
|
|
402
|
+
) {
|
|
403
|
+
this.logger.log(
|
|
404
|
+
`Normalizando columna enum ${columnName} (${enumTypeName}) en tabla ${schema}.${tableName}`
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
// Obtener los valores válidos para el enum si no están en caché
|
|
409
|
+
if (!this.enumValuesCache[enumTypeName]) {
|
|
410
|
+
this.enumValuesCache[enumTypeName] = await this.getEnumValues(
|
|
411
|
+
enumTypeName
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
const validEnumValues = this.enumValuesCache[enumTypeName];
|
|
415
|
+
|
|
416
|
+
if (validEnumValues.length === 0) {
|
|
417
|
+
this.logger.warn(
|
|
418
|
+
`No se encontraron valores para el enum ${enumTypeName}. Omitiendo.`
|
|
419
|
+
);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Obtener valores actuales en la columna
|
|
424
|
+
const currentValuesQuery = `
|
|
425
|
+
SELECT DISTINCT "${columnName}"
|
|
426
|
+
FROM "${schema}"."${tableName}"
|
|
427
|
+
WHERE "${columnName}" IS NOT NULL
|
|
428
|
+
`;
|
|
429
|
+
|
|
430
|
+
const currentValuesResult = await this.pool.query(currentValuesQuery);
|
|
431
|
+
const currentValues = currentValuesResult.rows.map((r) => r[columnName]);
|
|
432
|
+
|
|
433
|
+
// Encontrar valores inválidos
|
|
434
|
+
const invalidValues = currentValues.filter(
|
|
435
|
+
(val) => !validEnumValues.includes(val)
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
if (invalidValues.length === 0) {
|
|
439
|
+
this.logger.log(
|
|
440
|
+
`No se encontraron valores inválidos para columna enum ${columnName} en tabla ${schema}.${tableName}`
|
|
441
|
+
);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
this.logger.log(
|
|
446
|
+
`Encontrados ${
|
|
447
|
+
invalidValues.length
|
|
448
|
+
} valores inválidos para enum ${enumTypeName} en ${schema}.${tableName}: ${invalidValues.join(
|
|
449
|
+
", "
|
|
450
|
+
)}`
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
// Para cada valor inválido, intentar normalizarlo
|
|
454
|
+
for (const invalidValue of invalidValues) {
|
|
455
|
+
const normalizedValue = this.getNormalizedEnumValue(
|
|
456
|
+
enumTypeName,
|
|
457
|
+
invalidValue
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
if (normalizedValue && normalizedValue !== invalidValue) {
|
|
461
|
+
// Actualizar los registros con el valor normalizado
|
|
462
|
+
const updateQuery = `
|
|
463
|
+
UPDATE "${schema}"."${tableName}"
|
|
464
|
+
SET "${columnName}" = $1
|
|
465
|
+
WHERE "${columnName}" = $2
|
|
466
|
+
`;
|
|
467
|
+
|
|
468
|
+
const result = await this.pool.query(updateQuery, [
|
|
469
|
+
normalizedValue,
|
|
470
|
+
invalidValue,
|
|
471
|
+
]);
|
|
472
|
+
|
|
473
|
+
this.logger.log(
|
|
474
|
+
`Actualizado valor '${invalidValue}' a '${normalizedValue}' en ${result.rowCount} filas de ${schema}.${tableName}.${columnName}`
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
// Si el valor normalizado no está en el enum, agregarlo
|
|
478
|
+
if (!validEnumValues.includes(normalizedValue)) {
|
|
479
|
+
await this.addValueToEnum(enumTypeName, normalizedValue);
|
|
480
|
+
}
|
|
481
|
+
} else if (!normalizedValue) {
|
|
482
|
+
// Si no se puede normalizar, establecer a NULL
|
|
483
|
+
const updateQuery = `
|
|
484
|
+
UPDATE "${schema}"."${tableName}"
|
|
485
|
+
SET "${columnName}" = NULL
|
|
486
|
+
WHERE "${columnName}" = $1
|
|
487
|
+
`;
|
|
488
|
+
|
|
489
|
+
const result = await this.pool.query(updateQuery, [invalidValue]);
|
|
490
|
+
|
|
491
|
+
this.logger.log(
|
|
492
|
+
`Establecido valor '${invalidValue}' a NULL en ${result.rowCount} filas de ${schema}.${tableName}.${columnName}`
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
} catch (error) {
|
|
497
|
+
this.logger.error(
|
|
498
|
+
`Error normalizando columna enum ${columnName} en ${schema}.${tableName}: ${error.message}`,
|
|
499
|
+
error.stack
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Obtiene un valor de enum normalizado basado en mapeos predefinidos
|
|
506
|
+
*/
|
|
507
|
+
private getNormalizedEnumValue(
|
|
508
|
+
enumTypeName: string,
|
|
509
|
+
value: string
|
|
510
|
+
): string | null {
|
|
511
|
+
// Si el valor ya es válido, devolverlo tal cual
|
|
512
|
+
if (this.enumValuesCache[enumTypeName]?.includes(value)) {
|
|
513
|
+
return value;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Buscar en el mapa de normalización
|
|
517
|
+
if (
|
|
518
|
+
this.valueNormalizationMap[enumTypeName] &&
|
|
519
|
+
this.valueNormalizationMap[enumTypeName][value]
|
|
520
|
+
) {
|
|
521
|
+
return this.valueNormalizationMap[enumTypeName][value];
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Intentar normalizar basado en similitud (convertir a mayúsculas, quitar espacios, etc.)
|
|
525
|
+
const normalizedInput = value.toUpperCase().replace(/\s+/g, "_");
|
|
526
|
+
|
|
527
|
+
// Verificar si la versión normalizada existe en el mapa
|
|
528
|
+
if (
|
|
529
|
+
this.valueNormalizationMap[enumTypeName] &&
|
|
530
|
+
this.valueNormalizationMap[enumTypeName][normalizedInput]
|
|
531
|
+
) {
|
|
532
|
+
return this.valueNormalizationMap[enumTypeName][normalizedInput];
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Buscar coincidencias parciales en los valores válidos
|
|
536
|
+
const validValues = this.enumValuesCache[enumTypeName] || [];
|
|
537
|
+
for (const validValue of validValues) {
|
|
538
|
+
// Comparar ignorando mayúsculas/minúsculas
|
|
539
|
+
if (validValue.toLowerCase() === value.toLowerCase()) {
|
|
540
|
+
return validValue;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Si no se encuentra coincidencia, devolver null
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Ejecutar el script si se llama directamente
|
|
550
|
+
if (require.main === module) {
|
|
551
|
+
const run = async () => {
|
|
552
|
+
const synchronizer = new EnumValueSynchronizer(
|
|
553
|
+
process.env.SOURCE_DATABASE_URL,
|
|
554
|
+
process.env.DATABASE_URL
|
|
555
|
+
);
|
|
556
|
+
await synchronizer.synchronizeEnumValues();
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
run().catch((error) => {
|
|
560
|
+
console.error("Error ejecutando sincronización de valores de enum:", error);
|
|
561
|
+
process.exit(1);
|
|
562
|
+
});
|
|
563
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import * as pg from "pg";
|
|
2
|
+
import * as dotenv from "dotenv";
|
|
3
|
+
import { Logger } from "@nestjs/common";
|
|
4
|
+
|
|
5
|
+
dotenv.config();
|
|
6
|
+
|
|
7
|
+
export class DatabaseTruncateTool {
|
|
8
|
+
private readonly logger = new Logger("DatabaseTruncateTool");
|
|
9
|
+
private readonly pool: pg.Pool;
|
|
10
|
+
|
|
11
|
+
constructor(private readonly databaseUrl: string) {
|
|
12
|
+
this.pool = new pg.Pool({
|
|
13
|
+
connectionString: this.databaseUrl,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async truncateDatabase() {
|
|
18
|
+
try {
|
|
19
|
+
this.logger.log("Starting database truncate process");
|
|
20
|
+
|
|
21
|
+
// Obtener todos los schemas excepto los del sistema
|
|
22
|
+
const schemas = await this.getSchemas();
|
|
23
|
+
this.logger.log(`Found ${schemas.length} schemas to process`);
|
|
24
|
+
|
|
25
|
+
// Para cada schema, truncar todas sus tablas
|
|
26
|
+
for (const schema of schemas) {
|
|
27
|
+
await this.truncateSchema(schema);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.logger.log("Database has been successfully truncated");
|
|
31
|
+
} catch (error) {
|
|
32
|
+
this.logger.error(
|
|
33
|
+
`Error truncating database: ${error.message}`,
|
|
34
|
+
error.stack
|
|
35
|
+
);
|
|
36
|
+
throw error;
|
|
37
|
+
} finally {
|
|
38
|
+
await this.cleanup();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private async getSchemas(): Promise<string[]> {
|
|
43
|
+
const result = await this.pool.query(`
|
|
44
|
+
SELECT schema_name
|
|
45
|
+
FROM information_schema.schemata
|
|
46
|
+
WHERE schema_name NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
|
|
47
|
+
AND schema_name NOT LIKE 'pg_%'
|
|
48
|
+
`);
|
|
49
|
+
|
|
50
|
+
return result.rows.map(row => row.schema_name);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private async truncateSchema(schemaName: string) {
|
|
54
|
+
this.logger.log(`Processing schema: ${schemaName}`);
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
// Obtener todas las tablas del schema
|
|
58
|
+
const tablesResult = await this.pool.query(`
|
|
59
|
+
SELECT table_name
|
|
60
|
+
FROM information_schema.tables
|
|
61
|
+
WHERE table_schema = $1
|
|
62
|
+
AND table_type = 'BASE TABLE'
|
|
63
|
+
`, [schemaName]);
|
|
64
|
+
|
|
65
|
+
const tables = tablesResult.rows.map(row => row.table_name);
|
|
66
|
+
|
|
67
|
+
if (tables.length === 0) {
|
|
68
|
+
this.logger.log(`No tables found in schema ${schemaName}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.logger.log(`Found ${tables.length} tables in schema ${schemaName}`);
|
|
73
|
+
|
|
74
|
+
// Desactivar restricciones de clave foránea temporalmente
|
|
75
|
+
await this.pool.query(`SET session_replication_role = 'replica'`);
|
|
76
|
+
|
|
77
|
+
// Truncar cada tabla
|
|
78
|
+
for (const table of tables) {
|
|
79
|
+
try {
|
|
80
|
+
await this.pool.query(`TRUNCATE TABLE "${schemaName}"."${table}" CASCADE`);
|
|
81
|
+
this.logger.log(`Truncated table ${schemaName}.${table}`);
|
|
82
|
+
} catch (tableError) {
|
|
83
|
+
this.logger.error(`Error truncating table ${schemaName}.${table}: ${tableError.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Reactivar restricciones de clave foránea
|
|
88
|
+
await this.pool.query(`SET session_replication_role = 'origin'`);
|
|
89
|
+
|
|
90
|
+
this.logger.log(`Successfully processed schema: ${schemaName}`);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
this.logger.error(`Error processing schema ${schemaName}: ${error.message}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private async cleanup() {
|
|
97
|
+
this.logger.log("Cleaning up database connections");
|
|
98
|
+
await this.pool.end();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Script para ejecutar desde línea de comandos
|
|
103
|
+
if (require.main === module) {
|
|
104
|
+
const run = async () => {
|
|
105
|
+
try {
|
|
106
|
+
// Obtener la URL de la base de datos desde los argumentos o .env
|
|
107
|
+
const databaseUrl = process.argv[2] || process.env.DATABASE_URL;
|
|
108
|
+
|
|
109
|
+
if (!databaseUrl) {
|
|
110
|
+
console.error("Error: No database URL provided. Please provide a database URL as an argument or set DATABASE_URL environment variable.");
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const truncateTool = new DatabaseTruncateTool(databaseUrl);
|
|
115
|
+
await truncateTool.truncateDatabase();
|
|
116
|
+
process.exit(0);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error("Error:", error.message);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
run();
|
|
124
|
+
}
|