Skip to content

Commit c09a955

Browse files
Sujay JayakarConvex, Inc.
authored andcommitted
Recursively delete components (#30748)
GitOrigin-RevId: ef31f2926c553ce1284809adb75f6337c5638ae4
1 parent 2a44166 commit c09a955

File tree

3 files changed

+79
-34
lines changed

3 files changed

+79
-34
lines changed

crates/database/src/bootstrap_model/components/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ impl<'a, RT: Runtime> BootstrapComponentsModel<'a, RT> {
110110
.component_in_parent(parent_and_name, &mut self.tx.reads)
111111
}
112112

113+
pub fn component_children(
114+
&mut self,
115+
parent_id: DeveloperDocumentId,
116+
) -> anyhow::Result<Vec<ParsedDocument<ComponentMetadata>>> {
117+
self.tx
118+
.component_registry
119+
.component_children(parent_id, &mut self.tx.reads)
120+
}
121+
113122
pub fn root_component(&mut self) -> anyhow::Result<Option<ParsedDocument<ComponentMetadata>>> {
114123
self.component_in_parent(None)
115124
}

crates/database/src/component_registry.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,36 @@ impl ComponentRegistry {
228228
self.component_in_parent(None, reads)
229229
}
230230

231+
pub fn component_children(
232+
&self,
233+
parent_id: DeveloperDocumentId,
234+
reads: &mut TransactionReadSet,
235+
) -> anyhow::Result<Vec<ParsedDocument<ComponentMetadata>>> {
236+
let interval =
237+
Interval::prefix(values_to_bytes(&[Some(val!(parent_id.to_string()))]).into());
238+
reads.record_indexed_derived(
239+
TabletIndexName::new(
240+
self.components_tablet,
241+
COMPONENTS_BY_PARENT_INDEX.descriptor().clone(),
242+
)?,
243+
vec![PARENT_FIELD.clone(), NAME_FIELD.clone()].try_into()?,
244+
interval,
245+
);
246+
let child_ids = self
247+
.components
248+
.iter()
249+
.filter_map(|(_, doc)| {
250+
let (component_parent_id, _) = doc.parent_and_name()?;
251+
if component_parent_id == parent_id {
252+
Some(doc.clone())
253+
} else {
254+
None
255+
}
256+
})
257+
.collect();
258+
Ok(child_ids)
259+
}
260+
231261
pub fn component_in_parent(
232262
&self,
233263
parent_and_name: Option<(DeveloperDocumentId, ComponentName)>,

crates/model/src/components/config.rs

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -706,51 +706,57 @@ impl<'a, RT: Runtime> ComponentConfigModel<'a, RT> {
706706

707707
#[minitrace::trace]
708708
pub async fn delete_component(&mut self, component_id: ComponentId) -> anyhow::Result<()> {
709-
let ComponentId::Child(id) = component_id else {
709+
if component_id.is_root() {
710710
anyhow::bail!("Cannot delete root component");
711-
};
711+
}
712712

713+
// First, walk the component tree and validate that it's okay to delete each
714+
// component.
713715
let component = BootstrapComponentsModel::new(self.tx)
714716
.load_component(component_id)
715717
.await?;
716-
717-
match component {
718-
Some(component) => {
719-
if component.state != ComponentState::Unmounted {
720-
anyhow::bail!(ErrorMetadata::bad_request(
721-
"ComponentMustBeUnmounted",
722-
"Component must be unmounted before deletion"
723-
));
724-
}
725-
},
726-
None => {
727-
anyhow::bail!(ErrorMetadata::transient_not_found(
728-
"ComponentNotFound",
729-
format!("Component with ID {:?} not found", component_id)
718+
let Some(component) = component else {
719+
anyhow::bail!(ErrorMetadata::transient_not_found(
720+
"ComponentNotFound",
721+
format!("Component with ID {:?} not found", component_id)
722+
));
723+
};
724+
let mut stack = vec![component];
725+
let mut all_ids = vec![];
726+
while let Some(component) = stack.pop() {
727+
if component.state != ComponentState::Unmounted {
728+
anyhow::bail!(ErrorMetadata::bad_request(
729+
"ComponentMustBeUnmounted",
730+
"Component must be unmounted before deletion"
730731
));
731-
},
732+
}
733+
let children =
734+
BootstrapComponentsModel::new(self.tx).component_children(component.id().into())?;
735+
stack.extend(children);
736+
all_ids.push(component.id());
732737
}
733738

734-
let resolved_document_id =
735-
BootstrapComponentsModel::new(self.tx).resolve_component_id(id)?;
736-
SystemMetadataModel::new_global(self.tx)
737-
.delete(resolved_document_id)
738-
.await?;
739-
740-
let namespace = TableNamespace::from(component_id);
741-
// delete the schema table first
742-
// tables defined in the schema cannot be deleted, so we delete the _schemas
743-
// table first to remove that restriction
744-
TableModel::new(self.tx)
745-
.delete_table(namespace, SCHEMAS_TABLE.clone())
746-
.await?;
739+
// Delete the components we found.
740+
for component_id in all_ids {
741+
SystemMetadataModel::new_global(self.tx)
742+
.delete(component_id)
743+
.await?;
747744

748-
// then delete all tables, including system tables
749-
let namespaced_table_mapping = self.tx.table_mapping().namespace(namespace);
750-
for (tablet_id, ..) in namespaced_table_mapping.iter() {
745+
let namespace = TableNamespace::from(ComponentId::Child(component_id.into()));
746+
// delete the schema table first
747+
// tables defined in the schema cannot be deleted, so we delete the _schemas
748+
// table first to remove that restriction
751749
TableModel::new(self.tx)
752-
.delete_table_by_id(tablet_id)
750+
.delete_table(namespace, SCHEMAS_TABLE.clone())
753751
.await?;
752+
753+
// then delete all tables, including system tables
754+
let namespaced_table_mapping = self.tx.table_mapping().namespace(namespace);
755+
for (tablet_id, ..) in namespaced_table_mapping.iter() {
756+
TableModel::new(self.tx)
757+
.delete_table_by_id(tablet_id)
758+
.await?;
759+
}
754760
}
755761

756762
Ok(())

0 commit comments

Comments
 (0)