1use std::collections::{BTreeMap, BTreeSet};
2
3#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
5#[non_exhaustive]
6pub enum MetadataValue {
7 Bool(bool),
8 I64(i64),
9 U64(u64),
10 Text(String),
11 Bytes(Vec<u8>),
12}
13
14#[derive(Debug, Clone, Default, PartialEq, Eq)]
16pub struct Metadata {
17 fields: BTreeMap<String, MetadataValue>,
18}
19
20impl Metadata {
21 #[must_use]
22 pub const fn new() -> Self {
23 Self {
24 fields: BTreeMap::new(),
25 }
26 }
27
28 pub fn insert(
29 &mut self,
30 field: impl Into<String>,
31 value: MetadataValue,
32 ) -> Option<MetadataValue> {
33 self.fields.insert(field.into(), value)
34 }
35
36 #[must_use]
37 pub fn get(&self, field: &str) -> Option<&MetadataValue> {
38 self.fields.get(field)
39 }
40
41 pub fn remove(&mut self, field: &str) -> Option<MetadataValue> {
42 self.fields.remove(field)
43 }
44
45 #[must_use]
46 pub fn len(&self) -> usize {
47 self.fields.len()
48 }
49
50 #[must_use]
51 pub fn is_empty(&self) -> bool {
52 self.fields.is_empty()
53 }
54
55 #[must_use]
56 pub fn iter(&self) -> impl ExactSizeIterator<Item = (&str, &MetadataValue)> {
57 self.fields
58 .iter()
59 .map(|(field, value)| (field.as_str(), value))
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
65#[non_exhaustive]
66pub enum MetadataFilter {
67 All,
68 Exists(String),
69 Equal {
70 field: String,
71 value: MetadataValue,
72 },
73 I64Range {
74 field: String,
75 minimum: Option<i64>,
76 maximum: Option<i64>,
77 },
78 U64Range {
79 field: String,
80 minimum: Option<u64>,
81 maximum: Option<u64>,
82 },
83 TextPrefix {
84 field: String,
85 prefix: String,
86 },
87 And(Vec<Self>),
88 Or(Vec<Self>),
89 Not(Box<Self>),
90}
91
92impl MetadataFilter {
93 #[must_use]
94 pub fn matches(&self, metadata: &Metadata) -> bool {
95 match self {
96 Self::All => true,
97 Self::Exists(field) => metadata.get(field).is_some(),
98 Self::Equal { field, value } => metadata.get(field) == Some(value),
99 Self::I64Range {
100 field,
101 minimum,
102 maximum,
103 } => matches!(
104 metadata.get(field),
105 Some(MetadataValue::I64(value))
106 if minimum.is_none_or(|minimum| *value >= minimum)
107 && maximum.is_none_or(|maximum| *value <= maximum)
108 ),
109 Self::U64Range {
110 field,
111 minimum,
112 maximum,
113 } => matches!(
114 metadata.get(field),
115 Some(MetadataValue::U64(value))
116 if minimum.is_none_or(|minimum| *value >= minimum)
117 && maximum.is_none_or(|maximum| *value <= maximum)
118 ),
119 Self::TextPrefix { field, prefix } => matches!(
120 metadata.get(field),
121 Some(MetadataValue::Text(value)) if value.starts_with(prefix)
122 ),
123 Self::And(filters) => filters.iter().all(|filter| filter.matches(metadata)),
124 Self::Or(filters) => filters.iter().any(|filter| filter.matches(metadata)),
125 Self::Not(filter) => !filter.matches(metadata),
126 }
127 }
128}
129
130#[derive(Debug, Clone, Default)]
132pub struct MetadataIndex {
133 records: BTreeMap<u64, Metadata>,
134}
135
136impl MetadataIndex {
137 #[must_use]
138 pub const fn new() -> Self {
139 Self {
140 records: BTreeMap::new(),
141 }
142 }
143
144 pub fn insert(&mut self, key: u64, metadata: Metadata) -> Option<Metadata> {
145 self.records.insert(key, metadata)
146 }
147
148 pub fn remove(&mut self, key: u64) -> Option<Metadata> {
149 self.records.remove(&key)
150 }
151
152 #[must_use]
153 pub fn get(&self, key: u64) -> Option<&Metadata> {
154 self.records.get(&key)
155 }
156
157 #[must_use]
158 pub fn len(&self) -> usize {
159 self.records.len()
160 }
161
162 #[must_use]
163 pub fn is_empty(&self) -> bool {
164 self.records.is_empty()
165 }
166
167 #[must_use]
168 pub fn iter(&self) -> impl ExactSizeIterator<Item = (u64, &Metadata)> {
169 self.records.iter().map(|(key, metadata)| (*key, metadata))
170 }
171
172 #[must_use]
173 pub fn matches(&self, key: u64, filter: &MetadataFilter) -> bool {
174 self.records
175 .get(&key)
176 .is_some_and(|metadata| filter.matches(metadata))
177 }
178
179 #[must_use]
180 pub fn matching_keys(&self, filter: &MetadataFilter) -> BTreeSet<u64> {
181 self.records
182 .iter()
183 .filter_map(|(key, metadata)| filter.matches(metadata).then_some(*key))
184 .collect()
185 }
186}