1use serde::{Deserialize, Serialize};
2use std::collections::{btree_map::Entry, BTreeMap};
3use std::fmt;
4use std::sync::Arc;
5
6use super::super::error::Error;
7
8#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub enum IndexProjection {
11 #[default]
13 KeysOnly,
14 Include,
16 All,
18}
19
20#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct SecondaryIndexEntry {
23 pub term: Vec<u8>,
25 pub projection: Option<Vec<u8>>,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub struct SecondaryIndexEntryRef<'a> {
32 pub term: &'a [u8],
33 pub projection: Option<&'a [u8]>,
34}
35
36pub trait StreamingSecondaryIndexExtractor: Send + Sync {
38 fn extract(
39 &self,
40 primary_key: &[u8],
41 source_value: &[u8],
42 emit: &mut dyn FnMut(SecondaryIndexEntryRef<'_>) -> Result<(), SecondaryIndexError>,
43 ) -> Result<(), SecondaryIndexError>;
44}
45
46impl SecondaryIndexEntry {
47 pub fn term(term: impl AsRef<[u8]>) -> Self {
49 Self {
50 term: term.as_ref().to_vec(),
51 projection: None,
52 }
53 }
54
55 pub fn included(term: impl AsRef<[u8]>, projection: impl AsRef<[u8]>) -> Self {
57 Self {
58 term: term.as_ref().to_vec(),
59 projection: Some(projection.as_ref().to_vec()),
60 }
61 }
62}
63
64#[derive(Clone, Debug, PartialEq, Eq)]
66pub struct SecondaryIndexError {
67 reason: String,
68}
69
70impl SecondaryIndexError {
71 pub fn new(reason: impl Into<String>) -> Self {
73 Self {
74 reason: reason.into(),
75 }
76 }
77
78 pub fn reason(&self) -> &str {
80 &self.reason
81 }
82}
83
84impl fmt::Display for SecondaryIndexError {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 f.write_str(&self.reason)
87 }
88}
89
90impl std::error::Error for SecondaryIndexError {}
91
92pub trait SecondaryIndexExtractor: Send + Sync + 'static {
94 fn extract(
96 &self,
97 primary_key: &[u8],
98 source_value: &[u8],
99 ) -> Result<Vec<SecondaryIndexEntry>, SecondaryIndexError>;
100}
101
102impl<F> SecondaryIndexExtractor for F
103where
104 F: Fn(&[u8], &[u8]) -> Result<Vec<SecondaryIndexEntry>, SecondaryIndexError>
105 + Send
106 + Sync
107 + 'static,
108{
109 fn extract(
110 &self,
111 primary_key: &[u8],
112 source_value: &[u8],
113 ) -> Result<Vec<SecondaryIndexEntry>, SecondaryIndexError> {
114 self(primary_key, source_value)
115 }
116}
117
118struct TermsExtractor<F>(F);
119
120impl<F> SecondaryIndexExtractor for TermsExtractor<F>
121where
122 F: Fn(&[u8], &[u8]) -> Result<Vec<Vec<u8>>, SecondaryIndexError> + Send + Sync + 'static,
123{
124 fn extract(
125 &self,
126 primary_key: &[u8],
127 source_value: &[u8],
128 ) -> Result<Vec<SecondaryIndexEntry>, SecondaryIndexError> {
129 (self.0)(primary_key, source_value)
130 .map(|terms| terms.into_iter().map(SecondaryIndexEntry::term).collect())
131 }
132}
133
134#[derive(Clone, Debug, PartialEq, Eq)]
136pub struct SecondaryIndexLimits {
137 pub max_term_bytes: usize,
138 pub max_projection_bytes: usize,
139 pub max_all_value_bytes: usize,
140 pub max_terms_per_record: usize,
141 pub max_projected_bytes_per_record: usize,
142 pub max_derived_mutations_per_transaction: usize,
143 pub max_projected_bytes_per_transaction: usize,
144 pub max_indexes: usize,
145 pub build_page_size: usize,
146 pub max_temporary_sort_bytes: usize,
147 pub max_bundle_nodes: usize,
148 pub max_bundle_bytes: usize,
149 pub max_verification_entries: usize,
150 pub max_write_retries: usize,
151 pub max_build_retries: usize,
152}
153
154impl Default for SecondaryIndexLimits {
155 fn default() -> Self {
156 Self {
157 max_term_bytes: 4 * 1024,
158 max_projection_bytes: 64 * 1024,
159 max_all_value_bytes: 1024 * 1024,
160 max_terms_per_record: 1024,
161 max_projected_bytes_per_record: 1024 * 1024,
162 max_derived_mutations_per_transaction: 100_000,
163 max_projected_bytes_per_transaction: 64 * 1024 * 1024,
164 max_indexes: 32,
165 build_page_size: 4096,
166 max_temporary_sort_bytes: 256 * 1024 * 1024,
167 max_bundle_nodes: 1_000_000,
168 max_bundle_bytes: 1024 * 1024 * 1024,
169 max_verification_entries: 10_000_000,
170 max_write_retries: 8,
171 max_build_retries: 8,
172 }
173 }
174}
175
176impl SecondaryIndexLimits {
177 fn validate(&self) -> Result<(), Error> {
178 let values = [
179 ("max_term_bytes", self.max_term_bytes),
180 ("max_projection_bytes", self.max_projection_bytes),
181 ("max_all_value_bytes", self.max_all_value_bytes),
182 ("max_terms_per_record", self.max_terms_per_record),
183 (
184 "max_projected_bytes_per_record",
185 self.max_projected_bytes_per_record,
186 ),
187 (
188 "max_derived_mutations_per_transaction",
189 self.max_derived_mutations_per_transaction,
190 ),
191 (
192 "max_projected_bytes_per_transaction",
193 self.max_projected_bytes_per_transaction,
194 ),
195 ("max_indexes", self.max_indexes),
196 ("build_page_size", self.build_page_size),
197 ("max_temporary_sort_bytes", self.max_temporary_sort_bytes),
198 ("max_bundle_nodes", self.max_bundle_nodes),
199 ("max_bundle_bytes", self.max_bundle_bytes),
200 ("max_verification_entries", self.max_verification_entries),
201 ("max_write_retries", self.max_write_retries),
202 ("max_build_retries", self.max_build_retries),
203 ];
204 if let Some((field, _)) = values.into_iter().find(|(_, value)| *value == 0) {
205 return Err(Error::InvalidIndexDefinition {
206 reason: format!("{field} must be greater than zero"),
207 });
208 }
209 Ok(())
210 }
211}
212
213#[derive(Clone)]
215pub struct SecondaryIndex {
216 name: Vec<u8>,
217 generation: u64,
218 extractor_id: String,
219 projection: IndexProjection,
220 limits: SecondaryIndexLimits,
221 extractor: Arc<dyn SecondaryIndexExtractor>,
222}
223
224impl fmt::Debug for SecondaryIndex {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 f.debug_struct("SecondaryIndex")
227 .field("name", &self.name)
228 .field("generation", &self.generation)
229 .field("extractor_id", &self.extractor_id)
230 .field("projection", &self.projection)
231 .field("limits", &self.limits)
232 .finish_non_exhaustive()
233 }
234}
235
236impl SecondaryIndex {
237 pub fn non_unique<N, I, F>(
239 name: N,
240 generation: u64,
241 extractor_id: I,
242 extractor: F,
243 ) -> Result<Self, Error>
244 where
245 N: AsRef<[u8]>,
246 I: Into<String>,
247 F: Fn(&[u8], &[u8]) -> Result<Vec<Vec<u8>>, SecondaryIndexError> + Send + Sync + 'static,
248 {
249 Self::builder(name, generation, extractor_id)
250 .projection(IndexProjection::KeysOnly)
251 .extract_terms(extractor)
252 }
253
254 pub fn builder<N, I>(name: N, generation: u64, extractor_id: I) -> SecondaryIndexBuilder
256 where
257 N: AsRef<[u8]>,
258 I: Into<String>,
259 {
260 SecondaryIndexBuilder {
261 name: name.as_ref().to_vec(),
262 generation,
263 extractor_id: extractor_id.into(),
264 projection: IndexProjection::KeysOnly,
265 limits: SecondaryIndexLimits::default(),
266 }
267 }
268
269 pub fn name(&self) -> &[u8] {
271 &self.name
272 }
273 pub fn generation(&self) -> u64 {
275 self.generation
276 }
277 pub fn extractor_id(&self) -> &str {
279 &self.extractor_id
280 }
281 pub fn projection(&self) -> IndexProjection {
283 self.projection
284 }
285 pub fn limits(&self) -> &SecondaryIndexLimits {
287 &self.limits
288 }
289
290 pub fn extract(
292 &self,
293 primary_key: &[u8],
294 source_value: &[u8],
295 ) -> Result<Vec<SecondaryIndexEntry>, Error> {
296 if self.projection == IndexProjection::All
297 && source_value.len() > self.limits.max_all_value_bytes
298 {
299 return Err(Error::IndexResourceLimitExceeded {
300 resource: "all_source_value_bytes",
301 limit: self.limits.max_all_value_bytes,
302 actual: source_value.len(),
303 });
304 }
305 let entries = self
306 .extractor
307 .extract(primary_key, source_value)
308 .map_err(|error| Error::IndexExtractionFailed {
309 name: self.name.clone(),
310 primary_key: primary_key.to_vec(),
311 reason: error.to_string(),
312 })?;
313 self.validate_entries(primary_key, entries)
314 }
315
316 fn validate_entries(
317 &self,
318 primary_key: &[u8],
319 entries: Vec<SecondaryIndexEntry>,
320 ) -> Result<Vec<SecondaryIndexEntry>, Error> {
321 if entries.len() > self.limits.max_terms_per_record {
322 return Err(Error::IndexResourceLimitExceeded {
323 resource: "terms_per_record",
324 limit: self.limits.max_terms_per_record,
325 actual: entries.len(),
326 });
327 }
328 let mut canonical = BTreeMap::<Vec<u8>, Option<Vec<u8>>>::new();
329 let mut projected_bytes = 0usize;
330 for entry in entries {
331 if entry.term.len() > self.limits.max_term_bytes {
332 return Err(Error::IndexResourceLimitExceeded {
333 resource: "term_bytes",
334 limit: self.limits.max_term_bytes,
335 actual: entry.term.len(),
336 });
337 }
338 let projection_matches = match self.projection {
339 IndexProjection::KeysOnly | IndexProjection::All => entry.projection.is_none(),
340 IndexProjection::Include => entry.projection.is_some(),
341 };
342 if !projection_matches {
343 return Err(Error::IndexProjectionMismatch {
344 name: self.name.clone(),
345 mode: self.projection,
346 primary_key: primary_key.to_vec(),
347 });
348 }
349 if let Some(projection) = &entry.projection {
350 if projection.len() > self.limits.max_projection_bytes {
351 return Err(Error::IndexResourceLimitExceeded {
352 resource: "projection_bytes",
353 limit: self.limits.max_projection_bytes,
354 actual: projection.len(),
355 });
356 }
357 projected_bytes = projected_bytes.saturating_add(projection.len());
358 }
359 match canonical.entry(entry.term) {
360 Entry::Vacant(slot) => {
361 slot.insert(entry.projection);
362 }
363 Entry::Occupied(slot) if slot.get() == &entry.projection => {}
364 Entry::Occupied(slot) => {
365 return Err(Error::ConflictingIndexProjection {
366 name: self.name.clone(),
367 primary_key: primary_key.to_vec(),
368 term: slot.key().clone(),
369 });
370 }
371 }
372 }
373 if projected_bytes > self.limits.max_projected_bytes_per_record {
374 return Err(Error::IndexResourceLimitExceeded {
375 resource: "projected_bytes_per_record",
376 limit: self.limits.max_projected_bytes_per_record,
377 actual: projected_bytes,
378 });
379 }
380 Ok(canonical
381 .into_iter()
382 .map(|(term, projection)| SecondaryIndexEntry { term, projection })
383 .collect())
384 }
385}
386
387#[derive(Clone, Debug)]
389pub struct SecondaryIndexBuilder {
390 name: Vec<u8>,
391 generation: u64,
392 extractor_id: String,
393 projection: IndexProjection,
394 limits: SecondaryIndexLimits,
395}
396
397impl SecondaryIndexBuilder {
398 pub fn projection(mut self, projection: IndexProjection) -> Self {
400 self.projection = projection;
401 self
402 }
403 pub fn limits(mut self, limits: SecondaryIndexLimits) -> Self {
405 self.limits = limits;
406 self
407 }
408
409 pub fn extract<F>(self, extractor: F) -> Result<SecondaryIndex, Error>
411 where
412 F: Fn(&[u8], &[u8]) -> Result<Vec<SecondaryIndexEntry>, SecondaryIndexError>
413 + Send
414 + Sync
415 + 'static,
416 {
417 self.finish(Arc::new(extractor))
418 }
419
420 pub fn extract_terms<F>(self, extractor: F) -> Result<SecondaryIndex, Error>
422 where
423 F: Fn(&[u8], &[u8]) -> Result<Vec<Vec<u8>>, SecondaryIndexError> + Send + Sync + 'static,
424 {
425 if self.projection == IndexProjection::Include {
426 return Err(Error::InvalidIndexDefinition {
427 reason: "Include projection requires an entry extractor".to_string(),
428 });
429 }
430 self.finish(Arc::new(TermsExtractor(extractor)))
431 }
432
433 fn finish(self, extractor: Arc<dyn SecondaryIndexExtractor>) -> Result<SecondaryIndex, Error> {
434 if self.name.is_empty() {
435 return Err(Error::InvalidIndexDefinition {
436 reason: "index name must not be empty".to_string(),
437 });
438 }
439 if self.generation == 0 {
440 return Err(Error::InvalidIndexDefinition {
441 reason: "index generation must be greater than zero".to_string(),
442 });
443 }
444 if self.extractor_id.is_empty() {
445 return Err(Error::InvalidIndexDefinition {
446 reason: "extractor ID must not be empty".to_string(),
447 });
448 }
449 self.limits.validate()?;
450 Ok(SecondaryIndex {
451 name: self.name,
452 generation: self.generation,
453 extractor_id: self.extractor_id,
454 projection: self.projection,
455 limits: self.limits,
456 extractor,
457 })
458 }
459}
460
461#[derive(Clone, Debug, Default)]
463pub struct SecondaryIndexRegistry {
464 indexes: BTreeMap<Vec<u8>, SecondaryIndex>,
465 historical: BTreeMap<Vec<u8>, Vec<SecondaryIndex>>,
466}
467
468impl SecondaryIndexRegistry {
469 pub fn new() -> Self {
471 Self::default()
472 }
473
474 pub fn register(mut self, index: SecondaryIndex) -> Result<Self, Error> {
476 if self.indexes.contains_key(index.name()) {
477 return Err(Error::InvalidIndexDefinition {
478 reason: format!("duplicate index name {:?}", index.name()),
479 });
480 }
481 self.indexes.insert(index.name.clone(), index);
482 Ok(self)
483 }
484
485 pub fn replace(mut self, index: SecondaryIndex) -> Result<Self, Error> {
488 let name = index.name.clone();
489 let current = self
490 .indexes
491 .remove(&name)
492 .ok_or_else(|| Error::InvalidIndexDefinition {
493 reason: format!("cannot replace unregistered index name {:?}", name),
494 })?;
495 if index.generation() <= current.generation() {
496 self.indexes.insert(name, current);
497 return Err(Error::InvalidIndexDefinition {
498 reason: "replacement generation must be strictly greater than active generation"
499 .to_string(),
500 });
501 }
502 self.historical
503 .entry(name.clone())
504 .or_default()
505 .push(current);
506 self.indexes.insert(name, index);
507 Ok(self)
508 }
509
510 pub fn get(&self, name: &[u8]) -> Option<&SecondaryIndex> {
512 self.indexes.get(name)
513 }
514 pub fn iter(&self) -> impl ExactSizeIterator<Item = &SecondaryIndex> {
516 self.indexes.values()
517 }
518
519 pub(crate) fn definitions_for_name(&self, name: &[u8]) -> Vec<SecondaryIndex> {
520 self.indexes
521 .get(name)
522 .into_iter()
523 .chain(
524 self.historical
525 .get(name)
526 .into_iter()
527 .flat_map(|definitions| definitions.iter()),
528 )
529 .cloned()
530 .collect()
531 }
532 pub fn len(&self) -> usize {
534 self.indexes.len()
535 }
536 pub fn is_empty(&self) -> bool {
538 self.indexes.is_empty()
539 }
540}