1use super::codec::{other_error, read_segment, read_u64};
10use super::occurrence_keys::{
11 self as keys,
12 encoding::{controlled, Part},
13};
14use super::{
15 TAG_DOC_LENGTH, TAG_FIELD_STATS, TAG_OCCURRENCE_INDEX, TAG_POSTING,
16 TAG_POSTING_CLUSTER_POSITIONS, TAG_POSTING_CLUSTER_SCORE, TAG_POSTING_DOCUMENT,
17 TAG_REVERSE_POSTING,
18};
19use crate::{read_control::StorageReadControl, StorageBackendResult};
20use uqa_core::memory::BudgetedVec;
21use OccurrenceProjection::{
22 BlockMax, Document, Field, Format, LegacyDocument, LegacyField, LegacyLength, LegacyPositions,
23 LegacyPosting, LegacyReverse, LegacyScore, Length, Metadata, Positions, Score, Skip,
24};
25
26mod accelerators;
27pub use accelerators::BlockMaxValue;
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum OccurrenceProjection {
32 Score,
33 Positions,
34 Document,
35 Length,
36 Metadata,
37 Field,
38 Format,
39 Skip,
40 BlockMax,
41 LegacyPosting,
42 LegacyReverse,
43 LegacyScore,
44 LegacyPositions,
45 LegacyDocument,
46 LegacyLength,
47 LegacyField,
48}
49
50impl OccurrenceProjection {
51 fn tag(self) -> (u8, Option<u8>) {
52 match self {
53 Score => (TAG_OCCURRENCE_INDEX, Some(keys::SCORE)),
54 Positions => (TAG_OCCURRENCE_INDEX, Some(keys::POSITIONS)),
55 Document => (TAG_OCCURRENCE_INDEX, Some(keys::DOCUMENT)),
56 Length => (TAG_OCCURRENCE_INDEX, Some(keys::LENGTH)),
57 Metadata => (TAG_OCCURRENCE_INDEX, Some(keys::METADATA)),
58 Field => (TAG_OCCURRENCE_INDEX, Some(keys::FIELD)),
59 Format => (TAG_OCCURRENCE_INDEX, Some(keys::FORMAT)),
60 Skip => (TAG_OCCURRENCE_INDEX, Some(keys::SKIP)),
61 BlockMax => (TAG_OCCURRENCE_INDEX, Some(keys::BLOCK_MAX)),
62 LegacyPosting => (TAG_POSTING, None),
63 LegacyReverse => (TAG_REVERSE_POSTING, None),
64 LegacyScore => (TAG_POSTING_CLUSTER_SCORE, None),
65 LegacyPositions => (TAG_POSTING_CLUSTER_POSITIONS, None),
66 LegacyDocument => (TAG_POSTING_DOCUMENT, None),
67 LegacyLength => (TAG_DOC_LENGTH, None),
68 LegacyField => (TAG_FIELD_STATS, None),
69 }
70 }
71 pub fn is_legacy(self) -> bool {
72 self.tag().0 != TAG_OCCURRENCE_INDEX
73 }
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub struct OccurrenceAddress<'a> {
79 pub table: &'a str,
80 pub projection: Option<OccurrenceProjection>,
81 pub field: Option<&'a str>,
82 pub term: Option<&'a [u8]>,
83 pub document: Option<u64>,
84 pub cluster: Option<u64>,
85 pub ordinal: Option<u64>,
86}
87
88impl<'a> OccurrenceAddress<'a> {
89 pub fn table(table: &'a str) -> Self {
90 Self {
91 table,
92 projection: None,
93 field: None,
94 term: None,
95 document: None,
96 cluster: None,
97 ordinal: None,
98 }
99 }
100
101 pub fn decode(key: &'a [u8]) -> StorageBackendResult<Self> {
102 let tag = *key
103 .first()
104 .ok_or_else(|| other_error("missing occurrence key tag"))?;
105 let mut offset = 1;
106 let table = text(read_segment(key, &mut offset)?)?;
107 let mut address = Self::table(table);
108 address.projection = match tag {
109 TAG_OCCURRENCE_INDEX => match key.get(offset) {
110 None => return Ok(address),
111 Some(&keys::SCORE) => Some(Score),
112 Some(&keys::POSITIONS) => Some(Positions),
113 Some(&keys::DOCUMENT) => Some(Document),
114 Some(&keys::LENGTH) => Some(Length),
115 Some(&keys::METADATA) => Some(Metadata),
116 Some(&keys::FIELD) => Some(Field),
117 Some(&keys::FORMAT) => Some(Format),
118 Some(&keys::SKIP) => Some(Skip),
119 Some(&keys::BLOCK_MAX) => Some(BlockMax),
120 _ => return Err(other_error("unknown occurrence projection")),
121 },
122 TAG_POSTING => Some(LegacyPosting),
123 TAG_REVERSE_POSTING => Some(LegacyReverse),
124 TAG_POSTING_CLUSTER_SCORE => Some(LegacyScore),
125 TAG_POSTING_CLUSTER_POSITIONS => Some(LegacyPositions),
126 TAG_POSTING_DOCUMENT => Some(LegacyDocument),
127 TAG_DOC_LENGTH => Some(LegacyLength),
128 TAG_FIELD_STATS => Some(LegacyField),
129 _ => return Err(other_error("unknown occurrence namespace")),
130 };
131 if tag == TAG_OCCURRENCE_INDEX {
132 offset += 1;
133 }
134 if offset == key.len() {
135 return Ok(address);
136 }
137 match address.projection {
138 Some(Score | Positions | Skip | BlockMax) => {
139 address.field = Some(text(read_segment(key, &mut offset)?)?);
140 if offset < key.len() {
141 address.term = Some(read_segment(key, &mut offset)?);
142 }
143 if offset < key.len() {
144 let number = Some(read_u64(key, &mut offset)?);
145 match address.projection {
146 Some(Skip) => address.document = number,
147 Some(BlockMax) => address.ordinal = number,
148 _ => address.cluster = number,
149 }
150 }
151 }
152 Some(Document | Length) => {
153 address.document = Some(read_u64(key, &mut offset)?);
154 if offset < key.len() {
155 address.field = Some(text(read_segment(key, &mut offset)?)?);
156 }
157 }
158 Some(Metadata | Field) => {
159 address.field = Some(text(read_segment(key, &mut offset)?)?);
160 if address.projection == Some(Metadata) && offset < key.len() {
161 address.document = Some(read_u64(key, &mut offset)?);
162 }
163 }
164 _ => return Err(other_error("unexpected occurrence prefix components")),
165 }
166 if offset != key.len() {
167 return Err(other_error("trailing occurrence key bytes"));
168 }
169 Ok(address)
170 }
171
172 pub fn complete(self) -> bool {
173 match self.projection {
174 Some(Score | Positions) => {
175 self.field.is_some() && self.term.is_some() && self.cluster.is_some()
176 }
177 Some(Skip) => self.field.is_some() && self.term.is_some() && self.document.is_some(),
178 Some(BlockMax) => self.field.is_some() && self.term.is_some() && self.ordinal.is_some(),
179 Some(Document | Length | Metadata) => self.document.is_some() && self.field.is_some(),
180 Some(Field) => self.field.is_some(),
181 Some(Format) => true,
182 _ => false,
183 }
184 }
185
186 pub fn encode(self, control: &StorageReadControl) -> StorageBackendResult<BudgetedVec<u8>> {
188 let mut parts = [Part::Number(0); 3];
189 let mut len = 0;
190 let mut push = |part| {
191 parts[len] = part;
192 len += 1;
193 };
194 match self.projection {
195 Some(Score | Positions | Skip | BlockMax) => {
196 if let Some(field) = self.field {
197 push(Part::Segment(field.as_bytes()));
198 }
199 if let Some(term) = self.term {
200 push(Part::Segment(term));
201 }
202 if let Some(number) = match self.projection {
203 Some(Skip) => self.document,
204 Some(BlockMax) => self.ordinal,
205 _ => self.cluster,
206 } {
207 push(Part::Number(number));
208 }
209 }
210 Some(Document | Length) => {
211 if let Some(document) = self.document {
212 push(Part::Number(document));
213 }
214 if let Some(field) = self.field {
215 push(Part::Segment(field.as_bytes()));
216 }
217 }
218 Some(Metadata | Field) => {
219 if let Some(field) = self.field {
220 push(Part::Segment(field.as_bytes()));
221 }
222 if let Some(document) = self.document {
223 push(Part::Number(document));
224 }
225 }
226 _ => {}
227 }
228 let (tag, kind) = self
229 .projection
230 .map_or((TAG_OCCURRENCE_INDEX, None), OccurrenceProjection::tag);
231 let encoded = controlled(self.table, tag, kind, &parts[..len], control)?;
232 if OccurrenceAddress::decode(&encoded)? != self {
233 return Err(other_error("invalid occurrence address components"));
234 }
235 Ok(encoded)
236 }
237}
238
239fn text(bytes: &[u8]) -> StorageBackendResult<&str> {
240 std::str::from_utf8(bytes).map_err(|_| other_error("occurrence identifier is not UTF-8"))
241}
242
243#[cfg(test)]
244mod tests;