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