@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.
Files changed (150) hide show
  1. package/README.md +220 -0
  2. package/dist/index.d.ts +7 -0
  3. package/dist/index.js +34 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/prisma-factory.service.d.ts +9 -0
  6. package/dist/prisma-factory.service.js +47 -0
  7. package/dist/prisma-factory.service.js.map +1 -0
  8. package/dist/prisma.module.d.ts +2 -0
  9. package/dist/prisma.module.js +23 -0
  10. package/dist/prisma.module.js.map +1 -0
  11. package/dist/prisma.service.d.ts +6 -0
  12. package/dist/prisma.service.js +27 -0
  13. package/dist/prisma.service.js.map +1 -0
  14. package/dist/scripts/create-tenant-schemas.d.ts +1 -0
  15. package/dist/scripts/create-tenant-schemas.js +117 -0
  16. package/dist/scripts/create-tenant-schemas.js.map +1 -0
  17. package/dist/scripts/data-migration/batch-migrator.d.ts +25 -0
  18. package/dist/scripts/data-migration/batch-migrator.js +333 -0
  19. package/dist/scripts/data-migration/batch-migrator.js.map +1 -0
  20. package/dist/scripts/data-migration/data-transformer.d.ts +17 -0
  21. package/dist/scripts/data-migration/data-transformer.js +242 -0
  22. package/dist/scripts/data-migration/data-transformer.js.map +1 -0
  23. package/dist/scripts/data-migration/db-connector.d.ts +7 -0
  24. package/dist/scripts/data-migration/db-connector.js +58 -0
  25. package/dist/scripts/data-migration/db-connector.js.map +1 -0
  26. package/dist/scripts/data-migration/dependency-manager.d.ts +9 -0
  27. package/dist/scripts/data-migration/dependency-manager.js +86 -0
  28. package/dist/scripts/data-migration/dependency-manager.js.map +1 -0
  29. package/dist/scripts/data-migration/dependency-resolver.d.ts +18 -0
  30. package/dist/scripts/data-migration/dependency-resolver.js +251 -0
  31. package/dist/scripts/data-migration/dependency-resolver.js.map +1 -0
  32. package/dist/scripts/data-migration/entity-discovery.d.ts +11 -0
  33. package/dist/scripts/data-migration/entity-discovery.js +152 -0
  34. package/dist/scripts/data-migration/entity-discovery.js.map +1 -0
  35. package/dist/scripts/data-migration/foreign-key-manager.d.ts +17 -0
  36. package/dist/scripts/data-migration/foreign-key-manager.js +70 -0
  37. package/dist/scripts/data-migration/foreign-key-manager.js.map +1 -0
  38. package/dist/scripts/data-migration/migration-phases.d.ts +5 -0
  39. package/dist/scripts/data-migration/migration-phases.js +55 -0
  40. package/dist/scripts/data-migration/migration-phases.js.map +1 -0
  41. package/dist/scripts/data-migration/migration-tool.d.ts +29 -0
  42. package/dist/scripts/data-migration/migration-tool.js +250 -0
  43. package/dist/scripts/data-migration/migration-tool.js.map +1 -0
  44. package/dist/scripts/data-migration/phase-generator.d.ts +15 -0
  45. package/dist/scripts/data-migration/phase-generator.js +187 -0
  46. package/dist/scripts/data-migration/phase-generator.js.map +1 -0
  47. package/dist/scripts/data-migration/schema-utils.d.ts +18 -0
  48. package/dist/scripts/data-migration/schema-utils.js +164 -0
  49. package/dist/scripts/data-migration/schema-utils.js.map +1 -0
  50. package/dist/scripts/data-migration/tenant-migrator.d.ts +15 -0
  51. package/dist/scripts/data-migration/tenant-migrator.js +110 -0
  52. package/dist/scripts/data-migration/tenant-migrator.js.map +1 -0
  53. package/dist/scripts/data-migration/typecast-manager.d.ts +5 -0
  54. package/dist/scripts/data-migration/typecast-manager.js +35 -0
  55. package/dist/scripts/data-migration/typecast-manager.js.map +1 -0
  56. package/dist/scripts/data-migration/types.d.ts +34 -0
  57. package/dist/scripts/data-migration/types.js +3 -0
  58. package/dist/scripts/data-migration/types.js.map +1 -0
  59. package/dist/scripts/data-migration.d.ts +22 -0
  60. package/dist/scripts/data-migration.js +593 -0
  61. package/dist/scripts/data-migration.js.map +1 -0
  62. package/dist/scripts/drop-database.d.ts +10 -0
  63. package/dist/scripts/drop-database.js +81 -0
  64. package/dist/scripts/drop-database.js.map +1 -0
  65. package/dist/scripts/error-handler.d.ts +12 -0
  66. package/dist/scripts/error-handler.js +82 -0
  67. package/dist/scripts/error-handler.js.map +1 -0
  68. package/dist/scripts/fix-data-types.d.ts +10 -0
  69. package/dist/scripts/fix-data-types.js +185 -0
  70. package/dist/scripts/fix-data-types.js.map +1 -0
  71. package/dist/scripts/fix-enum-values.d.ts +17 -0
  72. package/dist/scripts/fix-enum-values.js +234 -0
  73. package/dist/scripts/fix-enum-values.js.map +1 -0
  74. package/dist/scripts/fix-schema-discrepancies.d.ts +21 -0
  75. package/dist/scripts/fix-schema-discrepancies.js +240 -0
  76. package/dist/scripts/fix-schema-discrepancies.js.map +1 -0
  77. package/dist/scripts/migrate-schema-structure.d.ts +1 -0
  78. package/dist/scripts/migrate-schema-structure.js +76 -0
  79. package/dist/scripts/migrate-schema-structure.js.map +1 -0
  80. package/dist/scripts/post-migration-validator.d.ts +21 -0
  81. package/dist/scripts/post-migration-validator.js +341 -0
  82. package/dist/scripts/post-migration-validator.js.map +1 -0
  83. package/dist/scripts/pre-migration-validator.d.ts +25 -0
  84. package/dist/scripts/pre-migration-validator.js +491 -0
  85. package/dist/scripts/pre-migration-validator.js.map +1 -0
  86. package/dist/scripts/reset-database.d.ts +17 -0
  87. package/dist/scripts/reset-database.js +202 -0
  88. package/dist/scripts/reset-database.js.map +1 -0
  89. package/dist/scripts/retry-failed-migrations.d.ts +14 -0
  90. package/dist/scripts/retry-failed-migrations.js +301 -0
  91. package/dist/scripts/retry-failed-migrations.js.map +1 -0
  92. package/dist/scripts/run-migration.d.ts +1 -0
  93. package/dist/scripts/run-migration.js +525 -0
  94. package/dist/scripts/run-migration.js.map +1 -0
  95. package/dist/scripts/schema-sync.d.ts +1 -0
  96. package/dist/scripts/schema-sync.js +85 -0
  97. package/dist/scripts/schema-sync.js.map +1 -0
  98. package/dist/scripts/sync-enum-types.d.ts +13 -0
  99. package/dist/scripts/sync-enum-types.js +139 -0
  100. package/dist/scripts/sync-enum-types.js.map +1 -0
  101. package/dist/scripts/sync-enum-values.d.ts +20 -0
  102. package/dist/scripts/sync-enum-values.js +336 -0
  103. package/dist/scripts/sync-enum-values.js.map +1 -0
  104. package/dist/scripts/truncate-database.d.ts +10 -0
  105. package/dist/scripts/truncate-database.js +100 -0
  106. package/dist/scripts/truncate-database.js.map +1 -0
  107. package/dist/scripts/verify-migration-setup.d.ts +11 -0
  108. package/dist/scripts/verify-migration-setup.js +120 -0
  109. package/dist/scripts/verify-migration-setup.js.map +1 -0
  110. package/dist/tsconfig.tsbuildinfo +1 -0
  111. package/migration-config-public.json +95 -0
  112. package/migration-config.json +95 -0
  113. package/package.json +33 -0
  114. package/prisma/migrations/migration_lock.toml +3 -0
  115. package/prisma/schema.prisma +360 -0
  116. package/src/index.ts +23 -0
  117. package/src/prisma-factory.service.ts +41 -0
  118. package/src/prisma.module.ts +10 -0
  119. package/src/prisma.service.ts +17 -0
  120. package/src/scripts/create-tenant-schemas.ts +146 -0
  121. package/src/scripts/data-migration/batch-migrator.ts +569 -0
  122. package/src/scripts/data-migration/data-transformer.ts +377 -0
  123. package/src/scripts/data-migration/db-connector.ts +67 -0
  124. package/src/scripts/data-migration/dependency-resolver.ts +319 -0
  125. package/src/scripts/data-migration/entity-discovery.ts +197 -0
  126. package/src/scripts/data-migration/foreign-key-manager.ts +95 -0
  127. package/src/scripts/data-migration/migration-tool.ts +357 -0
  128. package/src/scripts/data-migration/schema-utils.ts +186 -0
  129. package/src/scripts/data-migration/tenant-migrator.ts +194 -0
  130. package/src/scripts/data-migration/typecast-manager.ts +38 -0
  131. package/src/scripts/data-migration/types.ts +40 -0
  132. package/src/scripts/drop-database.ts +105 -0
  133. package/src/scripts/dump-source-db.sh +62 -0
  134. package/src/scripts/dumps/source_dump_20250413_112626.sql +1527 -0
  135. package/src/scripts/error-handler.ts +118 -0
  136. package/src/scripts/fix-data-types.ts +242 -0
  137. package/src/scripts/fix-enum-values.ts +357 -0
  138. package/src/scripts/fix-schema-discrepancies.ts +318 -0
  139. package/src/scripts/migrate-schema-structure.ts +90 -0
  140. package/src/scripts/post-migration-validator.ts +427 -0
  141. package/src/scripts/pre-migration-validator.ts +611 -0
  142. package/src/scripts/reset-database.ts +264 -0
  143. package/src/scripts/retry-failed-migrations.ts +416 -0
  144. package/src/scripts/run-migration.ts +691 -0
  145. package/src/scripts/schema-sync.ts +129 -0
  146. package/src/scripts/sync-enum-types.ts +171 -0
  147. package/src/scripts/sync-enum-values.ts +563 -0
  148. package/src/scripts/truncate-database.ts +124 -0
  149. package/src/scripts/verify-migration-setup.ts +136 -0
  150. package/tsconfig.json +18 -0
@@ -0,0 +1,357 @@
1
+ import { Logger } from "@nestjs/common";
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { SchemaUtils } from "./schema-utils";
5
+ import { DataTransformer } from "./data-transformer";
6
+ import { DatabaseConnections, ColumnSchema, EntityType } from "./types";
7
+ import { BatchMigrator } from "./batch-migrator";
8
+ import { ForeignKeyManager } from "./foreign-key-manager";
9
+ import { DependencyResolver } from "./dependency-resolver";
10
+
11
+ interface MigrationOptions {
12
+ publicOnly?: boolean;
13
+ multiTenant?: boolean;
14
+ sourceSchema?: string; // Potentially deprecated with config
15
+ targetSchema?: string; // Potentially deprecated with config
16
+ forceSingleTenant?: boolean; // Alias for publicOnly
17
+ configPath?: string;
18
+ }
19
+
20
+ interface TableConfig {
21
+ type: 'public' | 'filteredPublic' | 'tenantInfo' | 'tenant';
22
+ idField: string;
23
+ filterColumn?: string;
24
+ via?: string;
25
+ providerLink?: string;
26
+ tenantKey?: string;
27
+ }
28
+
29
+ interface MigrationConfig {
30
+ commonSchema: string;
31
+ tenantInfo: {
32
+ sourceTable: string;
33
+ tenantIdColumn: string;
34
+ providerIdColumn: string;
35
+ };
36
+ tables: Record<string, TableConfig>;
37
+ }
38
+
39
+ export class DataMigrationTool {
40
+ private readonly logger = new Logger("DataMigrationTool");
41
+ private readonly schemaUtils: SchemaUtils;
42
+ private readonly dataTransformer: DataTransformer;
43
+ private readonly foreignKeyManager: ForeignKeyManager;
44
+ private readonly batchMigrator: BatchMigrator;
45
+ private readonly dependencyResolver: DependencyResolver;
46
+ private migrationConfig: MigrationConfig;
47
+
48
+ private schemaCache: Record<string, ColumnSchema[]> = {};
49
+ private targetSchemaCache: Record<string, ColumnSchema[]> = {};
50
+
51
+
52
+ constructor(private readonly connections: DatabaseConnections) {
53
+ this.schemaUtils = new SchemaUtils(
54
+ connections.sourcePool,
55
+ connections.targetPool
56
+ );
57
+ this.dataTransformer = new DataTransformer(this.schemaUtils);
58
+ this.foreignKeyManager = new ForeignKeyManager(connections);
59
+ this.dependencyResolver = new DependencyResolver(
60
+ connections.sourcePool,
61
+ connections.targetPool
62
+ );
63
+ this.batchMigrator = new BatchMigrator(
64
+ this.dataTransformer,
65
+ this.schemaUtils,
66
+ connections,
67
+ this.dependencyResolver,
68
+ this.schemaCache,
69
+ this.targetSchemaCache
70
+ );
71
+ }
72
+
73
+ private loadConfig(configPath: string = 'migration-config.json'): void {
74
+ const fullPath = path.resolve(process.cwd(), configPath);
75
+ if (!fs.existsSync(fullPath)) {
76
+ throw new Error(`Migration config file not found at: ${fullPath}`);
77
+ }
78
+ try {
79
+ const fileContent = fs.readFileSync(fullPath, 'utf-8');
80
+ this.migrationConfig = JSON.parse(fileContent);
81
+ this.logger.log(`Loaded migration config from: ${fullPath}`);
82
+ } catch (error) {
83
+ throw new Error(`Error reading or parsing migration config file: ${error.message}`);
84
+ }
85
+ if (!this.migrationConfig.commonSchema || !this.migrationConfig.tables || !this.migrationConfig.tenantInfo) {
86
+ throw new Error('Invalid migration config structure.');
87
+ }
88
+ }
89
+
90
+ async migrateData(options: MigrationOptions = {}) {
91
+ try {
92
+ this.logger.log("Starting data migration process");
93
+ this.loadConfig(options.configPath);
94
+
95
+ // Check the publicOnly/forceSingleTenant flag AFTER loading config
96
+ if (options.forceSingleTenant || options.publicOnly) {
97
+ this.logger.log("Public-only migration requested. Migrating only 'public' type tables...");
98
+ await this.migratePublicTablesOnly(); // Call the dedicated function
99
+ } else {
100
+ this.logger.log("Multi-tenant migration requested based on config...");
101
+ await this.migrateMultiTenantWithConfig();
102
+ }
103
+ } catch (error) {
104
+ this.logger.error(`Migration failed: ${error.message}`);
105
+ console.error(error);
106
+ throw error;
107
+ }
108
+ }
109
+
110
+ // --- Function for Public-Only Migration ---
111
+ private async migratePublicTablesOnly() {
112
+ const { commonSchema, tables: tablesConfig } = this.migrationConfig;
113
+ const allTablesInOrder = await this.dependencyResolver.analyzeDependencies();
114
+
115
+ // Filter only the tables marked as 'public' in the config
116
+ const publicTablesToMigrate = allTablesInOrder.filter(t => tablesConfig[t]?.type === 'public');
117
+
118
+ this.logger.log(`Migrating public tables to '${commonSchema}' schema: ${publicTablesToMigrate.join(", ")}`);
119
+ if (publicTablesToMigrate.length === 0) {
120
+ this.logger.warn("No tables configured as 'public'. Public-only migration will not process any tables.");
121
+ return;
122
+ }
123
+
124
+ await this.connections.targetPool.query(`SET session_replication_role = 'replica';`);
125
+
126
+ for (const tableName of publicTablesToMigrate) {
127
+ const config = tablesConfig[tableName];
128
+ if (!config) {
129
+ this.logger.warn(`Config not found for public table ${tableName}, skipping.`); // Should not happen if filter worked
130
+ continue;
131
+ }
132
+ try {
133
+ await this.batchMigrator.migrateEntityDataInBatches(
134
+ this.connections.targetPrisma,
135
+ { name: tableName, idField: config.idField },
136
+ null, // No provider filter for public-only migration
137
+ commonSchema // Target is the common schema
138
+ );
139
+ } catch (error) {
140
+ this.logger.error(`Error migrating public table ${tableName}: ${error.message}`);
141
+ // Decide whether to stop or continue on error for public-only
142
+ throw error;
143
+ }
144
+ }
145
+
146
+ await this.connections.targetPool.query(`SET session_replication_role = 'origin';`);
147
+ this.logger.log("Finished migrating public tables.");
148
+ }
149
+
150
+
151
+ // Keep validateSourceData (potentially enhanced)
152
+ private async validateSourceData() {
153
+ this.logger.log("Validating source data...");
154
+ try {
155
+ const { sourceTable, providerIdColumn } = this.migrationConfig.tenantInfo;
156
+ const tablesConfig = this.migrationConfig.tables;
157
+
158
+ // Find the config for the transaction-like table and the provider table
159
+ let transactionTableKey = 'transactions'; // Default or find dynamically
160
+ let providerTableKey = 'providers'; // Default or find dynamically
161
+ let transactionProviderFk = providerIdColumn; // Default
162
+
163
+ for (const key in tablesConfig) {
164
+ if (tablesConfig[key].type === 'filteredPublic' && tablesConfig[key].filterColumn === providerIdColumn && !tablesConfig[key].via) {
165
+ transactionTableKey = key;
166
+ transactionProviderFk = tablesConfig[key].filterColumn;
167
+ }
168
+ if (tablesConfig[key].type === 'public' && tablesConfig[key].idField === providerIdColumn) {
169
+ providerTableKey = key;
170
+ }
171
+ }
172
+ this.logger.debug(`Validation check: Using ${transactionTableKey}.${transactionProviderFk} referencing ${providerTableKey}.${providerIdColumn}`);
173
+
174
+ // Dynamically build the query
175
+ // IMPORTANT: Ensure proper SQL identifier quoting if names contain special chars/are reserved words
176
+ const query = `
177
+ SELECT t."${tablesConfig[transactionTableKey].idField}" AS transaction_id, t."${transactionProviderFk}"
178
+ FROM "${transactionTableKey}" t
179
+ LEFT JOIN "${providerTableKey}" p ON t."${transactionProviderFk}" = p."${providerIdColumn}"
180
+ WHERE t."${transactionProviderFk}" IS NOT NULL AND p."${providerIdColumn}" IS NULL;
181
+ `;
182
+
183
+ const invalidTransactions = (await this.connections.sourcePrisma.$queryRawUnsafe(query)) as any[];
184
+
185
+ if (invalidTransactions.length > 0) {
186
+ this.logger.error(`${invalidTransactions.length} records found in ${transactionTableKey} with invalid foreign key reference to ${providerTableKey}.${providerIdColumn}`);
187
+ console.error("Invalid records (limit 10):", invalidTransactions.slice(0, 10));
188
+ throw new Error("Source data validation failed: Invalid foreign key references found. Please check the configuration or fix the source data.");
189
+ } else {
190
+ this.logger.log(`Source data validation passed: All ${transactionTableKey}.${transactionProviderFk} references are valid.`);
191
+ }
192
+ } catch (error) {
193
+ this.logger.error("Error during source data validation:");
194
+ console.error(error);
195
+ throw error;
196
+ }
197
+ }
198
+
199
+
200
+ // Renamed from migrateToTenantSchemas to reflect its purpose
201
+ private async migrateMultiTenantWithConfig() {
202
+ await this.validateSourceData(); // Validate first
203
+
204
+ const { commonSchema, tables: tablesConfig, tenantInfo } = this.migrationConfig;
205
+ const allTablesInOrder = await this.dependencyResolver.analyzeDependencies();
206
+
207
+ // --- PASADA 0: Migrar tablas públicas ---
208
+ this.logger.log(`Migrating public tables to '${commonSchema}' schema...`);
209
+ await this.connections.targetPool.query(`SET session_replication_role = 'replica';`);
210
+ const publicTables = allTablesInOrder.filter(t => tablesConfig[t]?.type === 'public');
211
+
212
+ for (const tableName of publicTables) {
213
+ const config = tablesConfig[tableName];
214
+ if (!config) {
215
+ this.logger.warn(`Skipping migration for table ${tableName}: Not found in config.`);
216
+ continue;
217
+ }
218
+ try {
219
+ await this.batchMigrator.migrateEntityDataInBatches(
220
+ this.connections.targetPrisma,
221
+ { name: tableName, idField: config.idField }, // Pass config details
222
+ null, // No provider filter for public tables
223
+ commonSchema
224
+ );
225
+ } catch (error) {
226
+ this.logger.error(`Error migrating public table ${tableName}: ${error.message}`);
227
+ throw error; // Stop if essential public table fails
228
+ }
229
+ }
230
+ await this.connections.targetPool.query(`SET session_replication_role = 'origin';`);
231
+ this.logger.log("Finished migrating public tables.");
232
+
233
+ // --- Obtener Inquilinos ---
234
+ this.logger.log(`Fetching tenant info from source table '${tenantInfo.sourceTable}'...`);
235
+ const tenants = await this.connections.sourcePrisma[tenantInfo.sourceTable].findMany({
236
+ select: {
237
+ [tenantInfo.tenantIdColumn]: true,
238
+ [tenantInfo.providerIdColumn]: true,
239
+ }
240
+ });
241
+ this.logger.log(`Found ${tenants.length} potential tenants.`);
242
+
243
+ // --- Crear Esquemas y Preparar Migración por Inquilino ---
244
+ const validTenants = new Map<string, number | null>(); // Map tenantId -> providerId
245
+ this.logger.log("Ensuring tenant schemas exist (if strategy requires them)...");
246
+ let requiresTenantSchemas = Object.values(tablesConfig).some(t => t.type === 'tenant');
247
+ if(requiresTenantSchemas) {
248
+ this.logger.log('Tenant-specific schemas are required by config.');
249
+ for (const tenant of tenants) {
250
+ const tenantId = tenant[tenantInfo.tenantIdColumn];
251
+ const providerId = tenant[tenantInfo.providerIdColumn] ?? null;
252
+ if (tenantId && typeof tenantId === 'string' && tenantId !== commonSchema) {
253
+ await this.schemaUtils.createSchema(tenantId);
254
+ validTenants.set(tenantId, providerId);
255
+ this.logger.log(`Ensured schema exists for tenant: ${tenantId}`);
256
+ } else {
257
+ this.logger.warn(`Skipping invalid/public tenant ID: ${tenantId}`);
258
+ }
259
+ }
260
+ } else {
261
+ this.logger.log('No tables configured with type "tenant". Tenant schema creation skipped.');
262
+ // Still populate validTenants map for filteredPublic logic
263
+ for (const tenant of tenants) {
264
+ const tenantId = tenant[tenantInfo.tenantIdColumn];
265
+ const providerId = tenant[tenantInfo.providerIdColumn] ?? null;
266
+ // Use tenantId as key even if schemas aren't created, it represents the logical tenant
267
+ if (tenantId && typeof tenantId === 'string') {
268
+ validTenants.set(tenantId, providerId);
269
+ }
270
+ }
271
+ }
272
+ this.logger.log(`Prepared ${validTenants.size} valid tenants for migration.`);
273
+
274
+ // --- PASADAS POR INQUILINO (para tablas filteredPublic y tenant) ---
275
+ const tablesToMigratePerTenant = allTablesInOrder.filter(t =>
276
+ tablesConfig[t]?.type === 'filteredPublic' || tablesConfig[t]?.type === 'tenant'
277
+ );
278
+ this.logger.log(`Migrating filtered/tenant tables: ${tablesToMigratePerTenant.join(", ")}`);
279
+
280
+ for (const [tenantId, providerId] of validTenants.entries()) {
281
+ // Note: tenantId here might just be logical identifier if not creating schemas
282
+ this.logger.log(`Starting migration pass related to tenant: ${tenantId} (Filtering by Provider ID: ${providerId ?? 'N/A'})`);
283
+ await this.connections.targetPool.query(`SET session_replication_role = 'replica';`);
284
+
285
+ for (const tableName of tablesToMigratePerTenant) {
286
+ const config = tablesConfig[tableName];
287
+ if (!config) {
288
+ this.logger.warn(`Skipping migration for table ${tableName} for tenant ${tenantId}: Not found in config.`);
289
+ continue;
290
+ }
291
+
292
+ // Determine schema destino basado en config.type
293
+ const targetSchemaForTable = config.type === 'tenant' ? tenantId : commonSchema;
294
+
295
+ try {
296
+ const entity: EntityType = {
297
+ name: tableName,
298
+ idField: config.idField,
299
+ filterColumn: config.filterColumn,
300
+ filterVia: config.via
301
+ };
302
+
303
+ await this.batchMigrator.migrateEntityDataInBatches(
304
+ this.connections.targetPrisma,
305
+ entity,
306
+ providerId, // providerId para FILTRAR origen
307
+ targetSchemaForTable // esquema donde INSERTAR
308
+ );
309
+ } catch (error) {
310
+ this.logger.error(`Error migrating table ${tableName} into schema ${targetSchemaForTable} (related to tenant ${tenantId}): ${error.message}`);
311
+ }
312
+ }
313
+ await this.connections.targetPool.query(`SET session_replication_role = 'origin';`);
314
+ this.logger.log(`Finished migration pass related to tenant: ${tenantId}`);
315
+ }
316
+
317
+ this.logger.log("Data migration process completed.");
318
+ // await this.verifyMigration(Array.from(validTenants.keys()));
319
+ }
320
+
321
+ private async verifyMigration(tenantIds: string[]) {
322
+ this.logger.log("Verifying migrated data...");
323
+ // Verificar commonSchema
324
+ try {
325
+ const publicResult = await this.connections.targetPool.query(
326
+ `SELECT tablename, n_live_tup FROM pg_stat_user_tables WHERE schemaname = $1`,
327
+ [this.migrationConfig.commonSchema]
328
+ );
329
+ this.logger.log(`Verification for schema '${this.migrationConfig.commonSchema}':`);
330
+ publicResult.rows.forEach(row => this.logger.log(` Table: ${row.tablename}, Rows: ${row.n_live_tup}`));
331
+ } catch (err) {
332
+ this.logger.error(`Error verifying public schema: ${err.message}`);
333
+ }
334
+
335
+ // Verificar schemas de inquilinos (only if they were supposed to be created)
336
+ let requiresTenantSchemas = Object.values(this.migrationConfig.tables).some(t => t.type === 'tenant');
337
+ if (requiresTenantSchemas) {
338
+ for (const tenantId of tenantIds) {
339
+ try {
340
+ const tenantResult = await this.connections.targetPool.query(
341
+ `SELECT tablename, n_live_tup FROM pg_stat_user_tables WHERE schemaname = $1`,
342
+ [tenantId]
343
+ );
344
+ this.logger.log(`Verification for tenant schema '${tenantId}':`);
345
+ if (tenantResult.rows.length === 0) {
346
+ this.logger.warn(` Schema ${tenantId} has no tables (or pg_stat_user_tables is empty).`);
347
+ } else {
348
+ tenantResult.rows.forEach(row => this.logger.log(` Table: ${row.tablename}, Rows: ${row.n_live_tup}`));
349
+ }
350
+ } catch (err) {
351
+ this.logger.error(`Error verifying tenant schema ${tenantId}: ${err.message}`);
352
+ }
353
+ }
354
+ }
355
+ this.logger.log("Verification finished.");
356
+ }
357
+ }
@@ -0,0 +1,186 @@
1
+ import { Logger } from "@nestjs/common";
2
+ import { ColumnSchema } from "./types";
3
+
4
+ export class SchemaUtils {
5
+ private readonly logger = new Logger("SchemaUtils");
6
+
7
+ constructor(private readonly sourcePool: any, private readonly targetPool: any) {}
8
+
9
+ // En el método getTableSchema de SchemaUtils
10
+ async getTableSchema(
11
+ tableName: string,
12
+ dbType: "source" | "target",
13
+ schema: string = "public"
14
+ ): Promise<ColumnSchema[]> {
15
+ const pool = dbType === "source" ? this.sourcePool : this.targetPool;
16
+
17
+ const query = `
18
+ SELECT
19
+ c.table_name,
20
+ c.column_name,
21
+ c.data_type,
22
+ c.udt_name,
23
+ c.character_maximum_length,
24
+ c.is_nullable,
25
+ c.column_default,
26
+ tc.constraint_type
27
+ FROM
28
+ information_schema.columns c
29
+ LEFT JOIN information_schema.constraint_column_usage ccu
30
+ ON c.column_name = ccu.column_name
31
+ AND c.table_name = ccu.table_name
32
+ LEFT JOIN information_schema.table_constraints tc
33
+ ON ccu.constraint_name = tc.constraint_name
34
+ WHERE
35
+ c.table_schema = $1
36
+ AND c.table_name = $2
37
+ ORDER BY
38
+ c.ordinal_position
39
+ `;
40
+
41
+ try {
42
+ const result = await pool.query(query, [schema, tableName]);
43
+ // No transformar los nombres de las columnas, mantener el case original
44
+ return result.rows;
45
+ } catch (error) {
46
+ this.logger.error(`Error getting schema for ${schema}.${tableName}: ${error.message}`);
47
+ return [];
48
+ }
49
+ }
50
+
51
+ async checkSchemaExists(schemaName: string): Promise<boolean> {
52
+ try {
53
+ const query = `
54
+ SELECT schema_name
55
+ FROM information_schema.schemata
56
+ WHERE schema_name = $1
57
+ `;
58
+ const result = await this.targetPool.query(query, [schemaName]);
59
+ return result.rows.length > 0;
60
+ } catch (error) {
61
+ this.logger.error(`Error checking if schema exists: ${error.message}`);
62
+ return false;
63
+ }
64
+ }
65
+
66
+ async getTargetTables(schemaName: string): Promise<string[]> {
67
+ try {
68
+ const query = `
69
+ SELECT table_name
70
+ FROM information_schema.tables
71
+ WHERE table_schema = $1
72
+ AND table_type = 'BASE TABLE'
73
+ `;
74
+ const result = await this.targetPool.query(query, [schemaName]);
75
+
76
+ // Log para depuración
77
+ this.logger.log(`Found ${result.rows.length} tables in schema ${schemaName}`);
78
+ result.rows.forEach(row => {
79
+ this.logger.log(`Table in schema ${schemaName}: ${row.table_name}`);
80
+ });
81
+
82
+ return result.rows.map((row) => row.table_name);
83
+ } catch (error) {
84
+ this.logger.error(`Error getting target tables: ${error.message}`);
85
+ return [];
86
+ }
87
+ }
88
+
89
+ async getEnumValues(
90
+ schemaName: string,
91
+ enumName: string
92
+ ): Promise<string[]> {
93
+ try {
94
+ // Consulta para obtener los valores posibles de un enum
95
+ const query = `
96
+ SELECT e.enumlabel
97
+ FROM pg_type t
98
+ JOIN pg_enum e ON t.oid = e.enumtypid
99
+ JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
100
+ WHERE t.typname = $1
101
+ AND n.nspname = $2
102
+ `;
103
+ const result = await this.targetPool.query(query, [enumName, schemaName]);
104
+ return result.rows.map((row) => row.enumlabel);
105
+ } catch (error) {
106
+ this.logger.error(
107
+ `Error getting enum values for ${enumName}: ${error.message}`
108
+ );
109
+ return [];
110
+ }
111
+ }
112
+
113
+ async executeQuery(query: string, params: any[] = []): Promise<any> {
114
+ return this.targetPool.query(query, params);
115
+ }
116
+
117
+ async queryTargetDb(query: string, params: any[]): Promise<{ rows: any[] }> {
118
+ return this.targetPool.query(query, params);
119
+ }
120
+
121
+ async createMissingColumns(tableName: string, schema: string, columns: ColumnSchema[]) {
122
+ for (const column of columns) {
123
+ try {
124
+ const query = `
125
+ ALTER TABLE "${schema}"."${tableName}"
126
+ ADD COLUMN IF NOT EXISTS "${column.column_name}" ${column.data_type}
127
+ ${column.is_nullable === 'NO' ? 'NOT NULL' : ''}
128
+ ${column.column_default ? `DEFAULT ${column.column_default}` : ''}
129
+ `;
130
+ await this.targetPool.query(query);
131
+
132
+ // Add unique constraint if needed
133
+ if (column.constraint_type === 'UNIQUE') {
134
+ const constraintName = `${tableName}_${column.column_name}_unique`;
135
+ const uniqueQuery = `
136
+ DO $$
137
+ BEGIN
138
+ IF NOT EXISTS (
139
+ SELECT 1 FROM pg_constraint WHERE conname = '${constraintName}'
140
+ ) THEN
141
+ ALTER TABLE "${schema}"."${tableName}"
142
+ ADD CONSTRAINT "${constraintName}" UNIQUE ("${column.column_name}");
143
+ END IF;
144
+ END $$;
145
+ `;
146
+ await this.targetPool.query(uniqueQuery);
147
+ }
148
+ } catch (error) {
149
+ this.logger.error(`Error creating column ${column.column_name}: ${error.message}`);
150
+ throw error;
151
+ }
152
+ }
153
+ }
154
+
155
+ async createSchema(schemaName: string): Promise<void> {
156
+ try {
157
+ // Create schema
158
+ await this.targetPool.query(`CREATE SCHEMA IF NOT EXISTS "${schemaName}"`);
159
+
160
+ // Copy table structure from public schema
161
+ const tables = await this.getPublicTables();
162
+
163
+ for (const table of tables) {
164
+ await this.targetPool.query(`
165
+ CREATE TABLE IF NOT EXISTS "${schemaName}"."${table}" (
166
+ LIKE public."${table}" INCLUDING ALL
167
+ );
168
+ `);
169
+ }
170
+
171
+ this.logger.log(`Created schema ${schemaName} with all tables`);
172
+ } catch (error) {
173
+ this.logger.error(`Error creating schema ${schemaName}: ${error.message}`);
174
+ throw error;
175
+ }
176
+ }
177
+
178
+ private async getPublicTables(): Promise<string[]> {
179
+ const result = await this.targetPool.query(`
180
+ SELECT tablename
181
+ FROM pg_tables
182
+ WHERE schemaname = 'public'
183
+ `);
184
+ return result.rows.map(row => row.tablename);
185
+ }
186
+ }