Skip to content

Support DROP TABLE ... CASCADE and RESTRICT - #3155

Open
zachmu wants to merge 7 commits into
mainfrom
zachmu/issue3120
Open

Support DROP TABLE ... CASCADE and RESTRICT#3155
zachmu wants to merge 7 commits into
mainfrom
zachmu/issue3120

Conversation

@zachmu

@zachmu zachmu commented Aug 20, 2026

Copy link
Copy Markdown
Member

Reworks DROP TABLE ... CASCADE to reuse the standard DROP TABLE plan node, with the pre-execution hook dropping dependent objects (views, foreign keys, row-type columns, and functions/procedures) at execution time.

Fixes #3120.

Companion PRs:

Fixes #3120.

DROP TABLE ... CASCADE previously failed with 'CASCADE is not yet
supported' (and RESTRICT with a similar error). RESTRICT is the default
behavior in Postgres, so it is now handled by the standard DROP TABLE
path. CASCADE converts to a new DropTableCascade node that first drops
the foreign key constraints on other tables that reference the dropped
tables (via the same sql.ForeignKeyTable interface calls the engine's
DROP TABLE execution uses), then delegates the actual drop to the
standard DROP TABLE path, which keeps its existing dependency
bookkeeping such as dropping sequences owned by the tables' columns.

Table names without an explicit schema are resolved against the search
path, and IF EXISTS skips missing tables as before.

Not yet cascaded (dependency tracking does not cover them yet): views
that reference a dropped table (they are left in place, matching the
behavior of plain DROP TABLE), and tables/functions/procedures using a
dropped table's row type as a column or parameter type (these still
error, as without CASCADE).
@itoqa

itoqa Bot commented Aug 20, 2026

Copy link
Copy Markdown

Ito QA test results
Commit: 94bfd08: 14 test cases ran, 14 passed ✅.

Summary

Coverage spans normal and edge-case table removal behavior, including dependency handling, cascading cleanup, rollback safety, concurrent operations, qualified and quoted names, schema resolution, metadata consistency, and preservation of related data and objects. It also checks that invalid operations fail safely without damaging existing protections or usability.

Safe to merge — the run found no PR-attributable regressions or new failures across the exercised database behaviors, and all checks passed. No merge blocker was identified.

Tests run by Ito

View full run

Result Severity Type Description
General Dropping the parent and middle tables removes only those requested tables. The external child and its data remain available, and its old foreign key no longer blocks new rows.
General Dropping the parent table removed the foreign key from both metadata views, and the child table still worked normally.
General Dropping a table with quote characters in its schema and table names removed the intended table and its owned sequence. A similarly named table and the surrounding schema stayed intact.
General The cascade command returned an error for a missing table, and the target table and external child were still present afterward. The child still rejected an invalid row, so its foreign-key protection was not lost.
General Two sessions tried to drop the same parent table at once. One succeeded, the other got a safe missing-table error, and the child table stayed usable with consistent foreign-key metadata.
Cascade Dropping the parent table removed the parent but kept the child table and its existing row. A new child row with an unknown parent value was also accepted after the drop.
Database A DROP TABLE command using the current database, schema, and table name succeeded. The table was gone when checked afterward.
Database A DROP TABLE command naming a different database was rejected, and the protected table stayed intact.
Dependency Dropping a table with CASCADE was refused because another table uses its row type, and both tables stayed available.
Resolution The database found the unqualified table through the active search path and also found the explicitly named table in another schema. Both tables were removed successfully.
Rev A failed table drop was rolled back cleanly. The parent table and its dependent function still worked, and the child table continued to reject an invalid foreign-key value.
Sequence Dropping the table with CASCADE removed both the table and its generated sequence. Looking up the old sequence then returned a relation-not-found error instead of generating another value.
Standard Dropping a parent table with a child table that depends on it returned a clear dependency error, both with the default behavior and with RESTRICT. An unrelated table was removed successfully.
View Dropping the base table removed it while keeping the dependent view in the catalog. Querying the view returned the expected invalid-dependency error.

Tip

Reply with @itoqa to send us feedback on this test run.

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor
Main PR
Total 42090 42090
Successful 19289 19529
Failures 22801 22561
Partial Successes1 5459 5458
Main PR
Successful 45.8280% 46.3982%
Failures 54.1720% 53.6018%

${\color{red}Regressions (5)}$

limit

QUERY:          select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2
order by s2 desc;
RECEIVED ERROR: expected row count 3 but received 9

tsrf

QUERY:          SELECT few.id, generate_series(1,3) g FROM few ORDER BY id, g DESC;
RECEIVED ERROR: expected row count 9 but received 27
QUERY:          select 'foo' as f, generate_series(1,2) as g from few order by 1;
RECEIVED ERROR: expected row count 6 but received 12

updatable_views

QUERY:          SELECT c.oid,
  n.nspname,
  c.relname
FROM pg_catalog.pg_class c
     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname OPERATOR(pg_catalog.~) '^(rw_view1)$' COLLATE pg_catalog.default
  AND pg_catalog.pg_table_is_visible(c.oid)
ORDER BY 2, 3;
RECEIVED ERROR: expected row count 1 but received 0
QUERY:          SELECT a.attname,
  pg_catalog.format_type(a.atttypid, a.atttypmod),
  (SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid, true)
   FROM pg_catalog.pg_attrdef d
   WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef),
  a.attnotnull,
  (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t
   WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) AS attcollation,
  a.attidentity,
  a.attgenerated,
  a.attstorage,
  pg_catalog.col_description(a.attrelid, a.attnum)
FROM pg_catalog.pg_attribute a
WHERE a.attrelid = '148785' AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum;
RECEIVED ERROR: expected row count 2 but received 0

${\color{lightgreen}Progressions (210)}$

aggregates

QUERY: drop table minmaxtest cascade;
QUERY: drop table t1 cascade;
QUERY: create temp table t1(f1 int, f2 bigint);
QUERY: select f1 from t1 left join t2 using (f1) group by f1;
QUERY: select f1 from t1 left join t2 using (f1) group by t1.f1;
QUERY: select t1.f1 from t1 left join t2 using (f1) group by t1.f1;

alter_table

QUERY: drop table atacc2 cascade;
QUERY: create table atacc2 (test2 int) inherits (atacc1);
QUERY: insert into atacc2 (test) values (-3);
QUERY: create table p1 (f1 int, f2 int);
QUERY: drop table p1 cascade;
QUERY: create table p1 (f1 int, f2 int);
QUERY: drop table p1 cascade;
QUERY: create table p1 (f1 int, f2 int);
QUERY: alter table only p1 drop column f1;
QUERY: drop table p1 cascade;
QUERY: create table p1 (f1 int, f2 int);
QUERY: alter table only p1 drop column f1;
QUERY: drop table p1 cascade;
QUERY: create table p1(id int, name text);
QUERY: create table p2(id2 int, name text, height int);
QUERY: alter table only p1 drop column name;
QUERY: alter table p2 drop column name;
QUERY: alter table p2 drop column height;
QUERY: drop table p1, p2 cascade;
QUERY: create table p1 (f1 int);
QUERY: insert into p1 values (1,2,'abc');
QUERY: drop table p1 cascade;
QUERY: DROP TABLE test_drop_constr_parent CASCADE;
QUERY: create table p1 (b int, a int not null) partition by range (b);
QUERY: insert into p1 (a, b) values (2, 3);
QUERY: drop table perm_part_parent cascade;
QUERY: drop table temp_part_parent cascade;

constraints

QUERY: DROP TABLE ATACC1 CASCADE;
QUERY: CREATE TABLE ATACC1 (TEST INT, TEST2 INT
	CHECK (TEST > 0), CHECK (TEST2 > 10) NO INHERIT);
QUERY: DROP TABLE ATACC1 CASCADE;

copy2

QUERY: DROP TABLE rls_t1 CASCADE;

copyselect

QUERY: drop table test3;

create_function_sql

QUERY: DROP TABLE functest1 CASCADE;
QUERY: DROP TABLE functest3 CASCADE;

Footnotes

  1. These are tests that we're marking as Successful, however they do not match the expected output in some way. This is due to small differences, such as different wording on the error messages, or the column names being incorrect while the data itself is correct.

@coffeegoddd

coffeegoddd commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

@coffeegoddd DOLT

read_tests from_latency to_latency percent_change
covering_index_scan_postgres 2.43 2.43 0.0
groupby_scan_postgres 75.82 77.19 1.81
index_join_postgres 2.22 2.22 0.0
index_join_scan_postgres 1.58 1.61 1.9
index_scan_postgres 467.3 458.96 -1.78
oltp_point_select 0.36 0.36 0.0
oltp_read_only 6.32 6.32 0.0
select_random_points 0.7 0.7 0.0
select_random_ranges 1.01 1.01 0.0
table_scan_postgres 467.3 467.3 0.0
types_table_scan_postgres 1170.65 1170.65 0.0
write_tests from_latency to_latency percent_change
oltp_delete_insert_postgres 6.67 6.67 0.0
oltp_insert 3.3 3.25 -1.52
oltp_read_write 13.22 13.22 0.0
oltp_update_index 3.55 3.55 0.0
oltp_update_non_index 3.25 3.25 0.0
oltp_write_only 7.04 7.04 0.0
types_delete_insert_postgres 7.17 7.17 0.0

zachmu added 2 commits August 31, 2026 14:19
Replaces the DropTableCascade node with a Cascade flag plumbed through
vitess and GMS to plan.DropTable. The BeforeTableDeletion hook now
performs the cascade at execution time: dependent views (transitively,
resolved the same way the engine resolves view definitions), foreign
keys on other tables referencing the dropped tables, functions and
procedures with parameters of a dropped table's row type, and columns
of other tables using a dropped table's row type.
@zachmu
zachmu requested a review from Hydrocharged August 31, 2026 22:46
@itoqa

itoqa Bot commented Aug 31, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Report94bfd08d09facd: 18 test cases ran, 1 regression ❌, 3 new failures ❌, 14 passing ✅.

Diff Summary

Coverage spans schema deletion and dependency handling, data import/export round trips, malformed-input recovery, startup protection, integrity repair, and trigger or record behavior across both normal and adversarial cases. The broader health is mixed: many supported workflows behave correctly, but table deletion can still remove objects unsafely or leave dependent constraints and routines behind.

Not safe to merge yet — this PR has a high-severity deletion-safety failure that can remove a table while leaving an unusable dependent object, along with regressions and new failures in foreign-key cleanup and restricted deletion behavior. These are directly attributable to the changed schema-management logic and represent a merge blocker, not a flag-for-later caveat.

Tests run by Ito

View full run

Result State Severity Type Description
🆕 Regression Medium severity Cascade The database rejected DROP TABLE parent CASCADE because the foreign key on child was still present. CASCADE should remove that constraint, drop parent, keep child, and allow child rows that no longer reference an existing parent.
❌ New Failure High severity Cascade DROP TABLE target6 CASCADE completed successfully and removed target6, while the dependent function unsupported6 remained in the catalog. The expected behavior was to reject the drop because this dependency is not handled, or to remove the dependent routine as part of CASCADE.
❌ New Failure High severity Drop RESTRICT returned success and removed the base table. The dependent view remained in the catalog but could no longer be queried; the expected behavior is a dependency error with both objects preserved.
❌ New Failure Medium severity General The command returned a dependency error for child_middle_id_fkey. The metadata still contained foreign keys for child, middle, and survivor, and parent, middle, child, and survivor all remained instead of the dropped tables being removed.
Passing Alter Changing the column type converted all four existing rows with their own source value: 1, 2, 3, and 4. The column now reports the integer type.
Passing Alter An incompatible value raised a conversion error, and the column type and all original values stayed unchanged.
Passing General Malformed binary COPY input was rejected without importing rows, and a later query still returned the expected value.
Passing Cascade Dropping a table with CASCADE removes its dependent views, routines, and row-type column while keeping the other table. The recorded rejection came from a stale runtime, not the current application code.
Passing Cascade Dropping a table with CASCADE is designed to remove views that depend on it, including views that depend on those views, while keeping an unrelated same-named table in another schema. The source implementation and matching regression tests cover this behavior; the earlier database replay used a runtime state that did not contain the current implementation.
Passing Copy Verified acceptable by independent adversarial review: the reported expectation does not match what the code actually promises. Review notes: The finding's decisive mechanism is impossible in the cited code: although readFull temporarily appends capacity for the requested bytes, it reslices record to the number actually read before saving it. Clean EOF while starting the next record therefore saves a zero-length slice, which Finish does not reject; if the trailer was already consumed, sawTrailer is true and partialRecord was explicitly …
Passing Copy Invalid binary headers are rejected, and unsupported binary options are refused before loading data. The table remains empty after every check.
Passing Copy Malformed binary row data was rejected, and the table stayed empty. The database did not accept incomplete rows or import partial data.
Passing Copy Text and CSV COPY preserve NULL, empty strings, delimiters, quotes, backslashes, and newlines. The exported data loaded back with all five rows unchanged, including one NULL and one empty string.
Passing Record Verified acceptable by independent adversarial review: the reported expectation does not match what the code actually promises. Review notes: The UPDATE-trigger path is real, but the finding's decisive causal premise is false in the checked-out code: trigger records, compiled references, and RETURN retrieval all use the same folded keys, and a direct repository test covers the asserted workflow. The reported panic text comes from an unrelated function-registry lookup, while trigger-binding failure has a distinct ordinary error path; wit…
Passing Rev The repair command fixed reachable corruption across three branches and one working set. A clean scan followed, and running repair again made no changes.
Passing Sequence Dropping a table with CASCADE removed both the table and the sequence created for its SERIAL column.
Passing Startup Starting with damaged storage stops the server, explains the corruption, and tells the operator how to back up and repair it. The database listener stays unavailable, so clients cannot connect to unsafe data.
Passing Startup Absent, malformed, and old-version sentinel files did not let corrupt data start the server. Each launch reported the corruption and no database listener became available.
⏸️ Skipped General Dropping the parent and middle tables removes only those requested tables. The external child and its data remain available, and its old foreign key no longer blocks new rows.
⏸️ Skipped General Dropping the parent table removed the foreign key from both metadata views, and the child table still worked normally.
⏸️ Skipped General Dropping a table with quote characters in its schema and table names removed the intended table and its owned sequence. A similarly named table and the surrounding schema stayed intact.
⏸️ Skipped General The cascade command returned an error for a missing table, and the target table and external child were still present afterward. The child still rejected an invalid row, so its foreign-key protection was not lost.
⏸️ Skipped General Two sessions tried to drop the same parent table at once. One succeeded, the other got a safe missing-table error, and the child table stayed usable with consistent foreign-key metadata.
⏸️ Skipped Database A DROP TABLE command using the current database, schema, and table name succeeded. The table was gone when checked afterward.
⏸️ Skipped Database A DROP TABLE command naming a different database was rejected, and the protected table stayed intact.
⏸️ Skipped Resolution The database found the unqualified table through the active search path and also found the explicitly named table in another schema. Both tables were removed successfully.
⏸️ Skipped Rev A failed table drop was rolled back cleanly. The parent table and its dependent function still worked, and the child table continued to reject an invalid foreign-key value.
⏸️ Skipped Standard Dropping a parent table with a child table that depends on it returned a clear dependency error, both with the default behavior and with RESTRICT. An unrelated table was removed successfully.
Tests that are no longer relevant

Below are tests that previously ran and are no longer relevant:

Type Test Description
Dependency Row-type dependencies still block cascade Dropped because The prior CASCADE-blocking row-type dependency behavior is gone: server/hook/delete_table_cascade.go adds cascadeDropDependentColumns and cascadeDropRoutines, and testing/go/drop_table_test.go now expects the dependent column or routine to be removed by CASCADE.
View Dependent view stays after table cascade Dropped because The prior assertion that a dependent view remains present but invalid is gone: server/hook/delete_table_cascade.go adds cascadeDropViews, which explicitly drops dependent views during CASCADE; testing/go/drop_table_test.go now expects the view lookup to return not found.

Tip

Reply with @itoqa to send us feedback on this test run.

Comment thread server/ast/drop_table.go
TargetType: auth.AuthTargetType_TableIdentifiers,
TargetNames: authTableNames,
}
return &vitess.DDL{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🆕 New Failure: identified in this diff run

Medium severity Cascade leaves foreign keys behind

What failed: The command returned a dependency error for child_middle_id_fkey. The metadata still contained foreign keys for child, middle, and survivor, and parent, middle, child, and survivor all remained instead of the dropped tables being removed.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: Database users cannot remove related tables with CASCADE when a foreign key is held by a table that remains. The tables stay in place, so schema changes and cleanup work are blocked until the constraints are handled another way.
  • Steps to Reproduce:
    1. Create parent, middle, and child tables where middle references parent and child references middle.
    2. Add additional foreign keys and a surviving table that reference the tables being dropped.
    3. Run DROP TABLE parent, middle CASCADE.
    4. Inspect the foreign-key metadata and then reconnect to try an insert into the surviving child table.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The PR routes DROP TABLE through the standard DDL node in server/ast/drop_table.go:45-54 and sets Cascade from tree.DropCascade at line 52. servercfg/config.go:50-56 registers hook.BeforeTableDeletion as the DropTable pre-execution hook. In server/hook/delete_table.go:39-68, the CASCADE branch calls cascadeDropDependencies before the standard dependency checks. The new server/hook/delete_table_cascade.go:216-254 implementation obtains the foreign-key collection, finds keys referencing each table in the drop set, skips only keys whose declaring table is also in the drop set, and calls ForeignKeyTable.DropForeignKey for keys declared by surviving tables. In the recorded multi-table execution, that cleanup did not remove child_middle_id_fkey: standard validation then rejected the drop and the catalog retained all relations. The smallest practical fix is to correct the foreign-key collection/table resolution or DropForeignKey invocation in cascadeDropForeignKeys so every foreign key declared by a surviving table and referencing any requested drop target is removed before standard validation; keep the existing in-drop-set skip for constraints owned by tables that will be deleted. Add the BF-CROSS-3 multi-table/transitive regression case to prevent this path from regressing.
  • Why this is likely a bug: This is a real application failure rather than a browser or setup issue: local PostgreSQL-wire execution of valid SQL returned the engine's foreign-key dependency error, and the post-failure catalog query showed that the relations and foreign keys remained. The expected behavior is also encoded in testing/go/drop_table_test.go:206-230, where dropping parent and middle must succeed and an insert that would have violated the removed child-to-middle key must succeed. The PR introduced the CASCADE flag, pre-hook, and foreign-key cleanup path, so the failed cleanup is directly within the changed feature surface. A targeted correction to the lookup or DropForeignKey call in cascadeDropForeignKeys is sufficient; no broad redesign is required.
Relevant code

server/ast/drop_table.go:45-54

return &vitess.DDL{ Action: vitess.DropStr, FromTables: tableNames, IfExists: node.IfExists, Cascade: node.DropBehavior == tree.DropCascade, Auth: authInformation }

server/hook/delete_table.go:50-55

if n.Cascade {
	if err := cascadeDropDependencies(ctx, runner, allTableNames); err != nil {
		return nil, err
	}
}

server/hook/delete_table_cascade.go:228-251

referencedByFk := fkc.KeysForTable(tblName)
...
if tableNameInSet(fk.TableName, allDeletedTables) { continue }
if err = fkTable.DropForeignKey(ctx, fk.Name, fk.TableName.Name, fk.TableName.Schema); err != nil { return err }

testing/go/drop_table_test.go:206-230

Query: `DROP TABLE parent, middle CASCADE;`
...
Query: `INSERT INTO child VALUES (1, 99);`, Expected: []sql.Row{}
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**Medium severity — Cascade leaves foreign keys behind**

**What failed:** The command returned a dependency error for child_middle_id_fkey. The metadata still contained foreign keys for child, middle, and survivor, and parent, middle, child, and survivor all remained instead of the dropped tables being removed.

- **Impact:** Database users cannot remove related tables with CASCADE when a foreign key is held by a table that remains. The tables stay in place, so schema changes and cleanup work are blocked until the constraints are handled another way.
- **Steps to reproduce:**
  1. Create parent, middle, and child tables where middle references parent and child references middle.
  2. Add additional foreign keys and a surviving table that reference the tables being dropped.
  3. Run DROP TABLE parent, middle CASCADE.
  4. Inspect the foreign-key metadata and then reconnect to try an insert into the surviving child table.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** The PR routes DROP TABLE through the standard DDL node in server/ast/drop_table.go:45-54 and sets Cascade from tree.DropCascade at line 52. servercfg/config.go:50-56 registers hook.BeforeTableDeletion as the DropTable pre-execution hook. In server/hook/delete_table.go:39-68, the CASCADE branch calls cascadeDropDependencies before the standard dependency checks. The new server/hook/delete_table_cascade.go:216-254 implementation obtains the foreign-key collection, finds keys referencing each table in the drop set, skips only keys whose declaring table is also in the drop set, and calls ForeignKeyTable.DropForeignKey for keys declared by surviving tables. In the recorded multi-table execution, that cleanup did not remove child_middle_id_fkey: standard validation then rejected the drop and the catalog retained all relations. The smallest practical fix is to correct the foreign-key collection/table resolution or DropForeignKey invocation in cascadeDropForeignKeys so every foreign key declared by a surviving table and referencing any requested drop target is removed before standard validation; keep the existing in-drop-set skip for constraints owned by tables that will be deleted. Add the BF-CROSS-3 multi-table/transitive regression case to prevent this path from regressing.
- **Why this is likely a bug:** This is a real application failure rather than a browser or setup issue: local PostgreSQL-wire execution of valid SQL returned the engine's foreign-key dependency error, and the post-failure catalog query showed that the relations and foreign keys remained. The expected behavior is also encoded in testing/go/drop_table_test.go:206-230, where dropping parent and middle must succeed and an insert that would have violated the removed child-to-middle key must succeed. The PR introduced the CASCADE flag, pre-hook, and foreign-key cleanup path, so the failed cleanup is directly within the changed feature surface. A targeted correction to the lookup or DropForeignKey call in cascadeDropForeignKeys is sufficient; no broad redesign is required.

**Relevant code:**

`server/ast/drop_table.go:45-54`

~~~go
return &vitess.DDL{ Action: vitess.DropStr, FromTables: tableNames, IfExists: node.IfExists, Cascade: node.DropBehavior == tree.DropCascade, Auth: authInformation }
~~~

`server/hook/delete_table.go:50-55`

~~~go
if n.Cascade {
	if err := cascadeDropDependencies(ctx, runner, allTableNames); err != nil {
		return nil, err
	}
}
~~~

`server/hook/delete_table_cascade.go:228-251`

~~~go
referencedByFk := fkc.KeysForTable(tblName)
...
if tableNameInSet(fk.TableName, allDeletedTables) { continue }
if err = fkTable.DropForeignKey(ctx, fk.Name, fk.TableName.Name, fk.TableName.Schema); err != nil { return err }
~~~

`testing/go/drop_table_test.go:206-230`

~~~go
Query: `DROP TABLE parent, middle CASCADE;`
...
Query: `INSERT INTO child VALUES (1, 99);`, Expected: []sql.Row{}
~~~

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting that this is saying foreign keys aren't being dropped

Comment thread server/ast/drop_table.go
TargetType: auth.AuthTargetType_TableIdentifiers,
TargetNames: authTableNames,
}
return &vitess.DDL{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔁 Regression: previously passing at 94bfd08

Medium severity Cascade cannot remove external foreign key

What failed: The database rejected DROP TABLE parent CASCADE because the foreign key on child was still present. CASCADE should remove that constraint, drop parent, keep child, and allow child rows that no longer reference an existing parent.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: When a user drops a parent table with CASCADE, the command fails and leaves both tables and the foreign-key constraint in place. They must remove the constraint separately before the table can be dropped.
  • Steps to Reproduce:
    1. Create a parent table with an integer primary key.
    2. Create a child table with a foreign key that references the parent key.
    3. Insert one parent row and one matching child row.
    4. Run DROP TABLE parent CASCADE.
    5. Observe that the statement fails with a dependency error naming the child's foreign-key constraint, and both tables remain.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The PostgreSQL AST conversion in server/ast/drop_table.go:45-53 maps DROP TABLE ... CASCADE to the standard vitess DDL DropTable node with Cascade=true. The PR wires that node into hook.BeforeTableDeletion through servercfg/config.go:54-56. In server/hook/delete_table.go:50-55, the pre-hook calls cascadeDropDependencies before the standard dependency checks and table drop. The new cascadeDropDependencies function in server/hook/delete_table_cascade.go:42-52 delegates foreign-key cleanup to cascadeDropForeignKeys. That function obtains the foreign-key collection at lines 219-225, asks for keys referencing each target at line 229, resolves the surviving table at lines 233-240, and calls DropForeignKey at lines 244-251. The observed error proves this path did not remove child_parent_id_fkey before standard validation; the standard path then correctly refuses to drop the still-referenced parent. The repository's own integration contract in testing/go/drop_table_test.go:114-153 expects the opposite behavior: the parent disappears, the child remains, and an insert violating the former foreign key succeeds. The targeted fix is to correct the lookup or removal arguments in cascadeDropForeignKeys so every external key returned for the dropped table is removed before validation, while retaining keys for tables that are themselves being dropped.
  • Why this is likely a bug: This is a reproducible local database failure, not a browser or connection observation: PostgreSQL reports that child_parent_id_fkey still references parent, and the parent and child remain. The requested CASCADE behavior has a precise product contract in the repository integration test, which requires removing only the external foreign key while preserving the child table. The implementation added by this PR is specifically responsible for performing that cleanup, yet its cleanup result is absent before standard dependency validation. A targeted correction to the foreign-key collection lookup or DropForeignKey call can address the failure without changing unrelated drop behavior.
Relevant code

server/ast/drop_table.go:45-53

return &vitess.DDL{
		Action:     vitess.DropStr,
		FromTables: tableNames,
		IfExists:   node.IfExists,
		Cascade:    node.DropBehavior == tree.DropCascade,
		Auth:       authInformation,
	}, nil

servercfg/config.go:50-56

Hooks: sql.ExecutionHooks{
			DropTable: sql.DropTable{
				PreSQLExecution: hook.BeforeTableDeletion,
			},
		},

server/hook/delete_table.go:50-55

if n.Cascade {
		// CASCADE drops the objects that depend on the dropped tables before the standard drop path runs.
		if err := cascadeDropDependencies(ctx, runner, allTableNames); err != nil {
			return nil, err
		}
	}

server/hook/delete_table_cascade.go:216-253

func cascadeDropForeignKeys(ctx *sql.Context, allDeletedTables []doltdb.TableName) error {
	_, root, err := core.GetRootFromContext(ctx)
	...
	_, referencedByFk := fkc.KeysForTable(tblName)
	...
	for _, fk := range referencedByFk {
		if tableNameInSet(fk.TableName, allDeletedTables) {
			continue
		}
		if err = fkTable.DropForeignKey(ctx, fk.Name, fk.TableName.Name, fk.TableName.Schema); err != nil {
			return err
		}
	}
	return nil
}

testing/go/drop_table_test.go:133-153

Query:    `DROP TABLE parent CASCADE;`,
Expected: []sql.Row{},
...
Query:    `SELECT * FROM child;`,
Expected: []sql.Row{{10, 1}},
...
Query:    `INSERT INTO child VALUES (11, 99);`,
Expected: []sql.Row{},
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**Medium severity — Cascade cannot remove external foreign key**

**What failed:** The database rejected DROP TABLE parent CASCADE because the foreign key on child was still present. CASCADE should remove that constraint, drop parent, keep child, and allow child rows that no longer reference an existing parent.

- **Impact:** When a user drops a parent table with CASCADE, the command fails and leaves both tables and the foreign-key constraint in place. They must remove the constraint separately before the table can be dropped.
- **Steps to reproduce:**
  1. Create a parent table with an integer primary key.
  2. Create a child table with a foreign key that references the parent key.
  3. Insert one parent row and one matching child row.
  4. Run DROP TABLE parent CASCADE.
  5. Observe that the statement fails with a dependency error naming the child's foreign-key constraint, and both tables remain.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** The PostgreSQL AST conversion in server/ast/drop_table.go:45-53 maps DROP TABLE ... CASCADE to the standard vitess DDL DropTable node with Cascade=true. The PR wires that node into hook.BeforeTableDeletion through servercfg/config.go:54-56. In server/hook/delete_table.go:50-55, the pre-hook calls cascadeDropDependencies before the standard dependency checks and table drop. The new cascadeDropDependencies function in server/hook/delete_table_cascade.go:42-52 delegates foreign-key cleanup to cascadeDropForeignKeys. That function obtains the foreign-key collection at lines 219-225, asks for keys referencing each target at line 229, resolves the surviving table at lines 233-240, and calls DropForeignKey at lines 244-251. The observed error proves this path did not remove child_parent_id_fkey before standard validation; the standard path then correctly refuses to drop the still-referenced parent. The repository's own integration contract in testing/go/drop_table_test.go:114-153 expects the opposite behavior: the parent disappears, the child remains, and an insert violating the former foreign key succeeds. The targeted fix is to correct the lookup or removal arguments in cascadeDropForeignKeys so every external key returned for the dropped table is removed before validation, while retaining keys for tables that are themselves being dropped.
- **Why this is likely a bug:** This is a reproducible local database failure, not a browser or connection observation: PostgreSQL reports that child_parent_id_fkey still references parent, and the parent and child remain. The requested CASCADE behavior has a precise product contract in the repository integration test, which requires removing only the external foreign key while preserving the child table. The implementation added by this PR is specifically responsible for performing that cleanup, yet its cleanup result is absent before standard dependency validation. A targeted correction to the foreign-key collection lookup or DropForeignKey call can address the failure without changing unrelated drop behavior.

**Relevant code:**

`server/ast/drop_table.go:45-53`

~~~go
return &vitess.DDL{
		Action:     vitess.DropStr,
		FromTables: tableNames,
		IfExists:   node.IfExists,
		Cascade:    node.DropBehavior == tree.DropCascade,
		Auth:       authInformation,
	}, nil
~~~

`servercfg/config.go:50-56`

~~~go
Hooks: sql.ExecutionHooks{
			DropTable: sql.DropTable{
				PreSQLExecution: hook.BeforeTableDeletion,
			},
		},
~~~

`server/hook/delete_table.go:50-55`

~~~go
if n.Cascade {
		// CASCADE drops the objects that depend on the dropped tables before the standard drop path runs.
		if err := cascadeDropDependencies(ctx, runner, allTableNames); err != nil {
			return nil, err
		}
	}
~~~

`server/hook/delete_table_cascade.go:216-253`

~~~go
func cascadeDropForeignKeys(ctx *sql.Context, allDeletedTables []doltdb.TableName) error {
	_, root, err := core.GetRootFromContext(ctx)
	...
	_, referencedByFk := fkc.KeysForTable(tblName)
	...
	for _, fk := range referencedByFk {
		if tableNameInSet(fk.TableName, allDeletedTables) {
			continue
		}
		if err = fkTable.DropForeignKey(ctx, fk.Name, fk.TableName.Name, fk.TableName.Schema); err != nil {
			return err
		}
	}
	return nil
}
~~~

`testing/go/drop_table_test.go:133-153`

~~~go
Query:    `DROP TABLE parent CASCADE;`,
Expected: []sql.Row{},
...
Query:    `SELECT * FROM child;`,
Expected: []sql.Row{{10, 1}},
...
Query:    `INSERT INTO child VALUES (11, 99);`,
Expected: []sql.Row{},
~~~

allTableNames = append(allTableNames, doltTable.TableName())
}
// TODO: handle DROP TABLE CASCADE
if n.Cascade {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🆕 New Failure: identified in this diff run

High severity Cascade drops a table but leaves its function

What failed: DROP TABLE target6 CASCADE completed successfully and removed target6, while the dependent function unsupported6 remained in the catalog. The expected behavior was to reject the drop because this dependency is not handled, or to remove the dependent routine as part of CASCADE.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: High High severity
  • Impact: Database administrators can remove a table while a dependent function remains behind, leaving the schema inconsistent and that function unusable. The command does not stop to warn them about the unsupported dependency.
  • Steps to Reproduce:
    1. Create a table named target6 with an integer column.
    2. Create a SQL function named unsupported6 that returns target6 and selects one row from target6.
    3. Run DROP TABLE target6 CASCADE.
    4. Check the catalog for target6 and unsupported6. The table is gone, but the function remains instead of the drop being rejected.
  • Stub / mock content: The test used an isolated local database and ordinary SQL objects. No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The standard AST conversion in server/ast/drop_table.go lines 45-53 maps DROP TABLE ... CASCADE to the standard vitess DDL node with Cascade=true. The PR diff removed the previous custom cascade node and changed server/hook/delete_table.go lines 50-55 to call cascadeDropDependencies before the standard drop path. In the new server/hook/delete_table_cascade.go, cascadeDropRoutines at lines 257-299 builds deletedTypes, iterates functions at lines 264-273, and marks a function only when one of f.AllParams has a deleted table type at lines 265-270. It never compares f.ReturnType with deletedTypes, even though core/functions/collection.go lines 40-54 shows Function stores ReturnType separately from AllParams. The later remaining-dependency validation in server/hook/delete_table.go lines 56-67 calls beforeTableDeletionCheckFuncsProcs, which also iterates only f.AllParams at lines 133-141 and therefore cannot catch a return-type dependency. With a function declared RETURNS target6 and no target6 parameter, both checks miss the dependency and the standard drop removes the table while retaining the function. The smallest practical fix is to treat a function or procedure return type that matches a deleted table row type as a dependency: either include ReturnType in cascadeDropRoutines' deletion check, or include it in the remaining-dependency validation if the intended behavior is to reject unsupported return-type cleanup. The chosen behavior should be covered by a regression test using a routine with RETURNS the_table.
  • Why this is likely a bug: The database result is specific and reproducible: target6 was absent after DROP TABLE target6 CASCADE, while unsupported6 was still present. This is not a browser or mock artifact; the SQL setup created a real table and function, and the source path independently explains the exact mismatch. A function's ReturnType is persisted as a separate field, but the PR's new cleanup and blocking checks inspect only parameter types, so a return-type dependency falls through to table deletion. Leaving a routine that references a deleted row type makes the catalog inconsistent and violates the test's dependency-safety contract. The targeted remediation is to inspect return types in the routine dependency handling and either drop those routines during CASCADE or reject the drop when that dependency is not supported.
Relevant code

server/hook/delete_table.go:50-67

if n.Cascade {
		if err := cascadeDropDependencies(ctx, runner, allTableNames); err != nil {
			return nil, err
		}
	}
	for _, doltTable := range resolvedTables {
		if err := beforeTableDeletionCheckTableColumns(ctx, doltTable, allTableNames); err != nil {
			return nil, err
		}
		if err := beforeTableDeletionCheckFuncsProcs(ctx, doltTable, allTableNames); err != nil {

server/hook/delete_table_cascade.go:257-279

func cascadeDropRoutines(ctx *sql.Context, allDeletedTables []doltdb.TableName) error {
	deletedTypes := deletedTableTypes(allDeletedTables)
	funcsColl, err := core.GetFunctionsCollectionFromContext(ctx, "")
	...
	err = funcsColl.IterateFunctions(ctx, func(f functions.Function) (stop bool, err error) {
		for _, param := range f.AllParams {
			if _, ok := deletedTypes[param.Type]; ok {
				funcIDs = append(funcIDs, f.ID)
				break
			}
		}
		return false, nil
	})
	...
	if err = funcsColl.DropFunction(ctx, funcIDs...); err != nil {

core/functions/collection.go:40-54

type Function struct {
	ID                 id.Function
	ReturnType         id.Type
	AllParams          []procedures.Parameter
	Variadic           bool
	IsNonDeterministic bool
	Strict             bool
	Definition         string
	ExtensionName      string
	ExtensionSymbol    string
	Operations         []plpgsql.InterpreterOperation
	SQLDefinition      string
	SetOf              bool
}

server/hook/delete_table.go:126-141

func beforeTableDeletionCheckFuncsProcs(ctx *sql.Context, doltTable *sqle.DoltTable, allDeletedTables []doltdb.TableName) error {
	tableName := doltTable.TableName()
	tableAsType := id.NewType(tableName.Schema, tableName.Name)
	...
	err = funcsColl.IterateFunctions(ctx, func(f functions.Function) (stop bool, err error) {
		for _, param := range f.AllParams {
			if param.Type == tableAsType {
				return true, errors.Newf("cannot drop table %s because other objects depend on it ...")
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**High severity — Cascade drops a table but leaves its function**

**What failed:** DROP TABLE target6 CASCADE completed successfully and removed target6, while the dependent function unsupported6 remained in the catalog. The expected behavior was to reject the drop because this dependency is not handled, or to remove the dependent routine as part of CASCADE.

- **Impact:** Database administrators can remove a table while a dependent function remains behind, leaving the schema inconsistent and that function unusable. The command does not stop to warn them about the unsupported dependency.
- **Steps to reproduce:**
  1. Create a table named target6 with an integer column.
  2. Create a SQL function named unsupported6 that returns target6 and selects one row from target6.
  3. Run DROP TABLE target6 CASCADE.
  4. Check the catalog for target6 and unsupported6. The table is gone, but the function remains instead of the drop being rejected.
- **Stub / mock content:** The test used an isolated local database and ordinary SQL objects. No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** The standard AST conversion in server/ast/drop_table.go lines 45-53 maps DROP TABLE ... CASCADE to the standard vitess DDL node with Cascade=true. The PR diff removed the previous custom cascade node and changed server/hook/delete_table.go lines 50-55 to call cascadeDropDependencies before the standard drop path. In the new server/hook/delete_table_cascade.go, cascadeDropRoutines at lines 257-299 builds deletedTypes, iterates functions at lines 264-273, and marks a function only when one of f.AllParams has a deleted table type at lines 265-270. It never compares f.ReturnType with deletedTypes, even though core/functions/collection.go lines 40-54 shows Function stores ReturnType separately from AllParams. The later remaining-dependency validation in server/hook/delete_table.go lines 56-67 calls beforeTableDeletionCheckFuncsProcs, which also iterates only f.AllParams at lines 133-141 and therefore cannot catch a return-type dependency. With a function declared RETURNS target6 and no target6 parameter, both checks miss the dependency and the standard drop removes the table while retaining the function. The smallest practical fix is to treat a function or procedure return type that matches a deleted table row type as a dependency: either include ReturnType in cascadeDropRoutines' deletion check, or include it in the remaining-dependency validation if the intended behavior is to reject unsupported return-type cleanup. The chosen behavior should be covered by a regression test using a routine with RETURNS the_table.
- **Why this is likely a bug:** The database result is specific and reproducible: target6 was absent after DROP TABLE target6 CASCADE, while unsupported6 was still present. This is not a browser or mock artifact; the SQL setup created a real table and function, and the source path independently explains the exact mismatch. A function's ReturnType is persisted as a separate field, but the PR's new cleanup and blocking checks inspect only parameter types, so a return-type dependency falls through to table deletion. Leaving a routine that references a deleted row type makes the catalog inconsistent and violates the test's dependency-safety contract. The targeted remediation is to inspect return types in the routine dependency handling and either drop those routines during CASCADE or reject the drop when that dependency is not supported.

**Relevant code:**

`server/hook/delete_table.go:50-67`

~~~go
if n.Cascade {
		if err := cascadeDropDependencies(ctx, runner, allTableNames); err != nil {
			return nil, err
		}
	}
	for _, doltTable := range resolvedTables {
		if err := beforeTableDeletionCheckTableColumns(ctx, doltTable, allTableNames); err != nil {
			return nil, err
		}
		if err := beforeTableDeletionCheckFuncsProcs(ctx, doltTable, allTableNames); err != nil {
~~~

`server/hook/delete_table_cascade.go:257-279`

~~~go
func cascadeDropRoutines(ctx *sql.Context, allDeletedTables []doltdb.TableName) error {
	deletedTypes := deletedTableTypes(allDeletedTables)
	funcsColl, err := core.GetFunctionsCollectionFromContext(ctx, "")
	...
	err = funcsColl.IterateFunctions(ctx, func(f functions.Function) (stop bool, err error) {
		for _, param := range f.AllParams {
			if _, ok := deletedTypes[param.Type]; ok {
				funcIDs = append(funcIDs, f.ID)
				break
			}
		}
		return false, nil
	})
	...
	if err = funcsColl.DropFunction(ctx, funcIDs...); err != nil {
~~~

`core/functions/collection.go:40-54`

~~~go
type Function struct {
	ID                 id.Function
	ReturnType         id.Type
	AllParams          []procedures.Parameter
	Variadic           bool
	IsNonDeterministic bool
	Strict             bool
	Definition         string
	ExtensionName      string
	ExtensionSymbol    string
	Operations         []plpgsql.InterpreterOperation
	SQLDefinition      string
	SetOf              bool
}
~~~

`server/hook/delete_table.go:126-141`

~~~go
func beforeTableDeletionCheckFuncsProcs(ctx *sql.Context, doltTable *sqle.DoltTable, allDeletedTables []doltdb.TableName) error {
	tableName := doltTable.TableName()
	tableAsType := id.NewType(tableName.Schema, tableName.Name)
	...
	err = funcsColl.IterateFunctions(ctx, func(f functions.Function) (stop bool, err error) {
		for _, param := range f.AllParams {
			if param.Type == tableAsType {
				return true, errors.Newf("cannot drop table %s because other objects depend on it ...")
~~~

Comment thread server/ast/drop_table.go
TargetType: auth.AuthTargetType_TableIdentifiers,
TargetNames: authTableNames,
}
return &vitess.DDL{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🆕 New Failure: identified in this diff run

High severity Restrict deletes tables behind views

What failed: RESTRICT returned success and removed the base table. The dependent view remained in the catalog but could no longer be queried; the expected behavior is a dependency error with both objects preserved.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: High High severity
  • Impact: A user who drops a table with RESTRICT can lose the table even when another view depends on it. The dependent view is left unusable, and restoring the table data may require a backup.
  • Steps to Reproduce:
    1. Create a table named qa_drop1_base and insert one row.
    2. Create a view named qa_drop1_view that selects from qa_drop1_base.
    3. Run DROP TABLE qa_drop1_base RESTRICT.
    4. Check that the base table is gone and then query qa_drop1_view.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The parser conversion in server/ast/drop_table.go:45-53 now always returns a vitess DDL statement and sets Cascade to true only for tree.DropCascade. Therefore an explicit RESTRICT clause produces a standard plan.DropTable with Cascade=false. The new server/hook/delete_table.go:33-68 pre-execution hook calls cascadeDropDependencies only inside the n.Cascade branch at lines 50-55, then performs only the existing table-row-type and function/procedure-parameter checks at lines 56-67. It does not inspect dependent views on the non-cascade path. View discovery and dependency handling are implemented in server/hook/delete_table_cascade.go:80-128, but that code is reachable only through cascadeDropDependencies and therefore only for CASCADE. The local SQL evidence matches this path: DROP TABLE qa_drop1_base RESTRICT returned DROP TABLE with return code 0, the base relation was absent, and qa_drop1_view remained present but unusable. The smallest practical fix is to add dependent-view rejection to the non-CASCADE validation path, or restore the standard dependency check for views before allowing the table deletion; do not run cascadeDropViews for RESTRICT because RESTRICT must reject rather than remove the dependent view.
  • Why this is likely a bug: PostgreSQL RESTRICT is expected to refuse a table drop when another relation depends on that table. The recorded local SQL result demonstrates the harmful state transition: the base table disappears, while the dependent view is left behind and cannot be queried. This is not explained by the unavailable browser service because the database command completed successfully and the source path explains exactly why view validation is skipped. The PR itself introduced the routing and hook condition that create the gap; a targeted non-cascade dependent-view check is the smallest practical remediation.
Relevant code

server/ast/drop_table.go:45-53

return &vitess.DDL{
		Action:     vitess.DropStr,
		FromTables: tableNames,
		IfExists:   node.IfExists,
		Cascade:    node.DropBehavior == tree.DropCascade,
		Auth:       authInformation,
	}, nil

server/hook/delete_table.go:50-68

if n.Cascade {
		// CASCADE drops the objects that depend on the dropped tables before the standard drop path runs.
		if err := cascadeDropDependencies(ctx, runner, allTableNames); err != nil {
			return nil, err
		}
	}
	// These checks error on any remaining dependency: everything with CASCADE was dropped above, so anything left
	// (or anything at all, without CASCADE) blocks the drop.
	for _, doltTable := range resolvedTables {

server/hook/delete_table_cascade.go:80-84

func cascadeDropViews(ctx *sql.Context, runner sql.StatementRunner, allDeletedTables []doltdb.TableName) error {
	views, viewExists, err := loadDatabaseViews(ctx)
	if err != nil || len(views) == 0 {
		return err
	}
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**High severity — Restrict deletes tables behind views**

**What failed:** RESTRICT returned success and removed the base table. The dependent view remained in the catalog but could no longer be queried; the expected behavior is a dependency error with both objects preserved.

- **Impact:** A user who drops a table with RESTRICT can lose the table even when another view depends on it. The dependent view is left unusable, and restoring the table data may require a backup.
- **Steps to reproduce:**
  1. Create a table named qa_drop1_base and insert one row.
  2. Create a view named qa_drop1_view that selects from qa_drop1_base.
  3. Run DROP TABLE qa_drop1_base RESTRICT.
  4. Check that the base table is gone and then query qa_drop1_view.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** The parser conversion in server/ast/drop_table.go:45-53 now always returns a vitess DDL statement and sets Cascade to true only for tree.DropCascade. Therefore an explicit RESTRICT clause produces a standard plan.DropTable with Cascade=false. The new server/hook/delete_table.go:33-68 pre-execution hook calls cascadeDropDependencies only inside the n.Cascade branch at lines 50-55, then performs only the existing table-row-type and function/procedure-parameter checks at lines 56-67. It does not inspect dependent views on the non-cascade path. View discovery and dependency handling are implemented in server/hook/delete_table_cascade.go:80-128, but that code is reachable only through cascadeDropDependencies and therefore only for CASCADE. The local SQL evidence matches this path: DROP TABLE qa_drop1_base RESTRICT returned DROP TABLE with return code 0, the base relation was absent, and qa_drop1_view remained present but unusable. The smallest practical fix is to add dependent-view rejection to the non-CASCADE validation path, or restore the standard dependency check for views before allowing the table deletion; do not run cascadeDropViews for RESTRICT because RESTRICT must reject rather than remove the dependent view.
- **Why this is likely a bug:** PostgreSQL RESTRICT is expected to refuse a table drop when another relation depends on that table. The recorded local SQL result demonstrates the harmful state transition: the base table disappears, while the dependent view is left behind and cannot be queried. This is not explained by the unavailable browser service because the database command completed successfully and the source path explains exactly why view validation is skipped. The PR itself introduced the routing and hook condition that create the gap; a targeted non-cascade dependent-view check is the smallest practical remediation.

**Relevant code:**

`server/ast/drop_table.go:45-53`

~~~go
return &vitess.DDL{
		Action:     vitess.DropStr,
		FromTables: tableNames,
		IfExists:   node.IfExists,
		Cascade:    node.DropBehavior == tree.DropCascade,
		Auth:       authInformation,
	}, nil
~~~

`server/hook/delete_table.go:50-68`

~~~go
if n.Cascade {
		// CASCADE drops the objects that depend on the dropped tables before the standard drop path runs.
		if err := cascadeDropDependencies(ctx, runner, allTableNames); err != nil {
			return nil, err
		}
	}
	// These checks error on any remaining dependency: everything with CASCADE was dropped above, so anything left
	// (or anything at all, without CASCADE) blocks the drop.
	for _, doltTable := range resolvedTables {
~~~

`server/hook/delete_table_cascade.go:80-84`

~~~go
func cascadeDropViews(ctx *sql.Context, runner sql.StatementRunner, allDeletedTables []doltdb.TableName) error {
	views, viewExists, err := loadDatabaseViews(ctx)
	if err != nil || len(views) == 0 {
		return err
	}
~~~

@Hydrocharged Hydrocharged left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! This approach of using hooks is within the original intention, so that anything that resolves to a table deletion will run through this code.

Comment thread server/ast/drop_table.go
TargetType: auth.AuthTargetType_TableIdentifiers,
TargetNames: authTableNames,
}
return &vitess.DDL{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting that this is saying foreign keys aren't being dropped

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DROP TABLE CASCADE support

3 participants