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