diff --git a/pgpm/core/src/migrate/sql/procedures.sql b/pgpm/core/src/migrate/sql/procedures.sql index c33db1f09..2d63bc18a 100644 --- a/pgpm/core/src/migrate/sql/procedures.sql +++ b/pgpm/core/src/migrate/sql/procedures.sql @@ -53,17 +53,6 @@ CREATE PROCEDURE pgpm_migrate.deploy( LANGUAGE plpgsql AS $$ DECLARE v_change_id TEXT; - -- Error diagnostic variables - v_sqlstate TEXT; - v_message TEXT; - v_detail TEXT; - v_hint TEXT; - v_context TEXT; - v_schema_name TEXT; - v_table_name TEXT; - v_column_name TEXT; - v_constraint_name TEXT; - v_datatype_name TEXT; BEGIN -- Ensure package exists CALL pgpm_migrate.register_package(p_package); @@ -108,30 +97,8 @@ BEGIN BEGIN EXECUTE p_deploy_sql; EXCEPTION WHEN OTHERS THEN - -- Capture all error diagnostics to preserve them in the re-raised exception - GET STACKED DIAGNOSTICS - v_sqlstate = RETURNED_SQLSTATE, - v_message = MESSAGE_TEXT, - v_detail = PG_EXCEPTION_DETAIL, - v_hint = PG_EXCEPTION_HINT, - v_context = PG_EXCEPTION_CONTEXT, - v_schema_name = SCHEMA_NAME, - v_table_name = TABLE_NAME, - v_column_name = COLUMN_NAME, - v_constraint_name = CONSTRAINT_NAME, - v_datatype_name = PG_DATATYPE_NAME; - - -- Re-raise with all captured diagnostics preserved - RAISE EXCEPTION USING - ERRCODE = v_sqlstate, - MESSAGE = v_message, - DETAIL = v_detail, - HINT = v_hint, - SCHEMA = v_schema_name, - TABLE = v_table_name, - COLUMN = v_column_name, - CONSTRAINT = v_constraint_name, - DATATYPE = v_datatype_name; + -- Re-raise the original exception to preserve full context including SQL statement + RAISE; END; END IF; @@ -158,18 +125,6 @@ CREATE PROCEDURE pgpm_migrate.revert( p_revert_sql TEXT ) LANGUAGE plpgsql AS $$ -DECLARE - -- Error diagnostic variables - v_sqlstate TEXT; - v_message TEXT; - v_detail TEXT; - v_hint TEXT; - v_context TEXT; - v_schema_name TEXT; - v_table_name TEXT; - v_column_name TEXT; - v_constraint_name TEXT; - v_datatype_name TEXT; BEGIN -- Check if deployed IF NOT pgpm_migrate.is_deployed(p_package, p_change_name) THEN @@ -211,34 +166,12 @@ BEGIN END; END IF; - -- Execute revert with error diagnostics preservation + -- Execute revert BEGIN EXECUTE p_revert_sql; EXCEPTION WHEN OTHERS THEN - -- Capture all error diagnostics to preserve them in the re-raised exception - GET STACKED DIAGNOSTICS - v_sqlstate = RETURNED_SQLSTATE, - v_message = MESSAGE_TEXT, - v_detail = PG_EXCEPTION_DETAIL, - v_hint = PG_EXCEPTION_HINT, - v_context = PG_EXCEPTION_CONTEXT, - v_schema_name = SCHEMA_NAME, - v_table_name = TABLE_NAME, - v_column_name = COLUMN_NAME, - v_constraint_name = CONSTRAINT_NAME, - v_datatype_name = PG_DATATYPE_NAME; - - -- Re-raise with all captured diagnostics preserved - RAISE EXCEPTION USING - ERRCODE = v_sqlstate, - MESSAGE = v_message, - DETAIL = v_detail, - HINT = v_hint, - SCHEMA = v_schema_name, - TABLE = v_table_name, - COLUMN = v_column_name, - CONSTRAINT = v_constraint_name, - DATATYPE = v_datatype_name; + -- Re-raise the original exception to preserve full context including SQL statement + RAISE; END; -- Remove from deployed diff --git a/postgres/pgsql-test/__tests__/__snapshots__/postgres-test.pgpm-migration-errors.test.ts.snap b/postgres/pgsql-test/__tests__/__snapshots__/postgres-test.pgpm-migration-errors.test.ts.snap index 510738cc2..46e796adc 100644 --- a/postgres/pgsql-test/__tests__/__snapshots__/postgres-test.pgpm-migration-errors.test.ts.snap +++ b/postgres/pgsql-test/__tests__/__snapshots__/postgres-test.pgpm-migration-errors.test.ts.snap @@ -17,7 +17,17 @@ PL/pgSQL function pgpm_migrate.deploy(text,text,text,text[],text,boolean) line 4 } `; -exports[`PGPM Migration Error Messages Constraint Violation in Migration snapshot: constraint violation in migration: error message 1`] = `"duplicate key value violates unique constraint "test_snapshot_products_sku_key""`; +exports[`PGPM Migration Error Messages Constraint Violation in Migration snapshot: constraint violation in migration: error message 1`] = ` +"duplicate key value violates unique constraint "test_snapshot_products_sku_key" +Detail: Key (sku)=(PROD-001) already exists. +Where: SQL statement " +INSERT INTO test_snapshot_products (sku) VALUES ('PROD-001'); + " +PL/pgSQL function pgpm_migrate.deploy(text,text,text,text[],text,boolean) line 46 at EXECUTE +Schema: public +Table: test_snapshot_products +Constraint: test_snapshot_products_sku_key" +`; exports[`PGPM Migration Error Messages JSON Type Mismatch in Migration snapshot: JSON type mismatch error in migration: error fields 1`] = ` { @@ -36,7 +46,16 @@ PL/pgSQL function pgpm_migrate.deploy(text,text,text,text[],text,boolean) line 4 } `; -exports[`PGPM Migration Error Messages JSON Type Mismatch in Migration snapshot: JSON type mismatch error in migration: error message 1`] = `"invalid input syntax for type json"`; +exports[`PGPM Migration Error Messages JSON Type Mismatch in Migration snapshot: JSON type mismatch error in migration: error message 1`] = ` +"invalid input syntax for type json +Detail: Token "not_valid_json" is invalid. +Where: JSON data, line 1: not_valid_json +PL/pgSQL function pgpm_migrate.deploy(text,text,text,text[],text,boolean) line 46 at EXECUTE +Internal Query: +INSERT INTO test_migration_config (name, settings) VALUES ('test', 'not_valid_json'); + +Internal Position: 69" +`; exports[`PGPM Migration Error Messages Nested EXECUTE Migration Errors snapshot: nested EXECUTE migration error: error fields 1`] = ` { @@ -60,4 +79,17 @@ PL/pgSQL function pgpm_migrate.deploy(text,text,text,text[],text,boolean) line 4 } `; -exports[`PGPM Migration Error Messages Nested EXECUTE Migration Errors snapshot: nested EXECUTE migration error: error message 1`] = `"relation "nonexistent_migration_table_xyz" does not exist"`; +exports[`PGPM Migration Error Messages Nested EXECUTE Migration Errors snapshot: nested EXECUTE migration error: error message 1`] = ` +"relation "nonexistent_migration_table_xyz" does not exist +Where: PL/pgSQL function inline_code_block line 3 at EXECUTE +SQL statement " +DO $$ +BEGIN + EXECUTE 'INSERT INTO nonexistent_migration_table_xyz (col) VALUES (1)'; +END; +$$; + " +PL/pgSQL function pgpm_migrate.deploy(text,text,text,text[],text,boolean) line 46 at EXECUTE +Internal Query: INSERT INTO nonexistent_migration_table_xyz (col) VALUES (1) +Internal Position: 13" +`; diff --git a/postgres/pgsql-test/src/index.ts b/postgres/pgsql-test/src/index.ts index 47d74a138..461eb95fb 100644 --- a/postgres/pgsql-test/src/index.ts +++ b/postgres/pgsql-test/src/index.ts @@ -3,4 +3,4 @@ export * from './connect'; export * from './manager'; export * from './seed'; export * from './test-client'; -export { snapshot } from './utils'; +export { snapshot, getErrorCode } from './utils'; diff --git a/postgres/pgsql-test/src/utils.ts b/postgres/pgsql-test/src/utils.ts index d521d6a56..5231d15dd 100644 --- a/postgres/pgsql-test/src/utils.ts +++ b/postgres/pgsql-test/src/utils.ts @@ -15,7 +15,26 @@ export { type PgErrorContext }; -const uuidRegexp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; +/** + * Extract the error code from an error message. + * + * Enhanced error messages from PgTestClient include additional context on subsequent lines + * (Where, Query, Values, etc.). This function returns only the first line, which contains + * the actual error code raised by PostgreSQL. + * + * @param message - The error message (may contain multiple lines with debug context) + * @returns The first line of the error message (the error code) + * + * @example + * // Error message with enhanced context: + * // "NONEXISTENT_TYPE\nWhere: PL/pgSQL function...\nQuery: INSERT INTO..." + * getErrorCode(err.message) // => "NONEXISTENT_TYPE" + */ +export function getErrorCode(message: string): string { + return message.split('\n')[0]; +} + +const uuidRegexp= /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; // ID hash map for tracking ID relationships in snapshots // Values can be numbers (e.g., 1 -> [ID-1]) or strings (e.g., 'user2' -> [ID-user2])