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