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(p) = idx_cfg.pruning {
925 let clamped = p.clamp(0.0, 1.0);
926 if (clamped - p).abs() > f32::EPSILON {
927 log::warn!(
928 "pruning {} clamped to valid range [0.0, 1.0]: {}",
929 p,
930 clamped
931 );
932 }
933 config.pruning = Some(clamped);
934 }
935 if let Some(mt) = idx_cfg.min_terms {
936 config.min_terms = mt;
937 }
938 if let Some(dm) = idx_cfg.doc_mass {
939 let clamped = dm.clamp(0.0, 1.0);
940 if (clamped - dm).abs() > f32::EPSILON {
941 log::warn!(
942 "doc_mass {} clamped to valid range [0.0, 1.0]: {}",
943 dm,
944 clamped
945 );
946 }
947 config.doc_mass = Some(clamped);
948 }
949 if let Some(d) = idx_cfg.dims {
950 config.dims = Some(d);
951 }
952 if let Some(mw) = idx_cfg.max_weight {
953 config.max_weight = Some(mw);
954 }
955 if idx_cfg.query_tokenizer.is_some()
957 || idx_cfg.query_weighting.is_some()
958 || idx_cfg.query_weight_threshold.is_some()
959 || idx_cfg.query_max_dims.is_some()
960 || idx_cfg.query_pruning.is_some()
961 || idx_cfg.query_min_query_dims.is_some()
962 {
963 let query_config = config
964 .query_config
965 .get_or_insert(SparseQueryConfig::default());
966 if let Some(tokenizer) = idx_cfg.query_tokenizer {
967 query_config.tokenizer = Some(tokenizer);
968 }
969 if let Some(weighting) = idx_cfg.query_weighting {
970 query_config.weighting = weighting;
971 }
972 if let Some(t) = idx_cfg.query_weight_threshold {
973 query_config.weight_threshold = t;
974 }
975 if let Some(d) = idx_cfg.query_max_dims {
976 query_config.max_query_dims = Some(d);
977 }
978 if let Some(p) = idx_cfg.query_pruning {
979 query_config.pruning = Some(p);
980 }
981 if let Some(m) = idx_cfg.query_min_query_dims {
982 query_config.min_query_dims = m;
983 }
984 }
985}
986
987fn parse_dense_vector_config(pair: pest::iterators::Pair<Rule>) -> DenseVectorConfig {
990 let mut dim: usize = 0;
991 let mut quantization = DenseVectorQuantization::F32;
992
993 for params in pair.into_inner() {
995 if params.as_rule() == Rule::dense_vector_params {
996 for inner in params.into_inner() {
997 match inner.as_rule() {
998 Rule::dense_vector_keyword_params => {
999 for kwarg in inner.into_inner() {
1000 match kwarg.as_rule() {
1001 Rule::dims_kwarg => {
1002 if let Some(d) = kwarg.into_inner().next() {
1003 dim = d.as_str().parse().unwrap_or(0);
1004 }
1005 }
1006 Rule::quant_type_spec => {
1007 quantization = parse_quant_type(kwarg.as_str());
1008 }
1009 _ => {}
1010 }
1011 }
1012 }
1013 Rule::dense_vector_positional_params => {
1014 for item in inner.into_inner() {
1015 match item.as_rule() {
1016 Rule::dimension_spec => {
1017 dim = item.as_str().parse().unwrap_or(0);
1018 }
1019 Rule::quant_type_spec => {
1020 quantization = parse_quant_type(item.as_str());
1021 }
1022 _ => {}
1023 }
1024 }
1025 }
1026 _ => {}
1027 }
1028 }
1029 }
1030 }
1031
1032 DenseVectorConfig::new(dim).with_quantization(quantization)
1033}
1034
1035fn parse_quant_type(s: &str) -> DenseVectorQuantization {
1036 match s.trim() {
1037 "f16" => DenseVectorQuantization::F16,
1038 "uint8" | "u8" => DenseVectorQuantization::UInt8,
1039 _ => DenseVectorQuantization::F32,
1040 }
1041}
1042
1043fn parse_default_fields_def(pair: pest::iterators::Pair<Rule>) -> Vec<String> {
1045 pair.into_inner().map(|p| p.as_str().to_string()).collect()
1046}
1047
1048fn parse_query_router_def(pair: pest::iterators::Pair<Rule>) -> Result<QueryRouterRule> {
1050 let mut pattern = String::new();
1051 let mut substitution = String::new();
1052 let mut target_field = String::new();
1053 let mut mode = RoutingMode::Additional;
1054
1055 for prop in pair.into_inner() {
1056 if prop.as_rule() != Rule::query_router_prop {
1057 continue;
1058 }
1059
1060 for inner in prop.into_inner() {
1061 match inner.as_rule() {
1062 Rule::query_router_pattern => {
1063 if let Some(regex_str) = inner.into_inner().next() {
1064 pattern = parse_string_value(regex_str);
1065 }
1066 }
1067 Rule::query_router_substitution => {
1068 if let Some(quoted) = inner.into_inner().next() {
1069 substitution = parse_string_value(quoted);
1070 }
1071 }
1072 Rule::query_router_target => {
1073 if let Some(ident) = inner.into_inner().next() {
1074 target_field = ident.as_str().to_string();
1075 }
1076 }
1077 Rule::query_router_mode => {
1078 if let Some(mode_val) = inner.into_inner().next() {
1079 mode = match mode_val.as_str() {
1080 "exclusive" => RoutingMode::Exclusive,
1081 "additional" => RoutingMode::Additional,
1082 _ => RoutingMode::Additional,
1083 };
1084 }
1085 }
1086 _ => {}
1087 }
1088 }
1089 }
1090
1091 if pattern.is_empty() {
1092 return Err(Error::Schema("query_router missing 'pattern'".to_string()));
1093 }
1094 if substitution.is_empty() {
1095 return Err(Error::Schema(
1096 "query_router missing 'substitution'".to_string(),
1097 ));
1098 }
1099 if target_field.is_empty() {
1100 return Err(Error::Schema(
1101 "query_router missing 'target_field'".to_string(),
1102 ));
1103 }
1104
1105 Ok(QueryRouterRule {
1106 pattern,
1107 substitution,
1108 target_field,
1109 mode,
1110 })
1111}
1112
1113fn parse_string_value(pair: pest::iterators::Pair<Rule>) -> String {
1115 let s = pair.as_str();
1116 match pair.as_rule() {
1117 Rule::regex_string => {
1118 if let Some(inner) = pair.into_inner().next() {
1120 parse_string_value(inner)
1121 } else {
1122 s.to_string()
1123 }
1124 }
1125 Rule::raw_string => {
1126 s[2..s.len() - 1].to_string()
1128 }
1129 Rule::quoted_string => {
1130 let inner = &s[1..s.len() - 1];
1132 inner
1134 .replace("\\n", "\n")
1135 .replace("\\t", "\t")
1136 .replace("\\\"", "\"")
1137 .replace("\\\\", "\\")
1138 }
1139 _ => s.to_string(),
1140 }
1141}
1142
1143fn parse_index_def(pair: pest::iterators::Pair<Rule>) -> Result<IndexDef> {
1145 let mut inner = pair.into_inner();
1146
1147 let name = inner
1148 .next()
1149 .ok_or_else(|| Error::Schema("Missing index name".to_string()))?
1150 .as_str()
1151 .to_string();
1152
1153 let mut fields = Vec::new();
1154 let mut default_fields = Vec::new();
1155 let mut query_routers = Vec::new();
1156 let mut reorder_on_merge = false;
1157
1158 for item in inner {
1159 match item.as_rule() {
1160 Rule::field_def => {
1161 fields.push(parse_field_def(item)?);
1162 }
1163 Rule::default_fields_def => {
1164 default_fields = parse_default_fields_def(item);
1165 }
1166 Rule::query_router_def => {
1167 query_routers.push(parse_query_router_def(item)?);
1168 }
1169 Rule::reorder_on_merge_def => {
1170 let value = item
1171 .into_inner()
1172 .next()
1173 .map(|b| b.as_str() == "true")
1174 .unwrap_or(false);
1175 reorder_on_merge = value;
1176 }
1177 _ => {}
1178 }
1179 }
1180
1181 let primary_fields: Vec<&FieldDef> = fields.iter().filter(|f| f.primary).collect();
1183 if primary_fields.len() > 1 {
1184 return Err(Error::Schema(format!(
1185 "Index '{}' has {} primary key fields, but at most one is allowed",
1186 name,
1187 primary_fields.len()
1188 )));
1189 }
1190 if let Some(pk) = primary_fields.first() {
1191 if pk.field_type != FieldType::Text {
1192 return Err(Error::Schema(format!(
1193 "Primary key field '{}' must be of type text, got {:?}",
1194 pk.name, pk.field_type
1195 )));
1196 }
1197 if pk.multi {
1198 return Err(Error::Schema(format!(
1199 "Primary key field '{}' cannot be multi-valued",
1200 pk.name
1201 )));
1202 }
1203 }
1204
1205 Ok(IndexDef {
1206 name,
1207 fields,
1208 default_fields,
1209 query_routers,
1210 reorder_on_merge,
1211 })
1212}
1213
1214pub fn parse_sdl(input: &str) -> Result<Vec<IndexDef>> {
1216 let pairs = SdlParser::parse(Rule::file, input)
1217 .map_err(|e| Error::Schema(format!("Parse error: {}", e)))?;
1218
1219 let mut indexes = Vec::new();
1220
1221 for pair in pairs {
1222 if pair.as_rule() == Rule::file {
1223 for inner in pair.into_inner() {
1224 if inner.as_rule() == Rule::index_def {
1225 indexes.push(parse_index_def(inner)?);
1226 }
1227 }
1228 }
1229 }
1230
1231 Ok(indexes)
1232}
1233
1234pub fn parse_single_index(input: &str) -> Result<IndexDef> {
1236 let indexes = parse_sdl(input)?;
1237
1238 if indexes.is_empty() {
1239 return Err(Error::Schema("No index definition found".to_string()));
1240 }
1241
1242 if indexes.len() > 1 {
1243 return Err(Error::Schema(
1244 "Multiple index definitions found, expected one".to_string(),
1245 ));
1246 }
1247
1248 Ok(indexes.into_iter().next().unwrap())
1249}
1250
1251#[cfg(test)]
1252mod tests {
1253 use super::*;
1254
1255 #[test]
1256 fn test_parse_simple_schema() {
1257 let sdl = r#"
1258 index articles {
1259 field title: text [indexed, stored]
1260 field body: text [indexed]
1261 }
1262 "#;
1263
1264 let indexes = parse_sdl(sdl).unwrap();
1265 assert_eq!(indexes.len(), 1);
1266
1267 let index = &indexes[0];
1268 assert_eq!(index.name, "articles");
1269 assert_eq!(index.fields.len(), 2);
1270
1271 assert_eq!(index.fields[0].name, "title");
1272 assert!(matches!(index.fields[0].field_type, FieldType::Text));
1273 assert!(index.fields[0].indexed);
1274 assert!(index.fields[0].stored);
1275
1276 assert_eq!(index.fields[1].name, "body");
1277 assert!(matches!(index.fields[1].field_type, FieldType::Text));
1278 assert!(index.fields[1].indexed);
1279 assert!(!index.fields[1].stored);
1280 }
1281
1282 #[test]
1283 fn test_parse_all_field_types() {
1284 let sdl = r#"
1285 index test {
1286 field text_field: text [indexed, stored]
1287 field u64_field: u64 [indexed, stored]
1288 field i64_field: i64 [indexed, stored]
1289 field f64_field: f64 [indexed, stored]
1290 field bytes_field: bytes [stored]
1291 }
1292 "#;
1293
1294 let indexes = parse_sdl(sdl).unwrap();
1295 let index = &indexes[0];
1296
1297 assert!(matches!(index.fields[0].field_type, FieldType::Text));
1298 assert!(matches!(index.fields[1].field_type, FieldType::U64));
1299 assert!(matches!(index.fields[2].field_type, FieldType::I64));
1300 assert!(matches!(index.fields[3].field_type, FieldType::F64));
1301 assert!(matches!(index.fields[4].field_type, FieldType::Bytes));
1302 }
1303
1304 #[test]
1305 fn test_parse_with_comments() {
1306 let sdl = r#"
1307 # This is a comment
1308 index articles {
1309 # Title field
1310 field title: text [indexed, stored]
1311 field body: text [indexed] # inline comment not supported yet
1312 }
1313 "#;
1314
1315 let indexes = parse_sdl(sdl).unwrap();
1316 assert_eq!(indexes[0].fields.len(), 2);
1317 }
1318
1319 #[test]
1320 fn test_parse_type_aliases() {
1321 let sdl = r#"
1322 index test {
1323 field a: string [indexed]
1324 field b: int [indexed]
1325 field c: uint [indexed]
1326 field d: float [indexed]
1327 field e: binary [stored]
1328 }
1329 "#;
1330
1331 let indexes = parse_sdl(sdl).unwrap();
1332 let index = &indexes[0];
1333
1334 assert!(matches!(index.fields[0].field_type, FieldType::Text));
1335 assert!(matches!(index.fields[1].field_type, FieldType::I64));
1336 assert!(matches!(index.fields[2].field_type, FieldType::U64));
1337 assert!(matches!(index.fields[3].field_type, FieldType::F64));
1338 assert!(matches!(index.fields[4].field_type, FieldType::Bytes));
1339 }
1340
1341 #[test]
1342 fn test_to_schema() {
1343 let sdl = r#"
1344 index articles {
1345 field title: text [indexed, stored]
1346 field views: u64 [indexed, stored]
1347 }
1348 "#;
1349
1350 let indexes = parse_sdl(sdl).unwrap();
1351 let schema = indexes[0].to_schema();
1352
1353 assert!(schema.get_field("title").is_some());
1354 assert!(schema.get_field("views").is_some());
1355 assert!(schema.get_field("nonexistent").is_none());
1356 }
1357
1358 #[test]
1359 fn test_default_attributes() {
1360 let sdl = r#"
1361 index test {
1362 field title: text
1363 }
1364 "#;
1365
1366 let indexes = parse_sdl(sdl).unwrap();
1367 let field = &indexes[0].fields[0];
1368
1369 assert!(field.indexed);
1371 assert!(field.stored);
1372 }
1373
1374 #[test]
1375 fn test_multiple_indexes() {
1376 let sdl = r#"
1377 index articles {
1378 field title: text [indexed, stored]
1379 }
1380
1381 index users {
1382 field name: text [indexed, stored]
1383 field email: text [indexed, stored]
1384 }
1385 "#;
1386
1387 let indexes = parse_sdl(sdl).unwrap();
1388 assert_eq!(indexes.len(), 2);
1389 assert_eq!(indexes[0].name, "articles");
1390 assert_eq!(indexes[1].name, "users");
1391 }
1392
1393 #[test]
1394 fn test_tokenizer_spec() {
1395 let sdl = r#"
1396 index articles {
1397 field title: text<en_stem> [indexed, stored]
1398 field body: text<simple> [indexed]
1399 field author: text [indexed, stored]
1400 }
1401 "#;
1402
1403 let indexes = parse_sdl(sdl).unwrap();
1404 let index = &indexes[0];
1405
1406 assert_eq!(index.fields[0].name, "title");
1407 assert_eq!(index.fields[0].tokenizer, Some("en_stem".to_string()));
1408
1409 assert_eq!(index.fields[1].name, "body");
1410 assert_eq!(index.fields[1].tokenizer, Some("simple".to_string()));
1411
1412 assert_eq!(index.fields[2].name, "author");
1413 assert_eq!(index.fields[2].tokenizer, None); }
1415
1416 #[test]
1417 fn test_tokenizer_in_schema() {
1418 let sdl = r#"
1419 index articles {
1420 field title: text<german> [indexed, stored]
1421 field body: text<en_stem> [indexed]
1422 }
1423 "#;
1424
1425 let indexes = parse_sdl(sdl).unwrap();
1426 let schema = indexes[0].to_schema();
1427
1428 let title_field = schema.get_field("title").unwrap();
1429 let title_entry = schema.get_field_entry(title_field).unwrap();
1430 assert_eq!(title_entry.tokenizer, Some("german".to_string()));
1431
1432 let body_field = schema.get_field("body").unwrap();
1433 let body_entry = schema.get_field_entry(body_field).unwrap();
1434 assert_eq!(body_entry.tokenizer, Some("en_stem".to_string()));
1435 }
1436
1437 #[test]
1438 fn test_query_router_basic() {
1439 let sdl = r#"
1440 index documents {
1441 field title: text [indexed, stored]
1442 field uri: text [indexed, stored]
1443
1444 query_router {
1445 pattern: "10\\.\\d{4,}/[^\\s]+"
1446 substitution: "doi://{0}"
1447 target_field: uris
1448 mode: exclusive
1449 }
1450 }
1451 "#;
1452
1453 let indexes = parse_sdl(sdl).unwrap();
1454 let index = &indexes[0];
1455
1456 assert_eq!(index.query_routers.len(), 1);
1457 let router = &index.query_routers[0];
1458 assert_eq!(router.pattern, r"10\.\d{4,}/[^\s]+");
1459 assert_eq!(router.substitution, "doi://{0}");
1460 assert_eq!(router.target_field, "uris");
1461 assert_eq!(router.mode, RoutingMode::Exclusive);
1462 }
1463
1464 #[test]
1465 fn test_query_router_raw_string() {
1466 let sdl = r#"
1467 index documents {
1468 field uris: text [indexed, stored]
1469
1470 query_router {
1471 pattern: r"^pmid:(\d+)$"
1472 substitution: "pubmed://{1}"
1473 target_field: uris
1474 mode: additional
1475 }
1476 }
1477 "#;
1478
1479 let indexes = parse_sdl(sdl).unwrap();
1480 let router = &indexes[0].query_routers[0];
1481
1482 assert_eq!(router.pattern, r"^pmid:(\d+)$");
1483 assert_eq!(router.substitution, "pubmed://{1}");
1484 assert_eq!(router.mode, RoutingMode::Additional);
1485 }
1486
1487 #[test]
1488 fn test_multiple_query_routers() {
1489 let sdl = r#"
1490 index documents {
1491 field uris: text [indexed, stored]
1492
1493 query_router {
1494 pattern: r"^doi:(10\.\d{4,}/[^\s]+)$"
1495 substitution: "doi://{1}"
1496 target_field: uris
1497 mode: exclusive
1498 }
1499
1500 query_router {
1501 pattern: r"^pmid:(\d+)$"
1502 substitution: "pubmed://{1}"
1503 target_field: uris
1504 mode: exclusive
1505 }
1506
1507 query_router {
1508 pattern: r"^arxiv:(\d+\.\d+)$"
1509 substitution: "arxiv://{1}"
1510 target_field: uris
1511 mode: additional
1512 }
1513 }
1514 "#;
1515
1516 let indexes = parse_sdl(sdl).unwrap();
1517 assert_eq!(indexes[0].query_routers.len(), 3);
1518 }
1519
1520 #[test]
1521 fn test_query_router_default_mode() {
1522 let sdl = r#"
1523 index documents {
1524 field uris: text [indexed, stored]
1525
1526 query_router {
1527 pattern: r"test"
1528 substitution: "{0}"
1529 target_field: uris
1530 }
1531 }
1532 "#;
1533
1534 let indexes = parse_sdl(sdl).unwrap();
1535 assert_eq!(indexes[0].query_routers[0].mode, RoutingMode::Additional);
1537 }
1538
1539 #[test]
1540 fn test_multi_attribute() {
1541 let sdl = r#"
1542 index documents {
1543 field uris: text [indexed, stored<multi>]
1544 field title: text [indexed, stored]
1545 }
1546 "#;
1547
1548 let indexes = parse_sdl(sdl).unwrap();
1549 assert_eq!(indexes.len(), 1);
1550
1551 let fields = &indexes[0].fields;
1552 assert_eq!(fields.len(), 2);
1553
1554 assert_eq!(fields[0].name, "uris");
1556 assert!(fields[0].multi, "uris field should have multi=true");
1557
1558 assert_eq!(fields[1].name, "title");
1560 assert!(!fields[1].multi, "title field should have multi=false");
1561
1562 let schema = indexes[0].to_schema();
1564 let uris_field = schema.get_field("uris").unwrap();
1565 let title_field = schema.get_field("title").unwrap();
1566
1567 assert!(schema.get_field_entry(uris_field).unwrap().multi);
1568 assert!(!schema.get_field_entry(title_field).unwrap().multi);
1569 }
1570
1571 #[test]
1572 fn test_sparse_vector_field() {
1573 let sdl = r#"
1574 index documents {
1575 field embedding: sparse_vector [indexed, stored]
1576 }
1577 "#;
1578
1579 let indexes = parse_sdl(sdl).unwrap();
1580 assert_eq!(indexes.len(), 1);
1581 assert_eq!(indexes[0].fields.len(), 1);
1582 assert_eq!(indexes[0].fields[0].name, "embedding");
1583 assert_eq!(indexes[0].fields[0].field_type, FieldType::SparseVector);
1584 assert!(indexes[0].fields[0].sparse_vector_config.is_none());
1585 }
1586
1587 #[test]
1588 fn test_sparse_vector_with_config() {
1589 let sdl = r#"
1590 index documents {
1591 field embedding: sparse_vector<u16> [indexed<quantization: uint8>, stored]
1592 field dense: sparse_vector<u32> [indexed<quantization: float32>]
1593 }
1594 "#;
1595
1596 let indexes = parse_sdl(sdl).unwrap();
1597 assert_eq!(indexes[0].fields.len(), 2);
1598
1599 let f1 = &indexes[0].fields[0];
1601 assert_eq!(f1.name, "embedding");
1602 let config1 = f1.sparse_vector_config.as_ref().unwrap();
1603 assert_eq!(config1.index_size, IndexSize::U16);
1604 assert_eq!(config1.weight_quantization, WeightQuantization::UInt8);
1605
1606 let f2 = &indexes[0].fields[1];
1608 assert_eq!(f2.name, "dense");
1609 let config2 = f2.sparse_vector_config.as_ref().unwrap();
1610 assert_eq!(config2.index_size, IndexSize::U32);
1611 assert_eq!(config2.weight_quantization, WeightQuantization::Float32);
1612 }
1613
1614 #[test]
1615 fn test_sparse_vector_bmp_block_size() {
1616 let sdl = r#"
1617 index documents {
1618 field emb: sparse_vector<u32> [indexed<format: bmp, dims: 105879, bmp_block_size: 256>]
1619 field emb2: sparse_vector<u32> [indexed<format: bmp, dims: 30522>]
1620 }
1621 "#;
1622
1623 let indexes = parse_sdl(sdl).unwrap();
1624 let config1 = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
1625 assert_eq!(config1.format, SparseFormat::Bmp);
1626 assert_eq!(config1.bmp_block_size, 256);
1627
1628 let config2 = indexes[0].fields[1].sparse_vector_config.as_ref().unwrap();
1630 assert_eq!(config2.bmp_block_size, 64);
1631 }
1632
1633 #[test]
1634 fn test_sparse_vector_with_weight_threshold() {
1635 let sdl = r#"
1636 index documents {
1637 field embedding: sparse_vector<u16> [indexed<quantization: uint8, weight_threshold: 0.1>, stored]
1638 field embedding2: sparse_vector<u32> [indexed<quantization: float16, weight_threshold: 0.05>]
1639 }
1640 "#;
1641
1642 let indexes = parse_sdl(sdl).unwrap();
1643 assert_eq!(indexes[0].fields.len(), 2);
1644
1645 let f1 = &indexes[0].fields[0];
1647 assert_eq!(f1.name, "embedding");
1648 let config1 = f1.sparse_vector_config.as_ref().unwrap();
1649 assert_eq!(config1.index_size, IndexSize::U16);
1650 assert_eq!(config1.weight_quantization, WeightQuantization::UInt8);
1651 assert!((config1.weight_threshold - 0.1).abs() < 0.001);
1652
1653 let f2 = &indexes[0].fields[1];
1655 assert_eq!(f2.name, "embedding2");
1656 let config2 = f2.sparse_vector_config.as_ref().unwrap();
1657 assert_eq!(config2.index_size, IndexSize::U32);
1658 assert_eq!(config2.weight_quantization, WeightQuantization::Float16);
1659 assert!((config2.weight_threshold - 0.05).abs() < 0.001);
1660 }
1661
1662 #[test]
1663 fn test_sparse_vector_with_pruning() {
1664 let sdl = r#"
1665 index documents {
1666 field embedding: sparse_vector [indexed<quantization: uint8, pruning: 0.1>, stored]
1667 }
1668 "#;
1669
1670 let indexes = parse_sdl(sdl).unwrap();
1671 let f = &indexes[0].fields[0];
1672 assert_eq!(f.name, "embedding");
1673 let config = f.sparse_vector_config.as_ref().unwrap();
1674 assert_eq!(config.weight_quantization, WeightQuantization::UInt8);
1675 assert_eq!(config.pruning, Some(0.1));
1676 }
1677
1678 #[test]
1679 fn test_sparse_vector_with_doc_mass() {
1680 let sdl = r#"
1681 index documents {
1682 field embedding: sparse_vector [indexed<quantization: uint8, doc_mass: 0.9>, stored]
1683 }
1684 "#;
1685
1686 let indexes = parse_sdl(sdl).unwrap();
1687 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
1688 assert_eq!(config.doc_mass, Some(0.9));
1689
1690 let sdl = r#"
1692 index documents {
1693 field embedding: sparse_vector [indexed<quantization: uint8>]
1694 }
1695 "#;
1696 let indexes = parse_sdl(sdl).unwrap();
1697 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
1698 assert_eq!(config.doc_mass, None);
1699 }
1700
1701 #[test]
1702 fn test_dense_vector_field() {
1703 let sdl = r#"
1704 index documents {
1705 field embedding: dense_vector<768> [indexed, stored]
1706 }
1707 "#;
1708
1709 let indexes = parse_sdl(sdl).unwrap();
1710 assert_eq!(indexes.len(), 1);
1711 assert_eq!(indexes[0].fields.len(), 1);
1712
1713 let f = &indexes[0].fields[0];
1714 assert_eq!(f.name, "embedding");
1715 assert_eq!(f.field_type, FieldType::DenseVector);
1716
1717 let config = f.dense_vector_config.as_ref().unwrap();
1718 assert_eq!(config.dim, 768);
1719 }
1720
1721 #[test]
1722 fn test_dense_vector_alias() {
1723 let sdl = r#"
1724 index documents {
1725 field embedding: vector<1536> [indexed]
1726 }
1727 "#;
1728
1729 let indexes = parse_sdl(sdl).unwrap();
1730 assert_eq!(indexes[0].fields[0].field_type, FieldType::DenseVector);
1731 assert_eq!(
1732 indexes[0].fields[0]
1733 .dense_vector_config
1734 .as_ref()
1735 .unwrap()
1736 .dim,
1737 1536
1738 );
1739 }
1740
1741 #[test]
1742 fn test_dense_vector_with_num_clusters() {
1743 let sdl = r#"
1744 index documents {
1745 field embedding: dense_vector<768> [indexed<ivf_rabitq, num_clusters: 256>, stored]
1746 }
1747 "#;
1748
1749 let indexes = parse_sdl(sdl).unwrap();
1750 assert_eq!(indexes.len(), 1);
1751
1752 let f = &indexes[0].fields[0];
1753 assert_eq!(f.name, "embedding");
1754 assert_eq!(f.field_type, FieldType::DenseVector);
1755
1756 let config = f.dense_vector_config.as_ref().unwrap();
1757 assert_eq!(config.dim, 768);
1758 assert_eq!(config.num_clusters, Some(256));
1759 assert_eq!(config.nprobe, 32); }
1761
1762 #[test]
1763 fn test_dense_vector_with_soar() {
1764 let sdl = r#"
1765 index documents {
1766 field embedding: dense_vector<768> [indexed<ivf_rabitq, num_clusters: 256, soar: selective>, stored]
1767 }
1768 "#;
1769
1770 let indexes = parse_sdl(sdl).unwrap();
1771 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1772
1773 let soar = config.soar.as_ref().expect("soar should be enabled");
1774 assert_eq!(soar.num_secondary, 1);
1775 assert!(soar.selective);
1776
1777 let sdl = r#"
1779 index documents {
1780 field embedding: dense_vector<768> [indexed<scann, soar: aggressive>]
1781 }
1782 "#;
1783 let indexes = parse_sdl(sdl).unwrap();
1784 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1785 let soar = config.soar.as_ref().expect("soar should be enabled");
1786 assert_eq!(soar.num_secondary, 2);
1787 assert!(!soar.selective);
1788
1789 let sdl = r#"
1791 index documents {
1792 field embedding: dense_vector<768> [indexed<ivf_rabitq, soar: off>]
1793 }
1794 "#;
1795 let indexes = parse_sdl(sdl).unwrap();
1796 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1797 assert!(config.soar.is_none());
1798 }
1799
1800 #[test]
1801 fn test_binary_dense_vector_with_ivf() {
1802 let sdl = r#"
1803 index documents {
1804 field hash: binary_dense_vector<512> [indexed<ivf, num_clusters: 128, nprobe: 16>, stored]
1805 }
1806 "#;
1807
1808 let indexes = parse_sdl(sdl).unwrap();
1809 let config = indexes[0].fields[0]
1810 .binary_dense_vector_config
1811 .as_ref()
1812 .unwrap();
1813 assert_eq!(config.dim, 512);
1814 assert_eq!(
1815 config.index_type,
1816 super::super::schema::BinaryIndexType::Ivf
1817 );
1818 assert_eq!(config.num_clusters, Some(128));
1819 assert_eq!(config.nprobe, 16);
1820
1821 let sdl = r#"
1823 index documents {
1824 field hash: binary_dense_vector<512> [indexed]
1825 }
1826 "#;
1827 let indexes = parse_sdl(sdl).unwrap();
1828 let config = indexes[0].fields[0]
1829 .binary_dense_vector_config
1830 .as_ref()
1831 .unwrap();
1832 assert_eq!(
1833 config.index_type,
1834 super::super::schema::BinaryIndexType::Flat
1835 );
1836 }
1837
1838 #[test]
1839 fn test_dense_vector_with_rabitq_bits() {
1840 let sdl = r#"
1841 index documents {
1842 field embedding: dense_vector<768> [indexed<ivf_rabitq, bits: 4>, stored]
1843 }
1844 "#;
1845
1846 let indexes = parse_sdl(sdl).unwrap();
1847 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1848 assert_eq!(config.rabitq_bits, Some(4));
1849
1850 let sdl = r#"
1852 index documents {
1853 field embedding: dense_vector<768> [indexed<ivf_rabitq>]
1854 }
1855 "#;
1856 let indexes = parse_sdl(sdl).unwrap();
1857 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1858 assert_eq!(config.rabitq_bits, None);
1859 }
1860
1861 #[test]
1862 fn test_dense_vector_with_num_clusters_and_nprobe() {
1863 let sdl = r#"
1864 index documents {
1865 field embedding: dense_vector<1536> [indexed<ivf_rabitq, num_clusters: 512, nprobe: 64>]
1866 }
1867 "#;
1868
1869 let indexes = parse_sdl(sdl).unwrap();
1870 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1871
1872 assert_eq!(config.dim, 1536);
1873 assert_eq!(config.num_clusters, Some(512));
1874 assert_eq!(config.nprobe, 64);
1875 }
1876
1877 #[test]
1878 fn test_dense_vector_keyword_syntax() {
1879 let sdl = r#"
1880 index documents {
1881 field embedding: dense_vector<dims: 1536> [indexed, stored]
1882 }
1883 "#;
1884
1885 let indexes = parse_sdl(sdl).unwrap();
1886 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1887
1888 assert_eq!(config.dim, 1536);
1889 assert!(config.num_clusters.is_none());
1890 }
1891
1892 #[test]
1893 fn test_dense_vector_keyword_syntax_full() {
1894 let sdl = r#"
1895 index documents {
1896 field embedding: dense_vector<dims: 1536> [indexed<ivf_rabitq, num_clusters: 256, nprobe: 64>]
1897 }
1898 "#;
1899
1900 let indexes = parse_sdl(sdl).unwrap();
1901 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1902
1903 assert_eq!(config.dim, 1536);
1904 assert_eq!(config.num_clusters, Some(256));
1905 assert_eq!(config.nprobe, 64);
1906 }
1907
1908 #[test]
1909 fn test_dense_vector_keyword_syntax_partial() {
1910 let sdl = r#"
1911 index documents {
1912 field embedding: dense_vector<dims: 768> [indexed<ivf_rabitq, num_clusters: 128>]
1913 }
1914 "#;
1915
1916 let indexes = parse_sdl(sdl).unwrap();
1917 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1918
1919 assert_eq!(config.dim, 768);
1920 assert_eq!(config.num_clusters, Some(128));
1921 assert_eq!(config.nprobe, 32); }
1923
1924 #[test]
1925 fn test_dense_vector_scann_index() {
1926 use crate::dsl::schema::VectorIndexType;
1927
1928 let sdl = r#"
1929 index documents {
1930 field embedding: dense_vector<dims: 768> [indexed<scann, 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, 768);
1938 assert_eq!(config.index_type, VectorIndexType::ScaNN);
1939 assert_eq!(config.num_clusters, Some(256));
1940 assert_eq!(config.nprobe, 64);
1941 }
1942
1943 #[test]
1944 fn test_dense_vector_ivf_rabitq_index() {
1945 use crate::dsl::schema::VectorIndexType;
1946
1947 let sdl = r#"
1948 index documents {
1949 field embedding: dense_vector<dims: 1536> [indexed<ivf_rabitq, num_clusters: 512>]
1950 }
1951 "#;
1952
1953 let indexes = parse_sdl(sdl).unwrap();
1954 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1955
1956 assert_eq!(config.dim, 1536);
1957 assert_eq!(config.index_type, VectorIndexType::IvfRaBitQ);
1958 assert_eq!(config.num_clusters, Some(512));
1959 }
1960
1961 #[test]
1962 fn test_dense_vector_rabitq_no_clusters() {
1963 use crate::dsl::schema::VectorIndexType;
1964
1965 let sdl = r#"
1966 index documents {
1967 field embedding: dense_vector<dims: 768> [indexed<rabitq>]
1968 }
1969 "#;
1970
1971 let indexes = parse_sdl(sdl).unwrap();
1972 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1973
1974 assert_eq!(config.dim, 768);
1975 assert_eq!(config.index_type, VectorIndexType::RaBitQ);
1976 assert!(config.num_clusters.is_none());
1977 }
1978
1979 #[test]
1980 fn test_dense_vector_flat_index() {
1981 use crate::dsl::schema::VectorIndexType;
1982
1983 let sdl = r#"
1984 index documents {
1985 field embedding: dense_vector<dims: 768> [indexed<flat>]
1986 }
1987 "#;
1988
1989 let indexes = parse_sdl(sdl).unwrap();
1990 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
1991
1992 assert_eq!(config.dim, 768);
1993 assert_eq!(config.index_type, VectorIndexType::Flat);
1994 }
1995
1996 #[test]
1997 fn test_dense_vector_default_index_type() {
1998 use crate::dsl::schema::VectorIndexType;
1999
2000 let sdl = r#"
2002 index documents {
2003 field embedding: dense_vector<dims: 768> [indexed]
2004 }
2005 "#;
2006
2007 let indexes = parse_sdl(sdl).unwrap();
2008 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2009
2010 assert_eq!(config.dim, 768);
2011 assert_eq!(config.index_type, VectorIndexType::RaBitQ);
2012 }
2013
2014 #[test]
2015 fn test_dense_vector_f16_quantization() {
2016 use crate::dsl::schema::{DenseVectorQuantization, VectorIndexType};
2017
2018 let sdl = r#"
2019 index documents {
2020 field embedding: dense_vector<768, f16> [indexed]
2021 }
2022 "#;
2023
2024 let indexes = parse_sdl(sdl).unwrap();
2025 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2026
2027 assert_eq!(config.dim, 768);
2028 assert_eq!(config.quantization, DenseVectorQuantization::F16);
2029 assert_eq!(config.index_type, VectorIndexType::RaBitQ);
2030 }
2031
2032 #[test]
2033 fn test_dense_vector_uint8_quantization() {
2034 use crate::dsl::schema::DenseVectorQuantization;
2035
2036 let sdl = r#"
2037 index documents {
2038 field embedding: dense_vector<1024, uint8> [indexed<ivf_rabitq>]
2039 }
2040 "#;
2041
2042 let indexes = parse_sdl(sdl).unwrap();
2043 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2044
2045 assert_eq!(config.dim, 1024);
2046 assert_eq!(config.quantization, DenseVectorQuantization::UInt8);
2047 }
2048
2049 #[test]
2050 fn test_dense_vector_u8_alias() {
2051 use crate::dsl::schema::DenseVectorQuantization;
2052
2053 let sdl = r#"
2054 index documents {
2055 field embedding: dense_vector<512, u8> [indexed]
2056 }
2057 "#;
2058
2059 let indexes = parse_sdl(sdl).unwrap();
2060 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2061
2062 assert_eq!(config.dim, 512);
2063 assert_eq!(config.quantization, DenseVectorQuantization::UInt8);
2064 }
2065
2066 #[test]
2067 fn test_dense_vector_default_f32_quantization() {
2068 use crate::dsl::schema::DenseVectorQuantization;
2069
2070 let sdl = r#"
2072 index documents {
2073 field embedding: dense_vector<768> [indexed]
2074 }
2075 "#;
2076
2077 let indexes = parse_sdl(sdl).unwrap();
2078 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2079
2080 assert_eq!(config.dim, 768);
2081 assert_eq!(config.quantization, DenseVectorQuantization::F32);
2082 }
2083
2084 #[test]
2085 fn test_dense_vector_keyword_with_quantization() {
2086 use crate::dsl::schema::DenseVectorQuantization;
2087
2088 let sdl = r#"
2089 index documents {
2090 field embedding: dense_vector<dims: 768, f16> [indexed]
2091 }
2092 "#;
2093
2094 let indexes = parse_sdl(sdl).unwrap();
2095 let config = indexes[0].fields[0].dense_vector_config.as_ref().unwrap();
2096
2097 assert_eq!(config.dim, 768);
2098 assert_eq!(config.quantization, DenseVectorQuantization::F16);
2099 }
2100
2101 #[test]
2102 fn test_json_field_type() {
2103 let sdl = r#"
2104 index documents {
2105 field title: text [indexed, stored]
2106 field metadata: json [stored]
2107 field extra: json
2108 }
2109 "#;
2110
2111 let indexes = parse_sdl(sdl).unwrap();
2112 let index = &indexes[0];
2113
2114 assert_eq!(index.fields.len(), 3);
2115
2116 assert_eq!(index.fields[1].name, "metadata");
2118 assert!(matches!(index.fields[1].field_type, FieldType::Json));
2119 assert!(index.fields[1].stored);
2120 assert_eq!(index.fields[2].name, "extra");
2124 assert!(matches!(index.fields[2].field_type, FieldType::Json));
2125
2126 let schema = index.to_schema();
2128 let metadata_field = schema.get_field("metadata").unwrap();
2129 let entry = schema.get_field_entry(metadata_field).unwrap();
2130 assert_eq!(entry.field_type, FieldType::Json);
2131 assert!(!entry.indexed); assert!(entry.stored);
2133 }
2134
2135 #[test]
2136 fn test_sparse_vector_query_config() {
2137 use crate::structures::QueryWeighting;
2138
2139 let sdl = r#"
2140 index documents {
2141 field embedding: sparse_vector<u16> [indexed<quantization: uint8, query<tokenizer: "Alibaba-NLP/gte-Qwen2-1.5B-instruct", weighting: idf>>]
2142 }
2143 "#;
2144
2145 let indexes = parse_sdl(sdl).unwrap();
2146 let index = &indexes[0];
2147
2148 assert_eq!(index.fields.len(), 1);
2149 assert_eq!(index.fields[0].name, "embedding");
2150 assert!(matches!(
2151 index.fields[0].field_type,
2152 FieldType::SparseVector
2153 ));
2154
2155 let config = index.fields[0].sparse_vector_config.as_ref().unwrap();
2156 assert_eq!(config.index_size, IndexSize::U16);
2157 assert_eq!(config.weight_quantization, WeightQuantization::UInt8);
2158
2159 let query_config = config.query_config.as_ref().unwrap();
2161 assert_eq!(
2162 query_config.tokenizer.as_deref(),
2163 Some("Alibaba-NLP/gte-Qwen2-1.5B-instruct")
2164 );
2165 assert_eq!(query_config.weighting, QueryWeighting::Idf);
2166
2167 let schema = index.to_schema();
2169 let embedding_field = schema.get_field("embedding").unwrap();
2170 let entry = schema.get_field_entry(embedding_field).unwrap();
2171 let sv_config = entry.sparse_vector_config.as_ref().unwrap();
2172 let qc = sv_config.query_config.as_ref().unwrap();
2173 assert_eq!(
2174 qc.tokenizer.as_deref(),
2175 Some("Alibaba-NLP/gte-Qwen2-1.5B-instruct")
2176 );
2177 assert_eq!(qc.weighting, QueryWeighting::Idf);
2178 }
2179
2180 #[test]
2181 fn test_sparse_vector_query_config_weighting_one() {
2182 use crate::structures::QueryWeighting;
2183
2184 let sdl = r#"
2185 index documents {
2186 field embedding: sparse_vector [indexed<query<weighting: one>>]
2187 }
2188 "#;
2189
2190 let indexes = parse_sdl(sdl).unwrap();
2191 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2192
2193 let query_config = config.query_config.as_ref().unwrap();
2194 assert!(query_config.tokenizer.is_none());
2195 assert_eq!(query_config.weighting, QueryWeighting::One);
2196 }
2197
2198 #[test]
2199 fn test_sparse_vector_query_config_weighting_idf_file() {
2200 use crate::structures::QueryWeighting;
2201
2202 let sdl = r#"
2203 index documents {
2204 field embedding: sparse_vector<u16> [indexed<quantization: uint8, query<tokenizer: "opensearch-neural-sparse-encoding-v1", weighting: idf_file>>]
2205 }
2206 "#;
2207
2208 let indexes = parse_sdl(sdl).unwrap();
2209 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2210
2211 let query_config = config.query_config.as_ref().unwrap();
2212 assert_eq!(
2213 query_config.tokenizer.as_deref(),
2214 Some("opensearch-neural-sparse-encoding-v1")
2215 );
2216 assert_eq!(query_config.weighting, QueryWeighting::IdfFile);
2217
2218 let schema = indexes[0].to_schema();
2220 let field = schema.get_field("embedding").unwrap();
2221 let entry = schema.get_field_entry(field).unwrap();
2222 let sc = entry.sparse_vector_config.as_ref().unwrap();
2223 let qc = sc.query_config.as_ref().unwrap();
2224 assert_eq!(qc.weighting, QueryWeighting::IdfFile);
2225 }
2226
2227 #[test]
2228 fn test_sparse_vector_query_config_pruning_params() {
2229 let sdl = r#"
2230 index documents {
2231 field embedding: sparse_vector<u16> [indexed<quantization: uint8, query<weighting: idf, weight_threshold: 0.03, max_dims: 25, pruning: 0.2>>]
2232 }
2233 "#;
2234
2235 let indexes = parse_sdl(sdl).unwrap();
2236 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2237
2238 let qc = config.query_config.as_ref().unwrap();
2239 assert_eq!(qc.weighting, QueryWeighting::Idf);
2240 assert!((qc.weight_threshold - 0.03).abs() < 0.001);
2241 assert_eq!(qc.max_query_dims, Some(25));
2242 assert!((qc.pruning.unwrap() - 0.2).abs() < 0.001);
2243
2244 let schema = indexes[0].to_schema();
2246 let field = schema.get_field("embedding").unwrap();
2247 let entry = schema.get_field_entry(field).unwrap();
2248 let sc = entry.sparse_vector_config.as_ref().unwrap();
2249 let rqc = sc.query_config.as_ref().unwrap();
2250 assert!((rqc.weight_threshold - 0.03).abs() < 0.001);
2251 assert_eq!(rqc.max_query_dims, Some(25));
2252 assert!((rqc.pruning.unwrap() - 0.2).abs() < 0.001);
2253 }
2254
2255 #[test]
2256 fn test_sparse_vector_format_maxscore() {
2257 let sdl = r#"
2258 index documents {
2259 field embedding: sparse_vector<u16> [indexed<format: maxscore, quantization: uint8>]
2260 }
2261 "#;
2262
2263 let indexes = parse_sdl(sdl).unwrap();
2264 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2265 assert_eq!(config.format, SparseFormat::MaxScore);
2266 assert_eq!(config.weight_quantization, WeightQuantization::UInt8);
2267
2268 let schema = indexes[0].to_schema();
2270 let field = schema.get_field("embedding").unwrap();
2271 let entry = schema.get_field_entry(field).unwrap();
2272 let sc = entry.sparse_vector_config.as_ref().unwrap();
2273 assert_eq!(sc.format, SparseFormat::MaxScore);
2274 }
2275
2276 #[test]
2277 fn test_sparse_vector_format_bmp() {
2278 let sdl = r#"
2279 index documents {
2280 field embedding: sparse_vector<u16> [indexed<format: bmp, quantization: uint8>]
2281 }
2282 "#;
2283
2284 let indexes = parse_sdl(sdl).unwrap();
2285 let config = indexes[0].fields[0].sparse_vector_config.as_ref().unwrap();
2286 assert_eq!(config.format, SparseFormat::Bmp);
2287 }
2288
2289 #[test]
2290 fn test_fast_attribute() {
2291 let sdl = r#"
2292 index products {
2293 field name: text [indexed, stored]
2294 field price: f64 [indexed, fast]
2295 field category: text [indexed, stored, fast]
2296 field count: u64 [fast]
2297 field score: i64 [indexed, stored, fast]
2298 }
2299 "#;
2300
2301 let indexes = parse_sdl(sdl).unwrap();
2302 assert_eq!(indexes.len(), 1);
2303 let index = &indexes[0];
2304 assert_eq!(index.fields.len(), 5);
2305
2306 assert!(!index.fields[0].fast);
2308 assert!(index.fields[1].fast);
2310 assert!(matches!(index.fields[1].field_type, FieldType::F64));
2311 assert!(index.fields[2].fast);
2313 assert!(matches!(index.fields[2].field_type, FieldType::Text));
2314 assert!(index.fields[3].fast);
2316 assert!(matches!(index.fields[3].field_type, FieldType::U64));
2317 assert!(index.fields[4].fast);
2319 assert!(matches!(index.fields[4].field_type, FieldType::I64));
2320
2321 let schema = index.to_schema();
2323 let price_field = schema.get_field("price").unwrap();
2324 assert!(schema.get_field_entry(price_field).unwrap().fast);
2325
2326 let category_field = schema.get_field("category").unwrap();
2327 assert!(schema.get_field_entry(category_field).unwrap().fast);
2328
2329 let name_field = schema.get_field("name").unwrap();
2330 assert!(!schema.get_field_entry(name_field).unwrap().fast);
2331 }
2332
2333 #[test]
2334 fn test_primary_attribute() {
2335 let sdl = r#"
2336 index documents {
2337 field id: text [primary, stored]
2338 field title: text [indexed, stored]
2339 }
2340 "#;
2341
2342 let indexes = parse_sdl(sdl).unwrap();
2343 assert_eq!(indexes.len(), 1);
2344 let index = &indexes[0];
2345 assert_eq!(index.fields.len(), 2);
2346
2347 let id_field = &index.fields[0];
2349 assert!(id_field.primary, "id should be primary");
2350 assert!(id_field.fast, "primary implies fast");
2351 assert!(id_field.indexed, "primary implies indexed");
2352
2353 assert!(!index.fields[1].primary);
2355
2356 let schema = index.to_schema();
2358 let id = schema.get_field("id").unwrap();
2359 let id_entry = schema.get_field_entry(id).unwrap();
2360 assert!(id_entry.primary_key);
2361 assert!(id_entry.fast);
2362 assert!(id_entry.indexed);
2363
2364 let title = schema.get_field("title").unwrap();
2365 assert!(!schema.get_field_entry(title).unwrap().primary_key);
2366
2367 assert_eq!(schema.primary_field(), Some(id));
2369 }
2370
2371 #[test]
2372 fn test_primary_with_other_attributes() {
2373 let sdl = r#"
2374 index documents {
2375 field id: text<simple> [primary, indexed, stored]
2376 field body: text [indexed]
2377 }
2378 "#;
2379
2380 let indexes = parse_sdl(sdl).unwrap();
2381 let id_field = &indexes[0].fields[0];
2382 assert!(id_field.primary);
2383 assert!(id_field.indexed);
2384 assert!(id_field.stored);
2385 assert!(id_field.fast);
2386 assert_eq!(id_field.tokenizer, Some("simple".to_string()));
2387 }
2388
2389 #[test]
2390 fn test_primary_only_one_allowed() {
2391 let sdl = r#"
2392 index documents {
2393 field id: text [primary]
2394 field alt_id: text [primary]
2395 }
2396 "#;
2397
2398 let result = parse_sdl(sdl);
2399 assert!(result.is_err());
2400 let err = result.unwrap_err().to_string();
2401 assert!(
2402 err.contains("primary key"),
2403 "Error should mention primary key: {}",
2404 err
2405 );
2406 }
2407
2408 #[test]
2409 fn test_primary_must_be_text() {
2410 let sdl = r#"
2411 index documents {
2412 field id: u64 [primary]
2413 }
2414 "#;
2415
2416 let result = parse_sdl(sdl);
2417 assert!(result.is_err());
2418 let err = result.unwrap_err().to_string();
2419 assert!(
2420 err.contains("text"),
2421 "Error should mention text type: {}",
2422 err
2423 );
2424 }
2425
2426 #[test]
2427 fn test_primary_cannot_be_multi() {
2428 let sdl = r#"
2429 index documents {
2430 field id: text [primary, stored<multi>]
2431 }
2432 "#;
2433
2434 let result = parse_sdl(sdl);
2435 assert!(result.is_err());
2436 let err = result.unwrap_err().to_string();
2437 assert!(err.contains("multi"), "Error should mention multi: {}", err);
2438 }
2439
2440 #[test]
2441 fn test_no_primary_field() {
2442 let sdl = r#"
2444 index documents {
2445 field title: text [indexed, stored]
2446 }
2447 "#;
2448
2449 let indexes = parse_sdl(sdl).unwrap();
2450 let schema = indexes[0].to_schema();
2451 assert!(schema.primary_field().is_none());
2452 }
2453
2454 #[test]
2455 fn test_reorder_attribute() {
2456 let sdl = r#"
2457 index documents {
2458 field embedding: sparse_vector<u16> [indexed<format: bmp, quantization: uint8>, reorder]
2459 field embedding2: sparse_vector [indexed<format: bmp>]
2460 }
2461 "#;
2462
2463 let indexes = parse_sdl(sdl).unwrap();
2464 assert_eq!(indexes[0].fields.len(), 2);
2465
2466 assert!(indexes[0].fields[0].reorder);
2468 assert!(!indexes[0].fields[1].reorder);
2470
2471 let schema = indexes[0].to_schema();
2473 let f1 = schema.get_field("embedding").unwrap();
2474 assert!(schema.get_field_entry(f1).unwrap().reorder);
2475
2476 let f2 = schema.get_field("embedding2").unwrap();
2477 assert!(!schema.get_field_entry(f2).unwrap().reorder);
2478
2479 assert!(!schema.reorder_on_merge());
2481 }
2482
2483 #[test]
2484 fn test_reorder_on_merge_index_option() {
2485 let sdl = r#"
2486 index documents {
2487 reorder_on_merge: true
2488 field embedding: sparse_vector<u16> [indexed<format: bmp>, reorder]
2489 }
2490 "#;
2491
2492 let indexes = parse_sdl(sdl).unwrap();
2493 assert!(indexes[0].reorder_on_merge);
2494 let schema = indexes[0].to_schema();
2495 assert!(schema.reorder_on_merge());
2496
2497 let sdl_off = r#"
2499 index documents {
2500 reorder_on_merge: false
2501 field embedding: sparse_vector<u16> [indexed<format: bmp>, reorder]
2502 }
2503 "#;
2504 let indexes = parse_sdl(sdl_off).unwrap();
2505 assert!(!indexes[0].reorder_on_merge);
2506 assert!(!indexes[0].to_schema().reorder_on_merge());
2507
2508 let schema_on = parse_sdl(sdl).unwrap()[0].to_schema();
2510 let json = serde_json::to_string(&schema_on).unwrap();
2511 let back: crate::dsl::Schema = serde_json::from_str(&json).unwrap();
2512 assert!(back.reorder_on_merge());
2513 }
2514}