--- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -1739,6 +1739,18 @@ { return currentEventTriggerState != NULL && !currentEventTriggerState->commandCollectionInhibited; +} + +/* + * Is the enclosing query explicitly suppressing collection of internal DDL? + * Unlike absence of a query state, inhibition must also apply to commands + * nested through SPI, before they create their own complete-query state. + */ +bool +EventTriggerCommandCollectionInhibited(void) +{ + return currentEventTriggerState != NULL && + currentEventTriggerState->commandCollectionInhibited; } /* --- a/src/include/commands/event_trigger.h +++ b/src/include/commands/event_trigger.h @@ -73,6 +73,7 @@ extern void EventTriggerInhibitCommandCollection(void); extern void EventTriggerUndoInhibitCommandCollection(void); extern bool EventTriggerCommandCollectionActive(void); +extern bool EventTriggerCommandCollectionInhibited(void); extern List *EventTriggerGetCollectedCommands(void); extern void EventTriggerCollectSimpleCommand(ObjectAddress address, --- a/src/test/modules/test_ddl_deparse/Makefile +++ b/src/test/modules/test_ddl_deparse/Makefile @@ -33,7 +33,8 @@ textsearch \ matviews \ deparse_create_table \ - deparse_alter_table + deparse_alter_table \ + deparse_nested TAP_TESTS = 1 --- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c +++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c @@ -81,12 +81,13 @@ * so it survives the rollback of the internal subtransaction the trial run * executes in. Only valid while capture_parsetree is set. * - * A single pointer, not a list: one intercepted statement is one complete - * query, which fires the capture event trigger exactly once (sub-commands - * such as the internal ALTER TABLE ADD CONSTRAINT generated by a CREATE - * TABLE with a foreign key are collected into that one firing, not fired + * A single pointer for the current capture frame: one intercepted statement + * is one complete query, which fires the capture event trigger exactly once + * (sub-commands such as the internal ALTER TABLE ADD CONSTRAINT generated by + * a CREATE TABLE with a foreign key are collected into that one firing, not fired * separately), and one firing deparses to exactly one command. The trigger - * errors out if that invariant is ever broken. + * errors out if that invariant is ever broken. Nested complete queries save + * and restore both capture pointers in the ProcessUtility hook. */ static char *captured_json = NULL; @@ -135,7 +136,6 @@ PGC_USERSET, 0, NULL, NULL, NULL); - MarkGUCPrefixReserved("test_ddl_deparse"); next_ProcessUtility_hook = ProcessUtility_hook; @@ -172,18 +172,16 @@ /* * Do nothing unless a statement of ours is being captured. This is the * common case: the trigger is installed for the whole session, but only - * top-level statements the module intercepts have a raw parse tree saved - * for the deparser to work from. + * statements the module intercepts have a raw parse tree saved for the + * deparser to work from. */ if (capture_parsetree == NULL) PG_RETURN_NULL(); /* - * A second firing while one statement is being captured means a nested - * complete query (say, DDL run by another event trigger function) also - * produced a supported command. Its raw statement is not the one saved - * above, and replaying both would double-execute the nested one when the - * replayed outer statement triggers it again, so refuse loudly. + * A frame must capture its complete query only once. Nested user commands + * have their own frame, so a second capture here is an instrumentation + * error rather than another command to append to this frame. */ if (captured_json != NULL) elog(ERROR, "multiple DDL deparse captures for a single statement"); @@ -364,12 +362,17 @@ QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *qc) { + const Node *previous_capture_parsetree = capture_parsetree; + char *previous_captured_json = captured_json; + /* - * Only top-level statements are captured, and only when something is - * going to be done with the result. Our own replay of a deparsed command - * must not be captured in turn. + * Capture user commands at any nesting depth under the same settings. + * Internal subcommands remain part of their enclosing command. Our own + * replay must not be captured in turn. */ - if (context != PROCESS_UTILITY_TOPLEVEL || in_roundtrip || + if (context == PROCESS_UTILITY_SUBCOMMAND || + EventTriggerCommandCollectionInhibited() || + in_roundtrip || (!execute_deparsed_ddl && print_deparsed_ddl == PRINT_DEPARSED_OFF)) { call_next_ProcessUtility(pstmt, queryString, readOnlyTree, @@ -387,8 +390,20 @@ if (!ddl_deparse_command_supported(pstmt->utilityStmt)) { report_declined_command(pstmt->utilityStmt, queryString); - call_next_ProcessUtility(pstmt, queryString, readOnlyTree, - context, params, queryEnv, dest, qc); + /* Its complete-query event must not use the enclosing raw tree. */ + capture_parsetree = NULL; + captured_json = NULL; + PG_TRY(); + { + call_next_ProcessUtility(pstmt, queryString, readOnlyTree, + context, params, queryEnv, dest, qc); + } + PG_FINALLY(); + { + capture_parsetree = previous_capture_parsetree; + captured_json = previous_captured_json; + } + PG_END_TRY(); return; } @@ -497,9 +512,8 @@ } PG_FINALLY(); { - capture_parsetree = NULL; - /* don't leave a dangling pointer into transaction-lifetime memory */ - captured_json = NULL; + capture_parsetree = previous_capture_parsetree; + captured_json = previous_captured_json; } PG_END_TRY(); } --- a/src/test/modules/test_ddl_deparse/meson.build +++ b/src/test/modules/test_ddl_deparse/meson.build @@ -51,6 +51,7 @@ 'matviews', 'deparse_create_table', 'deparse_alter_table', + 'deparse_nested', ], }, 'tap': { --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/deparse_nested.sql @@ -0,0 +1,148 @@ +-- Nested user DDL must be captured with its own raw statement. Expected +-- notices check capture counts and SQL text; queries check data and rollback. +-- Internal commands remain part of their enclosing user statement. +LOAD 'test_ddl_deparse'; +SET test_ddl_deparse.execute_deparsed_ddl = off; + +-- Silence the generic command inspector installed by the first regression +-- script, so the expected output focuses on the deparser's own notices. +ALTER EVENT TRIGGER test_ddl_deparse DISABLE; + +CREATE SCHEMA nested_tests; +CREATE FUNCTION nested_tests.sql_ddl() RETURNS integer LANGUAGE SQL AS $$ + CREATE TABLE nested_tests.from_sql (a int); + SELECT 10; +$$; +CREATE FUNCTION nested_tests.plpgsql_ddl() RETURNS void LANGUAGE plpgsql AS $$ +BEGIN + CREATE TABLE nested_tests.from_plpgsql (a int, b int); + INSERT INTO nested_tests.from_plpgsql VALUES (1, 42); + ALTER TABLE nested_tests.from_plpgsql + ALTER COLUMN a TYPE text USING b::text, DROP COLUMN b; +END; +$$; +CREATE FUNCTION nested_tests.caught_error() RETURNS void LANGUAGE plpgsql AS $$ +BEGIN + BEGIN + CREATE TABLE nested_tests.rolled_back (a int); + ALTER TABLE nested_tests.rolled_back ADD COLUMN b nonexistent_type; + EXCEPTION WHEN undefined_object THEN + NULL; + END; + CREATE TABLE nested_tests.after_error (a int); +END; +$$; +CREATE FUNCTION nested_tests.roundtrip_ddl() RETURNS void LANGUAGE plpgsql AS $$ +BEGIN + CREATE TABLE nested_tests.from_roundtrip (a int); + INSERT INTO nested_tests.from_roundtrip VALUES (1); + ALTER TABLE nested_tests.from_roundtrip + ADD COLUMN b int DEFAULT 1, ALTER COLUMN b SET DEFAULT 2; + INSERT INTO nested_tests.from_roundtrip (a) VALUES (2); +END; +$$; +CREATE FUNCTION nested_tests.nested_trigger() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +DECLARE + cmd record; +BEGIN + FOR cmd IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP + IF cmd.object_identity = 'nested_tests.parent' THEN + CREATE TABLE nested_tests.child (a int, b int); + INSERT INTO nested_tests.child VALUES (1, 42); + ALTER TABLE nested_tests.child + ALTER COLUMN a TYPE text USING b::text, DROP COLUMN b; + -- CTAS is unsupported, but must not borrow the parent's capture state. + CREATE TABLE nested_tests.child_ctas AS SELECT 7 AS a; + BEGIN + CREATE TABLE nested_tests.child_rolled_back (a int); + ALTER TABLE nested_tests.child_rolled_back ADD COLUMN b nonexistent_type; + EXCEPTION WHEN undefined_object THEN + NULL; + END; + CREATE TABLE nested_tests.child_after_error (a int); + END IF; + END LOOP; +END; +$$; + +SET test_ddl_deparse.print_deparsed_ddl = text; + +-- SQL and PL/pgSQL functions issue supported CREATE and ALTER commands. +SELECT nested_tests.sql_ddl(); +SELECT nested_tests.plpgsql_ddl(); +SELECT a FROM nested_tests.from_plpgsql; + +-- A caught error must restore capture state before the next nested command +-- and before the next top-level command. The failed block leaves no table. +SELECT nested_tests.caught_error(); +CREATE TABLE nested_tests.after_call (a int); +SELECT to_regclass('nested_tests.rolled_back') IS NULL AS rolled_back; + +-- Explicit transaction rollback must likewise leave capture usable. +BEGIN; +CREATE TABLE nested_tests.explicit_rollback (a int); +ROLLBACK; +CREATE TABLE nested_tests.after_rollback (a int); +SELECT to_regclass('nested_tests.explicit_rollback') IS NULL AS rolled_back; + +-- The child needs a separate capture frame whether its event trigger runs +-- before or after capture_deparsed_ddl. Unsupported CTAS must not borrow the +-- parent's frame, and a caught child error must not discard the parent JSON. + +CREATE EVENT TRIGGER aa_nested ON ddl_command_end + WHEN TAG IN ('CREATE TABLE') + EXECUTE FUNCTION nested_tests.nested_trigger(); +CREATE TABLE nested_tests.parent (a int); +SELECT a FROM nested_tests.child; +SELECT a FROM nested_tests.child_ctas; +SELECT to_regclass('nested_tests.child_rolled_back') IS NULL AS rolled_back; +DROP EVENT TRIGGER aa_nested; +DROP TABLE nested_tests.parent, nested_tests.child, + nested_tests.child_ctas, nested_tests.child_after_error; + +CREATE EVENT TRIGGER zz_nested ON ddl_command_end + WHEN TAG IN ('CREATE TABLE') + EXECUTE FUNCTION nested_tests.nested_trigger(); +CREATE TABLE nested_tests.parent (a int); +SELECT a FROM nested_tests.child; +SELECT a FROM nested_tests.child_ctas; +SELECT to_regclass('nested_tests.child_rolled_back') IS NULL AS rolled_back; +DROP EVENT TRIGGER zz_nested; +DROP TABLE nested_tests.parent, nested_tests.child, + nested_tests.child_ctas, nested_tests.child_after_error; + +-- Also reconstruct the enclosing command and its nested trial commands. +SET test_ddl_deparse.execute_deparsed_ddl = on; + +CREATE EVENT TRIGGER zz_nested_roundtrip ON ddl_command_end + WHEN TAG IN ('CREATE TABLE') + EXECUTE FUNCTION nested_tests.nested_trigger(); +CREATE TABLE nested_tests.parent (a int); +SELECT a FROM nested_tests.child; +SELECT a FROM nested_tests.child_ctas; +SELECT to_regclass('nested_tests.child_rolled_back') IS NULL AS rolled_back; +DROP EVENT TRIGGER zz_nested_roundtrip; +DROP TABLE nested_tests.parent, nested_tests.child, + nested_tests.child_ctas, nested_tests.child_after_error; + +-- Reconstruct function-issued DDL and verify that ADD COLUMN's default and +-- the later SET DEFAULT retain their distinct effects on row values. +SELECT nested_tests.roundtrip_ddl(); +SELECT a, b FROM nested_tests.from_roundtrip ORDER BY a; + +-- Only the user's CREATE is deparsed: internally generated sequence/index +-- commands and the replay itself must not produce additional deparse notices. +CREATE TABLE nested_tests.serial_table (a serial PRIMARY KEY); + +-- Concurrent refresh creates and alters temporary tables through SPI while +-- command collection is inhibited. Those are internal commands too. +CREATE MATERIALIZED VIEW nested_tests.mv AS SELECT 1 AS a; +CREATE UNIQUE INDEX mv_a_idx ON nested_tests.mv (a); +REFRESH MATERIALIZED VIEW CONCURRENTLY nested_tests.mv; +SELECT * FROM nested_tests.mv; + +RESET test_ddl_deparse.print_deparsed_ddl; +RESET test_ddl_deparse.execute_deparsed_ddl; +DROP SCHEMA nested_tests CASCADE; +ALTER EVENT TRIGGER test_ddl_deparse ENABLE; --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/deparse_nested.out @@ -0,0 +1,264 @@ +-- Nested user DDL must be captured with its own raw statement. Expected +-- notices check capture counts and SQL text; queries check data and rollback. +-- Internal commands remain part of their enclosing user statement. +LOAD 'test_ddl_deparse'; +SET test_ddl_deparse.execute_deparsed_ddl = off; +-- Silence the generic command inspector installed by the first regression +-- script, so the expected output focuses on the deparser's own notices. +ALTER EVENT TRIGGER test_ddl_deparse DISABLE; +CREATE SCHEMA nested_tests; +CREATE FUNCTION nested_tests.sql_ddl() RETURNS integer LANGUAGE SQL AS $$ + CREATE TABLE nested_tests.from_sql (a int); + SELECT 10; +$$; +CREATE FUNCTION nested_tests.plpgsql_ddl() RETURNS void LANGUAGE plpgsql AS $$ +BEGIN + CREATE TABLE nested_tests.from_plpgsql (a int, b int); + INSERT INTO nested_tests.from_plpgsql VALUES (1, 42); + ALTER TABLE nested_tests.from_plpgsql + ALTER COLUMN a TYPE text USING b::text, DROP COLUMN b; +END; +$$; +CREATE FUNCTION nested_tests.caught_error() RETURNS void LANGUAGE plpgsql AS $$ +BEGIN + BEGIN + CREATE TABLE nested_tests.rolled_back (a int); + ALTER TABLE nested_tests.rolled_back ADD COLUMN b nonexistent_type; + EXCEPTION WHEN undefined_object THEN + NULL; + END; + CREATE TABLE nested_tests.after_error (a int); +END; +$$; +CREATE FUNCTION nested_tests.roundtrip_ddl() RETURNS void LANGUAGE plpgsql AS $$ +BEGIN + CREATE TABLE nested_tests.from_roundtrip (a int); + INSERT INTO nested_tests.from_roundtrip VALUES (1); + ALTER TABLE nested_tests.from_roundtrip + ADD COLUMN b int DEFAULT 1, ALTER COLUMN b SET DEFAULT 2; + INSERT INTO nested_tests.from_roundtrip (a) VALUES (2); +END; +$$; +CREATE FUNCTION nested_tests.nested_trigger() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +DECLARE + cmd record; +BEGIN + FOR cmd IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP + IF cmd.object_identity = 'nested_tests.parent' THEN + CREATE TABLE nested_tests.child (a int, b int); + INSERT INTO nested_tests.child VALUES (1, 42); + ALTER TABLE nested_tests.child + ALTER COLUMN a TYPE text USING b::text, DROP COLUMN b; + -- CTAS is unsupported, but must not borrow the parent's capture state. + CREATE TABLE nested_tests.child_ctas AS SELECT 7 AS a; + BEGIN + CREATE TABLE nested_tests.child_rolled_back (a int); + ALTER TABLE nested_tests.child_rolled_back ADD COLUMN b nonexistent_type; + EXCEPTION WHEN undefined_object THEN + NULL; + END; + CREATE TABLE nested_tests.child_after_error (a int); + END IF; + END LOOP; +END; +$$; +SET test_ddl_deparse.print_deparsed_ddl = text; +-- SQL and PL/pgSQL functions issue supported CREATE and ALTER commands. +SELECT nested_tests.sql_ddl(); +NOTICE: deparsed DDL: CREATE TABLE nested_tests.from_sql (a pg_catalog.int4) + sql_ddl +--------- + 10 +(1 row) + +SELECT nested_tests.plpgsql_ddl(); +NOTICE: deparsed DDL: CREATE TABLE nested_tests.from_plpgsql (a pg_catalog.int4, b pg_catalog.int4) +NOTICE: deparsed DDL: ALTER TABLE nested_tests.from_plpgsql DROP COLUMN b, ALTER COLUMN a SET DATA TYPE pg_catalog.text USING (b)::text + plpgsql_ddl +------------- + +(1 row) + +SELECT a FROM nested_tests.from_plpgsql; + a +---- + 42 +(1 row) + +-- A caught error must restore capture state before the next nested command +-- and before the next top-level command. The failed block leaves no table. +SELECT nested_tests.caught_error(); +NOTICE: deparsed DDL: CREATE TABLE nested_tests.rolled_back (a pg_catalog.int4) +NOTICE: deparsed DDL: CREATE TABLE nested_tests.after_error (a pg_catalog.int4) + caught_error +-------------- + +(1 row) + +CREATE TABLE nested_tests.after_call (a int); +NOTICE: deparsed DDL: CREATE TABLE nested_tests.after_call (a pg_catalog.int4) +SELECT to_regclass('nested_tests.rolled_back') IS NULL AS rolled_back; + rolled_back +------------- + t +(1 row) + +-- Explicit transaction rollback must likewise leave capture usable. +BEGIN; +CREATE TABLE nested_tests.explicit_rollback (a int); +NOTICE: deparsed DDL: CREATE TABLE nested_tests.explicit_rollback (a pg_catalog.int4) +ROLLBACK; +CREATE TABLE nested_tests.after_rollback (a int); +NOTICE: deparsed DDL: CREATE TABLE nested_tests.after_rollback (a pg_catalog.int4) +SELECT to_regclass('nested_tests.explicit_rollback') IS NULL AS rolled_back; + rolled_back +------------- + t +(1 row) + +-- The child needs a separate capture frame whether its event trigger runs +-- before or after capture_deparsed_ddl. Unsupported CTAS must not borrow the +-- parent's frame, and a caught child error must not discard the parent JSON. +CREATE EVENT TRIGGER aa_nested ON ddl_command_end + WHEN TAG IN ('CREATE TABLE') + EXECUTE FUNCTION nested_tests.nested_trigger(); +CREATE TABLE nested_tests.parent (a int); +NOTICE: deparsed DDL: CREATE TABLE nested_tests.child (a pg_catalog.int4, b pg_catalog.int4) +NOTICE: deparsed DDL: ALTER TABLE nested_tests.child DROP COLUMN b, ALTER COLUMN a SET DATA TYPE pg_catalog.text USING (b)::text +NOTICE: deparsed DDL: CREATE TABLE nested_tests.child_rolled_back (a pg_catalog.int4) +NOTICE: deparsed DDL: CREATE TABLE nested_tests.child_after_error (a pg_catalog.int4) +NOTICE: deparsed DDL: CREATE TABLE nested_tests.parent (a pg_catalog.int4) +SELECT a FROM nested_tests.child; + a +---- + 42 +(1 row) + +SELECT a FROM nested_tests.child_ctas; + a +--- + 7 +(1 row) + +SELECT to_regclass('nested_tests.child_rolled_back') IS NULL AS rolled_back; + rolled_back +------------- + t +(1 row) + +DROP EVENT TRIGGER aa_nested; +DROP TABLE nested_tests.parent, nested_tests.child, + nested_tests.child_ctas, nested_tests.child_after_error; +CREATE EVENT TRIGGER zz_nested ON ddl_command_end + WHEN TAG IN ('CREATE TABLE') + EXECUTE FUNCTION nested_tests.nested_trigger(); +CREATE TABLE nested_tests.parent (a int); +NOTICE: deparsed DDL: CREATE TABLE nested_tests.parent (a pg_catalog.int4) +NOTICE: deparsed DDL: CREATE TABLE nested_tests.child (a pg_catalog.int4, b pg_catalog.int4) +NOTICE: deparsed DDL: ALTER TABLE nested_tests.child DROP COLUMN b, ALTER COLUMN a SET DATA TYPE pg_catalog.text USING (b)::text +NOTICE: deparsed DDL: CREATE TABLE nested_tests.child_rolled_back (a pg_catalog.int4) +NOTICE: deparsed DDL: CREATE TABLE nested_tests.child_after_error (a pg_catalog.int4) +SELECT a FROM nested_tests.child; + a +---- + 42 +(1 row) + +SELECT a FROM nested_tests.child_ctas; + a +--- + 7 +(1 row) + +SELECT to_regclass('nested_tests.child_rolled_back') IS NULL AS rolled_back; + rolled_back +------------- + t +(1 row) + +DROP EVENT TRIGGER zz_nested; +DROP TABLE nested_tests.parent, nested_tests.child, + nested_tests.child_ctas, nested_tests.child_after_error; +-- Also reconstruct the enclosing command and its nested trial commands. +SET test_ddl_deparse.execute_deparsed_ddl = on; +CREATE EVENT TRIGGER zz_nested_roundtrip ON ddl_command_end + WHEN TAG IN ('CREATE TABLE') + EXECUTE FUNCTION nested_tests.nested_trigger(); +CREATE TABLE nested_tests.parent (a int); +NOTICE: deparsed DDL: CREATE TABLE nested_tests.parent (a pg_catalog.int4) +NOTICE: deparsed DDL: CREATE TABLE nested_tests.child (a pg_catalog.int4, b pg_catalog.int4) +NOTICE: deparsed DDL: ALTER TABLE nested_tests.child DROP COLUMN b, ALTER COLUMN a SET DATA TYPE pg_catalog.text USING (b)::text +NOTICE: deparsed DDL: CREATE TABLE nested_tests.child_rolled_back (a pg_catalog.int4) +NOTICE: deparsed DDL: CREATE TABLE nested_tests.child_after_error (a pg_catalog.int4) +SELECT a FROM nested_tests.child; + a +---- + 42 +(1 row) + +SELECT a FROM nested_tests.child_ctas; + a +--- + 7 +(1 row) + +SELECT to_regclass('nested_tests.child_rolled_back') IS NULL AS rolled_back; + rolled_back +------------- + t +(1 row) + +DROP EVENT TRIGGER zz_nested_roundtrip; +DROP TABLE nested_tests.parent, nested_tests.child, + nested_tests.child_ctas, nested_tests.child_after_error; +-- Reconstruct function-issued DDL and verify that ADD COLUMN's default and +-- the later SET DEFAULT retain their distinct effects on row values. +SELECT nested_tests.roundtrip_ddl(); +NOTICE: deparsed DDL: CREATE TABLE nested_tests.from_roundtrip (a pg_catalog.int4) +NOTICE: deparsed DDL: ALTER TABLE nested_tests.from_roundtrip ADD COLUMN b pg_catalog.int4 DEFAULT 1, ALTER COLUMN b SET DEFAULT 2 + roundtrip_ddl +--------------- + +(1 row) + +SELECT a, b FROM nested_tests.from_roundtrip ORDER BY a; + a | b +---+--- + 1 | 1 + 2 | 2 +(2 rows) + +-- Only the user's CREATE is deparsed: internally generated sequence/index +-- commands and the replay itself must not produce additional deparse notices. +CREATE TABLE nested_tests.serial_table (a serial PRIMARY KEY); +NOTICE: deparsed DDL: CREATE TABLE nested_tests.serial_table (a serial, PRIMARY KEY (a)) +-- Concurrent refresh creates and alters temporary tables through SPI while +-- command collection is inhibited. Those are internal commands too. +CREATE MATERIALIZED VIEW nested_tests.mv AS SELECT 1 AS a; +CREATE UNIQUE INDEX mv_a_idx ON nested_tests.mv (a); +REFRESH MATERIALIZED VIEW CONCURRENTLY nested_tests.mv; +SELECT * FROM nested_tests.mv; + a +--- + 1 +(1 row) + +RESET test_ddl_deparse.print_deparsed_ddl; +RESET test_ddl_deparse.execute_deparsed_ddl; +DROP SCHEMA nested_tests CASCADE; +NOTICE: drop cascades to 13 other objects +DETAIL: drop cascades to function nested_tests.sql_ddl() +drop cascades to function nested_tests.plpgsql_ddl() +drop cascades to function nested_tests.caught_error() +drop cascades to function nested_tests.roundtrip_ddl() +drop cascades to function nested_tests.nested_trigger() +drop cascades to table nested_tests.from_sql +drop cascades to table nested_tests.from_plpgsql +drop cascades to table nested_tests.after_error +drop cascades to table nested_tests.after_call +drop cascades to table nested_tests.after_rollback +drop cascades to table nested_tests.from_roundtrip +drop cascades to table nested_tests.serial_table +drop cascades to materialized view nested_tests.mv +ALTER EVENT TRIGGER test_ddl_deparse ENABLE;