1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub struct Field(pub u32);
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub enum FieldType {
13 #[serde(rename = "text")]
15 Text,
16 #[serde(rename = "u64")]
18 U64,
19 #[serde(rename = "i64")]
21 I64,
22 #[serde(rename = "f64")]
24 F64,
25 #[serde(rename = "bytes")]
27 Bytes,
28 #[serde(rename = "sparse_vector")]
30 SparseVector,
31 #[serde(rename = "dense_vector")]
33 DenseVector,
34 #[serde(rename = "json")]
36 Json,
37 #[serde(rename = "binary_dense_vector")]
39 BinaryDenseVector,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct FieldEntry {
45 pub name: String,
46 pub field_type: FieldType,
47 pub indexed: bool,
48 pub stored: bool,
49 pub tokenizer: Option<String>,
51 #[serde(default)]
53 pub multi: bool,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub positions: Option<PositionMode>,
57 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub sparse_vector_config: Option<crate::structures::SparseVectorConfig>,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub dense_vector_config: Option<DenseVectorConfig>,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub binary_dense_vector_config: Option<BinaryDenseVectorConfig>,
66 #[serde(default)]
69 pub fast: bool,
70 #[serde(default)]
72 pub primary_key: bool,
73 #[serde(default)]
77 pub reorder: bool,
78 #[serde(default)]
81 pub chunked: bool,
82}
83
84impl FieldEntry {
85 pub fn tokenizer_spec(&self) -> Option<crate::tokenizer::TokenizerSpec> {
88 if self.field_type != FieldType::Text {
89 return None;
90 }
91 crate::tokenizer::TokenizerSpec::parse(self.tokenizer.as_deref()?).ok()
92 }
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
97#[serde(rename_all = "snake_case")]
98pub enum PositionMode {
99 Ordinal,
102 TokenPosition,
105 Full,
108}
109
110impl PositionMode {
111 pub fn tracks_ordinal(&self) -> bool {
113 matches!(self, PositionMode::Ordinal | PositionMode::Full)
114 }
115
116 pub fn tracks_token_position(&self) -> bool {
118 matches!(self, PositionMode::TokenPosition | PositionMode::Full)
119 }
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
124#[serde(rename_all = "snake_case")]
125pub enum VectorIndexType {
126 Flat,
128 IvfPq,
133 Tq,
137 #[default]
141 IvfTq,
142 Scann,
146}
147
148pub(crate) fn reject_removed_vector_index_types(schema: &Schema) -> Result<(), String> {
152 for (_, entry) in schema.fields() {
153 if let Some(config) = entry.dense_vector_config.as_ref() {
154 validate_target_vectors(
155 &entry.name,
156 config.target_vectors,
157 !matches!(
158 config.index_type,
159 VectorIndexType::Flat | VectorIndexType::Tq
160 ),
161 )?;
162 if config.index_type == VectorIndexType::IvfPq {
163 return Err(format!(
164 "dense field '{}' uses index_type `ivf_pq`, which was removed; \
165 recreate the index with `ivf_tq` (trained router, training-free \
166 TurboQuant leaves) and reindex — see docs/turboquant-quantization.md",
167 entry.name,
168 ));
169 }
170 if config.index_type == VectorIndexType::Scann && config.soar.is_some() {
171 return Err(format!(
172 "dense field '{}' enables SOAR for ScaNN, but ScaNN SOAR secondary assignments are not implemented; set soar to null/off",
173 entry.name,
174 ));
175 }
176 validate_persisted_scann_options(
177 &entry.name,
178 config.index_type == VectorIndexType::Scann,
179 config.num_clusters,
180 config.tree_levels,
181 config.nprobe,
182 config.ivf_routing,
183 )?;
184 }
185 if let Some(config) = entry.binary_dense_vector_config.as_ref() {
186 validate_target_vectors(
187 &entry.name,
188 config.target_vectors,
189 config.index_type != BinaryIndexType::Flat,
190 )?;
191 if config.soar.is_some() && config.index_type != BinaryIndexType::Scann {
192 return Err(format!(
193 "binary dense field '{}' enables binary SOAR spilling, but it requires the ScaNN index",
194 entry.name,
195 ));
196 }
197 if config.index_type == BinaryIndexType::Scann && !config.dim.is_multiple_of(8) {
198 return Err(format!(
199 "binary dense field '{}' uses ScaNN with dimension {}; binary ScaNN dimensions must be a multiple of 8 bits",
200 entry.name, config.dim,
201 ));
202 }
203 validate_persisted_scann_options(
204 &entry.name,
205 config.index_type == BinaryIndexType::Scann,
206 config.num_clusters,
207 config.tree_levels,
208 config.nprobe,
209 config.ivf_routing,
210 )?;
211 }
212 }
213 Ok(())
214}
215
216fn validate_target_vectors(
217 field_name: &str,
218 target_vectors: Option<u64>,
219 topology_is_automatic: bool,
220) -> Result<(), String> {
221 if target_vectors == Some(0) {
222 return Err(format!(
223 "field '{field_name}' has target_vectors 0; expected a positive steady-state vector count"
224 ));
225 }
226 if target_vectors.is_some() && !topology_is_automatic {
227 return Err(format!(
228 "field '{field_name}' sets target_vectors for a flat/training-free index; the hint is only valid for IVF or ScaNN automatic topology"
229 ));
230 }
231 Ok(())
232}
233
234fn validate_persisted_scann_options(
235 field_name: &str,
236 is_scann: bool,
237 num_clusters: Option<usize>,
238 tree_levels: Option<u8>,
239 nprobe: usize,
240 routing: IvfRoutingMode,
241) -> Result<(), String> {
242 if !is_scann {
243 if tree_levels.is_some() {
244 return Err(format!(
245 "field '{field_name}' sets tree_levels but does not use the ScaNN index"
246 ));
247 }
248 return Ok(());
249 }
250 if routing != IvfRoutingMode::Auto {
251 return Err(format!(
252 "field '{field_name}' sets routing {routing:?} for ScaNN, but ScaNN owns its hierarchical routing; remove the routing option"
253 ));
254 }
255
256 if let Some(levels) = tree_levels
257 && !(1..=3).contains(&levels)
258 {
259 return Err(format!(
260 "field '{field_name}' has ScaNN tree_levels {levels}; expected 1..=3"
261 ));
262 }
263 if let Some(leaves) = num_clusters {
264 if !(2..=30_000_000).contains(&leaves) {
265 return Err(format!(
266 "field '{field_name}' has ScaNN num_clusters {leaves}; expected 2..=30000000"
267 ));
268 }
269 if nprobe > leaves {
270 return Err(format!(
271 "field '{field_name}' has ScaNN nprobe {nprobe} greater than num_clusters {leaves}"
272 ));
273 }
274 }
275 if nprobe == 0 {
276 return Err(format!(
277 "field '{field_name}' has ScaNN nprobe 0; expected a positive probe count"
278 ));
279 }
280 Ok(())
281}
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
289#[serde(rename_all = "snake_case")]
290pub enum IvfRoutingMode {
291 #[default]
294 Auto,
295 Flat,
297 TwoLevel,
299 Hnsw,
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
309#[serde(rename_all = "snake_case")]
310pub enum DenseVectorQuantization {
311 #[default]
313 F32,
314 F16,
316 UInt8,
318 Binary,
321}
322
323impl DenseVectorQuantization {
324 pub fn element_size(self) -> usize {
327 match self {
328 Self::F32 => 4,
329 Self::F16 => 2,
330 Self::UInt8 => 1,
331 Self::Binary => panic!("element_size() not valid for Binary; use dim.div_ceil(8)"),
332 }
333 }
334
335 pub fn tag(self) -> u8 {
337 match self {
338 Self::F32 => 0,
339 Self::F16 => 1,
340 Self::UInt8 => 2,
341 Self::Binary => 3,
342 }
343 }
344
345 pub fn from_tag(tag: u8) -> Option<Self> {
347 match tag {
348 0 => Some(Self::F32),
349 1 => Some(Self::F16),
350 2 => Some(Self::UInt8),
351 3 => Some(Self::Binary),
352 _ => None,
353 }
354 }
355}
356
357#[derive(Debug, Clone, Serialize)]
367#[serde(deny_unknown_fields)]
368pub struct DenseVectorConfig {
369 pub dim: usize,
371 #[serde(default)]
374 pub index_type: VectorIndexType,
375 #[serde(default)]
377 pub quantization: DenseVectorQuantization,
378 #[serde(default, skip_serializing_if = "Option::is_none")]
382 pub num_clusters: Option<usize>,
383 #[serde(default, skip_serializing_if = "Option::is_none")]
387 pub target_vectors: Option<u64>,
388 #[serde(default, skip_serializing_if = "Option::is_none")]
391 pub tree_levels: Option<u8>,
392 #[serde(default)]
395 pub ivf_routing: IvfRoutingMode,
396 #[serde(default = "default_nprobe")]
398 pub nprobe: usize,
399 #[serde(default = "default_unit_norm")]
407 pub unit_norm: bool,
408 #[serde(default = "default_soar")]
419 pub soar: Option<crate::structures::SoarConfig>,
420}
421
422#[derive(Default)]
423enum PersistedSoar {
424 #[default]
425 Unspecified,
426 Specified(Option<crate::structures::SoarConfig>),
427}
428
429impl<'de> Deserialize<'de> for PersistedSoar {
430 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
431 Option::<crate::structures::SoarConfig>::deserialize(deserializer).map(Self::Specified)
432 }
433}
434
435#[derive(Deserialize)]
436#[serde(deny_unknown_fields)]
437struct DenseVectorConfigWire {
438 dim: usize,
439 #[serde(default)]
440 index_type: VectorIndexType,
441 #[serde(default)]
442 quantization: DenseVectorQuantization,
443 #[serde(default)]
444 num_clusters: Option<usize>,
445 #[serde(default)]
446 target_vectors: Option<u64>,
447 #[serde(default)]
448 tree_levels: Option<u8>,
449 #[serde(default)]
450 ivf_routing: IvfRoutingMode,
451 #[serde(default = "default_nprobe")]
452 nprobe: usize,
453 #[serde(default = "default_unit_norm")]
454 unit_norm: bool,
455 #[serde(default)]
456 soar: PersistedSoar,
457}
458
459impl<'de> Deserialize<'de> for DenseVectorConfig {
460 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
461 let wire = DenseVectorConfigWire::deserialize(deserializer)?;
462 let soar = match wire.soar {
463 PersistedSoar::Specified(soar) => soar,
464 PersistedSoar::Unspecified if wire.index_type == VectorIndexType::IvfTq => {
465 default_soar()
466 }
467 PersistedSoar::Unspecified => None,
468 };
469 Ok(Self {
470 dim: wire.dim,
471 index_type: wire.index_type,
472 quantization: wire.quantization,
473 num_clusters: wire.num_clusters,
474 target_vectors: wire.target_vectors,
475 tree_levels: wire.tree_levels,
476 ivf_routing: wire.ivf_routing,
477 nprobe: wire.nprobe,
478 unit_norm: wire.unit_norm,
479 soar,
480 })
481 }
482}
483
484fn default_nprobe() -> usize {
485 64
486}
487
488fn default_unit_norm() -> bool {
489 true
490}
491
492fn default_soar() -> Option<crate::structures::SoarConfig> {
493 Some(crate::structures::SoarConfig::default())
494}
495
496impl DenseVectorConfig {
497 pub fn new(dim: usize) -> Self {
498 Self {
499 dim,
500 index_type: VectorIndexType::IvfTq,
501 quantization: DenseVectorQuantization::F32,
502 num_clusters: None,
503 target_vectors: None,
504 tree_levels: None,
505 ivf_routing: IvfRoutingMode::Auto,
506 nprobe: 64,
507 unit_norm: true,
508 soar: Some(crate::structures::SoarConfig::default()),
509 }
510 }
511
512 pub fn flat(dim: usize) -> Self {
514 Self {
515 dim,
516 index_type: VectorIndexType::Flat,
517 quantization: DenseVectorQuantization::F32,
518 num_clusters: None,
519 target_vectors: None,
520 tree_levels: None,
521 ivf_routing: IvfRoutingMode::Auto,
522 nprobe: 0,
523 unit_norm: true,
524 soar: None,
525 }
526 }
527
528 pub fn tq(dim: usize) -> Self {
530 Self {
531 dim,
532 index_type: VectorIndexType::Tq,
533 quantization: DenseVectorQuantization::F32,
534 num_clusters: None,
535 target_vectors: None,
536 tree_levels: None,
537 ivf_routing: IvfRoutingMode::Flat,
538 nprobe: 0,
539 unit_norm: true,
540 soar: None,
541 }
542 }
543
544 pub fn ivf_tq(dim: usize, num_clusters: Option<usize>, nprobe: usize) -> Self {
546 Self {
547 dim,
548 index_type: VectorIndexType::IvfTq,
549 quantization: DenseVectorQuantization::F32,
550 num_clusters,
551 target_vectors: None,
552 tree_levels: None,
553 ivf_routing: IvfRoutingMode::Auto,
554 nprobe,
555 unit_norm: true,
556 soar: Some(crate::structures::SoarConfig::default()),
557 }
558 }
559
560 pub fn with_quantization(mut self, quantization: DenseVectorQuantization) -> Self {
562 self.quantization = quantization;
563 self
564 }
565
566 pub fn with_unit_norm(mut self) -> Self {
568 self.unit_norm = true;
569 self
570 }
571
572 pub fn with_num_clusters(mut self, num_clusters: usize) -> Self {
574 self.num_clusters = Some(num_clusters);
575 self
576 }
577
578 pub fn with_target_vectors(mut self, target_vectors: u64) -> Self {
580 self.target_vectors = Some(target_vectors);
581 self
582 }
583
584 pub fn with_ivf_routing(mut self, routing: IvfRoutingMode) -> Self {
586 self.ivf_routing = routing;
587 self
588 }
589 pub fn with_soar(mut self, soar: crate::structures::SoarConfig) -> Self {
591 self.soar = Some(soar);
592 self
593 }
594
595 pub fn without_soar(mut self) -> Self {
597 self.soar = None;
598 self
599 }
600
601 pub fn uses_ivf(&self) -> bool {
603 self.index_type == VectorIndexType::IvfTq
604 }
605
606 pub fn supports_soar(&self) -> bool {
608 self.index_type == VectorIndexType::IvfTq
609 }
610
611 pub fn is_flat(&self) -> bool {
613 self.index_type == VectorIndexType::Flat
614 }
615
616 pub fn optimal_num_clusters(&self, num_vectors: usize) -> usize {
618 self.num_clusters.unwrap_or_else(|| {
619 let num_vectors = self.target_vectors.map_or(num_vectors, |target| {
620 usize::try_from(target)
621 .unwrap_or(usize::MAX)
622 .max(num_vectors)
623 });
624 let optimal = 8.0 * (num_vectors as f64).sqrt();
628 (optimal as usize).clamp(16, 1_048_576)
629 })
630 }
631}
632
633#[derive(Debug, Clone, Serialize, Deserialize)]
639#[serde(deny_unknown_fields)]
640pub struct BinaryDenseVectorConfig {
641 pub dim: usize,
643 #[serde(default)]
647 pub index_type: BinaryIndexType,
648 #[serde(default, skip_serializing_if = "Option::is_none")]
650 pub num_clusters: Option<usize>,
651 #[serde(default, skip_serializing_if = "Option::is_none")]
655 pub target_vectors: Option<u64>,
656 #[serde(default, skip_serializing_if = "Option::is_none")]
659 pub tree_levels: Option<u8>,
660 #[serde(default)]
663 pub ivf_routing: IvfRoutingMode,
664 #[serde(default = "default_nprobe")]
666 pub nprobe: usize,
667 #[serde(default, skip_serializing_if = "Option::is_none")]
671 pub soar: Option<crate::structures::SoarConfig>,
672}
673
674#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
676#[serde(rename_all = "snake_case")]
677pub enum BinaryIndexType {
678 Flat,
680 #[default]
682 Ivf,
683 Scann,
685}
686
687#[derive(Debug, Clone)]
691pub enum VectorIndexAlter {
692 Dense(DenseVectorConfig),
693 Binary(BinaryDenseVectorConfig),
694}
695
696impl BinaryDenseVectorConfig {
697 pub fn new(dim: usize) -> Self {
698 assert!(
699 dim.is_multiple_of(8),
700 "BinaryDenseVector dimension must be a multiple of 8, got {dim}"
701 );
702 Self {
703 dim,
704 index_type: BinaryIndexType::Ivf,
705 num_clusters: None,
706 target_vectors: None,
707 tree_levels: None,
708 ivf_routing: IvfRoutingMode::Auto,
709 nprobe: 64,
710 soar: None,
711 }
712 }
713
714 pub fn with_ivf(mut self, num_clusters: Option<usize>, nprobe: usize) -> Self {
716 self.index_type = BinaryIndexType::Ivf;
717 self.num_clusters = num_clusters;
718 self.nprobe = nprobe;
719 self
720 }
721
722 pub fn with_target_vectors(mut self, target_vectors: u64) -> Self {
724 self.target_vectors = Some(target_vectors);
725 self
726 }
727
728 pub fn with_ivf_routing(mut self, routing: IvfRoutingMode) -> Self {
730 self.ivf_routing = routing;
731 self
732 }
733
734 pub fn with_soar(mut self, soar: crate::structures::SoarConfig) -> Self {
736 self.soar = Some(soar);
737 self
738 }
739
740 pub fn without_soar(mut self) -> Self {
742 self.soar = None;
743 self
744 }
745
746 pub fn optimal_num_clusters(&self, num_vectors: usize) -> usize {
748 self.num_clusters.unwrap_or_else(|| {
749 let num_vectors = self.target_vectors.map_or(num_vectors, |target| {
750 usize::try_from(target)
751 .unwrap_or(usize::MAX)
752 .max(num_vectors)
753 });
754 let balanced = (num_vectors as f64).sqrt().ceil() as usize;
758 balanced.clamp(16, 1_048_576)
759 })
760 }
761
762 pub fn byte_len(&self) -> usize {
764 self.dim.div_ceil(8)
765 }
766}
767
768use super::query_field_router::QueryRouterRule;
769
770#[derive(Debug, Clone, Default, Serialize, Deserialize)]
772pub struct Schema {
773 fields: Vec<FieldEntry>,
774 name_to_field: HashMap<String, Field>,
775 #[serde(default)]
777 default_fields: Vec<Field>,
778 #[serde(default)]
780 query_routers: Vec<QueryRouterRule>,
781 #[serde(default)]
786 reorder_on_merge: bool,
787 #[serde(default)]
791 index_name: String,
792}
793
794impl Schema {
795 pub fn builder() -> SchemaBuilder {
796 SchemaBuilder::default()
797 }
798
799 pub fn get_field(&self, name: &str) -> Option<Field> {
800 self.name_to_field.get(name).copied()
801 }
802
803 pub fn get_field_entry(&self, field: Field) -> Option<&FieldEntry> {
804 self.fields.get(field.0 as usize)
805 }
806
807 pub fn tokenizer_hint_field(&self, field: Field) -> Option<Field> {
810 let spec = self.get_field_entry(field)?.tokenizer_spec()?;
811 self.get_field(spec.hint_field()?)
812 }
813
814 pub fn with_vector_index_alter(
817 &self,
818 field: Field,
819 alter: VectorIndexAlter,
820 ) -> Result<Self, String> {
821 let mut next = self.clone();
822 let entry = next
823 .fields
824 .get_mut(field.0 as usize)
825 .ok_or_else(|| format!("vector ALTER references unknown field {}", field.0))?;
826 match alter {
827 VectorIndexAlter::Dense(config) => {
828 let current = entry
829 .dense_vector_config
830 .as_ref()
831 .ok_or_else(|| format!("field '{}' is not a dense vector field", entry.name))?;
832 if config.dim != current.dim || config.quantization != current.quantization {
833 return Err(format!(
834 "field '{}' ALTER cannot change dimension or storage quantization",
835 entry.name
836 ));
837 }
838 if matches!(
839 config.index_type,
840 VectorIndexType::Flat | VectorIndexType::Tq
841 ) {
842 return Err(format!(
843 "field '{}' ALTER target must be `ivf_tq` or `scann`",
844 entry.name
845 ));
846 }
847 entry.dense_vector_config = Some(config);
848 }
849 VectorIndexAlter::Binary(config) => {
850 let current = entry.binary_dense_vector_config.as_ref().ok_or_else(|| {
851 format!("field '{}' is not a binary dense vector field", entry.name)
852 })?;
853 if config.dim != current.dim {
854 return Err(format!(
855 "field '{}' ALTER cannot change binary dimension",
856 entry.name
857 ));
858 }
859 if config.index_type == BinaryIndexType::Flat {
860 return Err(format!(
861 "field '{}' ALTER target must be `ivf` or `scann`",
862 entry.name
863 ));
864 }
865 entry.binary_dense_vector_config = Some(config);
866 }
867 }
868 reject_removed_vector_index_types(&next)?;
869 Ok(next)
870 }
871
872 pub fn get_field_name(&self, field: Field) -> Option<&str> {
873 self.fields.get(field.0 as usize).map(|e| e.name.as_str())
874 }
875
876 pub fn fields(&self) -> impl Iterator<Item = (Field, &FieldEntry)> {
877 self.fields
878 .iter()
879 .enumerate()
880 .map(|(i, e)| (Field(i as u32), e))
881 }
882
883 pub fn num_fields(&self) -> usize {
884 self.fields.len()
885 }
886
887 pub fn has_reorder_fields(&self) -> bool {
890 self.fields.iter().any(|e| e.reorder)
891 }
892
893 pub fn reorder_on_merge(&self) -> bool {
896 self.reorder_on_merge
897 }
898
899 pub fn index_label(&self) -> &str {
902 if self.index_name.is_empty() {
903 "unknown"
904 } else {
905 &self.index_name
906 }
907 }
908
909 pub fn set_index_name(&mut self, name: impl Into<String>) {
911 self.index_name = name.into();
912 }
913
914 pub fn default_fields(&self) -> &[Field] {
916 &self.default_fields
917 }
918
919 pub fn set_default_fields(&mut self, fields: Vec<Field>) {
921 self.default_fields = fields;
922 }
923
924 pub fn query_routers(&self) -> &[QueryRouterRule] {
926 &self.query_routers
927 }
928
929 pub fn set_query_routers(&mut self, rules: Vec<QueryRouterRule>) {
931 self.query_routers = rules;
932 }
933
934 pub fn primary_field(&self) -> Option<Field> {
936 self.fields
937 .iter()
938 .enumerate()
939 .find(|(_, e)| e.primary_key)
940 .map(|(i, _)| Field(i as u32))
941 }
942}
943
944#[derive(Debug, Default)]
946pub struct SchemaBuilder {
947 fields: Vec<FieldEntry>,
948 default_fields: Vec<String>,
949 query_routers: Vec<QueryRouterRule>,
950 reorder_on_merge: bool,
951 index_name: String,
952}
953
954impl SchemaBuilder {
955 pub fn add_text_field(&mut self, name: &str, indexed: bool, stored: bool) -> Field {
956 self.add_field_with_tokenizer(
957 name,
958 FieldType::Text,
959 indexed,
960 stored,
961 Some("simple".to_string()),
962 )
963 }
964
965 pub fn add_text_field_with_tokenizer(
966 &mut self,
967 name: &str,
968 indexed: bool,
969 stored: bool,
970 tokenizer: &str,
971 ) -> Field {
972 self.add_field_with_tokenizer(
973 name,
974 FieldType::Text,
975 indexed,
976 stored,
977 Some(tokenizer.to_string()),
978 )
979 }
980
981 pub fn add_u64_field(&mut self, name: &str, indexed: bool, stored: bool) -> Field {
982 self.add_field(name, FieldType::U64, indexed, stored)
983 }
984
985 pub fn add_i64_field(&mut self, name: &str, indexed: bool, stored: bool) -> Field {
986 self.add_field(name, FieldType::I64, indexed, stored)
987 }
988
989 pub fn add_f64_field(&mut self, name: &str, indexed: bool, stored: bool) -> Field {
990 self.add_field(name, FieldType::F64, indexed, stored)
991 }
992
993 pub fn add_bytes_field(&mut self, name: &str, stored: bool) -> Field {
994 self.add_field(name, FieldType::Bytes, false, stored)
995 }
996
997 pub fn add_json_field(&mut self, name: &str, stored: bool) -> Field {
1002 self.add_field(name, FieldType::Json, false, stored)
1003 }
1004
1005 pub fn add_sparse_vector_field(&mut self, name: &str, indexed: bool, stored: bool) -> Field {
1010 self.add_sparse_vector_field_with_config(
1011 name,
1012 indexed,
1013 stored,
1014 crate::structures::SparseVectorConfig::default(),
1015 )
1016 }
1017
1018 pub fn add_sparse_vector_field_with_config(
1023 &mut self,
1024 name: &str,
1025 indexed: bool,
1026 stored: bool,
1027 config: crate::structures::SparseVectorConfig,
1028 ) -> Field {
1029 let field = Field(self.fields.len() as u32);
1030 self.fields.push(FieldEntry {
1031 name: name.to_string(),
1032 field_type: FieldType::SparseVector,
1033 indexed,
1034 stored,
1035 tokenizer: None,
1036 multi: false,
1037 positions: None,
1038 sparse_vector_config: Some(config),
1039 dense_vector_config: None,
1040 binary_dense_vector_config: None,
1041 fast: false,
1042 primary_key: false,
1043 reorder: false,
1044 chunked: false,
1045 });
1046 field
1047 }
1048
1049 pub fn set_sparse_vector_config(
1051 &mut self,
1052 field: Field,
1053 config: crate::structures::SparseVectorConfig,
1054 ) {
1055 if let Some(entry) = self.fields.get_mut(field.0 as usize) {
1056 entry.sparse_vector_config = Some(config);
1057 }
1058 }
1059
1060 pub fn add_dense_vector_field(
1065 &mut self,
1066 name: &str,
1067 dim: usize,
1068 indexed: bool,
1069 stored: bool,
1070 ) -> Field {
1071 self.add_dense_vector_field_with_config(name, indexed, stored, DenseVectorConfig::new(dim))
1072 }
1073
1074 pub fn add_dense_vector_field_with_config(
1076 &mut self,
1077 name: &str,
1078 indexed: bool,
1079 stored: bool,
1080 config: DenseVectorConfig,
1081 ) -> Field {
1082 let field = Field(self.fields.len() as u32);
1083 self.fields.push(FieldEntry {
1084 name: name.to_string(),
1085 field_type: FieldType::DenseVector,
1086 indexed,
1087 stored,
1088 tokenizer: None,
1089 multi: false,
1090 positions: None,
1091 sparse_vector_config: None,
1092 dense_vector_config: Some(config),
1093 binary_dense_vector_config: None,
1094 fast: false,
1095 primary_key: false,
1096 reorder: false,
1097 chunked: false,
1098 });
1099 field
1100 }
1101
1102 pub fn add_binary_dense_vector_field(
1108 &mut self,
1109 name: &str,
1110 dim: usize,
1111 indexed: bool,
1112 stored: bool,
1113 ) -> Field {
1114 self.add_binary_dense_vector_field_with_config(
1115 name,
1116 indexed,
1117 stored,
1118 BinaryDenseVectorConfig::new(dim),
1119 )
1120 }
1121
1122 pub fn add_binary_dense_vector_field_with_config(
1124 &mut self,
1125 name: &str,
1126 indexed: bool,
1127 stored: bool,
1128 config: BinaryDenseVectorConfig,
1129 ) -> Field {
1130 let field = Field(self.fields.len() as u32);
1131 self.fields.push(FieldEntry {
1132 name: name.to_string(),
1133 field_type: FieldType::BinaryDenseVector,
1134 indexed,
1135 stored,
1136 tokenizer: None,
1137 multi: false,
1138 positions: None,
1139 sparse_vector_config: None,
1140 dense_vector_config: None,
1141 binary_dense_vector_config: Some(config),
1142 fast: false,
1143 primary_key: false,
1144 reorder: false,
1145 chunked: false,
1146 });
1147 field
1148 }
1149
1150 fn add_field(
1151 &mut self,
1152 name: &str,
1153 field_type: FieldType,
1154 indexed: bool,
1155 stored: bool,
1156 ) -> Field {
1157 self.add_field_with_tokenizer(name, field_type, indexed, stored, None)
1158 }
1159
1160 fn add_field_with_tokenizer(
1161 &mut self,
1162 name: &str,
1163 field_type: FieldType,
1164 indexed: bool,
1165 stored: bool,
1166 tokenizer: Option<String>,
1167 ) -> Field {
1168 self.add_field_full(name, field_type, indexed, stored, tokenizer, false)
1169 }
1170
1171 fn add_field_full(
1172 &mut self,
1173 name: &str,
1174 field_type: FieldType,
1175 indexed: bool,
1176 stored: bool,
1177 tokenizer: Option<String>,
1178 multi: bool,
1179 ) -> Field {
1180 let field = Field(self.fields.len() as u32);
1181 self.fields.push(FieldEntry {
1182 name: name.to_string(),
1183 field_type,
1184 indexed,
1185 stored,
1186 tokenizer,
1187 multi,
1188 positions: None,
1189 sparse_vector_config: None,
1190 dense_vector_config: None,
1191 binary_dense_vector_config: None,
1192 fast: false,
1193 primary_key: false,
1194 reorder: false,
1195 chunked: false,
1196 });
1197 field
1198 }
1199
1200 pub fn set_multi(&mut self, field: Field, multi: bool) {
1202 if let Some(entry) = self.fields.get_mut(field.0 as usize) {
1203 entry.multi = multi;
1204 }
1205 }
1206
1207 pub fn set_fast(&mut self, field: Field, fast: bool) {
1210 if let Some(entry) = self.fields.get_mut(field.0 as usize) {
1211 entry.fast = fast;
1212 }
1213 }
1214
1215 pub fn set_primary_key(&mut self, field: Field) {
1221 if let Some(entry) = self.fields.get_mut(field.0 as usize) {
1222 entry.primary_key = true;
1223 entry.fast = true;
1224 entry.indexed = true;
1225 }
1226 }
1227
1228 pub fn set_reorder(&mut self, field: Field, reorder: bool) {
1230 if let Some(entry) = self.fields.get_mut(field.0 as usize) {
1231 entry.reorder = reorder;
1232 }
1233 }
1234
1235 pub fn set_chunked(&mut self, field: Field, chunked: bool) {
1238 if let Some(entry) = self.fields.get_mut(field.0 as usize) {
1239 entry.chunked = chunked;
1240 if chunked {
1241 entry.multi = true;
1242 }
1243 }
1244 }
1245
1246 pub fn set_reorder_on_merge(&mut self, on: bool) {
1249 self.reorder_on_merge = on;
1250 }
1251
1252 pub fn set_index_name(&mut self, name: impl Into<String>) {
1254 self.index_name = name.into();
1255 }
1256
1257 pub fn set_positions(&mut self, field: Field, mode: PositionMode) {
1259 if let Some(entry) = self.fields.get_mut(field.0 as usize) {
1260 entry.positions = Some(mode);
1261 }
1262 }
1263
1264 pub fn set_default_fields(&mut self, field_names: Vec<String>) {
1266 self.default_fields = field_names;
1267 }
1268
1269 pub fn set_query_routers(&mut self, rules: Vec<QueryRouterRule>) {
1271 self.query_routers = rules;
1272 }
1273
1274 pub fn build(self) -> Schema {
1275 let mut name_to_field = HashMap::new();
1276 for (i, entry) in self.fields.iter().enumerate() {
1277 name_to_field.insert(entry.name.clone(), Field(i as u32));
1278 }
1279
1280 let default_fields: Vec<Field> = self
1282 .default_fields
1283 .iter()
1284 .filter_map(|name| name_to_field.get(name).copied())
1285 .collect();
1286
1287 Schema {
1288 fields: self.fields,
1289 name_to_field,
1290 default_fields,
1291 query_routers: self.query_routers,
1292 reorder_on_merge: self.reorder_on_merge,
1293 index_name: self.index_name,
1294 }
1295 }
1296}
1297
1298#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1300pub enum FieldValue {
1301 #[serde(rename = "text")]
1302 Text(String),
1303 #[serde(rename = "u64")]
1304 U64(u64),
1305 #[serde(rename = "i64")]
1306 I64(i64),
1307 #[serde(rename = "f64")]
1308 F64(f64),
1309 #[serde(rename = "bytes")]
1310 Bytes(Vec<u8>),
1311 #[serde(rename = "sparse_vector")]
1313 SparseVector(Vec<(u32, f32)>),
1314 #[serde(rename = "dense_vector")]
1316 DenseVector(Vec<f32>),
1317 #[serde(rename = "json")]
1319 Json(serde_json::Value),
1320 #[serde(rename = "binary_dense_vector")]
1322 BinaryDenseVector(Vec<u8>),
1323}
1324
1325impl FieldValue {
1326 pub fn as_text(&self) -> Option<&str> {
1327 match self {
1328 FieldValue::Text(s) => Some(s),
1329 _ => None,
1330 }
1331 }
1332
1333 pub fn as_u64(&self) -> Option<u64> {
1334 match self {
1335 FieldValue::U64(v) => Some(*v),
1336 _ => None,
1337 }
1338 }
1339
1340 pub fn as_i64(&self) -> Option<i64> {
1341 match self {
1342 FieldValue::I64(v) => Some(*v),
1343 _ => None,
1344 }
1345 }
1346
1347 pub fn as_f64(&self) -> Option<f64> {
1348 match self {
1349 FieldValue::F64(v) => Some(*v),
1350 _ => None,
1351 }
1352 }
1353
1354 pub fn as_bytes(&self) -> Option<&[u8]> {
1355 match self {
1356 FieldValue::Bytes(b) => Some(b),
1357 _ => None,
1358 }
1359 }
1360
1361 pub fn as_sparse_vector(&self) -> Option<&[(u32, f32)]> {
1362 match self {
1363 FieldValue::SparseVector(entries) => Some(entries),
1364 _ => None,
1365 }
1366 }
1367
1368 pub fn as_dense_vector(&self) -> Option<&[f32]> {
1369 match self {
1370 FieldValue::DenseVector(v) => Some(v),
1371 _ => None,
1372 }
1373 }
1374
1375 pub fn as_json(&self) -> Option<&serde_json::Value> {
1376 match self {
1377 FieldValue::Json(v) => Some(v),
1378 _ => None,
1379 }
1380 }
1381
1382 pub fn as_binary_dense_vector(&self) -> Option<&[u8]> {
1383 match self {
1384 FieldValue::BinaryDenseVector(v) => Some(v),
1385 _ => None,
1386 }
1387 }
1388}
1389
1390#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1392pub struct Document {
1393 field_values: Vec<(Field, FieldValue)>,
1394}
1395
1396impl Document {
1397 pub fn new() -> Self {
1398 Self::default()
1399 }
1400
1401 pub fn add_text(&mut self, field: Field, value: impl Into<String>) {
1402 self.field_values
1403 .push((field, FieldValue::Text(value.into())));
1404 }
1405
1406 pub fn add_u64(&mut self, field: Field, value: u64) {
1407 self.field_values.push((field, FieldValue::U64(value)));
1408 }
1409
1410 pub fn add_i64(&mut self, field: Field, value: i64) {
1411 self.field_values.push((field, FieldValue::I64(value)));
1412 }
1413
1414 pub fn add_f64(&mut self, field: Field, value: f64) {
1415 self.field_values.push((field, FieldValue::F64(value)));
1416 }
1417
1418 pub fn add_bytes(&mut self, field: Field, value: Vec<u8>) {
1419 self.field_values.push((field, FieldValue::Bytes(value)));
1420 }
1421
1422 pub fn add_sparse_vector(&mut self, field: Field, entries: Vec<(u32, f32)>) {
1423 self.field_values
1424 .push((field, FieldValue::SparseVector(entries)));
1425 }
1426
1427 pub fn add_dense_vector(&mut self, field: Field, values: Vec<f32>) {
1428 self.field_values
1429 .push((field, FieldValue::DenseVector(values)));
1430 }
1431
1432 pub fn add_json(&mut self, field: Field, value: serde_json::Value) {
1433 self.field_values.push((field, FieldValue::Json(value)));
1434 }
1435
1436 pub fn add_binary_dense_vector(&mut self, field: Field, values: Vec<u8>) {
1437 self.field_values
1438 .push((field, FieldValue::BinaryDenseVector(values)));
1439 }
1440
1441 pub fn get_first(&self, field: Field) -> Option<&FieldValue> {
1442 self.field_values
1443 .iter()
1444 .find(|(f, _)| *f == field)
1445 .map(|(_, v)| v)
1446 }
1447
1448 pub fn get_all(&self, field: Field) -> impl Iterator<Item = &FieldValue> {
1449 self.field_values
1450 .iter()
1451 .filter(move |(f, _)| *f == field)
1452 .map(|(_, v)| v)
1453 }
1454
1455 pub fn field_values(&self) -> &[(Field, FieldValue)] {
1456 &self.field_values
1457 }
1458
1459 pub fn filter_stored(&self, schema: &Schema) -> Document {
1461 Document {
1462 field_values: self
1463 .field_values
1464 .iter()
1465 .filter(|(field, _)| {
1466 schema
1467 .get_field_entry(*field)
1468 .is_some_and(|entry| entry.stored)
1469 })
1470 .cloned()
1471 .collect(),
1472 }
1473 }
1474
1475 pub fn to_json(&self, schema: &Schema) -> serde_json::Value {
1481 use std::collections::HashMap;
1482
1483 let mut field_values_map: HashMap<Field, (String, bool, Vec<serde_json::Value>)> =
1485 HashMap::new();
1486
1487 for (field, value) in &self.field_values {
1488 if let Some(entry) = schema.get_field_entry(*field) {
1489 let json_value = match value {
1490 FieldValue::Text(s) => serde_json::Value::String(s.clone()),
1491 FieldValue::U64(n) => serde_json::Value::Number((*n).into()),
1492 FieldValue::I64(n) => serde_json::Value::Number((*n).into()),
1493 FieldValue::F64(n) => serde_json::json!(n),
1494 FieldValue::Bytes(b) => {
1495 use base64::Engine;
1496 serde_json::Value::String(
1497 base64::engine::general_purpose::STANDARD.encode(b),
1498 )
1499 }
1500 FieldValue::SparseVector(entries) => {
1501 let indices: Vec<u32> = entries.iter().map(|(i, _)| *i).collect();
1502 let values: Vec<f32> = entries.iter().map(|(_, v)| *v).collect();
1503 serde_json::json!({
1504 "indices": indices,
1505 "values": values
1506 })
1507 }
1508 FieldValue::DenseVector(values) => {
1509 serde_json::json!(values)
1510 }
1511 FieldValue::Json(v) => v.clone(),
1512 FieldValue::BinaryDenseVector(b) => {
1513 use base64::Engine;
1514 serde_json::Value::String(
1515 base64::engine::general_purpose::STANDARD.encode(b),
1516 )
1517 }
1518 };
1519 field_values_map
1520 .entry(*field)
1521 .or_insert_with(|| (entry.name.clone(), entry.multi, Vec::new()))
1522 .2
1523 .push(json_value);
1524 }
1525 }
1526
1527 let mut map = serde_json::Map::new();
1529 for (_field, (name, is_multi, values)) in field_values_map {
1530 let json_value = if is_multi || values.len() > 1 {
1531 serde_json::Value::Array(values)
1532 } else {
1533 values.into_iter().next().unwrap()
1534 };
1535 map.insert(name, json_value);
1536 }
1537
1538 serde_json::Value::Object(map)
1539 }
1540
1541 pub fn from_json(json: &serde_json::Value, schema: &Schema) -> Option<Self> {
1550 let obj = json.as_object()?;
1551 let mut doc = Document::new();
1552
1553 for (key, value) in obj {
1554 if let Some(field) = schema.get_field(key) {
1555 let field_entry = schema.get_field_entry(field)?;
1556 Self::add_json_value(&mut doc, field, &field_entry.field_type, value);
1557 }
1558 }
1559
1560 Some(doc)
1561 }
1562
1563 fn add_json_value(
1565 doc: &mut Document,
1566 field: Field,
1567 field_type: &FieldType,
1568 value: &serde_json::Value,
1569 ) {
1570 match value {
1571 serde_json::Value::String(s) => {
1572 if matches!(field_type, FieldType::Text) {
1573 doc.add_text(field, s.clone());
1574 }
1575 }
1576 serde_json::Value::Number(n) => {
1577 match field_type {
1578 FieldType::I64 => {
1579 if let Some(i) = n.as_i64() {
1580 doc.add_i64(field, i);
1581 }
1582 }
1583 FieldType::U64 => {
1584 if let Some(u) = n.as_u64() {
1585 doc.add_u64(field, u);
1586 } else if let Some(i) = n.as_i64() {
1587 if i >= 0 {
1589 doc.add_u64(field, i as u64);
1590 }
1591 }
1592 }
1593 FieldType::F64 => {
1594 if let Some(f) = n.as_f64() {
1595 doc.add_f64(field, f);
1596 }
1597 }
1598 _ => {}
1599 }
1600 }
1601 serde_json::Value::Array(arr) => {
1603 for item in arr {
1604 Self::add_json_value(doc, field, field_type, item);
1605 }
1606 }
1607 serde_json::Value::Object(obj) if matches!(field_type, FieldType::SparseVector) => {
1609 if let (Some(indices_val), Some(values_val)) =
1610 (obj.get("indices"), obj.get("values"))
1611 {
1612 let indices: Vec<u32> = indices_val
1613 .as_array()
1614 .map(|arr| {
1615 arr.iter()
1616 .filter_map(|v| v.as_u64().map(|n| n as u32))
1617 .collect()
1618 })
1619 .unwrap_or_default();
1620 let values: Vec<f32> = values_val
1621 .as_array()
1622 .map(|arr| {
1623 arr.iter()
1624 .filter_map(|v| v.as_f64().map(|n| n as f32))
1625 .collect()
1626 })
1627 .unwrap_or_default();
1628 if indices.len() == values.len() {
1629 let entries: Vec<(u32, f32)> = indices.into_iter().zip(values).collect();
1630 doc.add_sparse_vector(field, entries);
1631 }
1632 }
1633 }
1634 _ if matches!(field_type, FieldType::Json) => {
1636 doc.add_json(field, value.clone());
1637 }
1638 serde_json::Value::Object(_) => {}
1639 _ => {}
1640 }
1641 }
1642}
1643
1644#[cfg(test)]
1645mod tests {
1646 use super::*;
1647
1648 #[test]
1649 fn test_schema_builder() {
1650 let mut builder = Schema::builder();
1651 let title = builder.add_text_field("title", true, true);
1652 let body = builder.add_text_field("body", true, false);
1653 let count = builder.add_u64_field("count", true, true);
1654 let schema = builder.build();
1655
1656 assert_eq!(schema.get_field("title"), Some(title));
1657 assert_eq!(schema.get_field("body"), Some(body));
1658 assert_eq!(schema.get_field("count"), Some(count));
1659 assert_eq!(schema.get_field("nonexistent"), None);
1660 }
1661
1662 #[test]
1663 fn ivf_tq_defaults_to_selective_soar() {
1664 for config in [
1665 DenseVectorConfig::new(8),
1666 DenseVectorConfig::ivf_tq(8, Some(4), 2),
1667 ] {
1668 let soar = config.soar.expect("IVF-TQ should enable SOAR by default");
1669 assert_eq!(soar.num_secondary, 1);
1670 assert!(soar.selective);
1671 assert_eq!(soar.calibration_target(), Some(0.30));
1672 }
1673
1674 assert!(DenseVectorConfig::flat(8).soar.is_none());
1675 assert!(DenseVectorConfig::tq(8).soar.is_none());
1676 }
1677
1678 #[test]
1679 fn binary_ivf_uses_measured_balanced_fifteen_million_geometry() {
1680 let config = BinaryDenseVectorConfig::new(2_560);
1681 assert_eq!(config.optimal_num_clusters(15_000_000), 3_873);
1682
1683 let explicit = config.with_ivf(Some(8_192), 128);
1684 assert_eq!(explicit.optimal_num_clusters(15_000_000), 8_192);
1685 }
1686
1687 #[test]
1688 fn target_vectors_sizes_automatic_topology_but_explicit_clusters_win() {
1689 let hinted = BinaryDenseVectorConfig::new(2_560).with_target_vectors(1_000_000_000);
1690 assert_eq!(hinted.optimal_num_clusters(1_000_000), 31_623);
1691
1692 let lower_hint = BinaryDenseVectorConfig::new(2_560).with_target_vectors(1_000_000);
1693 assert_eq!(
1694 lower_hint.optimal_num_clusters(15_000_000),
1695 BinaryDenseVectorConfig::new(2_560).optimal_num_clusters(15_000_000),
1696 "a steady-state hint is a lower bound and must not shrink live-corpus geometry"
1697 );
1698
1699 let explicit = hinted.with_ivf(Some(8_192), 128);
1700 assert_eq!(explicit.optimal_num_clusters(1_000_000), 8_192);
1701
1702 let float = DenseVectorConfig::ivf_tq(1_024, None, 64).with_target_vectors(1_000_000_000);
1703 assert_eq!(float.optimal_num_clusters(1_000_000), 252_982);
1704 }
1705
1706 #[test]
1707 fn persisted_target_vectors_must_be_positive_and_topology_bearing() {
1708 let mut zero = BinaryDenseVectorConfig::new(256);
1709 zero.target_vectors = Some(0);
1710 let mut builder = Schema::builder();
1711 builder.add_binary_dense_vector_field_with_config("hash", true, false, zero);
1712 let error = reject_removed_vector_index_types(&builder.build()).unwrap_err();
1713 assert!(error.contains("positive steady-state"), "{error}");
1714
1715 let hinted = DenseVectorConfig::ivf_tq(128, None, 64).with_target_vectors(1_000_000_000);
1716 let encoded = serde_json::to_value(&hinted).unwrap();
1717 let decoded: DenseVectorConfig = serde_json::from_value(encoded).unwrap();
1718 assert_eq!(decoded.target_vectors, Some(1_000_000_000));
1719
1720 let binary = BinaryDenseVectorConfig::new(2_560).with_target_vectors(1_000_000_000);
1721 let encoded = serde_json::to_value(&binary).unwrap();
1722 let decoded: BinaryDenseVectorConfig = serde_json::from_value(encoded).unwrap();
1723 assert_eq!(decoded.target_vectors, Some(1_000_000_000));
1724
1725 let old_dense: DenseVectorConfig = serde_json::from_value(serde_json::json!({
1726 "dim": 128,
1727 "index_type": "ivf_tq"
1728 }))
1729 .unwrap();
1730 assert_eq!(old_dense.target_vectors, None);
1731 let old_binary: BinaryDenseVectorConfig = serde_json::from_value(serde_json::json!({
1732 "dim": 256,
1733 "index_type": "ivf"
1734 }))
1735 .unwrap();
1736 assert_eq!(old_binary.target_vectors, None);
1737
1738 let flat = DenseVectorConfig::flat(128).with_target_vectors(1_000_000);
1739 let mut builder = Schema::builder();
1740 builder.add_dense_vector_field_with_config("embedding", true, false, flat);
1741 let error = reject_removed_vector_index_types(&builder.build()).unwrap_err();
1742 assert!(error.contains("flat/training-free"), "{error}");
1743
1744 let mut binary_flat = BinaryDenseVectorConfig::new(256);
1745 binary_flat.index_type = BinaryIndexType::Flat;
1746 binary_flat.target_vectors = Some(1_000_000);
1747 let mut builder = Schema::builder();
1748 builder.add_binary_dense_vector_field_with_config("hash", true, false, binary_flat);
1749 let error = reject_removed_vector_index_types(&builder.build()).unwrap_err();
1750 assert!(error.contains("flat/training-free"), "{error}");
1751 }
1752
1753 #[test]
1754 fn omitted_and_explicitly_disabled_soar_are_distinct_in_serde() {
1755 let omitted: DenseVectorConfig = serde_json::from_value(serde_json::json!({
1756 "dim": 8,
1757 "index_type": "ivf_tq"
1758 }))
1759 .unwrap();
1760 let default_soar = omitted
1761 .soar
1762 .as_ref()
1763 .expect("an omitted SOAR setting should enable the selective default");
1764 assert_eq!(default_soar.num_secondary, 1);
1765 assert!(default_soar.selective);
1766 assert_eq!(default_soar.calibration_target(), Some(0.30));
1767
1768 let disabled: DenseVectorConfig = serde_json::from_value(serde_json::json!({
1769 "dim": 8,
1770 "index_type": "ivf_tq",
1771 "soar": null
1772 }))
1773 .unwrap();
1774 assert!(disabled.soar.is_none());
1775
1776 let encoded = serde_json::to_value(&disabled).unwrap();
1777 assert_eq!(encoded.get("soar"), Some(&serde_json::Value::Null));
1778 let round_trip: DenseVectorConfig = serde_json::from_value(encoded).unwrap();
1779 assert!(
1780 round_trip.soar.is_none(),
1781 "explicit off must survive a schema round trip"
1782 );
1783 }
1784
1785 #[test]
1786 fn scann_config_serde_preserves_old_json_defaults_and_new_parameters() {
1787 let old_dense: DenseVectorConfig = serde_json::from_value(serde_json::json!({
1788 "dim": 768,
1789 "index_type": "ivf_tq"
1790 }))
1791 .unwrap();
1792 assert_eq!(old_dense.tree_levels, None);
1793 let old_json = serde_json::to_value(&old_dense).unwrap();
1794 assert!(old_json.get("tree_levels").is_none());
1795
1796 let scann: DenseVectorConfig = serde_json::from_value(serde_json::json!({
1797 "dim": 1024,
1798 "index_type": "scann",
1799 "num_clusters": 10_000_000,
1800 "tree_levels": 2,
1801 "nprobe": 1024
1802 }))
1803 .unwrap();
1804 assert_eq!(scann.index_type, VectorIndexType::Scann);
1805 assert_eq!(scann.tree_levels, Some(2));
1806 assert!(scann.soar.is_none());
1807
1808 let binary: BinaryDenseVectorConfig = serde_json::from_value(serde_json::json!({
1809 "dim": 1024,
1810 "index_type": "scann",
1811 "tree_levels": 3
1812 }))
1813 .unwrap();
1814 assert_eq!(binary.index_type, BinaryIndexType::Scann);
1815 assert_eq!(binary.tree_levels, Some(3));
1816 }
1817
1818 #[test]
1819 fn persisted_scann_geometry_is_validated_on_schema_load() {
1820 let mut invalid_levels = DenseVectorConfig::new(128);
1821 invalid_levels.index_type = VectorIndexType::Scann;
1822 invalid_levels.tree_levels = Some(4);
1823 invalid_levels.soar = None;
1824 let mut builder = Schema::builder();
1825 builder.add_dense_vector_field_with_config("embedding", true, false, invalid_levels);
1826 let error = reject_removed_vector_index_types(&builder.build())
1827 .expect_err("invalid persisted ScaNN levels must fail at the schema gate");
1828 assert!(error.contains("1..=3"), "{error}");
1829
1830 let mut wrong_algorithm = BinaryDenseVectorConfig::new(256);
1831 wrong_algorithm.tree_levels = Some(2);
1832 let mut builder = Schema::builder();
1833 builder.add_binary_dense_vector_field_with_config("hash", true, false, wrong_algorithm);
1834 let error = reject_removed_vector_index_types(&builder.build())
1835 .expect_err("ScaNN-only persisted options must fail on IVF");
1836 assert!(error.contains("does not use the ScaNN index"), "{error}");
1837
1838 let mut invalid_soar = DenseVectorConfig::flat(128);
1839 invalid_soar.index_type = VectorIndexType::Scann;
1840 invalid_soar.nprobe = 1;
1841 invalid_soar.soar = Some(crate::structures::SoarConfig::default());
1842 let mut builder = Schema::builder();
1843 builder.add_dense_vector_field_with_config("embedding", true, false, invalid_soar);
1844 let error = reject_removed_vector_index_types(&builder.build())
1845 .expect_err("persisted ScaNN SOAR must fail until assignments exist");
1846 assert!(error.contains("not implemented"), "{error}");
1847
1848 let mut one_leaf = DenseVectorConfig::flat(128);
1849 one_leaf.index_type = VectorIndexType::Scann;
1850 one_leaf.num_clusters = Some(1);
1851 one_leaf.nprobe = 1;
1852 let mut builder = Schema::builder();
1853 builder.add_dense_vector_field_with_config("embedding", true, false, one_leaf);
1854 let error = reject_removed_vector_index_types(&builder.build())
1855 .expect_err("one-leaf ScaNN geometry must fail at schema load");
1856 assert!(error.contains("2..=30000000"), "{error}");
1857
1858 let binary = BinaryDenseVectorConfig {
1859 dim: 255,
1860 index_type: BinaryIndexType::Scann,
1861 num_clusters: Some(2),
1862 target_vectors: None,
1863 tree_levels: Some(1),
1864 ivf_routing: IvfRoutingMode::Auto,
1865 nprobe: 1,
1866 soar: None,
1867 };
1868 let mut builder = Schema::builder();
1869 builder.add_binary_dense_vector_field_with_config("hash", true, false, binary);
1870 let error = reject_removed_vector_index_types(&builder.build())
1871 .expect_err("binary ScaNN dimensions must be byte-aligned");
1872 assert!(error.contains("multiple of 8"), "{error}");
1873 }
1874
1875 #[test]
1876 fn test_set_primary_key_forces_fast_and_indexed() {
1877 let mut builder = Schema::builder();
1882 let id = builder.add_text_field("id", false, true);
1883 builder.set_primary_key(id);
1884 let schema = builder.build();
1885
1886 let entry = schema.get_field_entry(id).unwrap();
1887 assert!(entry.primary_key);
1888 assert!(
1889 entry.fast,
1890 "primary key must imply fast (dedup reads the fast-field text dict)"
1891 );
1892 assert!(entry.indexed, "primary key must imply indexed");
1893 }
1894
1895 #[test]
1896 fn test_document() {
1897 let mut builder = Schema::builder();
1898 let title = builder.add_text_field("title", true, true);
1899 let count = builder.add_u64_field("count", true, true);
1900 let _schema = builder.build();
1901
1902 let mut doc = Document::new();
1903 doc.add_text(title, "Hello World");
1904 doc.add_u64(count, 42);
1905
1906 assert_eq!(doc.get_first(title).unwrap().as_text(), Some("Hello World"));
1907 assert_eq!(doc.get_first(count).unwrap().as_u64(), Some(42));
1908 }
1909
1910 #[test]
1911 fn test_document_serialization() {
1912 let mut builder = Schema::builder();
1913 let title = builder.add_text_field("title", true, true);
1914 let count = builder.add_u64_field("count", true, true);
1915 let _schema = builder.build();
1916
1917 let mut doc = Document::new();
1918 doc.add_text(title, "Hello World");
1919 doc.add_u64(count, 42);
1920
1921 let json = serde_json::to_string(&doc).unwrap();
1923 println!("Serialized doc: {}", json);
1924
1925 let doc2: Document = serde_json::from_str(&json).unwrap();
1927 assert_eq!(
1928 doc2.field_values().len(),
1929 2,
1930 "Should have 2 field values after deserialization"
1931 );
1932 assert_eq!(
1933 doc2.get_first(title).unwrap().as_text(),
1934 Some("Hello World")
1935 );
1936 assert_eq!(doc2.get_first(count).unwrap().as_u64(), Some(42));
1937 }
1938
1939 #[test]
1940 fn test_multivalue_field() {
1941 let mut builder = Schema::builder();
1942 let uris = builder.add_text_field("uris", true, true);
1943 let title = builder.add_text_field("title", true, true);
1944 let schema = builder.build();
1945
1946 let mut doc = Document::new();
1948 doc.add_text(uris, "one");
1949 doc.add_text(uris, "two");
1950 doc.add_text(title, "Test Document");
1951
1952 assert_eq!(doc.get_first(uris).unwrap().as_text(), Some("one"));
1954
1955 let all_uris: Vec<_> = doc.get_all(uris).collect();
1957 assert_eq!(all_uris.len(), 2);
1958 assert_eq!(all_uris[0].as_text(), Some("one"));
1959 assert_eq!(all_uris[1].as_text(), Some("two"));
1960
1961 let json = doc.to_json(&schema);
1963 let uris_json = json.get("uris").unwrap();
1964 assert!(uris_json.is_array(), "Multi-value field should be an array");
1965 let uris_arr = uris_json.as_array().unwrap();
1966 assert_eq!(uris_arr.len(), 2);
1967 assert_eq!(uris_arr[0].as_str(), Some("one"));
1968 assert_eq!(uris_arr[1].as_str(), Some("two"));
1969
1970 let title_json = json.get("title").unwrap();
1972 assert!(
1973 title_json.is_string(),
1974 "Single-value field should be a string"
1975 );
1976 assert_eq!(title_json.as_str(), Some("Test Document"));
1977 }
1978
1979 #[test]
1980 fn test_multivalue_from_json() {
1981 let mut builder = Schema::builder();
1982 let uris = builder.add_text_field("uris", true, true);
1983 let title = builder.add_text_field("title", true, true);
1984 let schema = builder.build();
1985
1986 let json = serde_json::json!({
1988 "uris": ["one", "two"],
1989 "title": "Test Document"
1990 });
1991
1992 let doc = Document::from_json(&json, &schema).unwrap();
1994
1995 let all_uris: Vec<_> = doc.get_all(uris).collect();
1997 assert_eq!(all_uris.len(), 2);
1998 assert_eq!(all_uris[0].as_text(), Some("one"));
1999 assert_eq!(all_uris[1].as_text(), Some("two"));
2000
2001 assert_eq!(
2003 doc.get_first(title).unwrap().as_text(),
2004 Some("Test Document")
2005 );
2006
2007 let json_out = doc.to_json(&schema);
2009 let uris_out = json_out.get("uris").unwrap().as_array().unwrap();
2010 assert_eq!(uris_out.len(), 2);
2011 assert_eq!(uris_out[0].as_str(), Some("one"));
2012 assert_eq!(uris_out[1].as_str(), Some("two"));
2013 }
2014
2015 #[test]
2016 fn test_multi_attribute_forces_array() {
2017 let mut builder = Schema::builder();
2020 let uris = builder.add_text_field("uris", true, true);
2021 builder.set_multi(uris, true); let title = builder.add_text_field("title", true, true);
2023 let schema = builder.build();
2024
2025 assert!(schema.get_field_entry(uris).unwrap().multi);
2027 assert!(!schema.get_field_entry(title).unwrap().multi);
2028
2029 let mut doc = Document::new();
2031 doc.add_text(uris, "only_one");
2032 doc.add_text(title, "Test Document");
2033
2034 let json = doc.to_json(&schema);
2036
2037 let uris_json = json.get("uris").unwrap();
2038 assert!(
2039 uris_json.is_array(),
2040 "Multi field should be array even with single value"
2041 );
2042 let uris_arr = uris_json.as_array().unwrap();
2043 assert_eq!(uris_arr.len(), 1);
2044 assert_eq!(uris_arr[0].as_str(), Some("only_one"));
2045
2046 let title_json = json.get("title").unwrap();
2048 assert!(
2049 title_json.is_string(),
2050 "Non-multi single-value field should be a string"
2051 );
2052 assert_eq!(title_json.as_str(), Some("Test Document"));
2053 }
2054
2055 #[test]
2056 fn test_sparse_vector_field() {
2057 let mut builder = Schema::builder();
2058 let embedding = builder.add_sparse_vector_field("embedding", true, true);
2059 let title = builder.add_text_field("title", true, true);
2060 let schema = builder.build();
2061
2062 assert_eq!(schema.get_field("embedding"), Some(embedding));
2063 assert_eq!(
2064 schema.get_field_entry(embedding).unwrap().field_type,
2065 FieldType::SparseVector
2066 );
2067
2068 let mut doc = Document::new();
2070 doc.add_sparse_vector(embedding, vec![(0, 1.0), (5, 2.5), (10, 0.5)]);
2071 doc.add_text(title, "Test Document");
2072
2073 let entries = doc
2075 .get_first(embedding)
2076 .unwrap()
2077 .as_sparse_vector()
2078 .unwrap();
2079 assert_eq!(entries, &[(0, 1.0), (5, 2.5), (10, 0.5)]);
2080
2081 let json = doc.to_json(&schema);
2083 let embedding_json = json.get("embedding").unwrap();
2084 assert!(embedding_json.is_object());
2085 assert_eq!(
2086 embedding_json
2087 .get("indices")
2088 .unwrap()
2089 .as_array()
2090 .unwrap()
2091 .len(),
2092 3
2093 );
2094
2095 let doc2 = Document::from_json(&json, &schema).unwrap();
2097 let entries2 = doc2
2098 .get_first(embedding)
2099 .unwrap()
2100 .as_sparse_vector()
2101 .unwrap();
2102 assert_eq!(entries2[0].0, 0);
2103 assert!((entries2[0].1 - 1.0).abs() < 1e-6);
2104 assert_eq!(entries2[1].0, 5);
2105 assert!((entries2[1].1 - 2.5).abs() < 1e-6);
2106 assert_eq!(entries2[2].0, 10);
2107 assert!((entries2[2].1 - 0.5).abs() < 1e-6);
2108 }
2109
2110 #[test]
2111 fn test_json_field() {
2112 let mut builder = Schema::builder();
2113 let metadata = builder.add_json_field("metadata", true);
2114 let title = builder.add_text_field("title", true, true);
2115 let schema = builder.build();
2116
2117 assert_eq!(schema.get_field("metadata"), Some(metadata));
2118 assert_eq!(
2119 schema.get_field_entry(metadata).unwrap().field_type,
2120 FieldType::Json
2121 );
2122 assert!(!schema.get_field_entry(metadata).unwrap().indexed);
2124 assert!(schema.get_field_entry(metadata).unwrap().stored);
2125
2126 let json_value = serde_json::json!({
2128 "author": "John Doe",
2129 "tags": ["rust", "search"],
2130 "nested": {"key": "value"}
2131 });
2132 let mut doc = Document::new();
2133 doc.add_json(metadata, json_value.clone());
2134 doc.add_text(title, "Test Document");
2135
2136 let stored_json = doc.get_first(metadata).unwrap().as_json().unwrap();
2138 assert_eq!(stored_json, &json_value);
2139 assert_eq!(
2140 stored_json.get("author").unwrap().as_str(),
2141 Some("John Doe")
2142 );
2143
2144 let doc_json = doc.to_json(&schema);
2146 let metadata_out = doc_json.get("metadata").unwrap();
2147 assert_eq!(metadata_out, &json_value);
2148
2149 let doc2 = Document::from_json(&doc_json, &schema).unwrap();
2151 let stored_json2 = doc2.get_first(metadata).unwrap().as_json().unwrap();
2152 assert_eq!(stored_json2, &json_value);
2153 }
2154
2155 #[test]
2156 fn test_json_field_various_types() {
2157 let mut builder = Schema::builder();
2158 let data = builder.add_json_field("data", true);
2159 let _schema = builder.build();
2160
2161 let arr_value = serde_json::json!([1, 2, 3, "four", null]);
2163 let mut doc = Document::new();
2164 doc.add_json(data, arr_value.clone());
2165 assert_eq!(doc.get_first(data).unwrap().as_json().unwrap(), &arr_value);
2166
2167 let str_value = serde_json::json!("just a string");
2169 let mut doc2 = Document::new();
2170 doc2.add_json(data, str_value.clone());
2171 assert_eq!(doc2.get_first(data).unwrap().as_json().unwrap(), &str_value);
2172
2173 let num_value = serde_json::json!(42.5);
2175 let mut doc3 = Document::new();
2176 doc3.add_json(data, num_value.clone());
2177 assert_eq!(doc3.get_first(data).unwrap().as_json().unwrap(), &num_value);
2178
2179 let null_value = serde_json::Value::Null;
2181 let mut doc4 = Document::new();
2182 doc4.add_json(data, null_value.clone());
2183 assert_eq!(
2184 doc4.get_first(data).unwrap().as_json().unwrap(),
2185 &null_value
2186 );
2187
2188 let bool_value = serde_json::json!(true);
2190 let mut doc5 = Document::new();
2191 doc5.add_json(data, bool_value.clone());
2192 assert_eq!(
2193 doc5.get_first(data).unwrap().as_json().unwrap(),
2194 &bool_value
2195 );
2196 }
2197}