From b569af26cb0a75c54639adb7a2a81741b622b5f5 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Thu, 29 Oct 2026 13:14:55 +0100 Subject: [PATCH] Translate DROP FOREIGN TABLE and refuse it with CASCADE --- src/impls/translator_impls/statement.rs | 19 +++++++++++++------ tests/test_drop_statements.rs | 12 ++++++++++++ .../test_no_statement_is_silently_dropped.rs | 2 ++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/impls/translator_impls/statement.rs b/src/impls/translator_impls/statement.rs index 627193846..3888376e6 100644 --- a/src/impls/translator_impls/statement.rs +++ b/src/impls/translator_impls/statement.rs @@ -413,6 +413,7 @@ fn drop_label(object_type: ObjectType) -> &'static str { ObjectType::Stage => "DROP STAGE", ObjectType::Stream => "DROP STREAM", ObjectType::Warehouse => "DROP WAREHOUSE", + ObjectType::ForeignTable => "DROP FOREIGN TABLE", ObjectType::Table | ObjectType::View | ObjectType::Index @@ -1890,11 +1891,8 @@ impl crate::traits::translator::TranslatorWithContext for Statement { } statements } - // A schema, role, or type never reached the output, so - // dropping the statement removes nothing that exists. The - // exceptions are the two that would destroy tables: a - // cascading DROP SCHEMA removes every table in the schema, - // and DROP DATABASE removes the lot. + // These never reached the output, so a drop removes nothing + // unless it cascades into tables or views that did. ObjectType::Schema if *cascade => { return Err(reject_unsupported_statement( self, @@ -1903,6 +1901,14 @@ impl crate::traits::translator::TranslatorWithContext for Statement { determined. Drop the tables by name instead.", )); } + ObjectType::ForeignTable if *cascade => { + return Err(reject_unsupported_statement( + self, + "A cascading DROP FOREIGN TABLE also deletes the views that read the \ + table, and those views reach the output although the table does not. Drop \ + the views by name instead.", + )); + } ObjectType::Database | ObjectType::MaterializedView => { return Err(reject_unsupported_statement( self, @@ -1918,7 +1924,8 @@ impl crate::traits::translator::TranslatorWithContext for Statement { | ObjectType::Collation | ObjectType::Stage | ObjectType::Stream - | ObjectType::Warehouse => { + | ObjectType::Warehouse + | ObjectType::ForeignTable => { drop_with_warning( drop_label(*object_type), REASON_DROP_OF_ABSENT_OBJECT, diff --git a/tests/test_drop_statements.rs b/tests/test_drop_statements.rs index d11a8d3b0..75a12050f 100644 --- a/tests/test_drop_statements.rs +++ b/tests/test_drop_statements.rs @@ -531,6 +531,18 @@ fn drop_database_is_rejected() { ); } +/// A cascading `DROP FOREIGN TABLE` also deletes the views over the table, +/// which reach the output. +#[test] +fn drop_foreign_table_cascade_is_rejected() { + let error = translate_statement("DROP FOREIGN TABLE remote_users CASCADE;") + .expect_err("a cascading DROP FOREIGN TABLE must be reported"); + assert!( + error.to_string().contains("DROP FOREIGN TABLE remote_users CASCADE"), + "expected the error to name the statement, got {error}" + ); +} + #[test] fn test_drop_sequence_ignored() -> Result<(), Box> { let translated = translate_statement("DROP SEQUENCE my_sequence;")?; diff --git a/tests/test_no_statement_is_silently_dropped.rs b/tests/test_no_statement_is_silently_dropped.rs index edf69953d..32d61301c 100644 --- a/tests/test_no_statement_is_silently_dropped.rs +++ b/tests/test_no_statement_is_silently_dropped.rs @@ -212,6 +212,8 @@ const CASES: &[Case] = &[ // Warned and dropped: foreign data and credentials. warned("CREATE SERVER srv FOREIGN DATA WRAPPER postgres_fdw"), warned("CREATE FOREIGN TABLE ft (id INT) SERVER srv"), + warned("DROP FOREIGN TABLE ft"), + warned("COMMENT ON FOREIGN TABLE ft IS 'note'"), warned("CREATE CONNECTOR c TYPE 'mysql'"), warned("ALTER CONNECTOR c SET URL 'u'"), warned("DROP CONNECTOR c"),