schema-lint v1 commit 4: emit + apply DropType { Soft } (#99)

Wire the second half of the dormant Drop* family. Per
docs/dev/schema-lint-v1-plan.md, commit #4 of the schema-lint chassis
v1 series (MR-694). Builds on commit #3 (PR #90, DropProperty Soft).

Planner (schema_plan.rs):
- plan_nodes leftover loop: emit DropType { Node, name, Soft }
  instead of UnsupportedChange (OG-DS-102) for node-type removals.
- plan_edges leftover loop: emit DropType { Edge, name, Soft }
  instead of UnsupportedChange (OG-DS-103) for edge-type removals.

Apply (schema_apply.rs):
- New dropped_tables: BTreeSet<String> accumulator alongside
  added_tables / renamed_tables / rewritten_tables.
- DropType arm in the metadata loop populates dropped_tables for
  Soft mode. Hard mode errors (lands in commit #5 with
  --allow-data-loss).
- New tombstone-emission loop after the rename sidecar build:
  for each dropped table, push to sidecar_tombstones AND populate
  table_tombstones with table_version + 1. The existing manifest
  publish path converts table_tombstones into ManifestChange::Tombstone
  operations — no new manifest plumbing needed.
- Soft DropType has no Phase B per-table write; the tombstone is the
  entire change. Lance dataset files are retained — prior __manifest
  versions still reference them, so time travel + branch-from-snapshot
  can read the dropped table until cleanup_old_versions runs.
- Rides on SidecarKind::SchemaApply per MR-847 (already established
  by commit #3).

Tests:
- Planner unit test plan_emits_soft_drop_for_removed_node_and_edge_types
  asserts both Node and Edge DropType { Soft } emission for the
  Company + WorksAt combined drop, plus no UnsupportedChange.
- Integration test apply_schema_drops_node_and_referencing_edge_softly
  (replaces apply_schema_rejects_dropping_a_node_type): asserts
  plan emission, apply success, current manifest entries absent,
  pre-drop manifest entries present (time-travel reversibility),
  reopen consistency.
- Integration test apply_schema_drops_an_edge_type_softly (replaces
  apply_schema_rejects_dropping_an_edge_type): single edge drop,
  asserts other tables untouched, time-travel reversibility.

Test results:
- cargo test -p omnigraph-compiler --lib: 239 passed (1 new + 238)
- cargo test -p omnigraph-engine --test schema_apply: 11 passed
  (2 converted + 9 unchanged)

Pending for v1 completion:
- Commit #5: --allow-data-loss CLI flag + Hard mode promotion in
  planner + immediate compact_files + cleanup_old_versions for
  both DropProperty and DropType.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Andrew Altshuler 2026-05-16 20:25:42 +03:00 committed by GitHub
parent e98347eb7b
commit 58cee158d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 273 additions and 45 deletions

View file

@ -324,13 +324,18 @@ fn plan_nodes(
.iter()
.filter(|node| !consumed.contains(&node.name))
{
steps.push(SchemaMigrationStep::UnsupportedChange {
entity: format!("node:{}", leftover.name),
reason: format!(
"removing node type '{}' is not supported in schema migration v1",
leftover.name
),
code: Some(crate::lint::codes::OG_DS_102.code.to_string()),
// Node type removed from the desired schema: emit
// DropType { Node, Soft } per docs/dev/schema-lint-v1-plan.md
// commit #4. Soft = remove the table's entry from the current
// __manifest version; data files retained; previous manifest
// versions still reference the table, so Lance time travel
// restores it until cleanup_old_versions ages out the older
// __manifest entries. Hard mode (immediate dataset deletion)
// lands in commit #5 gated by --allow-data-loss.
steps.push(SchemaMigrationStep::DropType {
type_kind: SchemaTypeKind::Node,
name: leftover.name.clone(),
mode: DropMode::Soft,
});
}
@ -442,13 +447,15 @@ fn plan_edges(
.iter()
.filter(|edge| !consumed.contains(&edge.name))
{
steps.push(SchemaMigrationStep::UnsupportedChange {
entity: format!("edge:{}", leftover.name),
reason: format!(
"removing edge type '{}' is not supported in schema migration v1",
leftover.name
),
code: Some(crate::lint::codes::OG_DS_103.code.to_string()),
// Edge type removed from the desired schema: emit
// DropType { Edge, Soft } per docs/dev/schema-lint-v1-plan.md
// commit #4. Same Soft mechanics as node-type drops — manifest
// entry tombstoned, data files retained, reversible via Lance
// time travel until cleanup.
steps.push(SchemaMigrationStep::DropType {
type_kind: SchemaTypeKind::Edge,
name: leftover.name.clone(),
mode: DropMode::Soft,
});
}
}
@ -991,6 +998,81 @@ node Person {
);
}
#[test]
fn plan_emits_soft_drop_for_removed_node_and_edge_types() {
// Removing a node type + the edge type that references it
// emits two DropType { Soft } steps (chassis v1 commit #4,
// MR-694). The plan is `supported = true` — apply tombstones
// both manifest entries. Time-travel reversibility is verified
// at the integration level by
// `apply_schema_drops_node_and_referencing_edge_softly`
// in `crates/omnigraph/tests/schema_apply.rs`.
let accepted = build_schema_ir(
&parse_schema(
r#"
node Person {
name: String @key
}
node Company {
name: String @key
}
edge WorksAt: Person -> Company
"#,
)
.unwrap(),
)
.unwrap();
let desired = build_schema_ir(
&parse_schema(
r#"
node Person {
name: String @key
}
"#,
)
.unwrap(),
)
.unwrap();
let plan = plan_schema_migration(&accepted, &desired).unwrap();
assert!(
plan.supported,
"drop-type plan must be supported: {plan:?}"
);
assert!(
plan.steps.iter().any(|step| matches!(
step,
SchemaMigrationStep::DropType {
type_kind: SchemaTypeKind::Node,
name,
mode: DropMode::Soft,
} if name == "Company"
)),
"expected DropType {{ Node, Company, Soft }} in plan: {plan:?}",
);
assert!(
plan.steps.iter().any(|step| matches!(
step,
SchemaMigrationStep::DropType {
type_kind: SchemaTypeKind::Edge,
name,
mode: DropMode::Soft,
} if name == "WorksAt"
)),
"expected DropType {{ Edge, WorksAt, Soft }} in plan: {plan:?}",
);
// Negative: no UnsupportedChange anywhere in the plan.
assert!(
!plan
.steps
.iter()
.any(|step| matches!(step, UnsupportedChange { .. })),
"soft type drop must not emit UnsupportedChange: {plan:?}",
);
}
#[test]
fn plan_rejects_required_property_addition() {
let accepted = build_schema_ir(