Skip to content

fix(catalog): reject dropping non-empty MemoryCatalog namespaces - #2890

Open
fallintoplace wants to merge 2 commits into
apache:mainfrom
fallintoplace:fix-memory-non-empty-namespace
Open

fix(catalog): reject dropping non-empty MemoryCatalog namespaces#2890
fallintoplace wants to merge 2 commits into
apache:mainfrom
fallintoplace:fix-memory-non-empty-namespace

Conversation

@fallintoplace

@fallintoplace fallintoplace commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

N/A. Found while comparing MemoryCatalog namespace behavior with the Java reference implementation and other catalog implementations.

What changes are included in this PR?

MemoryCatalog previously removed a namespace state directly. Because that state owns its child namespaces and table registrations, dropping a non-empty namespace silently discarded the subtree and made registered tables unreachable.

This PR checks for child namespaces and registered tables before removal while holding the same catalog mutation lock. A non-empty namespace now returns an error that reports the remaining child and table counts, and no catalog state is modified.

Are these changes tested?

Yes. Unit tests verify that drops are rejected for namespaces containing child namespaces or tables, and that the rejected operation preserves the namespace, its children, and its tables.

Validated with:

  • cargo test -p iceberg --lib
  • cargo clippy -p iceberg --lib -- -D warnings

@fallintoplace fallintoplace changed the title fix(catalog): reject non-empty memory namespace drops fix(catalog): prevent MemoryCatalog from dropping non-empty namespaces Jul 25, 2026
@fallintoplace fallintoplace changed the title fix(catalog): prevent MemoryCatalog from dropping non-empty namespaces fix(catalog): reject dropping non-empty MemoryCatalog namespaces Jul 25, 2026
let table_count = namespace_state.table_metadata_locations.len();
if child_namespace_count > 0 || table_count > 0 {
return Err(Error::new(
ErrorKind::Unexpected,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PreconditionFailed might be a tad more fitting

Suggested change
ErrorKind::Unexpected,
ErrorKind::PreconditionFailed,

/// The operation was rejected because the system is not in a state required for the operation’s execution.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A better option would be to create a new ErrorKind::NamespaceNotEmpty (analogous to the existing ErrorKind::NamespaceAlreadyExists). Equivalents already exists in other languages like pyiceberg's NamespaceNotEmptyError and Java's NamespaceNotEmptyException.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks. All of these suggestions are valid. I have pushed the fix.


#[tokio::test]
async fn test_dropping_a_namespace_also_drops_namespaces_nested_under_that_one() {
async fn test_drop_namespace_throws_error_if_namespace_has_children() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: Rust doesn't really have a concept of throwing

Suggested change
async fn test_drop_namespace_throws_error_if_namespace_has_children() {
async fn test_drop_namespace_returns_error_if_namespace_has_children() {

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.

2 participants