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