Skip to content

Commit 9215dc9

Browse files
committed
debugger
1 parent d1352ce commit 9215dc9

File tree

6 files changed

+384
-30
lines changed

6 files changed

+384
-30
lines changed
Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
process.env.LAUNCHQL_DEBUG = 'true';
2+
13
import { CoreDeployTestFixture } from '../../test-utils/CoreDeployTestFixture';
24
import { TestDatabase } from '../../test-utils';
3-
import { writeFileSync, readFileSync, unlinkSync } from 'fs';
4-
import { join } from 'path';
55

66
describe('Forked Deployment with deployModules - my-third', () => {
77
let fixture: CoreDeployTestFixture;
@@ -16,27 +16,21 @@ describe('Forked Deployment with deployModules - my-third', () => {
1616
await fixture.cleanup();
1717
});
1818

19-
test('handles modified deployment scenario for my-third', async () => {
20-
const basePath = fixture.tempFixtureDir;
21-
const packagePath = join(basePath, 'packages', 'my-third');
22-
19+
test.only('handles modified deployment scenario for my-third', async () => {
2320
await fixture.deployModule('my-third', db.name, ['sqitch', 'simple-w-tags']);
24-
21+
2522
expect(await db.exists('schema', 'metaschema')).toBe(true);
2623
expect(await db.exists('table', 'metaschema.customers')).toBe(true);
27-
28-
const schemas = await db.query('SELECT schema_name FROM information_schema.schemata WHERE schema_name = \'metaschema\'');
29-
expect(schemas.rows).toHaveLength(1);
30-
expect(schemas.rows[0].schema_name).toBe('metaschema');
31-
32-
const tables = await db.query('SELECT table_name FROM information_schema.tables WHERE table_schema = \'metaschema\' ORDER BY table_name');
33-
expect(tables.rows).toHaveLength(1);
34-
expect(tables.rows.map((r: any) => r.table_name)).toEqual(['customers']);
35-
24+
3625
await fixture.revertModule('my-third', db.name, ['sqitch', 'simple-w-tags'], 'my-first:@v1.0.0');
3726

3827
expect(await db.exists('schema', 'metaschema')).toBe(false);
3928
expect(await db.exists('table', 'metaschema.customers')).toBe(false);
4029

30+
await fixture.deployModule('my-third', db.name, ['sqitch', 'simple-w-tags']);
31+
32+
expect(await db.exists('schema', 'metaschema')).toBe(true);
33+
expect(await db.exists('table', 'metaschema.customers')).toBe(true);
34+
4135
});
4236
});

packages/core/src/migrate/client.ts

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export class LaunchQLMigrate {
9494
async deploy(options: DeployOptions): Promise<DeployResult> {
9595
await this.initialize();
9696

97-
const { project, targetDatabase, planPath, toChange, useTransaction = true } = options;
97+
const { project, targetDatabase, planPath, toChange, useTransaction = true, debug = false } = options;
9898
const plan = parsePlanFile(planPath);
9999
const resolvedToChange = toChange && toChange.includes('@') ? resolveTagToChangeName(planPath, toChange, project || plan.project) : toChange;
100100
const changes = getChangesInOrder(planPath);
@@ -187,8 +187,66 @@ export class LaunchQLMigrate {
187187

188188
deployed.push(change.name);
189189
log.success(`Successfully deployed: ${change.name}`);
190-
} catch (error) {
191-
log.error(`Failed to deploy ${change.name}:`, error);
190+
} catch (error: any) {
191+
// Build comprehensive error message
192+
const errorLines = [];
193+
errorLines.push(`Failed to deploy ${change.name}:`);
194+
errorLines.push(` Change: ${change.name}`);
195+
errorLines.push(` Project: ${project || plan.project}`);
196+
errorLines.push(` Script Hash: ${scriptHash}`);
197+
errorLines.push(` Dependencies: ${resolvedChangeDeps.length > 0 ? resolvedChangeDeps.join(', ') : 'none'}`);
198+
errorLines.push(` Error Code: ${error.code || 'N/A'}`);
199+
errorLines.push(` Error Message: ${error.message || 'N/A'}`);
200+
201+
// Show SQL script preview for debugging
202+
if (cleanDeploySql) {
203+
const sqlLines = cleanDeploySql.split('\n');
204+
const previewLines = debug ? sqlLines : sqlLines.slice(0, 10);
205+
206+
if (debug) {
207+
errorLines.push(` Full SQL Script (${sqlLines.length} lines):`);
208+
previewLines.forEach((line, index) => {
209+
errorLines.push(` ${index + 1}: ${line}`);
210+
});
211+
} else {
212+
errorLines.push(` SQL Preview (first 10 lines):`);
213+
previewLines.forEach((line, index) => {
214+
errorLines.push(` ${index + 1}: ${line}`);
215+
});
216+
if (sqlLines.length > 10) {
217+
errorLines.push(` ... and ${sqlLines.length - 10} more lines`);
218+
errorLines.push(` 💡 Use debug mode to see full SQL script`);
219+
}
220+
}
221+
}
222+
223+
// Show resolved dependencies in debug mode
224+
if (debug && resolvedDeps) {
225+
errorLines.push(` Resolved Dependencies Context:`);
226+
const changeKey = `/deploy/${change.name}.sql`;
227+
const depInfo = resolvedDeps.deps[changeKey];
228+
if (depInfo) {
229+
errorLines.push(` Key: ${changeKey}`);
230+
errorLines.push(` Dependencies: ${JSON.stringify(depInfo, null, 2)}`);
231+
}
232+
}
233+
234+
// Provide debugging hints based on error code
235+
if (error.code === '25P02') {
236+
errorLines.push(`🔍 Debug Info: This error means a previous command in the transaction failed.`);
237+
errorLines.push(` The SQL script above may contain the failing command.`);
238+
errorLines.push(` Check the transaction query history for more details.`);
239+
} else if (error.code === '42P01') {
240+
errorLines.push(`💡 Hint: A table or view referenced in the SQL script does not exist.`);
241+
errorLines.push(` Check if dependencies are applied in the correct order.`);
242+
} else if (error.code === '42883') {
243+
errorLines.push(`💡 Hint: A function referenced in the SQL script does not exist.`);
244+
errorLines.push(` Check if required extensions or previous migrations are applied.`);
245+
}
246+
247+
// Log the consolidated error message
248+
log.error(errorLines.join('\n'));
249+
192250
failed = change.name;
193251
throw error; // Re-throw to trigger rollback if in transaction
194252
}

packages/core/src/migrate/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export interface DeployOptions {
2121
planPath: string;
2222
toChange?: string;
2323
useTransaction?: boolean;
24+
// Add debug mode for enhanced error reporting
25+
debug?: boolean;
2426
}
2527

2628
export interface RevertOptions {
@@ -29,6 +31,8 @@ export interface RevertOptions {
2931
planPath: string;
3032
toChange?: string;
3133
useTransaction?: boolean;
34+
// Add debug mode for enhanced error reporting
35+
debug?: boolean;
3236
}
3337

3438
export interface VerifyOptions {

packages/core/src/migrate/utils/transaction.ts

Lines changed: 120 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ export interface TransactionOptions {
1010
export interface TransactionContext {
1111
client: PoolClient | Pool;
1212
isTransaction: boolean;
13+
// Add query tracking for debugging
14+
queryHistory: QueryHistoryEntry[];
15+
addQuery: (query: string, params?: any[], startTime?: number) => void;
16+
}
17+
18+
export interface QueryHistoryEntry {
19+
query: string;
20+
params?: any[];
21+
timestamp: number;
22+
duration?: number;
23+
error?: any;
1324
}
1425

1526
/**
@@ -22,41 +33,142 @@ export async function withTransaction<T>(
2233
options: TransactionOptions,
2334
fn: (context: TransactionContext) => Promise<T>
2435
): Promise<T> {
36+
const queryHistory: QueryHistoryEntry[] = [];
37+
38+
const addQuery = (query: string, params?: any[], startTime?: number) => {
39+
queryHistory.push({
40+
query,
41+
params,
42+
timestamp: Date.now(),
43+
duration: startTime ? Date.now() - startTime : undefined
44+
});
45+
};
46+
2547
if (!options.useTransaction) {
2648
// No transaction - use pool directly
2749
log.debug('Executing without transaction');
28-
return fn({ client: pool, isTransaction: false });
50+
return fn({ client: pool, isTransaction: false, queryHistory, addQuery });
2951
}
3052

3153
// Use transaction
3254
const client = await pool.connect();
55+
const transactionStartTime = Date.now();
3356
log.debug('Starting transaction');
3457

3558
try {
59+
const beginTime = Date.now();
3660
await client.query('BEGIN');
61+
addQuery('BEGIN', [], beginTime);
3762

38-
const result = await fn({ client, isTransaction: true });
63+
const result = await fn({ client, isTransaction: true, queryHistory, addQuery });
3964

65+
const commitTime = Date.now();
4066
await client.query('COMMIT');
41-
log.debug('Transaction committed successfully');
67+
addQuery('COMMIT', [], commitTime);
68+
69+
const transactionDuration = Date.now() - transactionStartTime;
70+
log.debug(`Transaction committed successfully in ${transactionDuration}ms`);
4271

4372
return result;
44-
} catch (error) {
45-
await client.query('ROLLBACK');
46-
log.error('Transaction rolled back due to error:', error);
73+
} catch (error: any) {
74+
const rollbackTime = Date.now();
75+
76+
try {
77+
await client.query('ROLLBACK');
78+
addQuery('ROLLBACK', [], rollbackTime);
79+
} catch (rollbackError) {
80+
log.error('Failed to rollback transaction:', rollbackError);
81+
}
82+
83+
const transactionDuration = Date.now() - transactionStartTime;
84+
85+
// Enhanced error logging with context
86+
const errorLines = [];
87+
errorLines.push(`Transaction rolled back due to error after ${transactionDuration}ms:`);
88+
errorLines.push(`Error Code: ${error.code || 'N/A'}`);
89+
errorLines.push(`Error Message: ${error.message || 'N/A'}`);
90+
91+
// Log query history for debugging
92+
if (queryHistory.length > 0) {
93+
errorLines.push('Query history for this transaction:');
94+
queryHistory.forEach((entry, index) => {
95+
const duration = entry.duration ? ` (${entry.duration}ms)` : '';
96+
const params = entry.params && entry.params.length > 0
97+
? ` with params: ${JSON.stringify(entry.params.slice(0, 2))}${entry.params.length > 2 ? '...' : ''}`
98+
: '';
99+
errorLines.push(` ${index + 1}. ${entry.query.split('\n')[0].trim()}${params}${duration}`);
100+
});
101+
}
102+
103+
// For transaction aborted errors, provide additional context
104+
if (error.code === '25P02') {
105+
errorLines.push('🔍 Debug Info: Transaction was aborted due to a previous error.');
106+
errorLines.push(' This usually means a previous command in the transaction failed.');
107+
errorLines.push(' Check the query history above to identify the failing command.');
108+
}
109+
110+
// Log the consolidated error message
111+
log.error(errorLines.join('\n'));
112+
47113
throw error;
48114
} finally {
49115
client.release();
50116
}
51117
}
52118

53119
/**
54-
* Helper to execute a query within a transaction context
120+
* Helper to execute a query within a transaction context with enhanced logging
55121
*/
56122
export async function executeQuery(
57123
context: TransactionContext,
58124
query: string,
59125
params?: any[]
60126
): Promise<any> {
61-
return context.client.query(query, params);
127+
const startTime = Date.now();
128+
129+
try {
130+
const result = await context.client.query(query, params);
131+
const duration = Date.now() - startTime;
132+
133+
// Add to query history
134+
context.addQuery(query, params, startTime);
135+
136+
// Log slow queries for debugging
137+
if (duration > 1000) {
138+
log.warn(`Slow query detected (${duration}ms): ${query.split('\n')[0].trim()}`);
139+
}
140+
141+
return result;
142+
} catch (error: any) {
143+
const duration = Date.now() - startTime;
144+
145+
// Add failed query to history
146+
context.addQuery(query, params, startTime);
147+
148+
// Enhanced error logging
149+
const errorLines = [];
150+
errorLines.push(`Query failed after ${duration}ms:`);
151+
errorLines.push(` Query: ${query.split('\n')[0].trim()}`);
152+
if (params && params.length > 0) {
153+
errorLines.push(` Params: ${JSON.stringify(params.slice(0, 3))}${params.length > 3 ? '...' : ''}`);
154+
}
155+
errorLines.push(` Error Code: ${error.code || 'N/A'}`);
156+
errorLines.push(` Error Message: ${error.message || 'N/A'}`);
157+
158+
// Provide debugging hints for common errors
159+
if (error.code === '42P01') {
160+
errorLines.push('💡 Hint: Relation (table/view) does not exist. Check if migrations are applied in correct order.');
161+
} else if (error.code === '42883') {
162+
errorLines.push('💡 Hint: Function does not exist. Check if required extensions or functions are installed.');
163+
} else if (error.code === '23505') {
164+
errorLines.push('💡 Hint: Unique constraint violation. Check for duplicate data.');
165+
} else if (error.code === '23503') {
166+
errorLines.push('💡 Hint: Foreign key constraint violation. Check referential integrity.');
167+
}
168+
169+
// Log the consolidated error message
170+
log.error(errorLines.join('\n'));
171+
172+
throw error;
173+
}
62174
}

packages/core/src/projects/deploy.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,27 @@ export const deployProject = async (
108108
usePlan: options?.usePlan ?? true,
109109
extension: false
110110
});
111-
} catch (err) {
112-
log.error(`❌ Failed to package module "${extension}" at path: ${modulePath}`);
113-
log.error(` Error: ${err instanceof Error ? err.message : String(err)}`);
111+
} catch (err: any) {
112+
// Build comprehensive error message
113+
const errorLines = [];
114+
errorLines.push(`❌ Failed to package module "${extension}" at path: ${modulePath}`);
115+
errorLines.push(` Module Path: ${modulePath}`);
116+
errorLines.push(` Workspace Path: ${mod.workspacePath}`);
117+
errorLines.push(` Error Code: ${err.code || 'N/A'}`);
118+
errorLines.push(` Error Message: ${err.message || 'Unknown error'}`);
119+
120+
// Provide debugging hints
121+
if (err.code === 'ENOENT') {
122+
errorLines.push('💡 Hint: File or directory not found. Check if the module path is correct.');
123+
} else if (err.code === 'EACCES') {
124+
errorLines.push('💡 Hint: Permission denied. Check file permissions.');
125+
} else if (err.message && err.message.includes('launchql.plan')) {
126+
errorLines.push('💡 Hint: launchql.plan file issue. Check if the plan file exists and is valid.');
127+
}
128+
129+
// Log the consolidated error message
130+
log.error(errorLines.join('\n'));
131+
114132
console.error(err); // Preserve full stack trace
115133
throw errors.DEPLOYMENT_FAILED({
116134
type: 'Deployment',

0 commit comments

Comments
 (0)