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