Skip to main content

uqa_storage/key_value/diskann/
pruning.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7use super::{identity::validate_mapping, invalid, publication, KeyValueDiskANNSource};
8use crate::diskann_index::{
9    catalog::DiskANNIndexScope,
10    changes::{DiskANNChangeJournal, DiskANNChangeRead, DiskANNPruneRequest, DiskANNPruneResult},
11    maintenance::{DiskANNStatisticsPage, DiskANNStatisticsRequest},
12    pages::DiskANNOriginReader,
13};
14use crate::key_value::KeyValueRead;
15use crate::{read_control::StorageReadControl, KeyValueBatch, StorageBackendResult};
16use std::sync::Arc;
17
18/// Complete origin evidence retained from an actual immutable provider source. Verification occurs once per handle; bounded pruning pages reuse point lookup without rescanning the artifact or retaining build input.
19pub struct KeyValueDiskANNPruner {
20    source: Arc<KeyValueDiskANNSource>,
21    origins: DiskANNOriginReader,
22    control: StorageReadControl,
23}
24
25impl KeyValueDiskANNPruner {
26    /// Examine exact outstanding changes on the provider's single captured canonical/journal view under this selected generation. This does not stage writes or grant publication authority.
27    pub fn statistics(
28        &self,
29        journal: &dyn DiskANNChangeRead,
30        request: DiskANNStatisticsRequest,
31        control: &StorageReadControl,
32    ) -> StorageBackendResult<DiskANNStatisticsPage> {
33        self.control.check()?;
34        let page =
35            crate::diskann_index::maintenance::measure(&self.origins, journal, request, control)?;
36        self.control.check()?;
37        Ok(page)
38    }
39
40    pub fn open(
41        source: Arc<KeyValueDiskANNSource>,
42        maximum: usize,
43        control: &StorageReadControl,
44    ) -> StorageBackendResult<Self> {
45        let origins = DiskANNOriginReader::open(source.clone(), maximum, control)?;
46        Ok(Self {
47            source,
48            origins,
49            control: control.clone(),
50        })
51    }
52
53    /// Provider boundary: require this actual source's generation to remain the committed selected head in the supplied mutation. The provider must also guard its captured catalog binding and use this same mutation for journal deletion.
54    pub fn require_selected(
55        &self,
56        scope: &DiskANNIndexScope,
57        dimensions: u32,
58        parameters: crate::vector_index::DiskANNIndexParams,
59        read: &dyn KeyValueRead,
60        batch: &mut dyn KeyValueBatch,
61        control: &StorageReadControl,
62    ) -> StorageBackendResult<()> {
63        self.control.check()?;
64        control.check()?;
65        let input = self.origins.manifest().input();
66        if input.dimensions != dimensions || input.parameters != parameters {
67            return Err(invalid(
68                "selected generation differs from the current index configuration",
69            ));
70        }
71        let generation = input.generation;
72        self.source.check(control)?;
73        validate_mapping(scope, generation, &*self.source.read, control)?;
74        if publication::selected_generation(scope, read, control)? != Some(generation) {
75            return Err(invalid("journal pruning requires the selected generation"));
76        }
77        let key = publication::head_key(scope);
78        if read
79            .record_revision(&key)?
80            .is_none_or(|revision| revision.has_private_changes())
81        {
82            return Err(invalid("journal pruning requires a committed head"));
83        }
84        batch.require_unchanged(&key)?;
85        self.control.check()?;
86        control.check()
87    }
88
89    /// Evaluate bounded exact-key deletions after `require_selected` and the provider's catalog/private-input guards. On any error the enclosing mutation must discard all partial operations. Advance the returned cursor only after durable commit.
90    pub fn prune(
91        &self,
92        journal: &mut dyn DiskANNChangeJournal,
93        request: DiskANNPruneRequest,
94        control: &StorageReadControl,
95    ) -> StorageBackendResult<DiskANNPruneResult> {
96        self.control.check()?;
97        let result =
98            crate::diskann_index::changes::prune(&self.origins, journal, request, control)?;
99        self.control.check()?;
100        Ok(result)
101    }
102}