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