1use std::error::Error;
4use std::fmt;
5use std::num::NonZeroU32;
6use std::ops::{BitOr, BitOrAssign};
7
8use crate::{
9 AggregateSemanticSetV1, ProjectionProviderDescriptorHash, hash_projection_provider_descriptor,
10};
11
12pub const PROJECTION_PROVIDER_DESCRIPTOR_VERSION_V1: u16 = 1;
14pub const PROJECTION_PROVIDER_DESCRIPTOR_V1_BYTES: usize = 112;
16
17const MAGIC: [u8; 4] = *b"RPPD";
18const ALL_CAPABILITY_BITS: u16 = ProjectionProviderCapabilitiesV1::CANDIDATE.0
19 | ProjectionProviderCapabilitiesV1::FILTER.0
20 | ProjectionProviderCapabilitiesV1::RANK.0
21 | ProjectionProviderCapabilitiesV1::ORDER.0
22 | ProjectionProviderCapabilitiesV1::MEASURE.0
23 | ProjectionProviderCapabilitiesV1::FACET.0
24 | ProjectionProviderCapabilitiesV1::WINDOW.0
25 | ProjectionProviderCapabilitiesV1::OUTPUT.0;
26
27#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
29#[repr(u8)]
30pub enum ProjectionProviderKindV1 {
31 Columnar = 1,
33 Vector = 2,
35 ExactText = 3,
37 TokenizedText = 4,
39 LongPattern = 5,
41}
42
43#[derive(Clone, Copy, Debug, Eq, PartialEq)]
45pub enum ProjectionProviderPostureV1 {
46 Exact,
48 Approximate {
50 recall_target_bps: u16,
52 },
53}
54
55impl ProjectionProviderPostureV1 {
56 pub const fn approximate(
58 recall_target_bps: u16,
59 ) -> Result<Self, ProjectionProviderValidationError> {
60 if recall_target_bps == 0 || recall_target_bps > 10_000 {
61 Err(ProjectionProviderValidationError::InvalidRecallTarget)
62 } else {
63 Ok(Self::Approximate { recall_target_bps })
64 }
65 }
66}
67
68#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
70pub struct ProjectionProviderCapabilitiesV1(u16);
71
72impl ProjectionProviderCapabilitiesV1 {
73 pub const CANDIDATE: Self = Self(1 << 0);
75 pub const FILTER: Self = Self(1 << 1);
77 pub const RANK: Self = Self(1 << 2);
79 pub const ORDER: Self = Self(1 << 3);
81 pub const MEASURE: Self = Self(1 << 4);
83 pub const FACET: Self = Self(1 << 5);
85 pub const WINDOW: Self = Self(1 << 6);
87 pub const OUTPUT: Self = Self(1 << 7);
89
90 #[must_use]
92 pub const fn bits(self) -> u16 {
93 self.0
94 }
95
96 #[must_use]
98 pub const fn contains(self, required: Self) -> bool {
99 self.0 & required.0 == required.0
100 }
101}
102
103impl BitOr for ProjectionProviderCapabilitiesV1 {
104 type Output = Self;
105
106 fn bitor(self, rhs: Self) -> Self::Output {
107 Self(self.0 | rhs.0)
108 }
109}
110
111impl BitOrAssign for ProjectionProviderCapabilitiesV1 {
112 fn bitor_assign(&mut self, rhs: Self) {
113 self.0 |= rhs.0;
114 }
115}
116
117#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
119#[repr(u8)]
120pub enum ProjectionProviderPolicyModeV1 {
121 PartitionAligned = 1,
123 PolicySubpartition = 2,
125 BoundedRowAdmission = 3,
127}
128
129#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
131#[repr(u8)]
132pub enum ProjectionProviderFreshnessV1 {
133 RetainedExactEpoch = 1,
135}
136
137#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
139#[repr(u8)]
140pub enum ProjectionProviderLifecycleModelV1 {
141 RebuildableGeneration = 1,
143}
144
145#[derive(Clone, Copy, Debug, Eq, PartialEq)]
147pub struct ProjectionProviderStaticBoundsV1 {
148 pub max_candidates: u32,
150 pub max_output_rows: u32,
152 pub max_measures: u16,
154 pub max_input_bytes: u32,
156 pub max_work_units: u64,
158 pub max_state_bytes_per_row: u32,
160 pub max_diagnostic_bytes: u32,
162 pub retained_epochs: u64,
164 pub max_catchup_lag: u64,
166 pub max_epoch_lease_steps: u64,
168}
169
170#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
172pub struct ProjectionProviderStateIdentityV1 {
173 layout_version: NonZeroU32,
174 schema_hash: [u8; 32],
175}
176
177impl ProjectionProviderStateIdentityV1 {
178 #[must_use]
180 pub const fn new(layout_version: NonZeroU32, schema_hash: [u8; 32]) -> Self {
181 Self {
182 layout_version,
183 schema_hash,
184 }
185 }
186
187 #[must_use]
189 pub const fn layout_version(self) -> NonZeroU32 {
190 self.layout_version
191 }
192
193 #[must_use]
195 pub const fn schema_hash(self) -> [u8; 32] {
196 self.schema_hash
197 }
198}
199
200#[derive(Clone, Debug, Eq, PartialEq)]
202pub struct ProjectionProviderDescriptorV1 {
203 kind: ProjectionProviderKindV1,
204 posture: ProjectionProviderPostureV1,
205 capabilities: ProjectionProviderCapabilitiesV1,
206 policy_mode: ProjectionProviderPolicyModeV1,
207 freshness: ProjectionProviderFreshnessV1,
208 lifecycle: ProjectionProviderLifecycleModelV1,
209 bounds: ProjectionProviderStaticBoundsV1,
210 state: ProjectionProviderStateIdentityV1,
211}
212
213impl ProjectionProviderDescriptorV1 {
214 pub fn new(
216 kind: ProjectionProviderKindV1,
217 posture: ProjectionProviderPostureV1,
218 capabilities: ProjectionProviderCapabilitiesV1,
219 policy_mode: ProjectionProviderPolicyModeV1,
220 bounds: ProjectionProviderStaticBoundsV1,
221 state: ProjectionProviderStateIdentityV1,
222 ) -> Result<Self, ProjectionProviderValidationError> {
223 let descriptor = Self {
224 kind,
225 posture,
226 capabilities,
227 policy_mode,
228 freshness: ProjectionProviderFreshnessV1::RetainedExactEpoch,
229 lifecycle: ProjectionProviderLifecycleModelV1::RebuildableGeneration,
230 bounds,
231 state,
232 };
233 descriptor.validate()?;
234 Ok(descriptor)
235 }
236
237 pub fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, ProjectionProviderValidationError> {
239 if bytes.len() != PROJECTION_PROVIDER_DESCRIPTOR_V1_BYTES {
240 return Err(ProjectionProviderValidationError::InvalidLength);
241 }
242 if bytes[..4] != MAGIC || u16::from_be_bytes([bytes[4], bytes[5]]) != 1 {
243 return Err(ProjectionProviderValidationError::InvalidVersion);
244 }
245 if bytes[15] != 0 || bytes[26..28] != [0; 2] || bytes[108..112] != [0; 4] {
246 return Err(ProjectionProviderValidationError::NonCanonicalReservedBytes);
247 }
248 let kind = match bytes[6] {
249 1 => ProjectionProviderKindV1::Columnar,
250 2 => ProjectionProviderKindV1::Vector,
251 3 => ProjectionProviderKindV1::ExactText,
252 4 => ProjectionProviderKindV1::TokenizedText,
253 5 => ProjectionProviderKindV1::LongPattern,
254 _ => return Err(ProjectionProviderValidationError::UnknownProvider),
255 };
256 let recall = u16::from_be_bytes([bytes[8], bytes[9]]);
257 let posture = match bytes[7] {
258 1 if recall == 0 => ProjectionProviderPostureV1::Exact,
259 2 => ProjectionProviderPostureV1::approximate(recall)?,
260 _ => return Err(ProjectionProviderValidationError::InvalidPosture),
261 };
262 let capability_bits = u16::from_be_bytes([bytes[10], bytes[11]]);
263 if capability_bits & !ALL_CAPABILITY_BITS != 0 {
264 return Err(ProjectionProviderValidationError::UnknownCapability);
265 }
266 let capabilities = ProjectionProviderCapabilitiesV1(capability_bits);
267 let policy_mode = match bytes[12] {
268 1 => ProjectionProviderPolicyModeV1::PartitionAligned,
269 2 => ProjectionProviderPolicyModeV1::PolicySubpartition,
270 3 => ProjectionProviderPolicyModeV1::BoundedRowAdmission,
271 _ => return Err(ProjectionProviderValidationError::UnknownPolicyMode),
272 };
273 if bytes[13] != ProjectionProviderFreshnessV1::RetainedExactEpoch as u8
274 || bytes[14] != ProjectionProviderLifecycleModelV1::RebuildableGeneration as u8
275 {
276 return Err(ProjectionProviderValidationError::UnknownLifecycle);
277 }
278 let max_candidates = read_u32(bytes, 16);
279 let max_output_rows = read_u32(bytes, 20);
280 let max_measures = u16::from_be_bytes([bytes[24], bytes[25]]);
281 let retained_epochs = read_u64(bytes, 28);
282 let max_catchup_lag = read_u64(bytes, 36);
283 let layout_version = NonZeroU32::new(read_u32(bytes, 44))
284 .ok_or(ProjectionProviderValidationError::InvalidStateIdentity)?;
285 let mut schema_hash = [0; 32];
286 schema_hash.copy_from_slice(&bytes[48..80]);
287 let bounds = ProjectionProviderStaticBoundsV1 {
288 max_candidates,
289 max_output_rows,
290 max_measures,
291 retained_epochs,
292 max_catchup_lag,
293 max_input_bytes: read_u32(bytes, 80),
294 max_work_units: read_u64(bytes, 84),
295 max_state_bytes_per_row: read_u32(bytes, 92),
296 max_diagnostic_bytes: read_u32(bytes, 96),
297 max_epoch_lease_steps: read_u64(bytes, 100),
298 };
299 Self::new(
300 kind,
301 posture,
302 capabilities,
303 policy_mode,
304 bounds,
305 ProjectionProviderStateIdentityV1::new(layout_version, schema_hash),
306 )
307 }
308
309 #[must_use]
311 pub fn to_canonical_bytes(&self) -> [u8; PROJECTION_PROVIDER_DESCRIPTOR_V1_BYTES] {
312 let mut bytes = [0; PROJECTION_PROVIDER_DESCRIPTOR_V1_BYTES];
313 bytes[..4].copy_from_slice(&MAGIC);
314 bytes[4..6].copy_from_slice(&PROJECTION_PROVIDER_DESCRIPTOR_VERSION_V1.to_be_bytes());
315 bytes[6] = self.kind as u8;
316 let (posture, recall) = match self.posture {
317 ProjectionProviderPostureV1::Exact => (1, 0),
318 ProjectionProviderPostureV1::Approximate { recall_target_bps } => {
319 (2, recall_target_bps)
320 }
321 };
322 bytes[7] = posture;
323 bytes[8..10].copy_from_slice(&recall.to_be_bytes());
324 bytes[10..12].copy_from_slice(&self.capabilities.bits().to_be_bytes());
325 bytes[12] = self.policy_mode as u8;
326 bytes[13] = self.freshness as u8;
327 bytes[14] = self.lifecycle as u8;
328 bytes[16..20].copy_from_slice(&self.bounds.max_candidates.to_be_bytes());
329 bytes[20..24].copy_from_slice(&self.bounds.max_output_rows.to_be_bytes());
330 bytes[24..26].copy_from_slice(&self.bounds.max_measures.to_be_bytes());
331 bytes[28..36].copy_from_slice(&self.bounds.retained_epochs.to_be_bytes());
332 bytes[36..44].copy_from_slice(&self.bounds.max_catchup_lag.to_be_bytes());
333 bytes[44..48].copy_from_slice(&self.state.layout_version().get().to_be_bytes());
334 bytes[48..80].copy_from_slice(&self.state.schema_hash());
335 bytes[80..84].copy_from_slice(&self.bounds.max_input_bytes.to_be_bytes());
336 bytes[84..92].copy_from_slice(&self.bounds.max_work_units.to_be_bytes());
337 bytes[92..96].copy_from_slice(&self.bounds.max_state_bytes_per_row.to_be_bytes());
338 bytes[96..100].copy_from_slice(&self.bounds.max_diagnostic_bytes.to_be_bytes());
339 bytes[100..108].copy_from_slice(&self.bounds.max_epoch_lease_steps.to_be_bytes());
340 bytes
341 }
342
343 #[must_use]
345 pub fn digest(&self) -> ProjectionProviderDescriptorHash {
346 hash_projection_provider_descriptor(&self.to_canonical_bytes())
347 }
348
349 #[must_use]
351 pub const fn kind(&self) -> ProjectionProviderKindV1 {
352 self.kind
353 }
354 #[must_use]
356 pub const fn posture(&self) -> ProjectionProviderPostureV1 {
357 self.posture
358 }
359 #[must_use]
361 pub const fn capabilities(&self) -> ProjectionProviderCapabilitiesV1 {
362 self.capabilities
363 }
364 #[must_use]
370 pub const fn aggregate_semantics(&self) -> AggregateSemanticSetV1 {
371 if !self
372 .capabilities
373 .contains(ProjectionProviderCapabilitiesV1::MEASURE)
374 {
375 return AggregateSemanticSetV1::NONE;
376 }
377 match self.kind {
378 ProjectionProviderKindV1::Columnar => AggregateSemanticSetV1::BOUNDED_EXACT_CORE,
379 ProjectionProviderKindV1::ExactText => AggregateSemanticSetV1::EXACT_COUNT_ONLY,
380 ProjectionProviderKindV1::Vector
381 | ProjectionProviderKindV1::TokenizedText
382 | ProjectionProviderKindV1::LongPattern => AggregateSemanticSetV1::NONE,
383 }
384 }
385 #[must_use]
387 pub const fn policy_mode(&self) -> ProjectionProviderPolicyModeV1 {
388 self.policy_mode
389 }
390 #[must_use]
392 pub const fn freshness(&self) -> ProjectionProviderFreshnessV1 {
393 self.freshness
394 }
395 #[must_use]
397 pub const fn lifecycle(&self) -> ProjectionProviderLifecycleModelV1 {
398 self.lifecycle
399 }
400 #[must_use]
402 pub const fn static_bounds(&self) -> ProjectionProviderStaticBoundsV1 {
403 self.bounds
404 }
405 #[must_use]
407 pub const fn max_candidates(&self) -> u32 {
408 self.bounds.max_candidates
409 }
410 #[must_use]
412 pub const fn max_output_rows(&self) -> u32 {
413 self.bounds.max_output_rows
414 }
415 #[must_use]
417 pub const fn max_measures(&self) -> u16 {
418 self.bounds.max_measures
419 }
420 #[must_use]
422 pub const fn retained_epochs(&self) -> u64 {
423 self.bounds.retained_epochs
424 }
425 #[must_use]
427 pub const fn max_catchup_lag(&self) -> u64 {
428 self.bounds.max_catchup_lag
429 }
430 #[must_use]
432 pub const fn max_epoch_lease_steps(&self) -> u64 {
433 self.bounds.max_epoch_lease_steps
434 }
435 #[must_use]
437 pub const fn state_identity(&self) -> ProjectionProviderStateIdentityV1 {
438 self.state
439 }
440
441 fn validate(&self) -> Result<(), ProjectionProviderValidationError> {
442 let supported = match self.kind {
443 ProjectionProviderKindV1::Columnar => {
444 ALL_CAPABILITY_BITS & !ProjectionProviderCapabilitiesV1::RANK.bits()
445 }
446 ProjectionProviderKindV1::Vector => (ProjectionProviderCapabilitiesV1::CANDIDATE
447 | ProjectionProviderCapabilitiesV1::FILTER
448 | ProjectionProviderCapabilitiesV1::RANK
449 | ProjectionProviderCapabilitiesV1::WINDOW
450 | ProjectionProviderCapabilitiesV1::OUTPUT)
451 .bits(),
452 ProjectionProviderKindV1::ExactText => (ProjectionProviderCapabilitiesV1::CANDIDATE
453 | ProjectionProviderCapabilitiesV1::FILTER
454 | ProjectionProviderCapabilitiesV1::ORDER
455 | ProjectionProviderCapabilitiesV1::MEASURE
456 | ProjectionProviderCapabilitiesV1::WINDOW
457 | ProjectionProviderCapabilitiesV1::OUTPUT)
458 .bits(),
459 ProjectionProviderKindV1::TokenizedText => (ProjectionProviderCapabilitiesV1::CANDIDATE
460 | ProjectionProviderCapabilitiesV1::FILTER
461 | ProjectionProviderCapabilitiesV1::RANK
462 | ProjectionProviderCapabilitiesV1::ORDER
463 | ProjectionProviderCapabilitiesV1::WINDOW
464 | ProjectionProviderCapabilitiesV1::OUTPUT)
465 .bits(),
466 ProjectionProviderKindV1::LongPattern => (ProjectionProviderCapabilitiesV1::CANDIDATE
467 | ProjectionProviderCapabilitiesV1::FILTER
468 | ProjectionProviderCapabilitiesV1::WINDOW
469 | ProjectionProviderCapabilitiesV1::OUTPUT)
470 .bits(),
471 };
472 if self.capabilities.bits() & !supported != 0 {
473 return Err(ProjectionProviderValidationError::UnsupportedCapability);
474 }
475 let required = ProjectionProviderCapabilitiesV1::CANDIDATE
476 | ProjectionProviderCapabilitiesV1::WINDOW
477 | ProjectionProviderCapabilitiesV1::OUTPUT;
478 if !self.capabilities.contains(required) {
479 return Err(ProjectionProviderValidationError::MissingRequiredCapability);
480 }
481 if self.bounds.max_candidates == 0
482 || self.bounds.max_output_rows == 0
483 || self.bounds.max_output_rows > self.bounds.max_candidates
484 || self.bounds.max_input_bytes == 0
485 || self.bounds.max_work_units == 0
486 || self.bounds.max_state_bytes_per_row == 0
487 || self.bounds.max_diagnostic_bytes == 0
488 || self.bounds.retained_epochs == 0
489 || self.bounds.max_catchup_lag == 0
490 || self.bounds.max_epoch_lease_steps == 0
491 {
492 return Err(ProjectionProviderValidationError::InvalidBound);
493 }
494 if matches!(
495 self.kind,
496 ProjectionProviderKindV1::Columnar
497 | ProjectionProviderKindV1::ExactText
498 | ProjectionProviderKindV1::TokenizedText
499 | ProjectionProviderKindV1::LongPattern
500 ) && !matches!(self.posture, ProjectionProviderPostureV1::Exact)
501 {
502 return Err(ProjectionProviderValidationError::InvalidPosture);
503 }
504 if self.bounds.max_measures == 0
505 && (self
506 .capabilities
507 .contains(ProjectionProviderCapabilitiesV1::MEASURE)
508 || self
509 .capabilities
510 .contains(ProjectionProviderCapabilitiesV1::FACET))
511 {
512 return Err(ProjectionProviderValidationError::InvalidBound);
513 }
514 Ok(())
515 }
516}
517
518#[derive(Clone, Copy, Debug, Eq, PartialEq)]
520pub enum ProjectionProviderValidationError {
521 InvalidLength,
523 InvalidVersion,
525 UnknownProvider,
527 InvalidPosture,
529 InvalidRecallTarget,
531 UnknownCapability,
533 MissingRequiredCapability,
535 UnsupportedCapability,
537 UnknownPolicyMode,
539 UnknownLifecycle,
541 InvalidBound,
543 InvalidStateIdentity,
545 NonCanonicalReservedBytes,
547}
548
549impl fmt::Display for ProjectionProviderValidationError {
550 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
551 write!(
552 formatter,
553 "invalid projection provider descriptor: {self:?}"
554 )
555 }
556}
557
558impl Error for ProjectionProviderValidationError {}
559
560fn read_u32(bytes: &[u8], offset: usize) -> u32 {
561 u32::from_be_bytes(
562 bytes[offset..offset + 4]
563 .try_into()
564 .expect("fixed descriptor"),
565 )
566}
567
568fn read_u64(bytes: &[u8], offset: usize) -> u64 {
569 u64::from_be_bytes(
570 bytes[offset..offset + 8]
571 .try_into()
572 .expect("fixed descriptor"),
573 )
574}
575
576crate::inspect_owned_capacity_struct_v1!(ProjectionProviderDescriptorV1 {
578 owned: [],
579 inline: [
580 kind,
581 posture,
582 capabilities,
583 policy_mode,
584 freshness,
585 lifecycle,
586 bounds,
587 state
588 ]
589});
590
591crate::inspect_owned_capacity_copy_v1!(ProjectionProviderKindV1);
592
593crate::inspect_owned_capacity_copy_v1!(ProjectionProviderPostureV1);
594
595crate::inspect_owned_capacity_copy_v1!(ProjectionProviderCapabilitiesV1);
596
597crate::inspect_owned_capacity_copy_v1!(ProjectionProviderPolicyModeV1);
598
599crate::inspect_owned_capacity_copy_v1!(ProjectionProviderFreshnessV1);
600
601crate::inspect_owned_capacity_copy_v1!(ProjectionProviderLifecycleModelV1);
602
603crate::inspect_owned_capacity_copy_v1!(ProjectionProviderStaticBoundsV1);
604
605crate::inspect_owned_capacity_copy_v1!(ProjectionProviderStateIdentityV1);