1use pest::Parser;
46use pest_derive::Parser;
47
48use super::query_field_router::{QueryRouterRule, RoutingMode};
49use super::schema::{DenseVectorQuantization, FieldType, Schema, SchemaBuilder};
50use crate::Result;
51use crate::error::Error;
52
53#[derive(Parser)]
54#[grammar = "dsl/sdl/sdl.pest"]
55pub struct SdlParser;
56
57use super::schema::{BinaryDenseVectorConfig, DenseVectorConfig};
58use crate::structures::{
59 IndexSize, QueryWeighting, SparseFormat, SparseQueryConfig, SparseVectorConfig,
60 WeightQuantization,
61};
62
63#[derive(Debug, Clone)]
65pub struct FieldDef {
66 pub name: String,
67 pub field_type: FieldType,
68 pub indexed: bool,
69 pub stored: bool,
70 pub tokenizer: Option<String>,
72 pub multi: bool,
74 pub positions: Option<super::schema::PositionMode>,
76 pub sparse_vector_config: Option<SparseVectorConfig>,
78 pub dense_vector_config: Option<DenseVectorConfig>,
80 pub binary_dense_vector_config: Option<BinaryDenseVectorConfig>,
82 pub fast: bool,
84 pub primary: bool,
86 pub reorder: bool,
88}
89
90#[derive(Debug, Clone)]
92pub struct IndexDef {
93 pub name: String,
94 pub fields: Vec<FieldDef>,
95 pub default_fields: Vec<String>,
96 pub query_routers: Vec<QueryRouterRule>,
98 pub reorder_on_merge: bool,
101}
102
103impl IndexDef {
104 pub fn to_schema(&self) -> Schema {
106 let mut builder = SchemaBuilder::default();
107
108 for field in &self.fields {
109 let f = match field.field_type {
110 FieldType::Text => {
111 let tokenizer = field.tokenizer.as_deref().unwrap_or("simple");
112 builder.add_text_field_with_tokenizer(
113 &field.name,
114 field.indexed,
115 field.stored,
116 tokenizer,
117 )
118 }
119 FieldType::U64 => builder.add_u64_field(&field.name, field.indexed, field.stored),
120 FieldType::I64 => builder.add_i64_field(&field.name, field.indexed, field.stored),
121 FieldType::F64 => builder.add_f64_field(&field.name, field.indexed, field.stored),
122 FieldType::Bytes => builder.add_bytes_field(&field.name, field.stored),
123 FieldType::Json => builder.add_json_field(&field.name, field.stored),
124 FieldType::SparseVector => {
125 if let Some(config) = &field.sparse_vector_config {
126 builder.add_sparse_vector_field_with_config(
127 &field.name,
128 field.indexed,
129 field.stored,
130 config.clone(),
131 )
132 } else {
133 builder.add_sparse_vector_field(&field.name, field.indexed, field.stored)
134 }
135 }
136 FieldType::DenseVector => {
137 let config = field
139 .dense_vector_config
140 .as_ref()
141 .expect("DenseVector field requires dimension to be specified");
142 builder.add_dense_vector_field_with_config(
143 &field.name,
144 field.indexed,
145 field.stored,
146 config.clone(),
147 )
148 }
149 FieldType::BinaryDenseVector => {
150 let config = field
151 .binary_dense_vector_config
152 .as_ref()
153 .expect("BinaryDenseVector field requires dimension to be specified");
154 builder.add_binary_dense_vector_field_with_config(
155 &field.name,
156 field.indexed,
157 field.stored,
158 config.clone(),
159 )
160 }
161 };
162 if field.multi {
163 builder.set_multi(f, true);
164 }
165 if field.fast {
166 builder.set_fast(f, true);
167 }
168 if field.primary {
169 builder.set_primary_key(f);
170 }
171 if field.reorder {
172 builder.set_reorder(f, true);
173 }
174 let positions = field.positions.or({
176 if field.multi
178 && matches!(
179 field.field_type,
180 FieldType::SparseVector
181 | FieldType::DenseVector
182 | FieldType::BinaryDenseVector
183 )
184 {
185 Some(super::schema::PositionMode::Ordinal)
186 } else {
187 None
188 }
189 });
190 if let Some(mode) = positions {
191 builder.set_positions(f, mode);
192 }
193 }
194
195 if !self.default_fields.is_empty() {
197 builder.set_default_fields(self.default_fields.clone());
198 }
199
200 if !self.query_routers.is_empty() {
202 builder.set_query_routers(self.query_routers.clone());
203 }
204
205 builder.set_index_name(self.name.clone());
206
207 if self.reorder_on_merge {
208 if self.fields.iter().any(|f| f.reorder) {
209 builder.set_reorder_on_merge(true);
210 } else {
211 log::warn!(
214 "index '{}': reorder_on_merge is set but no field has the `reorder` attribute — merges will not reorder anything",
215 self.name,
216 );
217 builder.set_reorder_on_merge(true);
218 }
219 }
220
221 builder.build()
222 }
223
224 pub fn to_query_router(&self) -> Result<Option<super::query_field_router::QueryFieldRouter>> {
229 if self.query_routers.is_empty() {
230 return Ok(None);
231 }
232
233 super::query_field_router::QueryFieldRouter::from_rules(&self.query_routers)
234 .map(Some)
235 .map_err(Error::Schema)
236 }
237}
238
239fn parse_field_type(type_str: &str) -> Result<FieldType> {
241 match type_str {
242 "text" | "string" | "str" => Ok(FieldType::Text),
243 "u64" | "uint" | "unsigned" => Ok(FieldType::U64),
244 "i64" | "int" | "integer" => Ok(FieldType::I64),
245 "f64" | "float" | "double" => Ok(FieldType::F64),
246 "bytes" | "binary" | "blob" => Ok(FieldType::Bytes),
247 "json" => Ok(FieldType::Json),
248 "sparse_vector" => Ok(FieldType::SparseVector),
249 "dense_vector" | "vector" => Ok(FieldType::DenseVector),
250 "binary_dense_vector" | "binary_vector" => Ok(FieldType::BinaryDenseVector),
251 _ => Err(Error::Schema(format!("Unknown field type: {}", type_str))),
252 }
253}
254
255#[derive(Debug, Clone, Default)]
257struct IndexConfig {
258 index_type: Option<super::schema::VectorIndexType>,
259 num_clusters: Option<usize>,
260 nprobe: Option<usize>,
261 build_threshold: Option<usize>,
262 soar: Option<crate::structures::SoarConfig>,
263 rabitq_bits: Option<u8>,
264 binary_ivf: bool,
265 sparse_format: Option<SparseFormat>,
267 quantization: Option<WeightQuantization>,
268 weight_threshold: Option<f32>,
269 block_size: Option<usize>,
270 bmp_block_size: Option<u32>,
271 bmp_grid_bits: Option<u8>,
272 pruning: Option<f32>,
273 min_terms: Option<usize>,
274 doc_mass: Option<f32>,
275 query_tokenizer: Option<String>,
277 query_weighting: Option<QueryWeighting>,
278 query_weight_threshold: Option<f32>,
279 query_max_dims: Option<usize>,
280 query_pruning: Option<f32>,
281 query_min_query_dims: Option<usize>,
282 dims: Option<u32>,
284 max_weight: Option<f32>,
285 positions: Option<super::schema::PositionMode>,
287}
288
289struct ParsedAttributes {
291 indexed: bool,
292 stored: bool,
293 multi: bool,
294 fast: bool,
295 primary: bool,
296 reorder: bool,
297 index_config: Option<IndexConfig>,
298}
299
300fn parse_attributes(pair: pest::iterators::Pair<Rule>) -> ParsedAttributes {
302 let mut attrs = ParsedAttributes {
303 indexed: false,
304 stored: false,
305 multi: false,
306 fast: false,
307 primary: false,
308 reorder: false,
309 index_config: None,
310 };
311
312 for attr in pair.into_inner() {
313 if attr.as_rule() == Rule::attribute {
314 let mut found_config = false;
315 for inner in attr.clone().into_inner() {
316 match inner.as_rule() {
317 Rule::indexed_with_config => {
318 attrs.indexed = true;
319 attrs.index_config = Some(parse_index_config(inner));
320 found_config = true;
321 break;
322 }
323 Rule::stored_with_config => {
324 attrs.stored = true;
325 attrs.multi = true; found_config = true;
327 break;
328 }
329 _ => {}
330 }
331 }
332 if !found_config {
333 match attr.as_str() {
334 "indexed" => attrs.indexed = true,
335 "stored" => attrs.stored = true,
336 "fast" => attrs.fast = true,
337 "primary" => attrs.primary = true,
338 "reorder" => attrs.reorder = true,
339 _ => {}
340 }
341 }
342 }
343 }
344
345 attrs
346}
347
348fn parse_index_config(pair: pest::iterators::Pair<Rule>) -> IndexConfig {
350 let mut config = IndexConfig::default();
351
352 for inner in pair.into_inner() {
357 if inner.as_rule() == Rule::index_config_params {
358 for param in inner.into_inner() {
359 if param.as_rule() == Rule::index_config_param {
360 for p in param.into_inner() {
361 parse_single_index_config_param(&mut config, p);
362 }
363 }
364 }
365 }
366 }
367
368 config
369}
370
371fn parse_single_index_config_param(config: &mut IndexConfig, p: pest::iterators::Pair<Rule>) {
373 use super::schema::VectorIndexType;
374
375 match p.as_rule() {
376 Rule::index_type_spec => {
377 if p.as_str() == "ivf" {
378 config.binary_ivf = true;
380 return;
381 }
382 config.index_type = Some(match p.as_str() {
383 "flat" => VectorIndexType::Flat,
384 "rabitq" => VectorIndexType::RaBitQ,
385 "ivf_rabitq" => VectorIndexType::IvfRaBitQ,
386 "scann" => VectorIndexType::ScaNN,
387 _ => VectorIndexType::RaBitQ,
388 });
389 }
390 Rule::index_type_kwarg => {
391 if let Some(t) = p.into_inner().next() {
393 if t.as_str() == "ivf" {
394 config.binary_ivf = true;
395 return;
396 }
397 config.index_type = Some(match t.as_str() {
398 "flat" => VectorIndexType::Flat,
399 "rabitq" => VectorIndexType::RaBitQ,
400 "ivf_rabitq" => VectorIndexType::IvfRaBitQ,
401 "scann" => VectorIndexType::ScaNN,
402 _ => VectorIndexType::RaBitQ,
403 });
404 }
405 }
406 Rule::num_clusters_kwarg => {
407 if let Some(n) = p.into_inner().next() {
409 config.num_clusters = Some(n.as_str().parse().unwrap_or_else(|_| {
410 log::warn!(
411 "Invalid num_clusters value '{}', using default 256",
412 n.as_str()
413 );
414 256
415 }));
416 }
417 }
418 Rule::build_threshold_kwarg => {
419 if let Some(n) = p.into_inner().next() {
421 config.build_threshold = Some(n.as_str().parse().unwrap_or_else(|_| {
422 log::warn!(
423 "Invalid build_threshold value '{}', using default 10000",
424 n.as_str()
425 );
426 10000
427 }));
428 }
429 }
430 Rule::nprobe_kwarg => {
431 if let Some(n) = p.into_inner().next() {
433 config.nprobe = Some(n.as_str().parse().unwrap_or_else(|_| {
434 log::warn!("Invalid nprobe value '{}', using default 32", n.as_str());
435 32
436 }));
437 }
438 }
439 Rule::bits_kwarg => {
440 if let Some(b) = p.into_inner().next() {
442 config.rabitq_bits = Some(b.as_str().parse::<u8>().unwrap_or(1).clamp(1, 8));
443 }
444 }
445 Rule::soar_kwarg => {
446 if let Some(s) = p.into_inner().next() {
448 use crate::structures::SoarConfig;
449 config.soar = match s.as_str() {
450 "selective" => Some(SoarConfig::new()),
451 "full" => Some(SoarConfig::full()),
452 "aggressive" => Some(SoarConfig::aggressive()),
453 _ => None, };
455 }
456 }
457 Rule::quantization_kwarg => {
458 if let Some(q) = p.into_inner().next() {
460 config.quantization = Some(match q.as_str() {
461 "float32" | "f32" => WeightQuantization::Float32,
462 "float16" | "f16" => WeightQuantization::Float16,
463 "uint8" | "u8" => WeightQuantization::UInt8,
464 "uint4" | "u4" => WeightQuantization::UInt4,
465 _ => WeightQuantization::default(),
466 });
467 }
468 }
469 Rule::weight_threshold_kwarg => {
470 if let Some(t) = p.into_inner().next() {
472 config.weight_threshold = Some(t.as_str().parse().unwrap_or_else(|_| {
473 log::warn!(
474 "Invalid weight_threshold value '{}', using default 0.0",
475 t.as_str()
476 );
477 0.0
478 }));
479 }
480 }
481 Rule::block_size_kwarg => {
482 if let Some(n) = p.into_inner().next() {
484 config.block_size = Some(n.as_str().parse().unwrap_or_else(|_| {
485 log::warn!(
486 "Invalid block_size value '{}', using default 128",
487 n.as_str()
488 );
489 128
490 }));
491 }
492 }
493 Rule::bmp_grid_bits_kwarg => {
494 if let Some(n) = p.into_inner().next() {
496 config.bmp_grid_bits = Some(n.as_str().parse().unwrap_or_else(|_| {
497 log::warn!(
498 "Invalid bmp_grid_bits value '{}', using default 4",
499 n.as_str()
500 );
501 4
502 }));
503 }
504 }
505 Rule::bmp_block_size_kwarg => {
506 if let Some(n) = p.into_inner().next() {
508 config.bmp_block_size = Some(n.as_str().parse().unwrap_or_else(|_| {
509 log::warn!(
510 "Invalid bmp_block_size value '{}', using default 64",
511 n.as_str()
512 );
513 64
514 }));
515 }
516 }
517 Rule::pruning_kwarg => {
518 if let Some(f) = p.into_inner().next() {
520 config.pruning = Some(f.as_str().parse().unwrap_or_else(|_| {
521 log::warn!("Invalid pruning value '{}', using default 1.0", f.as_str());
522 1.0
523 }));
524 }
525 }
526 Rule::doc_mass_kwarg => {
527 if let Some(f) = p.into_inner().next() {
529 config.doc_mass = Some(f.as_str().parse().unwrap_or_else(|_| {
530 log::warn!("Invalid doc_mass value '{}', using 1.0 (off)", f.as_str());
531 1.0
532 }));
533 }
534 }
535 Rule::min_terms_kwarg => {
536 if let Some(n) = p.into_inner().next() {
537 config.min_terms = Some(n.as_str().parse().unwrap_or_else(|_| {
538 log::warn!("Invalid min_terms value '{}', using default 4", n.as_str());
539 4
540 }));
541 }
542 }
543 Rule::sparse_format_kwarg => {
544 if let Some(f) = p.into_inner().next() {
546 config.sparse_format = Some(match f.as_str() {
547 "bmp" => SparseFormat::Bmp,
548 "maxscore" => SparseFormat::MaxScore,
549 _ => SparseFormat::default(),
550 });
551 }
552 }
553 Rule::sparse_dims_kwarg => {
554 if let Some(n) = p.into_inner().next() {
555 config.dims = Some(n.as_str().parse().unwrap_or_else(|_| {
556 log::warn!("Invalid dims value '{}', using default 105879", n.as_str());
557 105879
558 }));
559 }
560 }
561 Rule::sparse_max_weight_kwarg => {
562 if let Some(f) = p.into_inner().next() {
563 config.max_weight = Some(f.as_str().parse().unwrap_or_else(|_| {
564 log::warn!(
565 "Invalid max_weight value '{}', using default 5.0",
566 f.as_str()
567 );
568 5.0
569 }));
570 }
571 }
572 Rule::query_config_block => {
573 parse_query_config_block(config, p);
575 }
576 Rule::positions_kwarg => {
577 use super::schema::PositionMode;
579 config.positions = Some(match p.as_str() {
580 "ordinal" => PositionMode::Ordinal,
581 "token_position" => PositionMode::TokenPosition,
582 _ => PositionMode::Full, });
584 }
585 _ => {}
586 }
587}
588
589fn parse_query_config_block(config: &mut IndexConfig, pair: pest::iterators::Pair<Rule>) {
591 for inner in pair.into_inner() {
592 if inner.as_rule() == Rule::query_config_params {
593 for param in inner.into_inner() {
594 if param.as_rule() == Rule::query_config_param {
595 for p in param.into_inner() {
596 match p.as_rule() {
597 Rule::query_tokenizer_kwarg => {
598 if let Some(path) = p.into_inner().next()
600 && let Some(inner_path) = path.into_inner().next()
601 {
602 config.query_tokenizer = Some(inner_path.as_str().to_string());
603 }
604 }
605 Rule::query_weighting_kwarg => {
606 if let Some(w) = p.into_inner().next() {
608 config.query_weighting = Some(match w.as_str() {
609 "one" => QueryWeighting::One,
610 "idf" => QueryWeighting::Idf,
611 "idf_file" => QueryWeighting::IdfFile,
612 _ => QueryWeighting::One,
613 });
614 }
615 }
616 Rule::query_weight_threshold_kwarg => {
617 if let Some(t) = p.into_inner().next() {
618 config.query_weight_threshold =
619 Some(t.as_str().parse().unwrap_or_else(|_| {
620 log::warn!(
621 "Invalid query weight_threshold '{}', using 0.0",
622 t.as_str()
623 );
624 0.0
625 }));
626 }
627 }
628 Rule::query_max_dims_kwarg => {
629 if let Some(t) = p.into_inner().next() {
630 config.query_max_dims =
631 Some(t.as_str().parse().unwrap_or_else(|_| {
632 log::warn!(
633 "Invalid query max_dims '{}', using 0",
634 t.as_str()
635 );
636 0
637 }));
638 }
639 }
640 Rule::query_pruning_kwarg => {
641 if let Some(t) = p.into_inner().next() {
642 config.query_pruning =
643 Some(t.as_str().parse().unwrap_or_else(|_| {
644 log::warn!(
645 "Invalid query pruning '{}', using 1.0",
646 t.as_str()
647 );
648 1.0
649 }));
650 }
651 }
652 Rule::query_min_query_dims_kwarg => {
653 if let Some(t) = p.into_inner().next() {
654 config.query_min_query_dims =
655 Some(t.as_str().parse().unwrap_or_else(|_| {
656 log::warn!(
657 "Invalid query min_query_dims '{}', using 4",
658 t.as_str()
659 );
660 4
661 }));
662 }
663 }
664 _ => {}
665 }
666 }
667 }
668 }
669 }
670 }
671}
672
673fn parse_field_def(pair: pest::iterators::Pair<Rule>) -> Result<FieldDef> {
675 let mut inner = pair.into_inner();
676
677 let name = inner
678 .next()
679 .ok_or_else(|| Error::Schema("Missing field name".to_string()))?
680 .as_str()
681 .to_string();
682
683 let field_type_str = inner
684 .next()
685 .ok_or_else(|| Error::Schema("Missing field type".to_string()))?
686 .as_str();
687
688 let field_type = parse_field_type(field_type_str)?;
689
690 let mut tokenizer = None;
692 let mut sparse_vector_config = None;
693 let mut dense_vector_config = None;
694 let mut binary_dense_vector_config = None;
695 let mut indexed = true;
696 let mut stored = true;
697 let mut multi = false;
698 let mut fast = false;
699 let mut primary = false;
700 let mut reorder = false;
701 let mut index_config: Option<IndexConfig> = None;
702
703 for item in inner {
704 match item.as_rule() {
705 Rule::tokenizer_spec => {
706 if let Some(tok_name) = item.into_inner().next() {
708 tokenizer = Some(tok_name.as_str().to_string());
709 }
710 }
711 Rule::sparse_vector_config => {
712 sparse_vector_config = Some(parse_sparse_vector_config(item));
714 }
715 Rule::dense_vector_config => {
716 dense_vector_config = Some(parse_dense_vector_config(item));
718 }
719 Rule::binary_dense_vector_config => {
720 let dim: usize = item
722 .into_inner()
723 .next()
724 .map(|d| d.as_str().parse().unwrap_or(0))
725 .unwrap_or(0);
726 if dim == 0 || !dim.is_multiple_of(8) {
727 return Err(Error::Schema(format!(
728 "BinaryDenseVector dimension must be a positive multiple of 8, got {dim}"
729 )));
730 }
731 binary_dense_vector_config = Some(BinaryDenseVectorConfig::new(dim));
732 }
733 Rule::attributes => {
734 let attrs = parse_attributes(item);
735 indexed = attrs.indexed;
736 stored = attrs.stored;
737 multi = attrs.multi;
738 fast = attrs.fast;
739 primary = attrs.primary;
740 reorder = attrs.reorder;
741 index_config = attrs.index_config;
742 }
743 _ => {}
744 }
745 }
746
747 if field_type == FieldType::BinaryDenseVector
751 && binary_dense_vector_config.is_none()
752 && let Some(ref dv_config) = dense_vector_config
753 {
754 let dim = dv_config.dim;
755 if dim == 0 || !dim.is_multiple_of(8) {
756 return Err(Error::Schema(format!(
757 "BinaryDenseVector dimension must be a positive multiple of 8, got {dim}"
758 )));
759 }
760 binary_dense_vector_config = Some(BinaryDenseVectorConfig::new(dim));
761 dense_vector_config = None;
762 }
763
764 if primary {
766 fast = true;
767 indexed = true;
768 }
769
770 let mut positions = None;
772 if let Some(idx_cfg) = index_config {
773 positions = idx_cfg.positions;
774 if let Some(ref mut bv_config) = binary_dense_vector_config {
775 apply_index_config_to_binary_dense_vector(bv_config, idx_cfg);
776 } else if let Some(ref mut dv_config) = dense_vector_config {
777 apply_index_config_to_dense_vector(dv_config, idx_cfg);
778 } else if field_type == FieldType::SparseVector {
779 let sv_config = sparse_vector_config.get_or_insert(SparseVectorConfig::default());
781 apply_index_config_to_sparse_vector(sv_config, idx_cfg);
782 }
783 }
784
785 Ok(FieldDef {
786 name,
787 field_type,
788 indexed,
789 stored,
790 tokenizer,
791 multi,
792 positions,
793 sparse_vector_config,
794 dense_vector_config,
795 binary_dense_vector_config,
796 fast,
797 primary,
798 reorder,
799 })
800}
801
802fn apply_index_config_to_binary_dense_vector(
804 config: &mut BinaryDenseVectorConfig,
805 idx_cfg: IndexConfig,
806) {
807 if idx_cfg.binary_ivf {
808 config.index_type = super::schema::BinaryIndexType::Ivf;
809 }
810 if idx_cfg.num_clusters.is_some() {
811 config.num_clusters = idx_cfg.num_clusters;
812 }
813 if let Some(nprobe) = idx_cfg.nprobe {
814 config.nprobe = nprobe;
815 }
816 if idx_cfg.build_threshold.is_some() {
817 config.build_threshold = idx_cfg.build_threshold;
818 }
819}
820
821fn apply_index_config_to_dense_vector(config: &mut DenseVectorConfig, idx_cfg: IndexConfig) {
823 if let Some(index_type) = idx_cfg.index_type {
825 config.index_type = index_type;
826 }
827
828 if idx_cfg.num_clusters.is_some() {
830 config.num_clusters = idx_cfg.num_clusters;
831 }
832
833 if let Some(nprobe) = idx_cfg.nprobe {
835 config.nprobe = nprobe;
836 }
837
838 if idx_cfg.build_threshold.is_some() {
840 config.build_threshold = idx_cfg.build_threshold;
841 }
842
843 if let Some(bits) = idx_cfg.rabitq_bits {
845 if config.index_type == super::schema::VectorIndexType::IvfRaBitQ {
846 config.rabitq_bits = Some(bits);
847 } else {
848 log::warn!(
849 "'bits' requires an ivf_rabitq index; ignoring for index type {:?}",
850 config.index_type
851 );
852 }
853 }
854
855 if idx_cfg.soar.is_some() {
857 if config.uses_ivf() {
858 config.soar = idx_cfg.soar;
859 } else {
860 log::warn!(
861 "'soar' requires an IVF-based index (ivf_rabitq, scann); \
862 ignoring for index type {:?}",
863 config.index_type
864 );
865 }
866 }
867}
868
869fn parse_sparse_vector_config(pair: pest::iterators::Pair<Rule>) -> SparseVectorConfig {
872 let mut index_size = IndexSize::default();
873
874 for inner in pair.into_inner() {
876 if inner.as_rule() == Rule::index_size_spec {
877 index_size = match inner.as_str() {
878 "u16" => IndexSize::U16,
879 "u32" => IndexSize::U32,
880 _ => IndexSize::default(),
881 };
882 }
883 }
884
885 SparseVectorConfig {
886 index_size,
887 ..SparseVectorConfig::default()
888 }
889}
890
891fn apply_index_config_to_sparse_vector(config: &mut SparseVectorConfig, idx_cfg: IndexConfig) {
893 if let Some(f) = idx_cfg.sparse_format {
894 config.format = f;
895 }
896 if let Some(q) = idx_cfg.quantization {
897 config.weight_quantization = q;
898 }
899 if let Some(t) = idx_cfg.weight_threshold {
900 config.weight_threshold = t;
901 }
902 if let Some(bs) = idx_cfg.block_size {
903 let adjusted = bs.next_power_of_two();
904 if adjusted != bs {
905 log::warn!(
906 "block_size {} adjusted to next power of two: {}",
907 bs,
908 adjusted
909 );
910 }
911 config.block_size = adjusted;
912 }
913 if let Some(bs) = idx_cfg.bmp_block_size {
914 let adjusted = bs.next_power_of_two().clamp(1, 256);
915 if adjusted != bs {
916 log::warn!(
917 "bmp_block_size {} adjusted to power of two in 1..=256: {}",
918 bs,
919 adjusted
920 );
921 }
922 config.bmp_block_size = adjusted;
923 }
924 if let Some(bits) = idx_cfg.bmp_grid_bits {
925 if bits == 2 || bits == 4 {
926 config.bmp_grid_bits = bits;
927 } else {
928 log::warn!(
929 "bmp_grid_bits {} unsupported (must be 2 or 4), using 4",
930 bits
931 );
932 config.bmp_grid_bits = 4;
933 }
934 }
935 if let Some(p) = idx_cfg.pruning {
936 let clamped = p.clamp(0.0, 1.0);
937 if (clamped - p).abs() > f32::EPSILON {
938 log::warn!(
939 "pruning {} clamped to valid range [0.0, 1.0]: {}",
940 p,
941 clamped
942 );
943 }
944 config.pruning = Some(clamped);
945 }
946 if let Some(mt) = idx_cfg.min_terms {
947 config.min_terms = mt;
948 }
949 if let Some(dm) = idx_cfg.doc_mass {
950 let clamped = dm.clamp(0.0, 1.0);
951 if (clamped - dm).abs() > f32::EPSILON {
952 log::warn!(
953 "doc_mass {} clamped to valid range [0.0, 1.0]: {}",
954 dm,
955 clamped
956 );
957 }
958 config.doc_mass = Some(clamped);
959 }
960 if let Some(d) = idx_cfg.dims {
961 config.dims = Some(d);
962 }
963 if let Some(mw) = idx_cfg.max_weight {
964 config.max_weight = Some(mw);
965 }
966 if idx_cfg.query_tokenizer.is_some()
968 || idx_cfg.query_weighting.is_some()
969 || idx_cfg.query_weight_threshold.is_some()
970 || idx_cfg.query_max_dims.is_some()
971 || idx_cfg.query_pruning.is_some()
972 || idx_cfg.query_min_query_dims.is_some()
973 {
974 let query_config = config
975 .query_config
976 .get_or_insert(SparseQueryConfig::default());
977 if let Some(tokenizer) = idx_cfg.query_tokenizer {
978 query_config.tokenizer = Some(tokenizer);
979 }
980 if let Some(weighting) = idx_cfg.query_weighting {
981 query_config.weighting = weighting;
982 }
983 if let Some(t) = idx_cfg.query_weight_threshold {
984 query_config.weight_threshold = t;
985 }
986 if let Some(d) = idx_cfg.query_max_dims {
987 query_config.max_query_dims = Some(d);
988 }
989 if let Some(p) = idx_cfg.query_pruning {
990 query_config.pruning = Some(p);
991 }
992 if let Some(m) = idx_cfg.query_min_query_dims {
993 query_config.min_query_dims = m;
994 }
995 }
996}
997
998fn parse_dense_vector_config(pair: pest::iterators::Pair<Rule>) -> DenseVectorConfig {
1001 let mut dim: usize = 0;
1002 let mut quantization = DenseVectorQuantization::F32;
1003
1004 for params in pair.into_inner() {
1006 if params.as_rule() == Rule::dense_vector_params {
1007 for inner in params.into_inner() {
1008 match inner.as_rule() {
1009 Rule::dense_vector_keyword_params => {
1010 for kwarg in inner.into_inner() {
1011 match kwarg.as_rule() {
1012 Rule::dims_kwarg => {
1013 if let Some(d) = kwarg.into_inner().next() {
1014 dim = d.as_str().parse().unwrap_or(0);
1015 }
1016 }
1017 Rule::quant_type_spec => {
1018 quantization = parse_quant_type(kwarg.as_str());
1019 }
1020 _ => {}
1021 }
1022 }
1023 }
1024 Rule::dense_vector_positional_params => {
1025 for item in inner.into_inner() {
1026 match item.as_rule() {
1027 Rule::dimension_spec => {
1028 dim = item.as_str().parse().unwrap_or(0);
1029 }
1030 Rule::quant_type_spec => {
1031 quantization = parse_quant_type(item.as_str());
1032 }
1033 _ => {}
1034 }
1035 }
1036 }
1037 _ => {}
1038 }
1039 }
1040 }
1041 }
1042
1043 DenseVectorConfig::new(dim).with_quantization(quantization)
1044}
1045
1046fn parse_quant_type(s: &str) -> DenseVectorQuantization {
1047 match s.trim() {
1048 "f16" => DenseVectorQuantization::F16,
1049 "uint8" | "u8" => DenseVectorQuantization::UInt8,
1050 _ => DenseVectorQuantization::F32,
1051 }
1052}
1053
1054fn parse_default_fields_def(pair: pest::iterators::Pair<Rule>) -> Vec<String> {
1056 pair.into_inner().map(|p| p.as_str().to_string()).collect()
1057}
1058
1059fn parse_query_router_def(pair: pest::iterators::Pair<Rule>) -> Result<QueryRouterRule> {
1061 let mut pattern = String::new();
1062 let mut substitution = String::new();
1063 let mut target_field = String::new();
1064 let mut mode = RoutingMode::Additional;
1065
1066 for prop in pair.into_inner() {
1067 if prop.as_rule() != Rule::query_router_prop {
1068 continue;
1069 }
1070
1071 for inner in prop.into_inner() {
1072 match inner.as_rule() {
1073 Rule::query_router_pattern => {
1074 if let Some(regex_str) = inner.into_inner().next() {
1075 pattern = parse_string_value(regex_str);
1076 }
1077 }
1078 Rule::query_router_substitution => {
1079 if let Some(quoted) = inner.into_inner().next() {
1080 substitution = parse_string_value(quoted);
1081 }
1082 }
1083 Rule::query_router_target => {
1084 if let Some(ident) = inner.into_inner().next() {
1085 target_field = ident.as_str().to_string();
1086 }
1087 }
1088 Rule::query_router_mode => {
1089 if let Some(mode_val) = inner.into_inner().next() {
1090 mode = match mode_val.as_str() {
1091 "exclusive" => RoutingMode::Exclusive,
1092 "additional" => RoutingMode::Additional,
1093 _ => RoutingMode::Additional,
1094 };
1095 }
1096 }
1097 _ => {}
1098 }
1099 }
1100 }
1101
1102 if pattern.is_empty() {
1103 return Err(Error::Schema("query_router missing 'pattern'".to_string()));
1104 }
1105 if substitution.is_empty() {
1106 return Err(Error::Schema(
1107 "query_router missing 'substitution'".to_string(),
1108 ));
1109 }
1110 if target_field.is_empty() {
1111 return Err(Error::Schema(
1112 "query_router missing 'target_field'".to_string(),
1113 ));
1114 }
1115
1116 Ok(QueryRouterRule {
1117 pattern,
1118 substitution,
1119 target_field,
1120 mode,
1121 })
1122}
1123
1124fn parse_string_value(pair: pest::iterators::Pair<Rule>) -> String {
1126 let s = pair.as_str();
1127 match pair.as_rule() {
1128 Rule::regex_string => {
1129 if let Some(inner) = pair.into_inner().next() {
1131 parse_string_value(inner)
1132 } else {
1133 s.to_string()
1134 }
1135 }
1136 Rule::raw_string => {
1137 s[2..s.len() - 1].to_string()
1139 }
1140 Rule::quoted_string => {
1141 let inner = &s[1..s.len() - 1];
1143 inner
1145 .replace("\\n", "\n")
1146 .replace("\\t", "\t")
1147 .replace("\\\"", "\"")
1148 .replace("\\\\", "\\")
1149 }
1150 _ => s.to_string(),
1151 }
1152}
1153
1154fn parse_index_def(pair: pest::iterators::Pair<Rule>) -> Result<IndexDef> {
1156 let mut inner = pair.into_inner();
1157
1158 let name = inner
1159 .next()
1160 .ok_or_else(|| Error::Schema("Missing index name".to_string()))?
1161 .as_str()
1162 .to_string();
1163
1164 let mut fields = Vec::new();
1165 let mut default_fields = Vec::new();
1166 let mut query_routers = Vec::new();
1167 let mut reorder_on_merge = false;
1168
1169 for item in inner {
1170 match item.as_rule() {
1171 Rule::field_def => {
1172 fields.push(parse_field_def(item)?);
1173 }
1174 Rule::default_fields_def => {
1175 default_fields = parse_default_fields_def(item);
1176 }
1177 Rule::query_router_def => {
1178 query_routers.push(parse_query_router_def(item)?);
1179 }
1180 Rule::reorder_on_merge_def => {
1181 let value = item
1182 .into_inner()
1183 .next()
1184 .map(|b| b.as_str() == "true")
1185 .unwrap_or(false);
1186 reorder_on_merge = value;
1187 }
1188 _ => {}
1189 }
1190 }
1191
1192 let primary_fields: Vec<&FieldDef> = fields.iter().filter(|f| f.primary).collect();
1194 if primary_fields.len() > 1 {
1195 return Err(Error::Schema(format!(
1196 "Index '{}' has {} primary key fields, but at most one is allowed",
1197 name,
1198 primary_fields.len()
1199 )));
1200 }
1201 if let Some(pk) = primary_fields.first() {
1202 if pk.field_type != FieldType::Text {
1203 return Err(Error::Schema(format!(
1204 "Primary key field '{}' must be of type text, got {:?}",
1205 pk.name, pk.field_type
1206 )));
1207 }
1208 if pk.multi {
1209 return Err(Error::Schema(format!(
1210 "Primary key field '{}' cannot be multi-valued",
1211 pk.name
1212 )));
1213 }
1214 }
1215
1216 Ok(IndexDef {
1217 name,
1218 fields,
1219 default_fields,
1220 query_routers,
1221 reorder_on_merge,
1222 })
1223}
1224
1225pub fn parse_sdl(input: &str) -> Result<Vec<IndexDef>> {
1227 let pairs = SdlParser::parse(Rule::file, input)
1228 .map_err(|e| Error::Schema(format!("Parse error: {}", e)))?;
1229
1230 let mut indexes = Vec::new();
1231
1232 for pair in pairs {
1233 if pair.as_rule() == Rule::file {
1234 for inner in pair.into_inner() {
1235 if inner.as_rule() == Rule::index_def {
1236 indexes.push(parse_index_def(inner)?);
1237 }
1238 }
1239 }
1240 }
1241
1242 Ok(indexes)
1243}
1244
1245pub fn parse_single_index(input: &str) -> Result<IndexDef> {
1247 let indexes = parse_sdl(input)?;
1248
1249 if indexes.is_empty() {
1250 return Err(Error::Schema("No index definition found".to_string()));
1251 }
1252
1253 if indexes.len() > 1 {
1254 return Err(Error::Schema(
1255 "Multiple index definitions found, expected one".to_string(),
1256 ));
1257 }
1258
1259 Ok(indexes.into_iter().next().unwrap())
1260}
1261
1262#[cfg(test)]
1263mod tests {
1264 use super::*;
1265
1266 #[test]
1267 fn test_parse_simple_schema() {
1268 let sdl = r#"
1269 index articles {
1270 field title: text [indexed, stored]
1271 field body: text [indexed]
1272 }
1273 "#;
1274
1275 let indexes = parse_sdl(sdl).unwrap();
1276 assert_eq!(indexes.len(), 1);
1277
1278 let index = &indexes[0];
1279 assert_eq!(index.name, "articles");
1280 assert_eq!(index.fields.len(), 2);
1281
1282 assert_eq!(index.fields[0].name, "title");
1283 assert!(matches!(index.fields[0].field_type, FieldType::Text));
1284 assert!(index.fields[0].indexed);
1285 assert!(index.fields[0].stored);
1286
1287 assert_eq!(index.fields[1].name, "body");
1288 assert!(matches!(index.fields[1].field_type, FieldType::Text));
1289 assert!(index.fields[1].indexed);
1290 assert!(!index.fields[1].stored);
1291 }
1292
1293 #[test]
1294 fn test_parse_all_field_types() {
1295 let sdl = r#"
1296 index test {
1297 field text_field: text [indexed, stored]
1298 field u64_field: u64 [indexed, stored]
1299 field i64_field: i64 [indexed, stored]
1300 field f64_field: f64 [indexed, stored]
1301 field bytes_field: bytes [stored]
1302 }
1303 "#;
1304
1305 let indexes = parse_sdl(sdl).unwrap();
1306 let index = &indexes[0];
1307
1308 assert!(matches!(index.fields[0].field_type, FieldType::Text));
1309 assert!(matches!(index.fields[1].field_type, FieldType::U64));
1310 assert!(matches!(index.fields[2].field_type, FieldType::I64));
1311 assert!(matches!(index.fields[3].field_type, FieldType::F64));
1312 assert!(matches!(index.fields[4].field_type, FieldType::Bytes));
1313 }
1314
1315 #[test]
1316 fn test_parse_with_comments() {
1317 let sdl = r#"
1318 # This is a comment
1319 index articles {
1320 # Title field
1321 field title: text [indexed, stored]
1322 field body: text [indexed] # inline comment not supported yet
1323 }
1324 "#;
1325
1326 let indexes = parse_sdl(sdl).unwrap();
1327 assert_eq!(indexes[0].fields.len(), 2);
1328 }
1329
1330 #[test]
1331 fn test_parse_type_aliases() {
1332 let sdl = r#"
1333 index test {
1334 field a: string [indexed]
1335 field b: int [indexed]
1336 field c: uint [indexed]
1337 field d: float [indexed]
1338 field e: binary [stored]
1339 }
1340 "#;
1341
1342 let indexes = parse_sdl(sdl).unwrap();
1343 let index = &indexes[0];
1344
1345 assert!(matches!(index.fields[0].field_type, FieldType::Text));
1346 assert!(matches!(index.fields[1].field_type, FieldType::I64));
1347 assert!(matches!(index.fields[2].field_type, FieldType::U64));
1348 assert!(matches!(index.fields[3].field_type, FieldType::F64));
1349 assert!(matches!(index.fields[4].field_type, FieldType::Bytes));
1350 }
1351
1352 #[test]
1353 fn test_to_schema() {
1354 let sdl = r#"
1355 index articles {
1356 field title: text [indexed, stored]
1357 field views: u64 [indexed, stored]
1358 }
1359 "#;
1360
1361 let indexes = parse_sdl(sdl).unwrap();
1362 let schema = indexes[0].to_schema();
1363
1364 assert!(schema.get_field("title").is_some());
1365 assert!(schema.get_field("views").is_some());
1366 assert!(schema.get_field("nonexistent").is_none());
1367 }
1368
1369 #[test]
1370 fn test_default_attributes() {
1371 let sdl = r#"
1372 index test {
1373 field title: text
1374 }
1375 "#;
1376
1377 let indexes = parse_sdl(sdl).unwrap();
1378 let field = &indexes[0].fields[0];
1379
1380 assert!(field.indexed);
1382 assert!(field.stored);
1383 }
1384
1385 #[test]
1386 fn test_multiple_indexes() {
1387 let sdl = r#"
1388 index articles {
1389 field title: text [indexed, stored]
1390 }
1391
1392 index users {
1393 field name: text [indexed, stored]
1394 field email: text [indexed, stored]
1395 }
1396 "#;
1397
1398 let indexes = parse_sdl(sdl).unwrap();
1399 assert_eq!(indexes.len(), 2);
1400 assert_eq!(indexes[0].name, "articles");
1401 assert_eq!(indexes[1].name, "users");
1402 }
1403
1404 #[test]
1405 fn test_tokenizer_spec() {
1406 let sdl = r#"
1407 index articles {
1408 field title: text<en_stem> [indexed, stored]
1409 field body: text<simple> [indexed]
1410 field author: text [indexed, stored]
1411 }
1412 "#;
1413
1414 let indexes = parse_sdl(sdl).unwrap();
1415 let index = &indexes[0];
1416
1417 assert_eq!(index.fields[0].name, "title");
1418 assert_eq!(index.fields[0].tokenizer, Some("en_stem".to_string()));
1419
1420 assert_eq!(index.fields[1].name, "body");
1421 assert_eq!(index.fields[1].tokenizer, Some("simple".to_string()));
1422
1423 assert_eq!(index.fields[2].name, "author");
1424 assert_eq!(index.fields[2].tokenizer, None); }
1426
1427 #[test]
1428 fn test_tokenizer_in_schema() {
1429 let sdl = r#"
1430 index articles {
1431 field title: text<german> [indexed, stored]
1432 field body: text<en_stem> [indexed]
1433 }
1434 "#;
1435
1436 let indexes = parse_sdl(sdl).unwrap();
1437 let schema = indexes[0].to_schema();
1438
1439 let title_field = schema.get_field("title").unwrap();
1440 let title_entry = schema.get_field_entry(title_field).unwrap();
1441 assert_eq!(title_entry.tokenizer, Some("german".to_string()));
1442
1443 let body_field = schema.get_field("body").unwrap();
1444 let body_entry = schema.get_field_entry(body_field).unwrap();
1445 assert_eq!(body_entry.tokenizer, Some("en_stem".to_string()));
1446 }
1447
1448 #[test]
1449 fn test_query_router_basic() {
1450 let sdl = r#"
1451 index documents {
1452 field title: text [indexed, stored]
1453 field uri: text [indexed, stored]
1454
1455 query_router {
1456 pattern: "10\\.\\d{4,}/[^\\s]+"
1457 substitution: "doi://{0}"
1458 target_field: uris
1459 mode: exclusive
1460 }
1461 }
1462 "#;
1463
1464 let indexes = parse_sdl(sdl).unwrap();
1465 let index = &indexes[0];
1466
1467 assert_eq!(index.query_routers.len(), 1);
1468 let router = &index.query_routers[0];
1469 assert_eq!(router.pattern, r"10\.\d{4,}/[^\s]+");
1470 assert_eq!(router.substitution, "doi://{0}");
1471 assert_eq!(router.target_field, "uris");
1472 assert_eq!(router.mode, RoutingMode::Exclusive);
1473 }
1474
1475 #[test]
1476 fn test_query_router_raw_string() {
1477 let sdl = r#"
1478 index documents {
1479 field uris: text [indexed, stored]
1480
1481 query_router {
1482 pattern: r"^pmid:(\d+)$"
1483 substitution: "pubmed://{1}"
1484 target_field: uris
1485 mode: additional
1486 }
1487 }
1488 "#;
1489
1490 let indexes = parse_sdl(sdl).unwrap();
1491 let router = &indexes[0].query_routers[0];
1492
1493 assert_eq!(router.pattern, r"^pmid:(\d+)$");
1494 assert_eq!(router.substitution, "pubmed://{1}");
1495 assert_eq!(router.mode, RoutingMode::Additional);
1496 }
1497
1498 #[test]
1499 fn test_multiple_query_routers() {
1500 let sdl = r#"
1501 index documents {
1502 field uris: text [indexed, stored]
1503
1504 query_router {
1505 pattern: r"^doi:(10\.\d{4,}/[^\s]+)$"
1506 substitution: "doi://{1}"
1507 target_field: uris
1508 mode: exclusive
1509 }
1510
1511 query_router {
1512 pattern: r"^pmid:(\d+)$"
1513 substitution: "pubmed://{1}"
1514 target_field: uris
1515 mode: exclusive
1516 }
1517
1518 query_router {
1519 pattern: r"^arxiv:(\d+\.\d+)$"
1520 substitution: "arxiv://{1}"
1521 target_field: uris
1522 mode: additional
1523 }
1524 }
1525 "#;
1526
1527 let indexes = parse_sdl(sdl).unwrap();
1528 assert_eq!(indexes[0].query_routers.len(), 3);
1529 }
1530
1531 #[test]
1532 fn test_query_router_default_mode() {
1533 let sdl = r#"
1534 index documents {
1535 field uris: text [indexed, stored]
1536
1537 query_router {
1538 pattern: r"test"
1539 substitution: "{0}"
1540 target_field: uris
1541 }
1542 }
1543 "#;
1544
1545 let indexes = parse_sdl(sdl).unwrap();
1546 assert_eq!(indexes[0].query_routers[0].mode, RoutingMode::Additional);
1548 }
1549
1550 #[test]
1551 fn test_multi_attribute() {
1552 let sdl = r#"
1553 index documents {
1554 field uris: text [indexed, stored<multi>]
1555 field title: text [indexed, stored]
1556 }
1557 "#;
1558
1559 let indexes = parse_sdl(sdl).unwrap();
1560 assert_eq!(indexes.len(), 1);
1561
1562 let fields = &indexes[0].fields;
1563 assert_eq!(fields.len(), 2);
1564
1565 assert_eq!(fields[0].name, "uris");
1567 assert!(fields[0].multi, "uris field should have multi=true");
1568
1569 assert_eq!(fields[1].name, "title");
1571 assert!(!fields[1].multi, "title field should have multi=false");
1572
1573 let schema = indexes[0].to_schema();
1575 let uris_field = schema.get_field("uris").unwrap();
1576 let title_field = schema.get_field("title").unwrap();
1577
1578 assert!(schema.get_field_entry(uris_field).unwrap().multi);
1579 assert!(!schema.get_field_entry(title_field).unwrap().multi);
1580 }
1581
1582 #[test]
1583 fn test_sparse_vector_field() {
1584 let sdl = r#"
1585 index documents {
1586 field embedding: sparse_vector [indexed, stored]
1587 }
1588 "#;
1589
1590 let indexes = parse_sdl(sdl).unwrap();
1591 assert_eq!(indexes.len(), 1);
1592 assert_eq!(indexes[0].fields.len(), 1);
1593 assert_eq!(indexes[0].fields[0].name, "embedding");
1594 assert_eq!(indexes[0].fields[0].field_type, FieldType::SparseVector);
1595 assert!(indexes[0].fields[0].sparse_vector_config.is_none());
1596 }
1597
1598 #[test]
1599 fn test_sparse_vector_with_config() {
1600 let sdl = r#"
1601 index documents {
1602 field embedding: sparse_vector<u16> [indexed<quantization: uint8>, stored]
1603 field dense: sparse_vector<u32> [indexed<quantization: float32>]
1604 }
1605 "#;
1606
1607 let indexes = parse_sdl(sdl).unwrap();
1608 assert_eq!(indexes[0].fields.len(), 2);
1609
1610 let f1 = &indexes[0].fields[0];
1612 assert_eq!(f1.name, "embedding");
1613 let config1 = f1.sparse_vector_config.as_ref().unwrap();
1614 assert_eq!(config1.index_size, IndexSize::U16);
1615 assert_eq!(config1.weight_quantization, WeightQuantization::UInt8);
1616
1617 let f2 = &indexes[0].fields[1];
1619 assert_eq!(f2.name, "dense");
1620 let config2 = f2.sparse_vector_config.as_ref().unwrap();
1621 assert_eq!(config2.index_size, IndexSize::U32);
1622 assert_eq!(config2.weight_quantization, WeightQuantization::Float32);
1623 }
1624
1625 #[test]
1626 fn test_sparse_vector_bmp_block_size() {
1627 let sdl = r#"
1628 index documents {
1629 field emb: sparse_vector<u32> [indexed<format: bmp, dims: 105879, bmp_block_size: 256>]
1630 field emb2: sparse_vector<u32> [indexed<format: bmp, dims: 30522>]
1631 }
1632 "#;
1633
1634 let indexes = parse_sdl(sdl).unwrap();
1635 let config1 = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
1636 assert_eq!(config1.format, SparseFormat::Bmp);
1637 assert_eq!(config1.bmp_block_size, 256);
1638
1639 let config2 = indexes[0].fields[1].sparse_vector_config.as_ref().unwrap();
1641 assert_eq!(config2.bmp_block_size, 64);
1642 }
1643
1644 #[test]
1647 fn test_sparse_vector_bmp_grid_bits() {
1648 let sdl = r#"
1649 index documents {
1650 field emb: sparse_vector<u32> [indexed<format: bmp, dims: 105879, bmp_block_size: 256, bmp_grid_bits: 2>]
1651 field emb2: sparse_vector<u32> [indexed<format: bmp, dims: 30522>]
1652 field emb3: sparse_vector<u32> [indexed<format: bmp, dims: 30522, bmp_grid_bits: 3>]
1653 }
1654 "#;
1655
1656 let indexes = parse_sdl(sdl).unwrap();
1657 let config1 = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
1658 assert_eq!(config1.bmp_grid_bits, 2);
1659 let config2 = indexes[0].fields[1].sparse_vector_config.as_ref().unwrap();
1661 assert_eq!(config2.bmp_grid_bits, 4);
1662 let config3 = indexes[0].fields[2].sparse_vector_config.as_ref().unwrap();
1664 assert_eq!(config3.bmp_grid_bits, 4);
1665 }
1666
1667 #[test]
1668 fn test_sparse_vector_with_weight_threshold() {
1669 let sdl = r#"
1670 index documents {
1671 field embedding: sparse_vector<u16> [indexed<quantization: uint8, weight_threshold: 0.1>, stored]
1672 field embedding2: sparse_vector<u32> [indexed<quantization: float16, weight_threshold: 0.05>]
1673 }
1674 "#;
1675
1676 let indexes = parse_sdl(sdl).unwrap();
1677 assert_eq!(indexes[0].fields.len(), 2);
1678
1679 let f1 = &indexes[0].fields[0];
1681 assert_eq!(f1.name, "embedding");
1682 let config1 = f1.sparse_vector_config.as_ref().unwrap();
1683 assert_eq!(config1.index_size, IndexSize::U16);
1684 assert_eq!(config1.weight_quantization, WeightQuantization::UInt8);
1685 assert!((config1.weight_threshold - 0.1).abs() < 0.001);
1686
1687 let f2 = &indexes[0].fields[1];
1689 assert_eq!(f2.name, "embedding2");
1690 let config2 = f2.sparse_vector_config.as_ref().unwrap();
1691 assert_eq!(config2.index_size, IndexSize::U32);
1692 assert_eq!(config2.weight_quantization, WeightQuantization::Float16);
1693 assert!((config2.weight_threshold - 0.05).abs() < 0.001);
1694 }
1695
1696 #[test]
1697 fn test_sparse_vector_with_pruning() {
1698 let sdl = r#"
1699 index documents {
1700 field embedding: sparse_vector [indexed<quantization: uint8, pruning: 0.1>, stored]
1701 }
1702 "#;
1703
1704 let indexes = parse_sdl(sdl).unwrap();
1705 let f = &indexes[0].fields[0];
1706 assert_eq!(f.name, "embedding");
1707 let config = f.sparse_vector_config.as_ref().unwrap();
1708 assert_eq!(config.weight_quantization, WeightQuantization::UInt8);
1709 assert_eq!(config.pruning, Some(0.1));
1710 }
1711
1712 #[test]
1713 fn test_sparse_vector_with_doc_mass() {
1714 let sdl = r#"
1715 index documents {
1716 field embedding: sparse_vector [indexed<quantization: uint8, doc_mass: 0.9>, stored]
1717 }
1718 "#;
1719
1720 let indexes = parse_sdl(sdl).unwrap();
1721 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
1722 assert_eq!(config.doc_mass, Some(0.9));
1723
1724 let sdl = r#"
1726 index documents {
1727 field embedding: sparse_vector [indexed<quantization: uint8>]
1728 }
1729 "#;
1730 let indexes = parse_sdl(sdl).unwrap();
1731 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
1732 assert_eq!(config.doc_mass, None);
1733 }
1734
1735 #[test]
1736 fn test_dense_vector_field() {
1737 let sdl = r#"
1738 index documents {
1739 field embedding: dense_vector<768> [indexed, stored]
1740 }
1741 "#;
1742
1743 let indexes = parse_sdl(sdl).unwrap();
1744 assert_eq!(indexes.len(), 1);
1745 assert_eq!(indexes[0].fields.len(), 1);
1746
1747 let f = &indexes[0].fields[0];
1748 assert_eq!(f.name, "embedding");
1749 assert_eq!(f.field_type, FieldType::DenseVector);
1750
1751 let config = f.dense_vector_config.as_ref().unwrap();
1752 assert_eq!(config.dim, 768);
1753 }
1754
1755 #[test]
1756 fn test_dense_vector_alias() {
1757 let sdl = r#"
1758 index documents {
1759 field embedding: vector<1536> [indexed]
1760 }
1761 "#;
1762
1763 let indexes = parse_sdl(sdl).unwrap();
1764 assert_eq!(indexes[0].fields[0].field_type, FieldType::DenseVector);
1765 assert_eq!(
1766 indexes[0].fields[0]
1767 .dense_vector_config
1768 .as_ref()
1769 .unwrap()
1770 .dim,
1771 1536
1772 );
1773 }
1774
1775 #[test]
1776 fn test_dense_vector_with_num_clusters() {
1777 let sdl = r#"
1778 index documents {
1779 field embedding: dense_vector<768> [indexed<ivf_rabitq, num_clusters: 256>, stored]
1780 }
1781 "#;
1782
1783 let indexes = parse_sdl(sdl).unwrap();
1784 assert_eq!(indexes.len(), 1);
1785
1786 let f = &indexes[0].fields[0];
1787 assert_eq!(f.name, "embedding");
1788 assert_eq!(f.field_type, FieldType::DenseVector);
1789
1790 let config = f.dense_vector_config.as_ref().unwrap();
1791 assert_eq!(config.dim, 768);
1792 assert_eq!(config.num_clusters, Some(256));
1793 assert_eq!(config.nprobe, 32); }
1795
1796 #[test]
1797 fn test_dense_vector_with_soar() {
1798 let sdl = r#"
1799 index documents {
1800 field embedding: dense_vector<768> [indexed<ivf_rabitq, num_clusters: 256, soar: selective>, stored]
1801 }
1802 "#;
1803
1804 let indexes = parse_sdl(sdl).unwrap();
1805 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1806
1807 let soar = config.soar.as_ref().expect("soar should be enabled");
1808 assert_eq!(soar.num_secondary, 1);
1809 assert!(soar.selective);
1810
1811 let sdl = r#"
1813 index documents {
1814 field embedding: dense_vector<768> [indexed<scann, soar: aggressive>]
1815 }
1816 "#;
1817 let indexes = parse_sdl(sdl).unwrap();
1818 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1819 let soar = config.soar.as_ref().expect("soar should be enabled");
1820 assert_eq!(soar.num_secondary, 2);
1821 assert!(!soar.selective);
1822
1823 let sdl = r#"
1825 index documents {
1826 field embedding: dense_vector<768> [indexed<ivf_rabitq, soar: off>]
1827 }
1828 "#;
1829 let indexes = parse_sdl(sdl).unwrap();
1830 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1831 assert!(config.soar.is_none());
1832 }
1833
1834 #[test]
1835 fn test_binary_dense_vector_with_ivf() {
1836 let sdl = r#"
1837 index documents {
1838 field hash: binary_dense_vector<512> [indexed<ivf, num_clusters: 128, nprobe: 16>, stored]
1839 }
1840 "#;
1841
1842 let indexes = parse_sdl(sdl).unwrap();
1843 let config = indexes[0].fields[0]
1844 .binary_dense_vector_config
1845 .as_ref()
1846 .unwrap();
1847 assert_eq!(config.dim, 512);
1848 assert_eq!(
1849 config.index_type,
1850 super::super::schema::BinaryIndexType::Ivf
1851 );
1852 assert_eq!(config.num_clusters, Some(128));
1853 assert_eq!(config.nprobe, 16);
1854
1855 let sdl = r#"
1857 index documents {
1858 field hash: binary_dense_vector<512> [indexed]
1859 }
1860 "#;
1861 let indexes = parse_sdl(sdl).unwrap();
1862 let config = indexes[0].fields[0]
1863 .binary_dense_vector_config
1864 .as_ref()
1865 .unwrap();
1866 assert_eq!(
1867 config.index_type,
1868 super::super::schema::BinaryIndexType::Flat
1869 );
1870 }
1871
1872 #[test]
1873 fn test_dense_vector_with_rabitq_bits() {
1874 let sdl = r#"
1875 index documents {
1876 field embedding: dense_vector<768> [indexed<ivf_rabitq, bits: 4>, stored]
1877 }
1878 "#;
1879
1880 let indexes = parse_sdl(sdl).unwrap();
1881 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1882 assert_eq!(config.rabitq_bits, Some(4));
1883
1884 let sdl = r#"
1886 index documents {
1887 field embedding: dense_vector<768> [indexed<ivf_rabitq>]
1888 }
1889 "#;
1890 let indexes = parse_sdl(sdl).unwrap();
1891 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1892 assert_eq!(config.rabitq_bits, None);
1893 }
1894
1895 #[test]
1896 fn test_dense_vector_with_num_clusters_and_nprobe() {
1897 let sdl = r#"
1898 index documents {
1899 field embedding: dense_vector<1536> [indexed<ivf_rabitq, num_clusters: 512, nprobe: 64>]
1900 }
1901 "#;
1902
1903 let indexes = parse_sdl(sdl).unwrap();
1904 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1905
1906 assert_eq!(config.dim, 1536);
1907 assert_eq!(config.num_clusters, Some(512));
1908 assert_eq!(config.nprobe, 64);
1909 }
1910
1911 #[test]
1912 fn test_dense_vector_keyword_syntax() {
1913 let sdl = r#"
1914 index documents {
1915 field embedding: dense_vector<dims: 1536> [indexed, stored]
1916 }
1917 "#;
1918
1919 let indexes = parse_sdl(sdl).unwrap();
1920 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1921
1922 assert_eq!(config.dim, 1536);
1923 assert!(config.num_clusters.is_none());
1924 }
1925
1926 #[test]
1927 fn test_dense_vector_keyword_syntax_full() {
1928 let sdl = r#"
1929 index documents {
1930 field embedding: dense_vector<dims: 1536> [indexed<ivf_rabitq, num_clusters: 256, nprobe: 64>]
1931 }
1932 "#;
1933
1934 let indexes = parse_sdl(sdl).unwrap();
1935 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1936
1937 assert_eq!(config.dim, 1536);
1938 assert_eq!(config.num_clusters, Some(256));
1939 assert_eq!(config.nprobe, 64);
1940 }
1941
1942 #[test]
1943 fn test_dense_vector_keyword_syntax_partial() {
1944 let sdl = r#"
1945 index documents {
1946 field embedding: dense_vector<dims: 768> [indexed<ivf_rabitq, num_clusters: 128>]
1947 }
1948 "#;
1949
1950 let indexes = parse_sdl(sdl).unwrap();
1951 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1952
1953 assert_eq!(config.dim, 768);
1954 assert_eq!(config.num_clusters, Some(128));
1955 assert_eq!(config.nprobe, 32); }
1957
1958 #[test]
1959 fn test_dense_vector_scann_index() {
1960 use crate::dsl::schema::VectorIndexType;
1961
1962 let sdl = r#"
1963 index documents {
1964 field embedding: dense_vector<dims: 768> [indexed<scann, num_clusters: 256, nprobe: 64>]
1965 }
1966 "#;
1967
1968 let indexes = parse_sdl(sdl).unwrap();
1969 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1970
1971 assert_eq!(config.dim, 768);
1972 assert_eq!(config.index_type, VectorIndexType::ScaNN);
1973 assert_eq!(config.num_clusters, Some(256));
1974 assert_eq!(config.nprobe, 64);
1975 }
1976
1977 #[test]
1978 fn test_dense_vector_ivf_rabitq_index() {
1979 use crate::dsl::schema::VectorIndexType;
1980
1981 let sdl = r#"
1982 index documents {
1983 field embedding: dense_vector<dims: 1536> [indexed<ivf_rabitq, num_clusters: 512>]
1984 }
1985 "#;
1986
1987 let indexes = parse_sdl(sdl).unwrap();
1988 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1989
1990 assert_eq!(config.dim, 1536);
1991 assert_eq!(config.index_type, VectorIndexType::IvfRaBitQ);
1992 assert_eq!(config.num_clusters, Some(512));
1993 }
1994
1995 #[test]
1996 fn test_dense_vector_rabitq_no_clusters() {
1997 use crate::dsl::schema::VectorIndexType;
1998
1999 let sdl = r#"
2000 index documents {
2001 field embedding: dense_vector<dims: 768> [indexed<rabitq>]
2002 }
2003 "#;
2004
2005 let indexes = parse_sdl(sdl).unwrap();
2006 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2007
2008 assert_eq!(config.dim, 768);
2009 assert_eq!(config.index_type, VectorIndexType::RaBitQ);
2010 assert!(config.num_clusters.is_none());
2011 }
2012
2013 #[test]
2014 fn test_dense_vector_flat_index() {
2015 use crate::dsl::schema::VectorIndexType;
2016
2017 let sdl = r#"
2018 index documents {
2019 field embedding: dense_vector<dims: 768> [indexed<flat>]
2020 }
2021 "#;
2022
2023 let indexes = parse_sdl(sdl).unwrap();
2024 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2025
2026 assert_eq!(config.dim, 768);
2027 assert_eq!(config.index_type, VectorIndexType::Flat);
2028 }
2029
2030 #[test]
2031 fn test_dense_vector_default_index_type() {
2032 use crate::dsl::schema::VectorIndexType;
2033
2034 let sdl = r#"
2036 index documents {
2037 field embedding: dense_vector<dims: 768> [indexed]
2038 }
2039 "#;
2040
2041 let indexes = parse_sdl(sdl).unwrap();
2042 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2043
2044 assert_eq!(config.dim, 768);
2045 assert_eq!(config.index_type, VectorIndexType::RaBitQ);
2046 }
2047
2048 #[test]
2049 fn test_dense_vector_f16_quantization() {
2050 use crate::dsl::schema::{DenseVectorQuantization, VectorIndexType};
2051
2052 let sdl = r#"
2053 index documents {
2054 field embedding: dense_vector<768, f16> [indexed]
2055 }
2056 "#;
2057
2058 let indexes = parse_sdl(sdl).unwrap();
2059 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2060
2061 assert_eq!(config.dim, 768);
2062 assert_eq!(config.quantization, DenseVectorQuantization::F16);
2063 assert_eq!(config.index_type, VectorIndexType::RaBitQ);
2064 }
2065
2066 #[test]
2067 fn test_dense_vector_uint8_quantization() {
2068 use crate::dsl::schema::DenseVectorQuantization;
2069
2070 let sdl = r#"
2071 index documents {
2072 field embedding: dense_vector<1024, uint8> [indexed<ivf_rabitq>]
2073 }
2074 "#;
2075
2076 let indexes = parse_sdl(sdl).unwrap();
2077 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2078
2079 assert_eq!(config.dim, 1024);
2080 assert_eq!(config.quantization, DenseVectorQuantization::UInt8);
2081 }
2082
2083 #[test]
2084 fn test_dense_vector_u8_alias() {
2085 use crate::dsl::schema::DenseVectorQuantization;
2086
2087 let sdl = r#"
2088 index documents {
2089 field embedding: dense_vector<512, u8> [indexed]
2090 }
2091 "#;
2092
2093 let indexes = parse_sdl(sdl).unwrap();
2094 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2095
2096 assert_eq!(config.dim, 512);
2097 assert_eq!(config.quantization, DenseVectorQuantization::UInt8);
2098 }
2099
2100 #[test]
2101 fn test_dense_vector_default_f32_quantization() {
2102 use crate::dsl::schema::DenseVectorQuantization;
2103
2104 let sdl = r#"
2106 index documents {
2107 field embedding: dense_vector<768> [indexed]
2108 }
2109 "#;
2110
2111 let indexes = parse_sdl(sdl).unwrap();
2112 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2113
2114 assert_eq!(config.dim, 768);
2115 assert_eq!(config.quantization, DenseVectorQuantization::F32);
2116 }
2117
2118 #[test]
2119 fn test_dense_vector_keyword_with_quantization() {
2120 use crate::dsl::schema::DenseVectorQuantization;
2121
2122 let sdl = r#"
2123 index documents {
2124 field embedding: dense_vector<dims: 768, f16> [indexed]
2125 }
2126 "#;
2127
2128 let indexes = parse_sdl(sdl).unwrap();
2129 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2130
2131 assert_eq!(config.dim, 768);
2132 assert_eq!(config.quantization, DenseVectorQuantization::F16);
2133 }
2134
2135 #[test]
2136 fn test_json_field_type() {
2137 let sdl = r#"
2138 index documents {
2139 field title: text [indexed, stored]
2140 field metadata: json [stored]
2141 field extra: json
2142 }
2143 "#;
2144
2145 let indexes = parse_sdl(sdl).unwrap();
2146 let index = &indexes[0];
2147
2148 assert_eq!(index.fields.len(), 3);
2149
2150 assert_eq!(index.fields[1].name, "metadata");
2152 assert!(matches!(index.fields[1].field_type, FieldType::Json));
2153 assert!(index.fields[1].stored);
2154 assert_eq!(index.fields[2].name, "extra");
2158 assert!(matches!(index.fields[2].field_type, FieldType::Json));
2159
2160 let schema = index.to_schema();
2162 let metadata_field = schema.get_field("metadata").unwrap();
2163 let entry = schema.get_field_entry(metadata_field).unwrap();
2164 assert_eq!(entry.field_type, FieldType::Json);
2165 assert!(!entry.indexed); assert!(entry.stored);
2167 }
2168
2169 #[test]
2170 fn test_sparse_vector_query_config() {
2171 use crate::structures::QueryWeighting;
2172
2173 let sdl = r#"
2174 index documents {
2175 field embedding: sparse_vector<u16> [indexed<quantization: uint8, query<tokenizer: "Alibaba-NLP/gte-Qwen2-1.5B-instruct", weighting: idf>>]
2176 }
2177 "#;
2178
2179 let indexes = parse_sdl(sdl).unwrap();
2180 let index = &indexes[0];
2181
2182 assert_eq!(index.fields.len(), 1);
2183 assert_eq!(index.fields[0].name, "embedding");
2184 assert!(matches!(
2185 index.fields[0].field_type,
2186 FieldType::SparseVector
2187 ));
2188
2189 let config = index.fields[0].sparse_vector_config.as_ref().unwrap();
2190 assert_eq!(config.index_size, IndexSize::U16);
2191 assert_eq!(config.weight_quantization, WeightQuantization::UInt8);
2192
2193 let query_config = config.query_config.as_ref().unwrap();
2195 assert_eq!(
2196 query_config.tokenizer.as_deref(),
2197 Some("Alibaba-NLP/gte-Qwen2-1.5B-instruct")
2198 );
2199 assert_eq!(query_config.weighting, QueryWeighting::Idf);
2200
2201 let schema = index.to_schema();
2203 let embedding_field = schema.get_field("embedding").unwrap();
2204 let entry = schema.get_field_entry(embedding_field).unwrap();
2205 let sv_config = entry.sparse_vector_config.as_ref().unwrap();
2206 let qc = sv_config.query_config.as_ref().unwrap();
2207 assert_eq!(
2208 qc.tokenizer.as_deref(),
2209 Some("Alibaba-NLP/gte-Qwen2-1.5B-instruct")
2210 );
2211 assert_eq!(qc.weighting, QueryWeighting::Idf);
2212 }
2213
2214 #[test]
2215 fn test_sparse_vector_query_config_weighting_one() {
2216 use crate::structures::QueryWeighting;
2217
2218 let sdl = r#"
2219 index documents {
2220 field embedding: sparse_vector [indexed<query<weighting: one>>]
2221 }
2222 "#;
2223
2224 let indexes = parse_sdl(sdl).unwrap();
2225 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2226
2227 let query_config = config.query_config.as_ref().unwrap();
2228 assert!(query_config.tokenizer.is_none());
2229 assert_eq!(query_config.weighting, QueryWeighting::One);
2230 }
2231
2232 #[test]
2233 fn test_sparse_vector_query_config_weighting_idf_file() {
2234 use crate::structures::QueryWeighting;
2235
2236 let sdl = r#"
2237 index documents {
2238 field embedding: sparse_vector<u16> [indexed<quantization: uint8, query<tokenizer: "opensearch-neural-sparse-encoding-v1", weighting: idf_file>>]
2239 }
2240 "#;
2241
2242 let indexes = parse_sdl(sdl).unwrap();
2243 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2244
2245 let query_config = config.query_config.as_ref().unwrap();
2246 assert_eq!(
2247 query_config.tokenizer.as_deref(),
2248 Some("opensearch-neural-sparse-encoding-v1")
2249 );
2250 assert_eq!(query_config.weighting, QueryWeighting::IdfFile);
2251
2252 let schema = indexes[0].to_schema();
2254 let field = schema.get_field("embedding").unwrap();
2255 let entry = schema.get_field_entry(field).unwrap();
2256 let sc = entry.sparse_vector_config.as_ref().unwrap();
2257 let qc = sc.query_config.as_ref().unwrap();
2258 assert_eq!(qc.weighting, QueryWeighting::IdfFile);
2259 }
2260
2261 #[test]
2262 fn test_sparse_vector_query_config_pruning_params() {
2263 let sdl = r#"
2264 index documents {
2265 field embedding: sparse_vector<u16> [indexed<quantization: uint8, query<weighting: idf, weight_threshold: 0.03, max_dims: 25, pruning: 0.2>>]
2266 }
2267 "#;
2268
2269 let indexes = parse_sdl(sdl).unwrap();
2270 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2271
2272 let qc = config.query_config.as_ref().unwrap();
2273 assert_eq!(qc.weighting, QueryWeighting::Idf);
2274 assert!((qc.weight_threshold - 0.03).abs() < 0.001);
2275 assert_eq!(qc.max_query_dims, Some(25));
2276 assert!((qc.pruning.unwrap() - 0.2).abs() < 0.001);
2277
2278 let schema = indexes[0].to_schema();
2280 let field = schema.get_field("embedding").unwrap();
2281 let entry = schema.get_field_entry(field).unwrap();
2282 let sc = entry.sparse_vector_config.as_ref().unwrap();
2283 let rqc = sc.query_config.as_ref().unwrap();
2284 assert!((rqc.weight_threshold - 0.03).abs() < 0.001);
2285 assert_eq!(rqc.max_query_dims, Some(25));
2286 assert!((rqc.pruning.unwrap() - 0.2).abs() < 0.001);
2287 }
2288
2289 #[test]
2290 fn test_sparse_vector_format_maxscore() {
2291 let sdl = r#"
2292 index documents {
2293 field embedding: sparse_vector<u16> [indexed<format: maxscore, quantization: uint8>]
2294 }
2295 "#;
2296
2297 let indexes = parse_sdl(sdl).unwrap();
2298 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2299 assert_eq!(config.format, SparseFormat::MaxScore);
2300 assert_eq!(config.weight_quantization, WeightQuantization::UInt8);
2301
2302 let schema = indexes[0].to_schema();
2304 let field = schema.get_field("embedding").unwrap();
2305 let entry = schema.get_field_entry(field).unwrap();
2306 let sc = entry.sparse_vector_config.as_ref().unwrap();
2307 assert_eq!(sc.format, SparseFormat::MaxScore);
2308 }
2309
2310 #[test]
2311 fn test_sparse_vector_format_bmp() {
2312 let sdl = r#"
2313 index documents {
2314 field embedding: sparse_vector<u16> [indexed<format: bmp, quantization: uint8>]
2315 }
2316 "#;
2317
2318 let indexes = parse_sdl(sdl).unwrap();
2319 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2320 assert_eq!(config.format, SparseFormat::Bmp);
2321 }
2322
2323 #[test]
2324 fn test_fast_attribute() {
2325 let sdl = r#"
2326 index products {
2327 field name: text [indexed, stored]
2328 field price: f64 [indexed, fast]
2329 field category: text [indexed, stored, fast]
2330 field count: u64 [fast]
2331 field score: i64 [indexed, stored, fast]
2332 }
2333 "#;
2334
2335 let indexes = parse_sdl(sdl).unwrap();
2336 assert_eq!(indexes.len(), 1);
2337 let index = &indexes[0];
2338 assert_eq!(index.fields.len(), 5);
2339
2340 assert!(!index.fields[0].fast);
2342 assert!(index.fields[1].fast);
2344 assert!(matches!(index.fields[1].field_type, FieldType::F64));
2345 assert!(index.fields[2].fast);
2347 assert!(matches!(index.fields[2].field_type, FieldType::Text));
2348 assert!(index.fields[3].fast);
2350 assert!(matches!(index.fields[3].field_type, FieldType::U64));
2351 assert!(index.fields[4].fast);
2353 assert!(matches!(index.fields[4].field_type, FieldType::I64));
2354
2355 let schema = index.to_schema();
2357 let price_field = schema.get_field("price").unwrap();
2358 assert!(schema.get_field_entry(price_field).unwrap().fast);
2359
2360 let category_field = schema.get_field("category").unwrap();
2361 assert!(schema.get_field_entry(category_field).unwrap().fast);
2362
2363 let name_field = schema.get_field("name").unwrap();
2364 assert!(!schema.get_field_entry(name_field).unwrap().fast);
2365 }
2366
2367 #[test]
2368 fn test_primary_attribute() {
2369 let sdl = r#"
2370 index documents {
2371 field id: text [primary, stored]
2372 field title: text [indexed, stored]
2373 }
2374 "#;
2375
2376 let indexes = parse_sdl(sdl).unwrap();
2377 assert_eq!(indexes.len(), 1);
2378 let index = &indexes[0];
2379 assert_eq!(index.fields.len(), 2);
2380
2381 let id_field = &index.fields[0];
2383 assert!(id_field.primary, "id should be primary");
2384 assert!(id_field.fast, "primary implies fast");
2385 assert!(id_field.indexed, "primary implies indexed");
2386
2387 assert!(!index.fields[1].primary);
2389
2390 let schema = index.to_schema();
2392 let id = schema.get_field("id").unwrap();
2393 let id_entry = schema.get_field_entry(id).unwrap();
2394 assert!(id_entry.primary_key);
2395 assert!(id_entry.fast);
2396 assert!(id_entry.indexed);
2397
2398 let title = schema.get_field("title").unwrap();
2399 assert!(!schema.get_field_entry(title).unwrap().primary_key);
2400
2401 assert_eq!(schema.primary_field(), Some(id));
2403 }
2404
2405 #[test]
2406 fn test_primary_with_other_attributes() {
2407 let sdl = r#"
2408 index documents {
2409 field id: text<simple> [primary, indexed, stored]
2410 field body: text [indexed]
2411 }
2412 "#;
2413
2414 let indexes = parse_sdl(sdl).unwrap();
2415 let id_field = &indexes[0].fields[0];
2416 assert!(id_field.primary);
2417 assert!(id_field.indexed);
2418 assert!(id_field.stored);
2419 assert!(id_field.fast);
2420 assert_eq!(id_field.tokenizer, Some("simple".to_string()));
2421 }
2422
2423 #[test]
2424 fn test_primary_only_one_allowed() {
2425 let sdl = r#"
2426 index documents {
2427 field id: text [primary]
2428 field alt_id: text [primary]
2429 }
2430 "#;
2431
2432 let result = parse_sdl(sdl);
2433 assert!(result.is_err());
2434 let err = result.unwrap_err().to_string();
2435 assert!(
2436 err.contains("primary key"),
2437 "Error should mention primary key: {}",
2438 err
2439 );
2440 }
2441
2442 #[test]
2443 fn test_primary_must_be_text() {
2444 let sdl = r#"
2445 index documents {
2446 field id: u64 [primary]
2447 }
2448 "#;
2449
2450 let result = parse_sdl(sdl);
2451 assert!(result.is_err());
2452 let err = result.unwrap_err().to_string();
2453 assert!(
2454 err.contains("text"),
2455 "Error should mention text type: {}",
2456 err
2457 );
2458 }
2459
2460 #[test]
2461 fn test_primary_cannot_be_multi() {
2462 let sdl = r#"
2463 index documents {
2464 field id: text [primary, stored<multi>]
2465 }
2466 "#;
2467
2468 let result = parse_sdl(sdl);
2469 assert!(result.is_err());
2470 let err = result.unwrap_err().to_string();
2471 assert!(err.contains("multi"), "Error should mention multi: {}", err);
2472 }
2473
2474 #[test]
2475 fn test_no_primary_field() {
2476 let sdl = r#"
2478 index documents {
2479 field title: text [indexed, stored]
2480 }
2481 "#;
2482
2483 let indexes = parse_sdl(sdl).unwrap();
2484 let schema = indexes[0].to_schema();
2485 assert!(schema.primary_field().is_none());
2486 }
2487
2488 #[test]
2489 fn test_reorder_attribute() {
2490 let sdl = r#"
2491 index documents {
2492 field embedding: sparse_vector<u16> [indexed<format: bmp, quantization: uint8>, reorder]
2493 field embedding2: sparse_vector [indexed<format: bmp>]
2494 }
2495 "#;
2496
2497 let indexes = parse_sdl(sdl).unwrap();
2498 assert_eq!(indexes[0].fields.len(), 2);
2499
2500 assert!(indexes[0].fields[0].reorder);
2502 assert!(!indexes[0].fields[1].reorder);
2504
2505 let schema = indexes[0].to_schema();
2507 let f1 = schema.get_field("embedding").unwrap();
2508 assert!(schema.get_field_entry(f1).unwrap().reorder);
2509
2510 let f2 = schema.get_field("embedding2").unwrap();
2511 assert!(!schema.get_field_entry(f2).unwrap().reorder);
2512
2513 assert!(!schema.reorder_on_merge());
2515 }
2516
2517 #[test]
2518 fn test_reorder_on_merge_index_option() {
2519 let sdl = r#"
2520 index documents {
2521 reorder_on_merge: true
2522 field embedding: sparse_vector<u16> [indexed<format: bmp>, reorder]
2523 }
2524 "#;
2525
2526 let indexes = parse_sdl(sdl).unwrap();
2527 assert!(indexes[0].reorder_on_merge);
2528 let schema = indexes[0].to_schema();
2529 assert!(schema.reorder_on_merge());
2530
2531 let sdl_off = r#"
2533 index documents {
2534 reorder_on_merge: false
2535 field embedding: sparse_vector<u16> [indexed<format: bmp>, reorder]
2536 }
2537 "#;
2538 let indexes = parse_sdl(sdl_off).unwrap();
2539 assert!(!indexes[0].reorder_on_merge);
2540 assert!(!indexes[0].to_schema().reorder_on_merge());
2541
2542 let schema_on = parse_sdl(sdl).unwrap()[0].to_schema();
2544 let json = serde_json::to_string(&schema_on).unwrap();
2545 let back: crate::dsl::Schema = serde_json::from_str(&json).unwrap();
2546 assert!(back.reorder_on_merge());
2547 }
2548}