From 43b3c7e8035ddba8d1a127078617db2a83e20695 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Fri, 2 Jan 2026 08:40:54 +0000 Subject: [PATCH 1/4] feat(pgsql-test): add getErrorCode helper for enhanced error messages Add a utility function to extract the error code from enhanced PostgreSQL error messages. Enhanced errors from PgTestClient include additional context (Where, Query, Values) on subsequent lines. This helper returns only the first line containing the actual error code. This is useful for tests that need to assert specific error codes while still benefiting from the enhanced debugging context. --- postgres/pgsql-test/src/index.ts | 2 +- postgres/pgsql-test/src/utils.ts | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) 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]) From 1fb2ddaaf2d78d794035b7c9571458bedc272eaf Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Fri, 2 Jan 2026 08:54:24 +0000 Subject: [PATCH 2/4] fix(pgsql-test): update snapshots for enhanced error message format Update snapshot expectations to match the new error format from pgpm_migrate: - hint field now returns '' instead of undefined - where field now shows 'line 73 at RAISE' instead of 'line 46 at EXECUTE' - Some fields changed from undefined to '' (empty string) - Error messages now include enhanced context (Detail, Where lines) --- ...es-test.pgpm-migration-errors.test.ts.snap | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) 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..810df3618 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 @@ -5,15 +5,12 @@ exports[`PGPM Migration Error Messages Constraint Violation in Migration snapsho "code": "23505", "constraint": "test_snapshot_products_sku_key", "detail": "Key (sku)=(PROD-001) already exists.", - "hint": undefined, + "hint": "", "internalQuery": undefined, "position": undefined, "schema": "public", "table": "test_snapshot_products", - "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", + "where": "PL/pgSQL function pgpm_migrate.deploy(text,text,text,text[],text,boolean) line 73 at RAISE", } `; @@ -22,21 +19,22 @@ exports[`PGPM Migration Error Messages Constraint Violation in Migration snapsho exports[`PGPM Migration Error Messages JSON Type Mismatch in Migration snapshot: JSON type mismatch error in migration: error fields 1`] = ` { "code": "22P02", - "constraint": undefined, + "constraint": "", "detail": "Token "not_valid_json" is invalid.", - "hint": undefined, - "internalQuery": " -INSERT INTO test_migration_config (name, settings) VALUES ('test', 'not_valid_json'); - ", + "hint": "", + "internalQuery": undefined, "position": undefined, - "schema": undefined, - "table": undefined, - "where": "JSON data, line 1: not_valid_json -PL/pgSQL function pgpm_migrate.deploy(text,text,text,text[],text,boolean) line 46 at EXECUTE", + "schema": "", + "table": "", + "where": "PL/pgSQL function pgpm_migrate.deploy(text,text,text,text[],text,boolean) line 73 at RAISE", } `; -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: PL/pgSQL function pgpm_migrate.deploy(text,text,text,text[],text,boolean) line 73 at RAISE" +`; exports[`PGPM Migration Error Messages Nested EXECUTE Migration Errors snapshot: nested EXECUTE migration error: error fields 1`] = ` { From 07f17eb415455896d21ff7440ee81e5927c1c63f Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Fri, 2 Jan 2026 09:01:50 +0000 Subject: [PATCH 3/4] fix(pgpm): preserve original exception context in deploy/revert procedures Use simple RAISE instead of RAISE EXCEPTION USING to preserve the full PostgreSQL exception context including SQL statements in the 'where' field. The previous approach captured diagnostics but lost the PG_EXCEPTION_CONTEXT when re-raising, which removed valuable debugging information like the actual SQL statement that failed. --- pgpm/core/src/migrate/sql/procedures.sql | 77 ++----------------- ...es-test.pgpm-migration-errors.test.ts.snap | 28 +++---- 2 files changed, 20 insertions(+), 85 deletions(-) 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 810df3618..510738cc2 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 @@ -5,12 +5,15 @@ exports[`PGPM Migration Error Messages Constraint Violation in Migration snapsho "code": "23505", "constraint": "test_snapshot_products_sku_key", "detail": "Key (sku)=(PROD-001) already exists.", - "hint": "", + "hint": undefined, "internalQuery": undefined, "position": undefined, "schema": "public", "table": "test_snapshot_products", - "where": "PL/pgSQL function pgpm_migrate.deploy(text,text,text,text[],text,boolean) line 73 at RAISE", + "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", } `; @@ -19,22 +22,21 @@ exports[`PGPM Migration Error Messages Constraint Violation in Migration snapsho exports[`PGPM Migration Error Messages JSON Type Mismatch in Migration snapshot: JSON type mismatch error in migration: error fields 1`] = ` { "code": "22P02", - "constraint": "", + "constraint": undefined, "detail": "Token "not_valid_json" is invalid.", - "hint": "", - "internalQuery": undefined, + "hint": undefined, + "internalQuery": " +INSERT INTO test_migration_config (name, settings) VALUES ('test', 'not_valid_json'); + ", "position": undefined, - "schema": "", - "table": "", - "where": "PL/pgSQL function pgpm_migrate.deploy(text,text,text,text[],text,boolean) line 73 at RAISE", + "schema": undefined, + "table": undefined, + "where": "JSON data, line 1: not_valid_json +PL/pgSQL function pgpm_migrate.deploy(text,text,text,text[],text,boolean) line 46 at EXECUTE", } `; -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: PL/pgSQL function pgpm_migrate.deploy(text,text,text,text[],text,boolean) line 73 at RAISE" -`; +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 Nested EXECUTE Migration Errors snapshot: nested EXECUTE migration error: error fields 1`] = ` { From 75b5087a37a43066ca0d1e44aa33748e0c04ef80 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Fri, 2 Jan 2026 09:12:58 +0000 Subject: [PATCH 4/4] fix(pgsql-test): update error message snapshots to include enhanced context The error message snapshots now include the enhanced debugging context (Where, Detail, Internal Query, etc.) that pgsql-test appends to error messages. This is the correct behavior - these tests verify that enhanced error messages work correctly in the pgpm migration flow. The error fields snapshots continue to show the raw PostgreSQL error properties including the SQL statement context in the 'where' field. --- ...es-test.pgpm-migration-errors.test.ts.snap | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) 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" +`;