Skip to main content

a3s_vec/collection/
configuration.rs

1//! Per-collection options and resolution of process defaults.
2
3use super::CollectionResourceLimits;
4use crate::config::{current_config, ConfigBuilder, Durability, IoBackend};
5use crate::error::Result;
6use crate::storage_ceilings::StorageCeilings;
7
8/// Supported options for creating or opening a collection.
9///
10/// Storage layout, buffer, and segment knobs remain outside the public
11/// contract:
12///
13/// ```compile_fail
14/// use a3s_vec::CollectionOptions;
15///
16/// let mut options = CollectionOptions::new().unwrap();
17/// options.set_max_buffer_size(1024).unwrap();
18/// options.set_segment_num(2).unwrap();
19/// ```
20#[derive(Debug, Clone, Default)]
21pub struct CollectionOptions {
22    pub(super) read_only: bool,
23    pub(super) durability: Option<Durability>,
24    pub(super) io_backend: Option<IoBackend>,
25    pub(super) resource_limits: CollectionResourceLimits,
26    pub(super) storage_ceilings: Option<StorageCeilings>,
27}
28
29impl CollectionOptions {
30    pub fn new() -> Result<Self> {
31        Ok(Self::default())
32    }
33
34    pub fn set_read_only(&mut self, read_only: bool) -> Result<()> {
35        self.read_only = read_only;
36        Ok(())
37    }
38
39    pub fn read_only(&self) -> bool {
40        self.read_only
41    }
42
43    pub fn set_durability(&mut self, value: Durability) -> Result<()> {
44        self.durability = Some(value);
45        Ok(())
46    }
47
48    pub fn durability(&self) -> Option<Durability> {
49        self.durability
50    }
51
52    /// Overrides the process-wide derived-sidecar I/O backend for this handle.
53    ///
54    /// When absent, the backend configured through [`crate::ConfigBuilder`] is
55    /// captured when the collection is created or opened.
56    pub fn set_io_backend(&mut self, value: IoBackend) -> Result<()> {
57        self.io_backend = Some(value);
58        Ok(())
59    }
60
61    /// Returns this handle's explicit I/O backend override, if any.
62    pub fn io_backend(&self) -> Option<IoBackend> {
63        self.io_backend
64    }
65
66    /// Applies a typed collection-local resource policy.
67    pub fn set_resource_limits(&mut self, value: CollectionResourceLimits) -> Result<()> {
68        self.resource_limits = value;
69        Ok(())
70    }
71
72    /// Returns the resource policy that this handle will capture.
73    pub fn resource_limits(&self) -> CollectionResourceLimits {
74        self.resource_limits
75    }
76
77    /// Overrides process-wide persistence `DoS` ceilings for this handle.
78    ///
79    /// When absent, the ceilings configured through [`crate::ConfigBuilder`]
80    /// (or product defaults) are captured at create/open. Values are never
81    /// inferred from host RAM or free disk.
82    pub fn set_storage_ceilings(&mut self, value: StorageCeilings) -> Result<()> {
83        self.storage_ceilings = Some(value);
84        Ok(())
85    }
86
87    /// Returns this handle's explicit storage-ceiling override, if any.
88    pub fn storage_ceilings(&self) -> Option<StorageCeilings> {
89        self.storage_ceilings
90    }
91}
92
93pub(super) fn options_config(options: &CollectionOptions) -> ConfigBuilder {
94    resolve_options_config(options, current_config())
95}
96
97/// Resolves the persistence ceilings captured by a new collection handle.
98pub(super) fn resolved_storage_ceilings(options: &CollectionOptions) -> StorageCeilings {
99    options
100        .storage_ceilings
101        .unwrap_or_else(|| current_config().storage_ceilings)
102}
103
104fn resolve_options_config(
105    options: &CollectionOptions,
106    mut process_config: ConfigBuilder,
107) -> ConfigBuilder {
108    if let Some(durability) = options.durability {
109        process_config.durability = durability;
110    }
111    if let Some(io_backend) = options.io_backend {
112        process_config.io_backend = io_backend;
113    }
114    if let Some(storage_ceilings) = options.storage_ceilings {
115        process_config.storage_ceilings = storage_ceilings;
116    }
117    process_config
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use crate::config::Durability;
124
125    #[test]
126    fn process_durability_is_used_without_a_collection_override() {
127        let process = ConfigBuilder::default().durability(Durability::Interval);
128        let resolved = resolve_options_config(&CollectionOptions::default(), process);
129
130        assert_eq!(resolved.durability, Durability::Interval);
131    }
132
133    #[test]
134    fn collection_durability_overrides_the_process_default() {
135        let process = ConfigBuilder::default()
136            .durability(Durability::Interval)
137            .wal_max_ops(3)
138            .wal_max_bytes(1024);
139        let mut options = CollectionOptions::default();
140        options
141            .set_durability(Durability::Manual)
142            .expect("durability override must be valid");
143        let resolved = resolve_options_config(&options, process);
144
145        assert_eq!(resolved.durability, Durability::Manual);
146        assert_eq!(resolved.wal_max_ops, Some(3));
147        assert_eq!(resolved.wal_max_bytes, Some(1024));
148    }
149
150    #[test]
151    fn collection_storage_ceilings_override_the_process_default() {
152        let process = ConfigBuilder::default().storage_ceilings(
153            StorageCeilings::new()
154                .try_with_max_snapshot_bytes(1_024)
155                .expect("process ceiling must be valid"),
156        );
157        let mut options = CollectionOptions::default();
158        options
159            .set_storage_ceilings(
160                StorageCeilings::new()
161                    .try_with_max_snapshot_bytes(4_096)
162                    .expect("collection ceiling must be valid"),
163            )
164            .expect("storage ceilings must be accepted");
165        let resolved = resolve_options_config(&options, process);
166        assert_eq!(resolved.storage_ceilings.max_snapshot_bytes(), 4_096);
167    }
168
169    #[test]
170    fn process_io_backend_is_used_without_a_collection_override() {
171        let process = ConfigBuilder::default().io_backend(IoBackend::Mmap);
172        let resolved = resolve_options_config(&CollectionOptions::default(), process.clone());
173        assert_eq!(resolved.io_backend, process.io_backend);
174    }
175}