Skip to main content

nodedb_types/
dropped_collection.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! `DroppedCollection` — row shape for `_system.dropped_collections`.
4//!
5//! Returned by `NodeDb::list_dropped_collections`. Each row describes
6//! one soft-deleted collection within its retention window (the
7//! `StoredCollection` redb row is still present with `is_active =
8//! false`). Once the retention window elapses, the sweeper hard-deletes
9//! the row and it disappears from this list.
10
11use serde::{Deserialize, Serialize};
12
13/// A soft-deleted collection awaiting either `UNDROP` or retention-driven
14/// hard deletion.
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct DroppedCollection {
17    /// Tenant that owns this collection.
18    pub tenant_id: u64,
19    /// Collection name (unique per tenant while soft-deleted).
20    pub name: String,
21    /// Preserved owner at the time of `DROP` — the user who can
22    /// `UNDROP` without superuser/tenant_admin elevation. Empty
23    /// string if the owner row could not be resolved at the time
24    /// of the catalog read.
25    pub owner: String,
26    /// Engine / collection-type slug resolved from
27    /// `StoredCollection.collection_type.as_str()`. One of
28    /// `"document"`, `"strict"`, `"columnar"`, `"timeseries"`,
29    /// `"columnar:spatial"`, `"kv"`. Drives operator dashboards that
30    /// need to group pending-purge rows by engine.
31    pub engine_type: String,
32    /// Wall-clock nanoseconds when `DROP COLLECTION` was committed
33    /// (from `stored.modification_hlc.wall_ns`).
34    pub deactivated_at_ns: u64,
35    /// Wall-clock nanoseconds at which the retention window elapses
36    /// and the sweeper will hard-delete this row. Derived from
37    /// `deactivated_at_ns + retention_window`.
38    pub retention_expires_at_ns: u64,
39}
40
41impl DroppedCollection {
42    /// Whether the retention window has already elapsed as of `now_ns`.
43    /// Rows with `is_expired(now_ns) == true` are candidates for the
44    /// next sweeper pass; they may appear in the list briefly before
45    /// the sweeper runs.
46    pub fn is_expired(&self, now_ns: u64) -> bool {
47        now_ns >= self.retention_expires_at_ns
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn is_expired_strictly_on_or_past_window() {
57        let d = DroppedCollection {
58            tenant_id: 1u64,
59            name: "orders".into(),
60            owner: "admin".into(),
61            engine_type: "document".into(),
62            deactivated_at_ns: 100,
63            retention_expires_at_ns: 200,
64        };
65        assert!(!d.is_expired(199));
66        assert!(d.is_expired(200));
67        assert!(d.is_expired(300));
68    }
69
70    #[test]
71    fn serde_roundtrip() {
72        let d = DroppedCollection {
73            tenant_id: 7u64,
74            name: "test".into(),
75            owner: "alice".into(),
76            engine_type: "timeseries".into(),
77            deactivated_at_ns: 1_700_000_000_000_000_000,
78            retention_expires_at_ns: 1_700_604_800_000_000_000,
79        };
80        let json = serde_json::to_string(&d).unwrap();
81        let back: DroppedCollection = serde_json::from_str(&json).unwrap();
82        assert_eq!(d, back);
83    }
84}