@javalabs/prisma-client 1.0.4 → 1.0.8
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/database-initializer.d.ts +5 -0
- package/dist/scripts/database-initializer.js +45 -0
- package/dist/scripts/database-initializer.js.map +1 -0
- 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/post-migration-validator.d.ts +18 -5
- package/dist/scripts/post-migration-validator.js +61 -39
- package/dist/scripts/post-migration-validator.js.map +1 -1
- package/dist/scripts/run-migration.js +83 -96
- package/dist/scripts/run-migration.js.map +1 -1
- package/dist/scripts/sequence-synchronizer.d.ts +8 -0
- package/dist/scripts/sequence-synchronizer.js +88 -0
- package/dist/scripts/sequence-synchronizer.js.map +1 -0
- 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 +10 -3
- package/prisma/migrations/{20250422001248_add_bank_account_id → 0_init}/migration.sql +269 -47
- package/prisma/migrations/20250505171324_add_bank_owner_name_in_manual_transfer/migration.sql +2 -0
- package/prisma/migrations/20250505173850_add_method_name_in_manual_transfer/migration.sql +2 -0
- package/prisma/migrations/migration_lock.toml +2 -2
- package/prisma/schema.prisma +214 -77
- 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/database-initializer.ts +49 -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 +140 -124
- package/src/scripts/sequence-synchronizer.ts +127 -0
- package/prisma/migrations/20250422001957_add_bank_account_id_relations/migration.sql +0 -14
- package/src/scripts/dumps/source_dump_20250413_112626.sql +0 -1527
|
@@ -4,7 +4,7 @@ import { ColumnSchema } from "./types";
|
|
|
4
4
|
export class SchemaUtils {
|
|
5
5
|
private readonly logger = new Logger("SchemaUtils");
|
|
6
6
|
|
|
7
|
-
constructor(private readonly
|
|
7
|
+
constructor(private readonly connections: any) {}
|
|
8
8
|
|
|
9
9
|
// En el método getTableSchema de SchemaUtils
|
|
10
10
|
async getTableSchema(
|
|
@@ -12,8 +12,11 @@ export class SchemaUtils {
|
|
|
12
12
|
dbType: "source" | "target",
|
|
13
13
|
schema: string = "public"
|
|
14
14
|
): Promise<ColumnSchema[]> {
|
|
15
|
-
const pool =
|
|
16
|
-
|
|
15
|
+
const pool =
|
|
16
|
+
dbType === "source"
|
|
17
|
+
? this.connections.sourcePool
|
|
18
|
+
: this.connections.targetPool;
|
|
19
|
+
|
|
17
20
|
const query = `
|
|
18
21
|
SELECT
|
|
19
22
|
c.table_name,
|
|
@@ -37,13 +40,15 @@ export class SchemaUtils {
|
|
|
37
40
|
ORDER BY
|
|
38
41
|
c.ordinal_position
|
|
39
42
|
`;
|
|
40
|
-
|
|
43
|
+
|
|
41
44
|
try {
|
|
42
45
|
const result = await pool.query(query, [schema, tableName]);
|
|
43
46
|
// No transformar los nombres de las columnas, mantener el case original
|
|
44
47
|
return result.rows;
|
|
45
48
|
} catch (error) {
|
|
46
|
-
this.logger.error(
|
|
49
|
+
this.logger.error(
|
|
50
|
+
`Error getting schema for ${schema}.${tableName}: ${error.message}`
|
|
51
|
+
);
|
|
47
52
|
return [];
|
|
48
53
|
}
|
|
49
54
|
}
|
|
@@ -55,7 +60,9 @@ export class SchemaUtils {
|
|
|
55
60
|
FROM information_schema.schemata
|
|
56
61
|
WHERE schema_name = $1
|
|
57
62
|
`;
|
|
58
|
-
const result = await this.targetPool.query(query, [
|
|
63
|
+
const result = await this.connections.targetPool.query(query, [
|
|
64
|
+
schemaName,
|
|
65
|
+
]);
|
|
59
66
|
return result.rows.length > 0;
|
|
60
67
|
} catch (error) {
|
|
61
68
|
this.logger.error(`Error checking if schema exists: ${error.message}`);
|
|
@@ -71,14 +78,18 @@ export class SchemaUtils {
|
|
|
71
78
|
WHERE table_schema = $1
|
|
72
79
|
AND table_type = 'BASE TABLE'
|
|
73
80
|
`;
|
|
74
|
-
const result = await this.targetPool.query(query, [
|
|
75
|
-
|
|
81
|
+
const result = await this.connections.targetPool.query(query, [
|
|
82
|
+
schemaName,
|
|
83
|
+
]);
|
|
84
|
+
|
|
76
85
|
// Log para depuración
|
|
77
|
-
this.logger.log(
|
|
78
|
-
|
|
86
|
+
this.logger.log(
|
|
87
|
+
`Found ${result.rows.length} tables in schema ${schemaName}`
|
|
88
|
+
);
|
|
89
|
+
result.rows.forEach((row) => {
|
|
79
90
|
this.logger.log(`Table in schema ${schemaName}: ${row.table_name}`);
|
|
80
91
|
});
|
|
81
|
-
|
|
92
|
+
|
|
82
93
|
return result.rows.map((row) => row.table_name);
|
|
83
94
|
} catch (error) {
|
|
84
95
|
this.logger.error(`Error getting target tables: ${error.message}`);
|
|
@@ -86,10 +97,7 @@ export class SchemaUtils {
|
|
|
86
97
|
}
|
|
87
98
|
}
|
|
88
99
|
|
|
89
|
-
async getEnumValues(
|
|
90
|
-
schemaName: string,
|
|
91
|
-
enumName: string
|
|
92
|
-
): Promise<string[]> {
|
|
100
|
+
async getEnumValues(schemaName: string, enumName: string): Promise<string[]> {
|
|
93
101
|
try {
|
|
94
102
|
// Consulta para obtener los valores posibles de un enum
|
|
95
103
|
const query = `
|
|
@@ -100,7 +108,10 @@ export class SchemaUtils {
|
|
|
100
108
|
WHERE t.typname = $1
|
|
101
109
|
AND n.nspname = $2
|
|
102
110
|
`;
|
|
103
|
-
const result = await this.targetPool.query(query, [
|
|
111
|
+
const result = await this.connections.targetPool.query(query, [
|
|
112
|
+
enumName,
|
|
113
|
+
schemaName,
|
|
114
|
+
]);
|
|
104
115
|
return result.rows.map((row) => row.enumlabel);
|
|
105
116
|
} catch (error) {
|
|
106
117
|
this.logger.error(
|
|
@@ -111,26 +122,30 @@ export class SchemaUtils {
|
|
|
111
122
|
}
|
|
112
123
|
|
|
113
124
|
async executeQuery(query: string, params: any[] = []): Promise<any> {
|
|
114
|
-
return this.targetPool.query(query, params);
|
|
125
|
+
return this.connections.targetPool.query(query, params);
|
|
115
126
|
}
|
|
116
127
|
|
|
117
128
|
async queryTargetDb(query: string, params: any[]): Promise<{ rows: any[] }> {
|
|
118
|
-
return this.targetPool.query(query, params);
|
|
129
|
+
return this.connections.targetPool.query(query, params);
|
|
119
130
|
}
|
|
120
131
|
|
|
121
|
-
async createMissingColumns(
|
|
132
|
+
async createMissingColumns(
|
|
133
|
+
tableName: string,
|
|
134
|
+
schema: string,
|
|
135
|
+
columns: ColumnSchema[]
|
|
136
|
+
) {
|
|
122
137
|
for (const column of columns) {
|
|
123
138
|
try {
|
|
124
139
|
const query = `
|
|
125
140
|
ALTER TABLE "${schema}"."${tableName}"
|
|
126
141
|
ADD COLUMN IF NOT EXISTS "${column.column_name}" ${column.data_type}
|
|
127
|
-
${column.is_nullable ===
|
|
128
|
-
${column.column_default ? `DEFAULT ${column.column_default}` :
|
|
142
|
+
${column.is_nullable === "NO" ? "NOT NULL" : ""}
|
|
143
|
+
${column.column_default ? `DEFAULT ${column.column_default}` : ""}
|
|
129
144
|
`;
|
|
130
|
-
await this.targetPool.query(query);
|
|
145
|
+
await this.connections.targetPool.query(query);
|
|
131
146
|
|
|
132
147
|
// Add unique constraint if needed
|
|
133
|
-
if (column.constraint_type ===
|
|
148
|
+
if (column.constraint_type === "UNIQUE") {
|
|
134
149
|
const constraintName = `${tableName}_${column.column_name}_unique`;
|
|
135
150
|
const uniqueQuery = `
|
|
136
151
|
DO $$
|
|
@@ -143,10 +158,12 @@ export class SchemaUtils {
|
|
|
143
158
|
END IF;
|
|
144
159
|
END $$;
|
|
145
160
|
`;
|
|
146
|
-
await this.targetPool.query(uniqueQuery);
|
|
161
|
+
await this.connections.targetPool.query(uniqueQuery);
|
|
147
162
|
}
|
|
148
163
|
} catch (error) {
|
|
149
|
-
this.logger.error(
|
|
164
|
+
this.logger.error(
|
|
165
|
+
`Error creating column ${column.column_name}: ${error.message}`
|
|
166
|
+
);
|
|
150
167
|
throw error;
|
|
151
168
|
}
|
|
152
169
|
}
|
|
@@ -155,13 +172,15 @@ export class SchemaUtils {
|
|
|
155
172
|
async createSchema(schemaName: string): Promise<void> {
|
|
156
173
|
try {
|
|
157
174
|
// Create schema
|
|
158
|
-
await this.targetPool.query(
|
|
175
|
+
await this.connections.targetPool.query(
|
|
176
|
+
`CREATE SCHEMA IF NOT EXISTS "${schemaName}"`
|
|
177
|
+
);
|
|
159
178
|
|
|
160
179
|
// Copy table structure from public schema
|
|
161
180
|
const tables = await this.getPublicTables();
|
|
162
|
-
|
|
181
|
+
|
|
163
182
|
for (const table of tables) {
|
|
164
|
-
await this.targetPool.query(`
|
|
183
|
+
await this.connections.targetPool.query(`
|
|
165
184
|
CREATE TABLE IF NOT EXISTS "${schemaName}"."${table}" (
|
|
166
185
|
LIKE public."${table}" INCLUDING ALL
|
|
167
186
|
);
|
|
@@ -170,17 +189,60 @@ export class SchemaUtils {
|
|
|
170
189
|
|
|
171
190
|
this.logger.log(`Created schema ${schemaName} with all tables`);
|
|
172
191
|
} catch (error) {
|
|
173
|
-
this.logger.error(
|
|
192
|
+
this.logger.error(
|
|
193
|
+
`Error creating schema ${schemaName}: ${error.message}`
|
|
194
|
+
);
|
|
174
195
|
throw error;
|
|
175
196
|
}
|
|
176
197
|
}
|
|
177
198
|
|
|
178
199
|
private async getPublicTables(): Promise<string[]> {
|
|
179
|
-
const result = await this.targetPool.query(`
|
|
200
|
+
const result = await this.connections.targetPool.query(`
|
|
180
201
|
SELECT tablename
|
|
181
202
|
FROM pg_tables
|
|
182
203
|
WHERE schemaname = 'public'
|
|
183
204
|
`);
|
|
184
|
-
return result.rows.map(row => row.tablename);
|
|
205
|
+
return result.rows.map((row) => row.tablename);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async getTableColumns(
|
|
209
|
+
schema: string,
|
|
210
|
+
table: string
|
|
211
|
+
): Promise<ColumnSchema[]> {
|
|
212
|
+
const query = `
|
|
213
|
+
SELECT
|
|
214
|
+
column_name,
|
|
215
|
+
data_type,
|
|
216
|
+
is_nullable,
|
|
217
|
+
column_default,
|
|
218
|
+
character_maximum_length,
|
|
219
|
+
numeric_precision,
|
|
220
|
+
numeric_scale
|
|
221
|
+
FROM information_schema.columns
|
|
222
|
+
WHERE table_schema = $1
|
|
223
|
+
AND table_name = $2
|
|
224
|
+
ORDER BY ordinal_position;
|
|
225
|
+
`;
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const result = await this.connections.sourcePool.query(query, [
|
|
229
|
+
schema,
|
|
230
|
+
table,
|
|
231
|
+
]);
|
|
232
|
+
return result.rows.map((row: any) => ({
|
|
233
|
+
column_name: row.column_name,
|
|
234
|
+
data_type: row.data_type,
|
|
235
|
+
is_nullable: row.is_nullable,
|
|
236
|
+
column_default: row.column_default,
|
|
237
|
+
character_maximum_length: row.character_maximum_length,
|
|
238
|
+
numeric_precision: row.numeric_precision,
|
|
239
|
+
numeric_scale: row.numeric_scale,
|
|
240
|
+
}));
|
|
241
|
+
} catch (error) {
|
|
242
|
+
this.logger.error(
|
|
243
|
+
`Error obteniendo columnas para ${schema}.${table}: ${error.message}`
|
|
244
|
+
);
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
185
247
|
}
|
|
186
|
-
}
|
|
248
|
+
}
|
|
@@ -162,7 +162,7 @@ export class TenantMigrator {
|
|
|
162
162
|
const bIndex = sortedTables.indexOf(b.name);
|
|
163
163
|
return aIndex - bIndex;
|
|
164
164
|
})
|
|
165
|
-
.filter(entity => sortedTables.includes(entity.name));
|
|
165
|
+
.filter((entity) => sortedTables.includes(entity.name));
|
|
166
166
|
|
|
167
167
|
this.logger.log(
|
|
168
168
|
`Migration order: ${orderedEntities.map((e) => e.name).join(" -> ")}`
|
|
@@ -175,10 +175,17 @@ export class TenantMigrator {
|
|
|
175
175
|
// Migrate each entity type separately to avoid transaction timeouts
|
|
176
176
|
for (const entity of orderedEntities) {
|
|
177
177
|
await this.batchMigrator.migrateEntityDataInBatches(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
"public", // esquema fuente
|
|
179
|
+
tenantId, // esquema destino
|
|
180
|
+
{
|
|
181
|
+
type: "tenant",
|
|
182
|
+
idField: entity.idField,
|
|
183
|
+
sourceTable: entity.name,
|
|
184
|
+
targetTable: entity.name,
|
|
185
|
+
filterColumn: entity.filterColumn,
|
|
186
|
+
via: entity.filterVia,
|
|
187
|
+
},
|
|
188
|
+
providerId?.toString()
|
|
182
189
|
);
|
|
183
190
|
}
|
|
184
191
|
|
|
@@ -1,38 +1,193 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PrismaClient } from "@prisma/client";
|
|
2
2
|
|
|
3
3
|
export class TypecastManager {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
4
|
+
private readonly prisma = new PrismaClient();
|
|
5
|
+
private readonly cache = new Map<string, Map<string, string>>(); // cache tabla -> columna -> typecast
|
|
6
|
+
|
|
7
|
+
private readonly compatibilityMap: Record<string, string[]> = {
|
|
8
|
+
integer: ["bigint", "smallint", "numeric", "decimal"],
|
|
9
|
+
bigint: ["integer", "numeric", "decimal"],
|
|
10
|
+
smallint: ["integer", "bigint", "numeric", "decimal"],
|
|
11
|
+
numeric: [
|
|
12
|
+
"integer",
|
|
13
|
+
"bigint",
|
|
14
|
+
"smallint",
|
|
15
|
+
"decimal",
|
|
16
|
+
"real",
|
|
17
|
+
"double precision",
|
|
18
|
+
],
|
|
19
|
+
decimal: [
|
|
20
|
+
"numeric",
|
|
21
|
+
"integer",
|
|
22
|
+
"bigint",
|
|
23
|
+
"smallint",
|
|
24
|
+
"real",
|
|
25
|
+
"double precision",
|
|
26
|
+
],
|
|
27
|
+
real: ["numeric", "decimal", "double precision"],
|
|
28
|
+
"double precision": ["numeric", "decimal", "real"],
|
|
29
|
+
"character varying": ["text", "char", "varchar"],
|
|
30
|
+
varchar: ["text", "character varying", "char"],
|
|
31
|
+
char: ["text", "character varying", "varchar"],
|
|
32
|
+
text: ["character varying", "varchar", "char"],
|
|
33
|
+
timestamp: [
|
|
34
|
+
"timestamptz",
|
|
35
|
+
"timestamp without time zone",
|
|
36
|
+
"timestamp with time zone",
|
|
37
|
+
],
|
|
38
|
+
timestamptz: [
|
|
39
|
+
"timestamp",
|
|
40
|
+
"timestamp without time zone",
|
|
41
|
+
"timestamp with time zone",
|
|
42
|
+
],
|
|
43
|
+
date: ["timestamp", "timestamptz"],
|
|
44
|
+
boolean: ["bool"],
|
|
45
|
+
json: ["jsonb"],
|
|
46
|
+
jsonb: ["json"],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
async getTypecastForColumn(
|
|
50
|
+
tableName: string,
|
|
51
|
+
columnName: string
|
|
52
|
+
): Promise<string | undefined> {
|
|
53
|
+
if (!this.cache.has(tableName)) {
|
|
54
|
+
await this.loadTableSchema(tableName);
|
|
55
|
+
}
|
|
56
|
+
const tableSchema = this.cache.get(tableName);
|
|
57
|
+
return tableSchema?.get(columnName);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private async loadTableSchema(tableName: string) {
|
|
61
|
+
const columns = await this.prisma.$queryRawUnsafe<any[]>(`
|
|
62
|
+
SELECT column_name, data_type, udt_name, is_nullable
|
|
63
|
+
FROM information_schema.columns
|
|
64
|
+
WHERE table_name = '${tableName}'
|
|
65
|
+
`);
|
|
66
|
+
|
|
67
|
+
const tableSchema = new Map<string, string>();
|
|
68
|
+
|
|
69
|
+
for (const column of columns) {
|
|
70
|
+
const { column_name, data_type, udt_name, is_nullable } = column;
|
|
71
|
+
|
|
72
|
+
let typecast: string | undefined;
|
|
73
|
+
|
|
74
|
+
switch (data_type.toLowerCase()) {
|
|
75
|
+
case "timestamp with time zone":
|
|
76
|
+
case "timestamp without time zone":
|
|
77
|
+
case "timestamptz":
|
|
78
|
+
typecast = "::timestamp with time zone";
|
|
79
|
+
break;
|
|
80
|
+
case "timestamp":
|
|
81
|
+
typecast = "::timestamp";
|
|
82
|
+
break;
|
|
83
|
+
case "integer":
|
|
84
|
+
typecast = "::integer";
|
|
85
|
+
break;
|
|
86
|
+
case "bigint":
|
|
87
|
+
typecast = "::bigint";
|
|
88
|
+
break;
|
|
89
|
+
case "boolean":
|
|
90
|
+
typecast = "::boolean";
|
|
91
|
+
break;
|
|
92
|
+
case "numeric":
|
|
93
|
+
case "decimal":
|
|
94
|
+
typecast = "::numeric";
|
|
95
|
+
break;
|
|
96
|
+
case "double precision":
|
|
97
|
+
typecast = "::double precision";
|
|
98
|
+
break;
|
|
99
|
+
case "text":
|
|
100
|
+
case "character varying":
|
|
101
|
+
case "varchar":
|
|
102
|
+
typecast = "::text";
|
|
103
|
+
break;
|
|
104
|
+
case "date":
|
|
105
|
+
typecast = "::date";
|
|
106
|
+
break;
|
|
107
|
+
case "time":
|
|
108
|
+
case "time without time zone":
|
|
109
|
+
typecast = "::time";
|
|
110
|
+
break;
|
|
111
|
+
case "time with time zone":
|
|
112
|
+
typecast = "::timetz";
|
|
113
|
+
break;
|
|
114
|
+
case "json":
|
|
115
|
+
typecast = "::json";
|
|
116
|
+
break;
|
|
117
|
+
case "jsonb":
|
|
118
|
+
typecast = "::jsonb";
|
|
119
|
+
break;
|
|
120
|
+
case "uuid":
|
|
121
|
+
typecast = "::uuid";
|
|
122
|
+
break;
|
|
123
|
+
default:
|
|
124
|
+
if (udt_name.startsWith("_")) {
|
|
125
|
+
// Arrays
|
|
126
|
+
typecast = `::${udt_name.substring(1)}[]`;
|
|
127
|
+
} else if (udt_name.startsWith("enum_")) {
|
|
128
|
+
// Enums
|
|
129
|
+
typecast = `::${udt_name}`;
|
|
130
|
+
} else {
|
|
131
|
+
typecast = undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
tableSchema.set(column_name, typecast);
|
|
27
136
|
}
|
|
137
|
+
|
|
138
|
+
this.cache.set(tableName, tableSchema);
|
|
28
139
|
}
|
|
29
140
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
141
|
+
areTypesCompatible(sourceType: string, targetType: string): boolean {
|
|
142
|
+
// Si los tipos son iguales, son compatibles
|
|
143
|
+
if (sourceType.toLowerCase() === targetType.toLowerCase()) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Verificar si el tipo fuente puede convertirse al tipo destino
|
|
148
|
+
const compatibleTypes =
|
|
149
|
+
this.compatibilityMap[sourceType.toLowerCase()] || [];
|
|
150
|
+
return compatibleTypes.includes(targetType.toLowerCase());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
castValue(value: any, sourceType: string, targetType: string): any {
|
|
154
|
+
if (value === null || value === undefined) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
switch (targetType.toLowerCase()) {
|
|
160
|
+
case "integer":
|
|
161
|
+
case "bigint":
|
|
162
|
+
case "smallint":
|
|
163
|
+
return parseInt(value, 10);
|
|
164
|
+
case "numeric":
|
|
165
|
+
case "decimal":
|
|
166
|
+
case "real":
|
|
167
|
+
case "double precision":
|
|
168
|
+
return parseFloat(value);
|
|
169
|
+
case "boolean":
|
|
170
|
+
return Boolean(value);
|
|
171
|
+
case "json":
|
|
172
|
+
case "jsonb":
|
|
173
|
+
return typeof value === "string" ? JSON.parse(value) : value;
|
|
174
|
+
case "timestamp":
|
|
175
|
+
case "timestamptz":
|
|
176
|
+
return new Date(value).toISOString();
|
|
177
|
+
case "date":
|
|
178
|
+
return new Date(value).toISOString().split("T")[0];
|
|
179
|
+
case "text":
|
|
180
|
+
case "character varying":
|
|
181
|
+
case "varchar":
|
|
182
|
+
case "char":
|
|
183
|
+
return String(value);
|
|
184
|
+
default:
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`Error al convertir valor "${value}" de ${sourceType} a ${targetType}: ${error.message}`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
37
192
|
}
|
|
38
193
|
}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { Pool } from "pg";
|
|
2
2
|
import { PrismaClient } from "@prisma/client";
|
|
3
3
|
|
|
4
|
+
export interface DatabaseConnection {
|
|
5
|
+
pool?: Pool;
|
|
6
|
+
prisma?: PrismaClient;
|
|
7
|
+
query: (sql: string, params?: any[]) => Promise<any>;
|
|
8
|
+
sourceId: string; // Identificador único para la fuente (ej: "colombia", "rd", "guatemala")
|
|
9
|
+
}
|
|
10
|
+
|
|
4
11
|
export interface DatabaseConnections {
|
|
5
|
-
|
|
12
|
+
sourceConnections?: DatabaseConnection[];
|
|
13
|
+
sourcePool?: Pool;
|
|
6
14
|
targetPool: Pool;
|
|
7
|
-
sourcePrisma: PrismaClient;
|
|
8
15
|
targetPrisma: PrismaClient;
|
|
16
|
+
sourcePrisma?: PrismaClient;
|
|
9
17
|
}
|
|
10
18
|
|
|
11
19
|
export interface ColumnSchema {
|
|
@@ -21,10 +29,10 @@ export interface ColumnSchema {
|
|
|
21
29
|
|
|
22
30
|
// Updated EntityType to include filter configuration
|
|
23
31
|
export interface EntityType {
|
|
24
|
-
name: string;
|
|
25
|
-
idField: string;
|
|
32
|
+
name: string; // Table name
|
|
33
|
+
idField: string; // Primary key column name
|
|
26
34
|
filterColumn?: string; // Column used for filtering (either directly or via join)
|
|
27
|
-
filterVia?: string;
|
|
35
|
+
filterVia?: string; // Intermediate table for filtering join (if needed)
|
|
28
36
|
// Keep optional legacy fields for potential backward compatibility or specific use cases
|
|
29
37
|
foreignKey?: string;
|
|
30
38
|
joinTable?: string;
|
|
@@ -38,3 +46,68 @@ export interface EnumCastValue {
|
|
|
38
46
|
value: any;
|
|
39
47
|
enumType: string;
|
|
40
48
|
}
|
|
49
|
+
|
|
50
|
+
export interface MigrationOptions {
|
|
51
|
+
publicOnly?: boolean;
|
|
52
|
+
multiTenant?: boolean;
|
|
53
|
+
sourceSchema?: string;
|
|
54
|
+
targetSchema?: string;
|
|
55
|
+
forceSingleTenant?: boolean;
|
|
56
|
+
configPath?: string;
|
|
57
|
+
batchSize?: number;
|
|
58
|
+
retryAttempts?: number;
|
|
59
|
+
validateData?: boolean;
|
|
60
|
+
logLevel?: "debug" | "info" | "warn" | "error";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ColumnConfig {
|
|
64
|
+
sourceColumn?: string;
|
|
65
|
+
targetColumn?: string;
|
|
66
|
+
type?: string;
|
|
67
|
+
nullable?: boolean;
|
|
68
|
+
defaultValue?: any;
|
|
69
|
+
transform?: (value: any) => any;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface TableConfig {
|
|
73
|
+
type: "public" | "filteredPublic" | "tenantInfo" | "tenant";
|
|
74
|
+
idField: string;
|
|
75
|
+
filterColumn?: string;
|
|
76
|
+
via?: string;
|
|
77
|
+
providerLink?: string;
|
|
78
|
+
tenantKey?: string;
|
|
79
|
+
dependencies?: string[];
|
|
80
|
+
sourceTable?: string;
|
|
81
|
+
targetTable?: string;
|
|
82
|
+
columns?: Record<string, ColumnConfig>;
|
|
83
|
+
skipIfExists?: boolean;
|
|
84
|
+
batchSize?: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ExtendedTableConfig extends TableConfig {
|
|
88
|
+
processed?: boolean;
|
|
89
|
+
retryCount?: number;
|
|
90
|
+
lastError?: Error;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ExtendedMigrationConfig {
|
|
94
|
+
tables: Record<string, ExtendedTableConfig>;
|
|
95
|
+
version: string;
|
|
96
|
+
sourceSchema?: string;
|
|
97
|
+
targetSchema?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface MigrationConfig {
|
|
101
|
+
commonSchema: string;
|
|
102
|
+
tables: Record<string, TableConfig>;
|
|
103
|
+
tenantInfo: {
|
|
104
|
+
sourceTable: string;
|
|
105
|
+
tenantIdColumn: string;
|
|
106
|
+
providerIdColumn: string;
|
|
107
|
+
};
|
|
108
|
+
migrationPriorities?: {
|
|
109
|
+
high?: string[];
|
|
110
|
+
medium?: string[];
|
|
111
|
+
low?: string[];
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { PrismaClient } from "@prisma/client";
|
|
2
|
+
import { exec } from "child_process";
|
|
3
|
+
import { promisify } from "util";
|
|
4
|
+
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
|
|
7
|
+
export class DatabaseInitializer {
|
|
8
|
+
constructor(private readonly databaseUrl: string) {}
|
|
9
|
+
|
|
10
|
+
async initialize(): Promise<void> {
|
|
11
|
+
try {
|
|
12
|
+
console.log("Checking if database needs initialization...");
|
|
13
|
+
|
|
14
|
+
const { Pool } = await import("pg");
|
|
15
|
+
const pool = new Pool({ connectionString: this.databaseUrl });
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// Verificar si existen tablas en la base de datos
|
|
19
|
+
const result = await pool.query(`
|
|
20
|
+
SELECT COUNT(*)
|
|
21
|
+
FROM information_schema.tables
|
|
22
|
+
WHERE table_schema = 'public'
|
|
23
|
+
AND table_type = 'BASE TABLE'
|
|
24
|
+
`);
|
|
25
|
+
|
|
26
|
+
const tableCount = parseInt(result.rows[0].count);
|
|
27
|
+
|
|
28
|
+
if (tableCount === 0) {
|
|
29
|
+
console.log(
|
|
30
|
+
"No tables found in database. Running initial migration..."
|
|
31
|
+
);
|
|
32
|
+
// Ejecutar migración inicial usando Prisma
|
|
33
|
+
await execAsync("npx prisma migrate deploy");
|
|
34
|
+
await execAsync("npx prisma db push");
|
|
35
|
+
console.log("Initial database migration completed successfully.");
|
|
36
|
+
} else {
|
|
37
|
+
console.log(
|
|
38
|
+
`Database already contains ${tableCount} tables. Skipping initialization.`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
} finally {
|
|
42
|
+
await pool.end();
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error("Error initializing database:", error);
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|