1use super::inverted_index::FieldStats;
10use super::occurrence_format::{OccurrenceAddress as Address, OccurrenceProjection as Projection};
11use super::occurrence_keys::{
12 self as keys,
13 encoding::{controlled, Part},
14};
15use crate::mvcc::{
16 OccurrenceRecordKind as Kind, OccurrenceRecordLayout, OccurrenceRecordValue as Value,
17 OccurrenceRelatedKey as Related, VersionError, VersionResult,
18};
19use crate::read_control::StorageReadControl;
20use uqa_core::memory::BudgetedVec;
21
22const GUARDS: u8 = b'z';
24
25pub(crate) fn guard(
26 table: &str,
27 document: Option<u64>,
28 control: &StorageReadControl,
29) -> VersionResult<BudgetedVec<u8>> {
30 Ok(match document {
31 Some(document) => controlled(
32 table,
33 GUARDS,
34 Some(b'd'),
35 &[Part::Number(document)],
36 control,
37 )?,
38 None => controlled(table, GUARDS, Some(b's'), &[], control)?,
39 })
40}
41
42pub(crate) fn format(table: &str, control: &StorageReadControl) -> VersionResult<BudgetedVec<u8>> {
43 Ok(controlled(
44 table,
45 super::TAG_OCCURRENCE_INDEX,
46 Some(keys::FORMAT),
47 &[],
48 control,
49 )?)
50}
51
52fn address(key: &[u8]) -> VersionResult<Address<'_>> {
53 let address = Address::decode(key)?;
54 if !address.complete() {
55 return Err(VersionError::InvalidEncoding(
56 "incomplete occurrence replacement",
57 ));
58 }
59 Ok(address)
60}
61
62pub struct KeyValueOccurrenceRecords;
63
64impl OccurrenceRecordLayout for KeyValueOccurrenceRecords {
65 fn kind(&self, key: &[u8], control: &StorageReadControl) -> VersionResult<Kind> {
66 control.cancellation().check()?;
67 let address = address(key)?;
68 Ok(match address.projection {
69 Some(Projection::Score) => Kind::Score(address.cluster.expect("complete cluster")),
70 Some(Projection::Positions) => Kind::Positions,
71 Some(Projection::Field) => Kind::Statistics,
72 Some(Projection::Format) => Kind::Format,
73 Some(Projection::Skip | Projection::BlockMax) => Kind::Cache,
74 _ => {
75 return Err(VersionError::InvalidEncoding(
76 "occurrence merge targets a canonical record",
77 ))
78 }
79 })
80 }
81
82 fn related_key(
83 &self,
84 key: &[u8],
85 related: Related,
86 control: &StorageReadControl,
87 ) -> VersionResult<BudgetedVec<u8>> {
88 let mut address = address(key)?;
89 match related {
90 Related::Structure => guard(address.table, None, control),
91 Related::Format => format(address.table, control),
92 Related::Score | Related::Positions => {
93 address.projection = Some(if matches!(related, Related::Score) {
94 Projection::Score
95 } else {
96 Projection::Positions
97 });
98 if !address.complete() {
99 return Err(VersionError::InvalidEncoding(
100 "invalid occurrence cluster peer",
101 ));
102 }
103 Ok(address.encode(control)?)
104 }
105 Related::Skips | Related::BlockMax => Ok(controlled(
106 address.table,
107 super::TAG_OCCURRENCE_INDEX,
108 Some(if matches!(related, Related::Skips) {
109 keys::SKIP
110 } else {
111 keys::BLOCK_MAX
112 }),
113 &[],
114 control,
115 )?),
116 }
117 }
118
119 fn decode<'a>(
120 &self,
121 key: &[u8],
122 value: &'a [u8],
123 control: &StorageReadControl,
124 ) -> VersionResult<Value<'a>> {
125 Ok(match self.kind(key, control)? {
126 Kind::Score(_) => Value::Score(value),
127 Kind::Positions => Value::Positions(value),
128 Kind::Statistics => {
129 let stats = FieldStats::from_bytes(value)?;
130 Value::Statistics {
131 revision: stats.revision,
132 doc_count: stats.doc_count,
133 total_length: stats.total_length,
134 }
135 }
136 Kind::Format => Value::Format(value),
137 _ => {
138 return Err(VersionError::InvalidEncoding(
139 "invalid occurrence value projection",
140 ))
141 }
142 })
143 }
144
145 fn encode(
146 &self,
147 key: &[u8],
148 _template: &[u8],
149 value: Value<'_>,
150 control: &StorageReadControl,
151 ) -> VersionResult<BudgetedVec<u8>> {
152 let mut bytes = BudgetedVec::new(control.memory());
153 match (self.kind(key, control)?, value) {
154 (Kind::Score(_), Value::Score(value))
155 | (Kind::Positions, Value::Positions(value))
156 | (Kind::Format, Value::Format(value)) => bytes.extend_from_slice(value)?,
157 (
158 Kind::Statistics,
159 Value::Statistics {
160 revision,
161 doc_count,
162 total_length,
163 },
164 ) => bytes.extend_from_slice(
165 &FieldStats {
166 revision,
167 doc_count,
168 total_length,
169 }
170 .to_bytes()?,
171 )?,
172 _ => {
173 return Err(VersionError::InvalidEncoding(
174 "occurrence key and value disagree",
175 ))
176 }
177 }
178 Ok(bytes)
179 }
180}