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