@javalabs/prisma-client 1.0.3 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scripts/data-migration/batch-migrator.d.ts +14 -19
- package/dist/scripts/data-migration/batch-migrator.js +98 -297
- package/dist/scripts/data-migration/batch-migrator.js.map +1 -1
- package/dist/scripts/data-migration/data-transformer.d.ts +16 -7
- package/dist/scripts/data-migration/data-transformer.js +169 -133
- package/dist/scripts/data-migration/data-transformer.js.map +1 -1
- package/dist/scripts/data-migration/db-connector.d.ts +6 -1
- package/dist/scripts/data-migration/db-connector.js +44 -8
- package/dist/scripts/data-migration/db-connector.js.map +1 -1
- package/dist/scripts/data-migration/dependency-resolver.d.ts +10 -10
- package/dist/scripts/data-migration/dependency-resolver.js +92 -211
- package/dist/scripts/data-migration/dependency-resolver.js.map +1 -1
- package/dist/scripts/data-migration/foreign-key-manager.d.ts +6 -5
- package/dist/scripts/data-migration/foreign-key-manager.js +108 -18
- package/dist/scripts/data-migration/foreign-key-manager.js.map +1 -1
- package/dist/scripts/data-migration/migration-config.json +63 -0
- package/dist/scripts/data-migration/migration-tool.d.ts +25 -6
- package/dist/scripts/data-migration/migration-tool.js +78 -38
- package/dist/scripts/data-migration/migration-tool.js.map +1 -1
- package/dist/scripts/data-migration/multi-source-migrator.d.ts +17 -0
- package/dist/scripts/data-migration/multi-source-migrator.js +130 -0
- package/dist/scripts/data-migration/multi-source-migrator.js.map +1 -0
- package/dist/scripts/data-migration/schema-utils.d.ts +3 -3
- package/dist/scripts/data-migration/schema-utils.js +62 -19
- package/dist/scripts/data-migration/schema-utils.js.map +1 -1
- package/dist/scripts/data-migration/tenant-migrator.js +9 -2
- package/dist/scripts/data-migration/tenant-migrator.js.map +1 -1
- package/dist/scripts/data-migration/typecast-manager.d.ts +7 -3
- package/dist/scripts/data-migration/typecast-manager.js +169 -25
- package/dist/scripts/data-migration/typecast-manager.js.map +1 -1
- package/dist/scripts/data-migration/types.d.ts +68 -2
- package/dist/scripts/fix-table-indexes.d.ts +26 -0
- package/dist/scripts/fix-table-indexes.js +460 -0
- package/dist/scripts/fix-table-indexes.js.map +1 -0
- package/dist/scripts/multi-db-migration.d.ts +1 -0
- package/dist/scripts/multi-db-migration.js +55 -0
- package/dist/scripts/multi-db-migration.js.map +1 -0
- package/dist/scripts/run-migration.js +41 -75
- package/dist/scripts/run-migration.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/migration-config.json +40 -72
- package/{migration-config-public.json → migration-config.json.bk} +14 -14
- package/package.json +6 -3
- package/prisma/migrations/20250422001248_add_bank_account_id/migration.sql +458 -0
- package/prisma/migrations/20250422001957_add_bank_account_id_relations/migration.sql +14 -0
- package/prisma/schema.prisma +13 -12
- package/src/scripts/data-migration/batch-migrator.ts +192 -513
- package/src/scripts/data-migration/data-transformer.ts +252 -203
- package/src/scripts/data-migration/db-connector.ts +66 -13
- package/src/scripts/data-migration/dependency-resolver.ts +121 -266
- package/src/scripts/data-migration/foreign-key-manager.ts +214 -32
- package/src/scripts/data-migration/migration-config.json +63 -0
- package/src/scripts/data-migration/migration-tool.ts +377 -225
- package/src/scripts/data-migration/schema-utils.ts +94 -32
- package/src/scripts/data-migration/tenant-migrator.ts +12 -5
- package/src/scripts/data-migration/typecast-manager.ts +186 -31
- package/src/scripts/data-migration/types.ts +78 -5
- package/src/scripts/dumps/source_dump_20250428_145606.sql +323 -0
- package/src/scripts/fix-table-indexes.ts +602 -0
- package/src/scripts/post-migration-validator.ts +206 -107
- package/src/scripts/run-migration.ts +87 -101
|
@@ -1,89 +1,136 @@
|
|
|
1
1
|
import { Logger } from "@nestjs/common";
|
|
2
2
|
import { ColumnSchema, EnumCastValue } from "./types";
|
|
3
3
|
import { SchemaUtils } from "./schema-utils";
|
|
4
|
+
import {
|
|
5
|
+
DatabaseConnections,
|
|
6
|
+
MigrationConfig,
|
|
7
|
+
MigrationOptions,
|
|
8
|
+
} from "./types";
|
|
9
|
+
import { ForeignKeyManager } from "./foreign-key-manager";
|
|
10
|
+
import { TypecastManager } from "./typecast-manager";
|
|
4
11
|
|
|
5
12
|
export class DataTransformer {
|
|
6
13
|
private readonly logger = new Logger("DataTransformer");
|
|
7
|
-
private
|
|
8
|
-
private
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
private readonly schemaUtils: SchemaUtils;
|
|
15
|
+
private readonly sourceConnection: any;
|
|
16
|
+
private connections: DatabaseConnections;
|
|
17
|
+
private migrationConfig: MigrationConfig;
|
|
18
|
+
private options: MigrationOptions;
|
|
19
|
+
private foreignKeyManager: ForeignKeyManager;
|
|
20
|
+
private readonly typecastManager: TypecastManager;
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
schemaUtils: SchemaUtils,
|
|
24
|
+
sourceConnection: any,
|
|
25
|
+
connections: DatabaseConnections
|
|
26
|
+
) {
|
|
27
|
+
this.schemaUtils = schemaUtils;
|
|
28
|
+
this.sourceConnection = sourceConnection;
|
|
29
|
+
this.connections = connections;
|
|
30
|
+
this.typecastManager = new TypecastManager();
|
|
31
|
+
}
|
|
11
32
|
|
|
12
33
|
async validateEnumValue(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
value: string
|
|
34
|
+
tableName: string,
|
|
35
|
+
columnName: string,
|
|
36
|
+
value: string | null,
|
|
37
|
+
isNullable: boolean = true
|
|
16
38
|
): Promise<string | null> {
|
|
17
39
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
JOIN pg_enum e ON t.oid = e.enumtypid
|
|
24
|
-
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
25
|
-
WHERE t.typname = $1 AND n.nspname = $2
|
|
26
|
-
`;
|
|
27
|
-
|
|
28
|
-
const result = await this.schemaUtils.queryTargetDb(query, [
|
|
29
|
-
enumType,
|
|
30
|
-
schemaName,
|
|
31
|
-
]);
|
|
32
|
-
const uniqueValues: Set<string> = new Set(
|
|
33
|
-
result.rows.map((row): string => String(row.enumlabel))
|
|
34
|
-
);
|
|
40
|
+
if (value === null) {
|
|
41
|
+
return isNullable
|
|
42
|
+
? null
|
|
43
|
+
: await this.getDefaultEnumValue(tableName, columnName);
|
|
44
|
+
}
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
result.rows.forEach((row) => {
|
|
39
|
-
const original = String(row.enumlabel);
|
|
40
|
-
const normalized = original.toLowerCase().trim();
|
|
41
|
-
normalizedMap.set(normalized, original);
|
|
42
|
-
});
|
|
46
|
+
const enumTypeName = await this.getEnumTypeName(tableName, columnName);
|
|
47
|
+
const normalizedValue = value.toString().trim().toLowerCase();
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
// Obtener valores válidos del enum desde la base de datos
|
|
50
|
+
const query = `SELECT unnest(enum_range(NULL::${enumTypeName})) as enum_value`;
|
|
51
|
+
const validValues = await this.connections.sourcePool.query(query);
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
if (!validValues || !validValues.rows) {
|
|
54
|
+
this.logger.warn(
|
|
55
|
+
`No se pudieron obtener valores válidos para el enum ${enumTypeName}`
|
|
49
56
|
);
|
|
57
|
+
return await this.getDefaultEnumValue(enumTypeName);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const enumValues = validValues.rows.map((row: any) =>
|
|
61
|
+
row.enum_value.toLowerCase()
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Verificar coincidencia exacta
|
|
65
|
+
if (enumValues.includes(normalizedValue)) {
|
|
66
|
+
return value.toString().trim();
|
|
50
67
|
}
|
|
51
68
|
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
69
|
+
// Verificar coincidencia parcial
|
|
70
|
+
const partialMatch = enumValues.find(
|
|
71
|
+
(enumValue) =>
|
|
72
|
+
enumValue.includes(normalizedValue) ||
|
|
73
|
+
normalizedValue.includes(enumValue)
|
|
74
|
+
);
|
|
56
75
|
|
|
57
|
-
if (
|
|
58
|
-
|
|
76
|
+
if (partialMatch) {
|
|
77
|
+
this.logger.debug(
|
|
78
|
+
`Valor enum corregido: ${value} -> ${partialMatch} para ${tableName}.${columnName}`
|
|
79
|
+
);
|
|
80
|
+
return partialMatch;
|
|
59
81
|
}
|
|
60
82
|
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
83
|
+
// Si no se encuentra coincidencia, usar valor por defecto
|
|
84
|
+
const defaultValue = await this.getDefaultEnumValue(enumTypeName);
|
|
85
|
+
if (defaultValue) {
|
|
86
|
+
this.logger.warn(
|
|
87
|
+
`Usando valor por defecto ${defaultValue} para ${tableName}.${columnName}`
|
|
88
|
+
);
|
|
89
|
+
return defaultValue;
|
|
64
90
|
}
|
|
65
91
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return normalizedMap.get(normalizedValue);
|
|
92
|
+
if (!isNullable) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`No se pudo encontrar un valor válido para el enum ${enumTypeName}`
|
|
95
|
+
);
|
|
71
96
|
}
|
|
72
97
|
|
|
73
|
-
this.logger.warn(
|
|
74
|
-
`Invalid enum value "${value}" for type ${enumType}. Valid values are: ${[
|
|
75
|
-
...validValues,
|
|
76
|
-
].join(", ")}`
|
|
77
|
-
);
|
|
78
98
|
return null;
|
|
79
99
|
} catch (error) {
|
|
80
100
|
this.logger.error(
|
|
81
|
-
`Error validating enum value
|
|
101
|
+
`Error validating enum value for ${tableName}.${columnName}: ${error.message}`
|
|
82
102
|
);
|
|
103
|
+
if (!isNullable) {
|
|
104
|
+
return await this.getDefaultEnumValue(tableName, columnName);
|
|
105
|
+
}
|
|
83
106
|
return null;
|
|
84
107
|
}
|
|
85
108
|
}
|
|
86
109
|
|
|
110
|
+
private async getEnumTypeName(
|
|
111
|
+
tableName: string,
|
|
112
|
+
columnName: string
|
|
113
|
+
): Promise<string> {
|
|
114
|
+
const result = await this.connections.sourcePool.query(
|
|
115
|
+
`
|
|
116
|
+
SELECT udt_name
|
|
117
|
+
FROM information_schema.columns
|
|
118
|
+
WHERE table_name = $1
|
|
119
|
+
AND column_name = $2
|
|
120
|
+
AND table_schema = 'public'
|
|
121
|
+
`,
|
|
122
|
+
[tableName.replace(/^public\./, ""), columnName]
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (result.rows.length === 0) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`No se encontró el tipo de enum para ${tableName}.${columnName}`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return result.rows[0].udt_name;
|
|
132
|
+
}
|
|
133
|
+
|
|
87
134
|
transformToNumeric(value: any): number | null {
|
|
88
135
|
if (value === null || value === "") {
|
|
89
136
|
return null;
|
|
@@ -102,23 +149,34 @@ export class DataTransformer {
|
|
|
102
149
|
tenantId: string = "public"
|
|
103
150
|
): Promise<string | null> {
|
|
104
151
|
try {
|
|
105
|
-
//
|
|
152
|
+
// Mapeo específico para tipos de enum conocidos
|
|
153
|
+
const enumDefaults = {
|
|
154
|
+
enum_users_role: "user",
|
|
155
|
+
enum_account_type: "savings",
|
|
156
|
+
enum_transaction_type: "payin",
|
|
157
|
+
enum_transaction_status: "pending",
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
if (enumDefaults[enumType]) {
|
|
161
|
+
return enumDefaults[enumType];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Si no hay un valor por defecto específico, obtener el primer valor del enum
|
|
106
165
|
const query = `
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
166
|
+
SELECT e.enumlabel
|
|
167
|
+
FROM pg_type t
|
|
168
|
+
JOIN pg_enum e ON t.oid = e.enumtypid
|
|
169
|
+
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
170
|
+
WHERE t.typname = $1 AND n.nspname = $2
|
|
171
|
+
ORDER BY e.enumsortorder
|
|
172
|
+
LIMIT 1
|
|
173
|
+
`;
|
|
115
174
|
|
|
116
175
|
const result = await this.schemaUtils.queryTargetDb(query, [
|
|
117
176
|
enumType,
|
|
118
177
|
tenantId,
|
|
119
178
|
]);
|
|
120
179
|
|
|
121
|
-
// Retornar el primer valor del enum como valor por defecto
|
|
122
180
|
if (result.rows.length > 0) {
|
|
123
181
|
return result.rows[0].enumlabel;
|
|
124
182
|
}
|
|
@@ -173,166 +231,159 @@ export class DataTransformer {
|
|
|
173
231
|
async transformColumnValue(
|
|
174
232
|
value: any,
|
|
175
233
|
columnName: string,
|
|
176
|
-
targetColumn: ColumnSchema,
|
|
234
|
+
targetColumn: ColumnSchema & { source_type?: string },
|
|
177
235
|
tenantId: string
|
|
178
236
|
): Promise<any> {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
)}, type: ${targetColumn.udt_name}`
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
if (value === null && targetColumn.is_nullable === "YES") {
|
|
187
|
-
return null;
|
|
237
|
+
if (value === null || value === undefined) {
|
|
238
|
+
return targetColumn.is_nullable === "YES"
|
|
239
|
+
? null
|
|
240
|
+
: this.getDefaultValue(targetColumn);
|
|
188
241
|
}
|
|
189
242
|
|
|
190
243
|
try {
|
|
191
|
-
//
|
|
192
|
-
if (targetColumn.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return await this.prepareEnumValue(
|
|
198
|
-
tenantId,
|
|
199
|
-
targetColumn.udt_name,
|
|
200
|
-
stringValue
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Handle direct string values
|
|
206
|
-
if (typeof value === "string") {
|
|
207
|
-
return await this.prepareEnumValue(
|
|
208
|
-
tenantId,
|
|
209
|
-
targetColumn.udt_name,
|
|
210
|
-
value
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// If no valid value found, get default
|
|
215
|
-
const defaultValue = await this.getDefaultEnumValue(
|
|
216
|
-
targetColumn.udt_name,
|
|
217
|
-
tenantId
|
|
244
|
+
// Si tenemos el tipo de origen, usar el TypecastManager
|
|
245
|
+
if (targetColumn.source_type) {
|
|
246
|
+
return this.typecastManager.castValue(
|
|
247
|
+
value,
|
|
248
|
+
targetColumn.source_type,
|
|
249
|
+
targetColumn.data_type
|
|
218
250
|
);
|
|
219
|
-
if (defaultValue) {
|
|
220
|
-
this.logger.warn(
|
|
221
|
-
`Using default enum value '${defaultValue}' for column ${columnName}`
|
|
222
|
-
);
|
|
223
|
-
return {
|
|
224
|
-
needsEnumCast: true,
|
|
225
|
-
value: defaultValue,
|
|
226
|
-
enumType: targetColumn.udt_name,
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
251
|
}
|
|
230
252
|
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
targetColumn.udt_name === "bool" ||
|
|
246
|
-
targetColumn.data_type === "boolean"
|
|
247
|
-
) {
|
|
248
|
-
if (typeof value === "string") {
|
|
249
|
-
return value.toLowerCase() === "true" || value === "1";
|
|
250
|
-
}
|
|
251
|
-
return Boolean(value);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Handle date/timestamp types
|
|
255
|
-
if (
|
|
256
|
-
targetColumn.data_type.includes("timestamp") ||
|
|
257
|
-
targetColumn.data_type.includes("date")
|
|
258
|
-
) {
|
|
259
|
-
if (!value) return null;
|
|
260
|
-
const date = new Date(value);
|
|
261
|
-
return isNaN(date.getTime()) ? null : date.toISOString();
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Handle JSON/JSONB types
|
|
265
|
-
if (
|
|
266
|
-
targetColumn.data_type === "json" ||
|
|
267
|
-
targetColumn.data_type === "jsonb"
|
|
268
|
-
) {
|
|
269
|
-
if (typeof value === "string") {
|
|
270
|
-
try {
|
|
271
|
-
return JSON.parse(value);
|
|
272
|
-
} catch {
|
|
273
|
-
return value;
|
|
253
|
+
// Si no tenemos el tipo de origen, intentar convertir basado en el tipo destino
|
|
254
|
+
switch (targetColumn.data_type.toLowerCase()) {
|
|
255
|
+
case "integer":
|
|
256
|
+
case "bigint":
|
|
257
|
+
case "smallint":
|
|
258
|
+
return typeof value === "string" ? parseInt(value, 10) : value;
|
|
259
|
+
case "numeric":
|
|
260
|
+
case "decimal":
|
|
261
|
+
case "real":
|
|
262
|
+
case "double precision":
|
|
263
|
+
return typeof value === "string" ? parseFloat(value) : value;
|
|
264
|
+
case "boolean":
|
|
265
|
+
if (typeof value === "string") {
|
|
266
|
+
return value.toLowerCase() === "true" || value === "1";
|
|
274
267
|
}
|
|
275
|
-
|
|
276
|
-
|
|
268
|
+
return Boolean(value);
|
|
269
|
+
case "json":
|
|
270
|
+
case "jsonb":
|
|
271
|
+
if (typeof value === "string") {
|
|
272
|
+
try {
|
|
273
|
+
return JSON.parse(value);
|
|
274
|
+
} catch {
|
|
275
|
+
return value;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return value;
|
|
279
|
+
case "timestamp":
|
|
280
|
+
case "timestamptz":
|
|
281
|
+
case "timestamp with time zone":
|
|
282
|
+
case "timestamp without time zone":
|
|
283
|
+
if (value instanceof Date) {
|
|
284
|
+
return value.toISOString();
|
|
285
|
+
}
|
|
286
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
287
|
+
return new Date(value).toISOString();
|
|
288
|
+
}
|
|
289
|
+
return value;
|
|
290
|
+
case "date":
|
|
291
|
+
if (value instanceof Date) {
|
|
292
|
+
return value.toISOString().split("T")[0];
|
|
293
|
+
}
|
|
294
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
295
|
+
return new Date(value).toISOString().split("T")[0];
|
|
296
|
+
}
|
|
297
|
+
return value;
|
|
298
|
+
case "text":
|
|
299
|
+
case "character varying":
|
|
300
|
+
case "varchar":
|
|
301
|
+
case "char":
|
|
302
|
+
return String(value);
|
|
303
|
+
default:
|
|
304
|
+
return value;
|
|
277
305
|
}
|
|
278
|
-
|
|
279
|
-
// Default case: return value as-is
|
|
280
|
-
return value;
|
|
281
306
|
} catch (error) {
|
|
282
|
-
this.logger.
|
|
283
|
-
`Error
|
|
307
|
+
this.logger.warn(
|
|
308
|
+
`Error transformando valor para columna ${columnName}: ${error.message}`
|
|
284
309
|
);
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return this.getDefaultValueForType(targetColumn.udt_name);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
private extractEnumValueFromObject(value: any): string | null {
|
|
296
|
-
const possibleKeys = ["type", "value", "document_type", "status", "state"];
|
|
297
|
-
|
|
298
|
-
for (const key of possibleKeys) {
|
|
299
|
-
if (value[key] && typeof value[key] === "string") {
|
|
300
|
-
return value[key];
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Try to find any string value in the object
|
|
305
|
-
for (const val of Object.values(value)) {
|
|
306
|
-
if (typeof val === "string") {
|
|
307
|
-
return val;
|
|
308
|
-
}
|
|
310
|
+
return targetColumn.is_nullable === "YES"
|
|
311
|
+
? null
|
|
312
|
+
: this.getDefaultValue(targetColumn);
|
|
309
313
|
}
|
|
310
|
-
|
|
311
|
-
return null;
|
|
312
314
|
}
|
|
313
315
|
|
|
314
|
-
private
|
|
315
|
-
switch (
|
|
316
|
-
case "
|
|
317
|
-
case "
|
|
318
|
-
case "
|
|
316
|
+
private getDefaultValue(column: ColumnSchema): any {
|
|
317
|
+
switch (column.data_type.toLowerCase()) {
|
|
318
|
+
case "integer":
|
|
319
|
+
case "bigint":
|
|
320
|
+
case "smallint":
|
|
319
321
|
return 0;
|
|
320
|
-
case "
|
|
322
|
+
case "numeric":
|
|
323
|
+
case "decimal":
|
|
324
|
+
case "real":
|
|
325
|
+
case "double precision":
|
|
326
|
+
return 0.0;
|
|
327
|
+
case "boolean":
|
|
321
328
|
return false;
|
|
322
329
|
case "text":
|
|
330
|
+
case "character varying":
|
|
323
331
|
case "varchar":
|
|
332
|
+
case "char":
|
|
324
333
|
return "";
|
|
325
|
-
case "jsonb":
|
|
326
334
|
case "json":
|
|
327
|
-
|
|
335
|
+
case "jsonb":
|
|
336
|
+
return "{}";
|
|
337
|
+
case "timestamp":
|
|
338
|
+
case "timestamptz":
|
|
339
|
+
case "timestamp with time zone":
|
|
340
|
+
case "timestamp without time zone":
|
|
341
|
+
return new Date().toISOString();
|
|
342
|
+
case "date":
|
|
343
|
+
return new Date().toISOString().split("T")[0];
|
|
328
344
|
default:
|
|
329
|
-
if (udtName.startsWith("enum_")) {
|
|
330
|
-
return null; // Enums should be handled by prepareEnumValue
|
|
331
|
-
}
|
|
332
345
|
return null;
|
|
333
346
|
}
|
|
334
347
|
}
|
|
335
348
|
|
|
349
|
+
private extractEnumValueFromObject(value: any): string | null {
|
|
350
|
+
if (typeof value === "object" && value !== null) {
|
|
351
|
+
// Intentar extraer el valor de propiedades comunes que podrían contener el valor enum
|
|
352
|
+
const possibleProperties = ["value", "type", "name", "code"];
|
|
353
|
+
for (const prop of possibleProperties) {
|
|
354
|
+
if (value[prop] && typeof value[prop] === "string") {
|
|
355
|
+
return value[prop].trim();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private async handleEnumType(
|
|
363
|
+
value: any,
|
|
364
|
+
columnName: string,
|
|
365
|
+
targetColumn: ColumnSchema,
|
|
366
|
+
tenantId: string
|
|
367
|
+
): Promise<string | null> {
|
|
368
|
+
// Si el valor es null y la columna permite null
|
|
369
|
+
if (value === null && targetColumn.is_nullable === "YES") {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Obtener el tipo específico de enum
|
|
374
|
+
const enumType = targetColumn.udt_name;
|
|
375
|
+
|
|
376
|
+
// Validar el valor según el tipo específico de enum
|
|
377
|
+
const validatedValue = await this.validateEnumValue(
|
|
378
|
+
tenantId,
|
|
379
|
+
columnName,
|
|
380
|
+
value,
|
|
381
|
+
targetColumn.is_nullable === "YES"
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
return validatedValue;
|
|
385
|
+
}
|
|
386
|
+
|
|
336
387
|
async transformRecord(
|
|
337
388
|
record: any,
|
|
338
389
|
sourceSchema: ColumnSchema[],
|
|
@@ -343,9 +394,7 @@ export class DataTransformer {
|
|
|
343
394
|
|
|
344
395
|
for (const [key, value] of Object.entries(record)) {
|
|
345
396
|
// Primero intentar encontrar una coincidencia exacta
|
|
346
|
-
let targetColumn = targetSchema.find(
|
|
347
|
-
(col) => col.column_name === key
|
|
348
|
-
);
|
|
397
|
+
let targetColumn = targetSchema.find((col) => col.column_name === key);
|
|
349
398
|
|
|
350
399
|
// Si no se encuentra coincidencia exacta, intentar case-insensitive
|
|
351
400
|
if (!targetColumn) {
|
|
@@ -361,7 +410,7 @@ export class DataTransformer {
|
|
|
361
410
|
|
|
362
411
|
// Usar el nombre exacto de la columna del schema destino
|
|
363
412
|
const columnName = targetColumn.column_name;
|
|
364
|
-
|
|
413
|
+
|
|
365
414
|
const transformedValue = await this.transformColumnValue(
|
|
366
415
|
value,
|
|
367
416
|
columnName,
|
|
@@ -1,20 +1,54 @@
|
|
|
1
1
|
import { PrismaClient } from "@prisma/client";
|
|
2
2
|
import * as pg from "pg";
|
|
3
3
|
import * as dotenv from "dotenv";
|
|
4
|
-
import { DatabaseConnections } from "./types";
|
|
4
|
+
import { DatabaseConnections, DatabaseConnection } from "./types";
|
|
5
5
|
|
|
6
6
|
dotenv.config();
|
|
7
7
|
|
|
8
|
+
export interface SourceDatabase {
|
|
9
|
+
id: string;
|
|
10
|
+
url: string;
|
|
11
|
+
name: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
8
14
|
export class DatabaseConnector {
|
|
9
|
-
static createConnections(
|
|
10
|
-
|
|
15
|
+
static createConnections(
|
|
16
|
+
sourceDatabases?: SourceDatabase[]
|
|
17
|
+
): DatabaseConnections {
|
|
18
|
+
// Crear conexiones para cada base de datos de origen
|
|
19
|
+
const sourceConnections: DatabaseConnection[] = (sourceDatabases || []).map(
|
|
20
|
+
(source): DatabaseConnection => {
|
|
21
|
+
// Conexión de pool para la base de datos de origen
|
|
22
|
+
const pool = new pg.Pool({
|
|
23
|
+
connectionString: source.url,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Cliente Prisma para la base de datos de origen
|
|
27
|
+
const prisma = new PrismaClient({
|
|
28
|
+
datasources: {
|
|
29
|
+
db: {
|
|
30
|
+
url: source.url,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const connection: DatabaseConnection = {
|
|
36
|
+
pool,
|
|
37
|
+
prisma,
|
|
38
|
+
sourceId: source.id,
|
|
39
|
+
async query(sql: string, params?: any[]) {
|
|
40
|
+
return pool.query(sql, params);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return connection;
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
11
48
|
const sourcePool = new pg.Pool({
|
|
12
49
|
connectionString: process.env.SOURCE_DATABASE_URL,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// Target database connection (our multi-tenant database)
|
|
16
|
-
const targetPool = new pg.Pool({
|
|
17
|
-
connectionString: process.env.DATABASE_URL,
|
|
50
|
+
max: 20,
|
|
51
|
+
idleTimeoutMillis: 30000,
|
|
18
52
|
});
|
|
19
53
|
|
|
20
54
|
// Source Prisma client
|
|
@@ -26,6 +60,13 @@ export class DatabaseConnector {
|
|
|
26
60
|
},
|
|
27
61
|
});
|
|
28
62
|
|
|
63
|
+
// Target database connection (our multi-tenant database)
|
|
64
|
+
const targetPool = new pg.Pool({
|
|
65
|
+
connectionString: process.env.DATABASE_URL,
|
|
66
|
+
max: 20,
|
|
67
|
+
idleTimeoutMillis: 30000,
|
|
68
|
+
});
|
|
69
|
+
|
|
29
70
|
// Target Prisma client
|
|
30
71
|
const targetPrisma = new PrismaClient({
|
|
31
72
|
datasources: {
|
|
@@ -36,10 +77,11 @@ export class DatabaseConnector {
|
|
|
36
77
|
});
|
|
37
78
|
|
|
38
79
|
return {
|
|
39
|
-
|
|
80
|
+
sourceConnections,
|
|
40
81
|
targetPrisma,
|
|
41
|
-
sourcePool,
|
|
42
82
|
targetPool,
|
|
83
|
+
sourcePool,
|
|
84
|
+
sourcePrisma,
|
|
43
85
|
};
|
|
44
86
|
}
|
|
45
87
|
|
|
@@ -59,9 +101,20 @@ export class DatabaseConnector {
|
|
|
59
101
|
}
|
|
60
102
|
|
|
61
103
|
static async cleanup(connections: DatabaseConnections): Promise<void> {
|
|
62
|
-
|
|
104
|
+
// Desconectar todas las conexiones de origen
|
|
105
|
+
if (connections.sourceConnections) {
|
|
106
|
+
for (const sourceConn of connections.sourceConnections) {
|
|
107
|
+
if (sourceConn.pool) await sourceConn.pool.end();
|
|
108
|
+
if (sourceConn.prisma) await sourceConn.prisma.$disconnect();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Desconectar conexiones de destino
|
|
63
113
|
await connections.targetPrisma.$disconnect();
|
|
64
|
-
await connections.sourcePool.end();
|
|
65
114
|
await connections.targetPool.end();
|
|
115
|
+
|
|
116
|
+
// Desconectar conexiones principales de origen si existen
|
|
117
|
+
if (connections.sourcePrisma) await connections.sourcePrisma.$disconnect();
|
|
118
|
+
if (connections.sourcePool) await connections.sourcePool.end();
|
|
66
119
|
}
|
|
67
|
-
}
|
|
120
|
+
}
|