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