1use std::collections::HashMap;
16use std::str::FromStr;
17use std::{fmt, mem};
18
19use arrow::datatypes::Field;
20use arrow_schema::extension::{
21 EXTENSION_TYPE_METADATA_KEY, EXTENSION_TYPE_NAME_KEY, ExtensionType,
22};
23use serde::{Deserialize, Serialize};
24use snafu::{ResultExt, ensure};
25use sqlparser_derive::{Visit, VisitMut};
26
27use crate::data_type::{ConcreteDataType, DataType};
28use crate::error::{
29 self, ArrowMetadataSnafu, Error, InvalidFulltextOptionSnafu, ParseExtendedTypeSnafu, Result,
30};
31use crate::extension::json::Json2ExtensionType;
32use crate::schema::TYPE_KEY;
33use crate::schema::constraint::ColumnDefaultConstraint;
34use crate::value::Value;
35use crate::vectors::json::builder::JsonVectorBuilder;
36use crate::vectors::{MutableVector, VectorRef};
37
38pub type Metadata = HashMap<String, String>;
39
40pub const TIME_INDEX_KEY: &str = "greptime:time_index";
42pub const COMMENT_KEY: &str = "greptime:storage:comment";
43const DEFAULT_CONSTRAINT_KEY: &str = "greptime:default_constraint";
45pub const FULLTEXT_KEY: &str = "greptime:fulltext";
47pub const INVERTED_INDEX_KEY: &str = "greptime:inverted_index";
49pub const SKIPPING_INDEX_KEY: &str = "greptime:skipping_index";
51pub const VECTOR_INDEX_KEY: &str = "greptime:vector_index";
53
54pub const COLUMN_FULLTEXT_CHANGE_OPT_KEY_ENABLE: &str = "enable";
56pub const COLUMN_FULLTEXT_OPT_KEY_ANALYZER: &str = "analyzer";
57pub const COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE: &str = "case_sensitive";
58pub const COLUMN_FULLTEXT_OPT_KEY_BACKEND: &str = "backend";
59pub const COLUMN_FULLTEXT_OPT_KEY_GRANULARITY: &str = "granularity";
60pub const COLUMN_FULLTEXT_OPT_KEY_FALSE_POSITIVE_RATE: &str = "false_positive_rate";
61
62pub const COLUMN_SKIPPING_INDEX_OPT_KEY_GRANULARITY: &str = "granularity";
64pub const COLUMN_SKIPPING_INDEX_OPT_KEY_FALSE_POSITIVE_RATE: &str = "false_positive_rate";
65pub const COLUMN_SKIPPING_INDEX_OPT_KEY_TYPE: &str = "type";
66
67pub const COLUMN_VECTOR_INDEX_OPT_KEY_ENGINE: &str = "engine";
69pub const COLUMN_VECTOR_INDEX_OPT_KEY_METRIC: &str = "metric";
70pub const COLUMN_VECTOR_INDEX_OPT_KEY_CONNECTIVITY: &str = "connectivity";
71pub const COLUMN_VECTOR_INDEX_OPT_KEY_EXPANSION_ADD: &str = "expansion_add";
72pub const COLUMN_VECTOR_INDEX_OPT_KEY_EXPANSION_SEARCH: &str = "expansion_search";
73
74pub const DEFAULT_GRANULARITY: u32 = 10240;
75
76pub const DEFAULT_FALSE_POSITIVE_RATE: f64 = 0.01;
77
78#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
80pub struct ColumnSchema {
81 pub name: String,
82 pub data_type: ConcreteDataType,
83 is_nullable: bool,
84 is_time_index: bool,
85 default_constraint: Option<ColumnDefaultConstraint>,
86 metadata: Metadata,
87}
88
89impl fmt::Debug for ColumnSchema {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 write!(
92 f,
93 "{} {} {}",
94 self.name,
95 self.data_type,
96 if self.is_nullable { "null" } else { "not null" },
97 )?;
98
99 if self.is_time_index {
100 write!(f, " time_index")?;
101 }
102
103 if let Some(default_constraint) = &self.default_constraint {
105 write!(f, " default={:?}", default_constraint)?;
106 }
107
108 if !self.metadata.is_empty() {
110 write!(f, " metadata={:?}", self.metadata)?;
111 }
112
113 Ok(())
114 }
115}
116
117impl ColumnSchema {
118 pub fn new<T: Into<String>>(
119 name: T,
120 data_type: ConcreteDataType,
121 is_nullable: bool,
122 ) -> ColumnSchema {
123 ColumnSchema {
124 name: name.into(),
125 data_type,
126 is_nullable,
127 is_time_index: false,
128 default_constraint: None,
129 metadata: Metadata::new(),
130 }
131 }
132
133 pub fn create_mutable_vector(&self, capacity: usize) -> Box<dyn MutableVector> {
135 if self.data_type.is_json2()
136 && let Some(extension) = self.extension_type::<Json2ExtensionType>().ok().flatten()
137 && extension.metadata().is_version_2()
138 {
139 Box::new(JsonVectorBuilder::with_settings(
140 extension.metadata().json_settings(),
141 capacity,
142 ))
143 } else {
144 self.data_type.create_mutable_vector(capacity)
145 }
146 }
147
148 #[inline]
149 pub fn is_time_index(&self) -> bool {
150 self.is_time_index
151 }
152
153 #[inline]
154 pub fn is_nullable(&self) -> bool {
155 self.is_nullable
156 }
157
158 #[inline]
159 pub fn default_constraint(&self) -> Option<&ColumnDefaultConstraint> {
160 self.default_constraint.as_ref()
161 }
162
163 pub fn is_default_impure(&self) -> bool {
165 self.default_constraint
166 .as_ref()
167 .map(|c| c.is_function())
168 .unwrap_or(false)
169 }
170
171 #[inline]
172 pub fn metadata(&self) -> &Metadata {
173 &self.metadata
174 }
175
176 #[inline]
177 pub fn mut_metadata(&mut self) -> &mut Metadata {
178 &mut self.metadata
179 }
180
181 pub fn column_comment(&self) -> Option<&String> {
183 self.metadata.get(COMMENT_KEY)
184 }
185
186 pub fn with_time_index(mut self, is_time_index: bool) -> Self {
187 self.is_time_index = is_time_index;
188 if is_time_index {
189 let _ = self
190 .metadata
191 .insert(TIME_INDEX_KEY.to_string(), "true".to_string());
192 } else {
193 let _ = self.metadata.remove(TIME_INDEX_KEY);
194 }
195 self
196 }
197
198 pub fn estimated_size(&self) -> usize {
200 mem::size_of_val(self) - mem::size_of_val(&self.data_type)
201 + self.data_type.as_arrow_type().size()
202 + self.name.capacity()
203 + self
204 .default_constraint
205 .as_ref()
206 .map(column_default_constraint_size)
207 .unwrap_or_default()
208 + metadata_size(&self.metadata)
209 }
210
211 pub fn set_inverted_index(&mut self, value: bool) {
216 match value {
217 true => {
218 self.metadata
219 .insert(INVERTED_INDEX_KEY.to_string(), value.to_string());
220 }
221 false => {
222 self.metadata.remove(INVERTED_INDEX_KEY);
223 }
224 }
225 }
226
227 pub fn with_inverted_index(mut self, value: bool) -> Self {
232 self.set_inverted_index(value);
233 self
234 }
235
236 pub fn is_inverted_indexed(&self) -> bool {
237 self.metadata
238 .get(INVERTED_INDEX_KEY)
239 .map(|v| v.eq_ignore_ascii_case("true"))
240 .unwrap_or(false)
241 }
242
243 pub fn is_fulltext_indexed(&self) -> bool {
244 self.fulltext_options()
245 .unwrap_or_default()
246 .map(|option| option.enable)
247 .unwrap_or_default()
248 }
249
250 pub fn is_skipping_indexed(&self) -> bool {
251 self.skipping_index_options().unwrap_or_default().is_some()
252 }
253
254 pub fn has_inverted_index_key(&self) -> bool {
255 self.metadata.contains_key(INVERTED_INDEX_KEY)
256 }
257
258 pub fn is_vector_indexed(&self) -> bool {
260 match self.vector_index_options() {
261 Ok(opts) => opts.is_some(),
262 Err(e) => {
263 common_telemetry::warn!(
264 "Failed to deserialize vector_index_options for column '{}': {}",
265 self.name,
266 e
267 );
268 false
269 }
270 }
271 }
272
273 pub fn vector_index_options(&self) -> Result<Option<VectorIndexOptions>> {
275 match self.metadata.get(VECTOR_INDEX_KEY) {
276 None => Ok(None),
277 Some(json) => {
278 let options =
279 serde_json::from_str(json).context(error::DeserializeSnafu { json })?;
280 Ok(Some(options))
281 }
282 }
283 }
284
285 pub fn set_vector_index_options(&mut self, options: &VectorIndexOptions) -> Result<()> {
287 self.metadata.insert(
288 VECTOR_INDEX_KEY.to_string(),
289 serde_json::to_string(options).context(error::SerializeSnafu)?,
290 );
291 Ok(())
292 }
293
294 pub fn unset_vector_index_options(&mut self) {
296 self.metadata.remove(VECTOR_INDEX_KEY);
297 }
298
299 pub fn with_vector_index_options(mut self, options: &VectorIndexOptions) -> Result<Self> {
301 self.set_vector_index_options(options)?;
302 Ok(self)
303 }
304
305 pub fn with_default_constraint(
310 mut self,
311 default_constraint: Option<ColumnDefaultConstraint>,
312 ) -> Result<Self> {
313 if let Some(constraint) = &default_constraint {
314 constraint.validate(&self.data_type, self.is_nullable)?;
315 }
316
317 self.default_constraint = default_constraint;
318 Ok(self)
319 }
320
321 pub fn with_nullable_set(mut self) -> Self {
326 self.is_nullable = true;
327 self
328 }
329
330 pub fn set_nullable(&mut self) {
335 self.is_nullable = true;
336 }
337
338 pub fn set_time_index(&mut self) {
343 self.is_time_index = true;
344 }
345
346 pub fn with_metadata(mut self, metadata: Metadata) -> Self {
348 self.metadata = metadata;
349 self
350 }
351
352 pub fn create_default_vector(&self, num_rows: usize) -> Result<Option<VectorRef>> {
356 match &self.default_constraint {
357 Some(c) => c
358 .create_default_vector(&self.data_type, self.is_nullable, num_rows)
359 .map(Some),
360 None => {
361 if self.is_nullable {
362 ColumnDefaultConstraint::null_value()
365 .create_default_vector(&self.data_type, self.is_nullable, num_rows)
366 .map(Some)
367 } else {
368 Ok(None)
369 }
370 }
371 }
372 }
373
374 pub fn create_default_vector_for_padding(&self, num_rows: usize) -> VectorRef {
380 let padding_value = if self.is_nullable {
381 Value::Null
382 } else {
383 self.data_type.default_value()
386 };
387 let value_ref = padding_value.as_value_ref();
388 let mut mutable_vector = self.data_type.create_mutable_vector(num_rows);
389 for _ in 0..num_rows {
390 mutable_vector.push_value_ref(&value_ref);
391 }
392 mutable_vector.to_vector()
393 }
394
395 pub fn create_default(&self) -> Result<Option<Value>> {
399 match &self.default_constraint {
400 Some(c) => c
401 .create_default(&self.data_type, self.is_nullable)
402 .map(Some),
403 None => {
404 if self.is_nullable {
405 ColumnDefaultConstraint::null_value()
407 .create_default(&self.data_type, self.is_nullable)
408 .map(Some)
409 } else {
410 Ok(None)
411 }
412 }
413 }
414 }
415
416 pub fn create_impure_default(&self) -> Result<Option<Value>> {
419 match &self.default_constraint {
420 Some(c) => c.create_impure_default(&self.data_type),
421 None => Ok(None),
422 }
423 }
424
425 pub fn fulltext_options(&self) -> Result<Option<FulltextOptions>> {
427 match self.metadata.get(FULLTEXT_KEY) {
428 None => Ok(None),
429 Some(json) => {
430 let options =
431 serde_json::from_str(json).context(error::DeserializeSnafu { json })?;
432 Ok(Some(options))
433 }
434 }
435 }
436
437 pub fn with_fulltext_options(mut self, options: FulltextOptions) -> Result<Self> {
438 self.metadata.insert(
439 FULLTEXT_KEY.to_string(),
440 serde_json::to_string(&options).context(error::SerializeSnafu)?,
441 );
442 Ok(self)
443 }
444
445 pub fn set_fulltext_options(&mut self, options: &FulltextOptions) -> Result<()> {
446 self.metadata.insert(
447 FULLTEXT_KEY.to_string(),
448 serde_json::to_string(options).context(error::SerializeSnafu)?,
449 );
450 Ok(())
451 }
452
453 pub fn skipping_index_options(&self) -> Result<Option<SkippingIndexOptions>> {
455 match self.metadata.get(SKIPPING_INDEX_KEY) {
456 None => Ok(None),
457 Some(json) => {
458 let options =
459 serde_json::from_str(json).context(error::DeserializeSnafu { json })?;
460 Ok(Some(options))
461 }
462 }
463 }
464
465 pub fn with_skipping_options(mut self, options: SkippingIndexOptions) -> Result<Self> {
466 self.metadata.insert(
467 SKIPPING_INDEX_KEY.to_string(),
468 serde_json::to_string(&options).context(error::SerializeSnafu)?,
469 );
470 Ok(self)
471 }
472
473 pub fn set_skipping_options(&mut self, options: &SkippingIndexOptions) -> Result<()> {
474 self.metadata.insert(
475 SKIPPING_INDEX_KEY.to_string(),
476 serde_json::to_string(options).context(error::SerializeSnafu)?,
477 );
478 Ok(())
479 }
480
481 pub fn unset_skipping_options(&mut self) -> Result<()> {
482 self.metadata.remove(SKIPPING_INDEX_KEY);
483 Ok(())
484 }
485
486 pub fn extension_type<E>(&self) -> Result<Option<E>>
487 where
488 E: ExtensionType,
489 {
490 let extension_type_name = self.metadata.get(EXTENSION_TYPE_NAME_KEY);
491
492 if extension_type_name.map(|s| s.as_str()) == Some(E::NAME) {
493 let extension_metadata = self.metadata.get(EXTENSION_TYPE_METADATA_KEY);
494 let extension_metadata =
495 E::deserialize_metadata(extension_metadata.map(|s| s.as_str()))
496 .context(ArrowMetadataSnafu)?;
497
498 let extension = E::try_new(&self.data_type.as_arrow_type(), extension_metadata)
499 .context(ArrowMetadataSnafu)?;
500 Ok(Some(extension))
501 } else {
502 Ok(None)
503 }
504 }
505
506 pub fn with_extension_type<E>(&mut self, extension_type: &E)
508 where
509 E: ExtensionType,
510 {
511 self.metadata
512 .insert(EXTENSION_TYPE_NAME_KEY.to_string(), E::NAME.to_string());
513
514 if let Some(extension_metadata) = extension_type.serialize_metadata() {
515 self.metadata
516 .insert(EXTENSION_TYPE_METADATA_KEY.to_string(), extension_metadata);
517 } else {
518 self.metadata.remove(EXTENSION_TYPE_METADATA_KEY);
520 }
521 }
522
523 pub fn is_indexed(&self) -> bool {
524 self.is_inverted_indexed() || self.is_fulltext_indexed() || self.is_skipping_indexed()
525 }
526}
527
528fn metadata_size(metadata: &Metadata) -> usize {
529 mem::size_of::<(String, String)>() * metadata.capacity()
530 + metadata
531 .iter()
532 .map(|(key, value)| key.capacity() + value.capacity())
533 .sum::<usize>()
534}
535
536fn column_default_constraint_size(default_constraint: &ColumnDefaultConstraint) -> usize {
537 match default_constraint {
538 ColumnDefaultConstraint::Function(expr) => expr.capacity(),
539 ColumnDefaultConstraint::Value(value) => value.as_value_ref().data_size(),
540 }
541}
542
543#[derive(Debug, Clone, PartialEq, Eq)]
545pub enum ColumnExtType {
546 Json,
548
549 Vector(u32),
551}
552
553impl fmt::Display for ColumnExtType {
554 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
555 match self {
556 ColumnExtType::Json => write!(f, "Json"),
557 ColumnExtType::Vector(dim) => write!(f, "Vector({})", dim),
558 }
559 }
560}
561
562impl FromStr for ColumnExtType {
563 type Err = String;
564
565 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
566 match s {
567 "Json" => Ok(ColumnExtType::Json),
568 _ if s.starts_with("Vector(") && s.ends_with(')') => s[7..s.len() - 1]
569 .parse::<u32>()
570 .map(ColumnExtType::Vector)
571 .map_err(|_| "Invalid dimension for Vector".to_string()),
572 _ => Err("Unknown variant".to_string()),
573 }
574 }
575}
576
577impl TryFrom<&Field> for ColumnSchema {
578 type Error = Error;
579
580 fn try_from(field: &Field) -> Result<ColumnSchema> {
581 let mut data_type = ConcreteDataType::try_from(field.data_type())?;
582 if let Some(s) = field.metadata().get(TYPE_KEY) {
584 let extype = ColumnExtType::from_str(s)
585 .map_err(|_| ParseExtendedTypeSnafu { value: s }.build())?;
586 match extype {
587 ColumnExtType::Json => {
588 data_type = ConcreteDataType::json_datatype();
589 }
590 ColumnExtType::Vector(dim) => {
591 data_type = ConcreteDataType::vector_datatype(dim);
592 }
593 }
594 }
595 let mut metadata = field.metadata().clone();
596 let default_constraint = match metadata.remove(DEFAULT_CONSTRAINT_KEY) {
597 Some(json) => {
598 Some(serde_json::from_str(&json).context(error::DeserializeSnafu { json })?)
599 }
600 None => None,
601 };
602 let mut is_time_index = metadata.contains_key(TIME_INDEX_KEY);
603 if is_time_index && !data_type.is_timestamp() {
604 is_time_index = false;
610 metadata.remove(TIME_INDEX_KEY);
611 common_telemetry::debug!(
612 "Column {} is not timestamp ({:?}) but has time index metadata",
613 data_type,
614 field.name(),
615 );
616 }
617
618 Ok(ColumnSchema {
619 name: field.name().clone(),
620 data_type,
621 is_nullable: field.is_nullable(),
622 is_time_index,
623 default_constraint,
624 metadata,
625 })
626 }
627}
628
629impl TryFrom<&ColumnSchema> for Field {
630 type Error = Error;
631
632 fn try_from(column_schema: &ColumnSchema) -> Result<Field> {
633 let mut metadata = column_schema.metadata.clone();
634 if let Some(value) = &column_schema.default_constraint {
635 let old = metadata.insert(
637 DEFAULT_CONSTRAINT_KEY.to_string(),
638 serde_json::to_string(&value).context(error::SerializeSnafu)?,
639 );
640
641 ensure!(
642 old.is_none(),
643 error::DuplicateMetaSnafu {
644 key: DEFAULT_CONSTRAINT_KEY,
645 }
646 );
647 }
648
649 Ok(Field::new(
650 &column_schema.name,
651 column_schema.data_type.as_arrow_type(),
652 column_schema.is_nullable(),
653 )
654 .with_metadata(metadata))
655 }
656}
657
658#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Visit, VisitMut)]
660#[serde(rename_all = "kebab-case")]
661pub struct FulltextOptions {
662 pub enable: bool,
664 #[serde(default)]
666 pub analyzer: FulltextAnalyzer,
667 #[serde(default)]
669 pub case_sensitive: bool,
670 #[serde(default)]
672 pub backend: FulltextBackend,
673 #[serde(default = "fulltext_options_default_granularity")]
675 pub granularity: u32,
676 #[serde(default = "index_options_default_false_positive_rate_in_10000")]
678 pub false_positive_rate_in_10000: u32,
679}
680
681fn fulltext_options_default_granularity() -> u32 {
682 DEFAULT_GRANULARITY
683}
684
685fn index_options_default_false_positive_rate_in_10000() -> u32 {
686 (DEFAULT_FALSE_POSITIVE_RATE * 10000.0) as u32
687}
688
689impl FulltextOptions {
690 pub fn new(
692 enable: bool,
693 analyzer: FulltextAnalyzer,
694 case_sensitive: bool,
695 backend: FulltextBackend,
696 granularity: u32,
697 false_positive_rate: f64,
698 ) -> Result<Self> {
699 ensure!(
700 0.0 < false_positive_rate && false_positive_rate <= 1.0,
701 error::InvalidFulltextOptionSnafu {
702 msg: format!(
703 "Invalid false positive rate: {false_positive_rate}, expected: 0.0 < rate <= 1.0"
704 ),
705 }
706 );
707 ensure!(
708 granularity > 0,
709 error::InvalidFulltextOptionSnafu {
710 msg: format!("Invalid granularity: {granularity}, expected: positive integer"),
711 }
712 );
713 Ok(Self::new_unchecked(
714 enable,
715 analyzer,
716 case_sensitive,
717 backend,
718 granularity,
719 false_positive_rate,
720 ))
721 }
722
723 pub fn new_unchecked(
725 enable: bool,
726 analyzer: FulltextAnalyzer,
727 case_sensitive: bool,
728 backend: FulltextBackend,
729 granularity: u32,
730 false_positive_rate: f64,
731 ) -> Self {
732 Self {
733 enable,
734 analyzer,
735 case_sensitive,
736 backend,
737 granularity,
738 false_positive_rate_in_10000: (false_positive_rate * 10000.0) as u32,
739 }
740 }
741
742 pub fn false_positive_rate(&self) -> f64 {
744 self.false_positive_rate_in_10000 as f64 / 10000.0
745 }
746}
747
748impl Default for FulltextOptions {
749 fn default() -> Self {
750 Self::new_unchecked(
751 false,
752 FulltextAnalyzer::default(),
753 false,
754 FulltextBackend::default(),
755 DEFAULT_GRANULARITY,
756 DEFAULT_FALSE_POSITIVE_RATE,
757 )
758 }
759}
760
761impl fmt::Display for FulltextOptions {
762 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
763 write!(f, "enable={}", self.enable)?;
764 if self.enable {
765 write!(f, ", analyzer={}", self.analyzer)?;
766 write!(f, ", case_sensitive={}", self.case_sensitive)?;
767 write!(f, ", backend={}", self.backend)?;
768 if self.backend == FulltextBackend::Bloom {
769 write!(f, ", granularity={}", self.granularity)?;
770 write!(f, ", false_positive_rate={}", self.false_positive_rate())?;
771 }
772 }
773 Ok(())
774 }
775}
776
777#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Visit, VisitMut)]
779#[serde(rename_all = "kebab-case")]
780pub enum FulltextBackend {
781 #[default]
782 Bloom,
783 Tantivy,
784}
785
786impl fmt::Display for FulltextBackend {
787 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
788 match self {
789 FulltextBackend::Tantivy => write!(f, "tantivy"),
790 FulltextBackend::Bloom => write!(f, "bloom"),
791 }
792 }
793}
794
795impl TryFrom<HashMap<String, String>> for FulltextOptions {
796 type Error = Error;
797
798 fn try_from(options: HashMap<String, String>) -> Result<Self> {
799 let mut fulltext_options = FulltextOptions {
800 enable: true,
801 ..Default::default()
802 };
803
804 if let Some(enable) = options.get(COLUMN_FULLTEXT_CHANGE_OPT_KEY_ENABLE) {
805 match enable.to_ascii_lowercase().as_str() {
806 "true" => fulltext_options.enable = true,
807 "false" => fulltext_options.enable = false,
808 _ => {
809 return InvalidFulltextOptionSnafu {
810 msg: format!("{enable}, expected: 'true' | 'false'"),
811 }
812 .fail();
813 }
814 }
815 };
816
817 if let Some(analyzer) = options.get(COLUMN_FULLTEXT_OPT_KEY_ANALYZER) {
818 match analyzer.to_ascii_lowercase().as_str() {
819 "english" => fulltext_options.analyzer = FulltextAnalyzer::English,
820 "chinese" => fulltext_options.analyzer = FulltextAnalyzer::Chinese,
821 _ => {
822 return InvalidFulltextOptionSnafu {
823 msg: format!("{analyzer}, expected: 'English' | 'Chinese'"),
824 }
825 .fail();
826 }
827 }
828 };
829
830 if let Some(case_sensitive) = options.get(COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE) {
831 match case_sensitive.to_ascii_lowercase().as_str() {
832 "true" => fulltext_options.case_sensitive = true,
833 "false" => fulltext_options.case_sensitive = false,
834 _ => {
835 return InvalidFulltextOptionSnafu {
836 msg: format!("{case_sensitive}, expected: 'true' | 'false'"),
837 }
838 .fail();
839 }
840 }
841 }
842
843 if let Some(backend) = options.get(COLUMN_FULLTEXT_OPT_KEY_BACKEND) {
844 match backend.to_ascii_lowercase().as_str() {
845 "bloom" => fulltext_options.backend = FulltextBackend::Bloom,
846 "tantivy" => fulltext_options.backend = FulltextBackend::Tantivy,
847 _ => {
848 return InvalidFulltextOptionSnafu {
849 msg: format!("{backend}, expected: 'bloom' | 'tantivy'"),
850 }
851 .fail();
852 }
853 }
854 }
855
856 if fulltext_options.backend == FulltextBackend::Bloom {
857 let granularity = match options.get(COLUMN_FULLTEXT_OPT_KEY_GRANULARITY) {
859 Some(value) => value
860 .parse::<u32>()
861 .ok()
862 .filter(|&v| v > 0)
863 .ok_or_else(|| {
864 error::InvalidFulltextOptionSnafu {
865 msg: format!(
866 "Invalid granularity: {value}, expected: positive integer"
867 ),
868 }
869 .build()
870 })?,
871 None => DEFAULT_GRANULARITY,
872 };
873 fulltext_options.granularity = granularity;
874
875 let false_positive_rate = match options.get(COLUMN_FULLTEXT_OPT_KEY_FALSE_POSITIVE_RATE)
877 {
878 Some(value) => value
879 .parse::<f64>()
880 .ok()
881 .filter(|&v| v > 0.0 && v <= 1.0)
882 .ok_or_else(|| {
883 error::InvalidFulltextOptionSnafu {
884 msg: format!(
885 "Invalid false positive rate: {value}, expected: 0.0 < rate <= 1.0"
886 ),
887 }
888 .build()
889 })?,
890 None => DEFAULT_FALSE_POSITIVE_RATE,
891 };
892 fulltext_options.false_positive_rate_in_10000 = (false_positive_rate * 10000.0) as u32;
893 }
894
895 Ok(fulltext_options)
896 }
897}
898
899#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Visit, VisitMut)]
901pub enum FulltextAnalyzer {
902 #[default]
903 English,
904 Chinese,
905}
906
907impl fmt::Display for FulltextAnalyzer {
908 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
909 match self {
910 FulltextAnalyzer::English => write!(f, "English"),
911 FulltextAnalyzer::Chinese => write!(f, "Chinese"),
912 }
913 }
914}
915
916#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Visit, VisitMut)]
918#[serde(rename_all = "kebab-case")]
919pub struct SkippingIndexOptions {
920 pub granularity: u32,
922 #[serde(default = "index_options_default_false_positive_rate_in_10000")]
924 pub false_positive_rate_in_10000: u32,
925 #[serde(default)]
927 pub index_type: SkippingIndexType,
928}
929
930impl SkippingIndexOptions {
931 pub fn new_unchecked(
933 granularity: u32,
934 false_positive_rate: f64,
935 index_type: SkippingIndexType,
936 ) -> Self {
937 Self {
938 granularity,
939 false_positive_rate_in_10000: (false_positive_rate * 10000.0) as u32,
940 index_type,
941 }
942 }
943
944 pub fn new(
946 granularity: u32,
947 false_positive_rate: f64,
948 index_type: SkippingIndexType,
949 ) -> Result<Self> {
950 ensure!(
951 0.0 < false_positive_rate && false_positive_rate <= 1.0,
952 error::InvalidSkippingIndexOptionSnafu {
953 msg: format!(
954 "Invalid false positive rate: {false_positive_rate}, expected: 0.0 < rate <= 1.0"
955 ),
956 }
957 );
958 ensure!(
959 granularity > 0,
960 error::InvalidSkippingIndexOptionSnafu {
961 msg: format!("Invalid granularity: {granularity}, expected: positive integer"),
962 }
963 );
964 Ok(Self::new_unchecked(
965 granularity,
966 false_positive_rate,
967 index_type,
968 ))
969 }
970
971 pub fn false_positive_rate(&self) -> f64 {
973 self.false_positive_rate_in_10000 as f64 / 10000.0
974 }
975}
976
977impl Default for SkippingIndexOptions {
978 fn default() -> Self {
979 Self::new_unchecked(
980 DEFAULT_GRANULARITY,
981 DEFAULT_FALSE_POSITIVE_RATE,
982 SkippingIndexType::default(),
983 )
984 }
985}
986
987impl fmt::Display for SkippingIndexOptions {
988 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
989 write!(f, "granularity={}", self.granularity)?;
990 write!(f, ", false_positive_rate={}", self.false_positive_rate())?;
991 write!(f, ", index_type={}", self.index_type)?;
992 Ok(())
993 }
994}
995
996#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Visit, VisitMut)]
998pub enum SkippingIndexType {
999 #[default]
1000 BloomFilter,
1001}
1002
1003impl fmt::Display for SkippingIndexType {
1004 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1005 match self {
1006 SkippingIndexType::BloomFilter => write!(f, "BLOOM"),
1007 }
1008 }
1009}
1010
1011impl TryFrom<HashMap<String, String>> for SkippingIndexOptions {
1012 type Error = Error;
1013
1014 fn try_from(options: HashMap<String, String>) -> Result<Self> {
1015 let granularity = match options.get(COLUMN_SKIPPING_INDEX_OPT_KEY_GRANULARITY) {
1017 Some(value) => value
1018 .parse::<u32>()
1019 .ok()
1020 .filter(|&v| v > 0)
1021 .ok_or_else(|| {
1022 error::InvalidSkippingIndexOptionSnafu {
1023 msg: format!("Invalid granularity: {value}, expected: positive integer"),
1024 }
1025 .build()
1026 })?,
1027 None => DEFAULT_GRANULARITY,
1028 };
1029
1030 let false_positive_rate =
1032 match options.get(COLUMN_SKIPPING_INDEX_OPT_KEY_FALSE_POSITIVE_RATE) {
1033 Some(value) => value
1034 .parse::<f64>()
1035 .ok()
1036 .filter(|&v| v > 0.0 && v <= 1.0)
1037 .ok_or_else(|| {
1038 error::InvalidSkippingIndexOptionSnafu {
1039 msg: format!(
1040 "Invalid false positive rate: {value}, expected: 0.0 < rate <= 1.0"
1041 ),
1042 }
1043 .build()
1044 })?,
1045 None => DEFAULT_FALSE_POSITIVE_RATE,
1046 };
1047
1048 let index_type = match options.get(COLUMN_SKIPPING_INDEX_OPT_KEY_TYPE) {
1050 Some(typ) => match typ.to_ascii_uppercase().as_str() {
1051 "BLOOM" => SkippingIndexType::BloomFilter,
1052 _ => {
1053 return error::InvalidSkippingIndexOptionSnafu {
1054 msg: format!("Invalid index type: {typ}, expected: 'BLOOM'"),
1055 }
1056 .fail();
1057 }
1058 },
1059 None => SkippingIndexType::default(),
1060 };
1061
1062 Ok(SkippingIndexOptions::new_unchecked(
1063 granularity,
1064 false_positive_rate,
1065 index_type,
1066 ))
1067 }
1068}
1069
1070#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, Visit, VisitMut)]
1072#[serde(rename_all = "lowercase")]
1073pub enum VectorDistanceMetric {
1074 #[default]
1076 L2sq,
1077 Cosine,
1079 #[serde(alias = "ip")]
1081 InnerProduct,
1082}
1083
1084impl fmt::Display for VectorDistanceMetric {
1085 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1086 match self {
1087 VectorDistanceMetric::L2sq => write!(f, "l2sq"),
1088 VectorDistanceMetric::Cosine => write!(f, "cosine"),
1089 VectorDistanceMetric::InnerProduct => write!(f, "ip"),
1090 }
1091 }
1092}
1093
1094impl std::str::FromStr for VectorDistanceMetric {
1095 type Err = String;
1096
1097 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1098 match s.to_lowercase().as_str() {
1099 "l2sq" | "l2" | "euclidean" => Ok(VectorDistanceMetric::L2sq),
1100 "cosine" | "cos" => Ok(VectorDistanceMetric::Cosine),
1101 "inner_product" | "ip" | "dot" => Ok(VectorDistanceMetric::InnerProduct),
1102 _ => Err(format!(
1103 "Unknown distance metric: {}. Expected: l2sq, cosine, or ip",
1104 s
1105 )),
1106 }
1107 }
1108}
1109
1110impl VectorDistanceMetric {
1111 pub fn as_u8(&self) -> u8 {
1113 match self {
1114 Self::L2sq => 0,
1115 Self::Cosine => 1,
1116 Self::InnerProduct => 2,
1117 }
1118 }
1119
1120 pub fn try_from_u8(v: u8) -> Option<Self> {
1122 match v {
1123 0 => Some(Self::L2sq),
1124 1 => Some(Self::Cosine),
1125 2 => Some(Self::InnerProduct),
1126 _ => None,
1127 }
1128 }
1129}
1130
1131const DEFAULT_VECTOR_INDEX_CONNECTIVITY: u32 = 16;
1133const DEFAULT_VECTOR_INDEX_EXPANSION_ADD: u32 = 128;
1135const DEFAULT_VECTOR_INDEX_EXPANSION_SEARCH: u32 = 64;
1137
1138fn default_vector_index_connectivity() -> u32 {
1139 DEFAULT_VECTOR_INDEX_CONNECTIVITY
1140}
1141
1142fn default_vector_index_expansion_add() -> u32 {
1143 DEFAULT_VECTOR_INDEX_EXPANSION_ADD
1144}
1145
1146fn default_vector_index_expansion_search() -> u32 {
1147 DEFAULT_VECTOR_INDEX_EXPANSION_SEARCH
1148}
1149
1150#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, Visit, VisitMut)]
1152#[serde(rename_all = "lowercase")]
1153pub enum VectorIndexEngineType {
1154 #[default]
1156 Usearch,
1157 }
1159
1160impl VectorIndexEngineType {
1161 pub fn as_u8(&self) -> u8 {
1163 match self {
1164 Self::Usearch => 0,
1165 }
1166 }
1167
1168 pub fn try_from_u8(v: u8) -> Option<Self> {
1170 match v {
1171 0 => Some(Self::Usearch),
1172 _ => None,
1173 }
1174 }
1175}
1176
1177impl fmt::Display for VectorIndexEngineType {
1178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1179 match self {
1180 Self::Usearch => write!(f, "usearch"),
1181 }
1182 }
1183}
1184
1185impl std::str::FromStr for VectorIndexEngineType {
1186 type Err = String;
1187
1188 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1189 match s.to_lowercase().as_str() {
1190 "usearch" => Ok(Self::Usearch),
1191 _ => Err(format!(
1192 "Unknown vector index engine: {}. Expected: usearch",
1193 s
1194 )),
1195 }
1196 }
1197}
1198
1199#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Visit, VisitMut)]
1201#[serde(rename_all = "kebab-case")]
1202pub struct VectorIndexOptions {
1203 #[serde(default)]
1205 pub engine: VectorIndexEngineType,
1206 #[serde(default)]
1208 pub metric: VectorDistanceMetric,
1209 #[serde(default = "default_vector_index_connectivity")]
1212 pub connectivity: u32,
1213 #[serde(default = "default_vector_index_expansion_add")]
1216 pub expansion_add: u32,
1217 #[serde(default = "default_vector_index_expansion_search")]
1220 pub expansion_search: u32,
1221}
1222
1223impl Default for VectorIndexOptions {
1224 fn default() -> Self {
1225 Self {
1226 engine: VectorIndexEngineType::default(),
1227 metric: VectorDistanceMetric::default(),
1228 connectivity: DEFAULT_VECTOR_INDEX_CONNECTIVITY,
1229 expansion_add: DEFAULT_VECTOR_INDEX_EXPANSION_ADD,
1230 expansion_search: DEFAULT_VECTOR_INDEX_EXPANSION_SEARCH,
1231 }
1232 }
1233}
1234
1235impl fmt::Display for VectorIndexOptions {
1236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1237 write!(
1238 f,
1239 "engine={}, metric={}, connectivity={}, expansion_add={}, expansion_search={}",
1240 self.engine, self.metric, self.connectivity, self.expansion_add, self.expansion_search
1241 )
1242 }
1243}
1244
1245#[cfg(test)]
1246mod tests {
1247 use std::sync::Arc;
1248
1249 use arrow::datatypes::{DataType as ArrowDataType, TimeUnit};
1250
1251 use super::*;
1252 use crate::extension::json::{Json2ExtensionType, JsonExtensionType};
1253 use crate::types::{StructField, StructType};
1254 use crate::value::Value;
1255 use crate::vectors::Int32Vector;
1256
1257 #[test]
1258 fn test_column_schema() {
1259 let column_schema = ColumnSchema::new("test", ConcreteDataType::int32_datatype(), true);
1260 let field = Field::try_from(&column_schema).unwrap();
1261 assert_eq!("test", field.name());
1262 assert_eq!(ArrowDataType::Int32, *field.data_type());
1263 assert!(field.is_nullable());
1264
1265 let new_column_schema = ColumnSchema::try_from(&field).unwrap();
1266 assert_eq!(column_schema, new_column_schema);
1267 }
1268
1269 #[test]
1270 fn test_with_extension_type_replaces_metadata() {
1271 let mut schema = ColumnSchema::new("j", ConcreteDataType::json_datatype(), true);
1272
1273 schema.with_extension_type(&Json2ExtensionType::default());
1274 assert_eq!(
1275 Some(Json2ExtensionType::NAME),
1276 schema
1277 .metadata()
1278 .get(EXTENSION_TYPE_NAME_KEY)
1279 .map(String::as_str)
1280 );
1281 assert!(schema.metadata().contains_key(EXTENSION_TYPE_METADATA_KEY));
1282
1283 schema.with_extension_type(&JsonExtensionType);
1284 assert_eq!(
1285 Some(JsonExtensionType::NAME),
1286 schema
1287 .metadata()
1288 .get(EXTENSION_TYPE_NAME_KEY)
1289 .map(String::as_str)
1290 );
1291 assert!(!schema.metadata().contains_key(EXTENSION_TYPE_METADATA_KEY));
1292 }
1293
1294 #[test]
1295 fn test_column_schema_with_default_constraint() {
1296 let column_schema = ColumnSchema::new("test", ConcreteDataType::int32_datatype(), true)
1297 .with_default_constraint(Some(ColumnDefaultConstraint::Value(Value::from(99))))
1298 .unwrap();
1299 assert!(
1300 column_schema
1301 .metadata()
1302 .get(DEFAULT_CONSTRAINT_KEY)
1303 .is_none()
1304 );
1305
1306 let field = Field::try_from(&column_schema).unwrap();
1307 assert_eq!("test", field.name());
1308 assert_eq!(ArrowDataType::Int32, *field.data_type());
1309 assert!(field.is_nullable());
1310 assert_eq!(
1311 "{\"Value\":{\"Int32\":99}}",
1312 field.metadata().get(DEFAULT_CONSTRAINT_KEY).unwrap()
1313 );
1314
1315 let new_column_schema = ColumnSchema::try_from(&field).unwrap();
1316 assert_eq!(column_schema, new_column_schema);
1317 }
1318
1319 #[test]
1320 fn test_column_schema_with_metadata() {
1321 let metadata = Metadata::from([
1322 ("k1".to_string(), "v1".to_string()),
1323 (COMMENT_KEY.to_string(), "test comment".to_string()),
1324 ]);
1325 let column_schema = ColumnSchema::new("test", ConcreteDataType::int32_datatype(), true)
1326 .with_metadata(metadata)
1327 .with_default_constraint(Some(ColumnDefaultConstraint::null_value()))
1328 .unwrap();
1329 assert_eq!("v1", column_schema.metadata().get("k1").unwrap());
1330 assert_eq!("test comment", column_schema.column_comment().unwrap());
1331 assert!(
1332 column_schema
1333 .metadata()
1334 .get(DEFAULT_CONSTRAINT_KEY)
1335 .is_none()
1336 );
1337
1338 let field = Field::try_from(&column_schema).unwrap();
1339 assert_eq!("v1", field.metadata().get("k1").unwrap());
1340 let _ = field.metadata().get(DEFAULT_CONSTRAINT_KEY).unwrap();
1341
1342 let new_column_schema = ColumnSchema::try_from(&field).unwrap();
1343 assert_eq!(column_schema, new_column_schema);
1344 }
1345
1346 #[test]
1347 fn test_column_schema_with_duplicate_metadata() {
1348 let metadata = Metadata::from([(DEFAULT_CONSTRAINT_KEY.to_string(), "v1".to_string())]);
1349 let column_schema = ColumnSchema::new("test", ConcreteDataType::int32_datatype(), true)
1350 .with_metadata(metadata)
1351 .with_default_constraint(Some(ColumnDefaultConstraint::null_value()))
1352 .unwrap();
1353 assert!(Field::try_from(&column_schema).is_err());
1354 }
1355
1356 #[test]
1357 fn test_column_schema_invalid_default_constraint() {
1358 assert!(
1359 ColumnSchema::new("test", ConcreteDataType::int32_datatype(), false)
1360 .with_default_constraint(Some(ColumnDefaultConstraint::null_value()))
1361 .is_err()
1362 );
1363 }
1364
1365 #[test]
1366 fn test_column_default_constraint_try_into_from() {
1367 let default_constraint = ColumnDefaultConstraint::Value(Value::from(42i64));
1368
1369 let bytes: Vec<u8> = default_constraint.clone().try_into().unwrap();
1370 let from_value = ColumnDefaultConstraint::try_from(&bytes[..]).unwrap();
1371
1372 assert_eq!(default_constraint, from_value);
1373 }
1374
1375 #[test]
1376 fn test_column_schema_create_default_null() {
1377 let column_schema = ColumnSchema::new("test", ConcreteDataType::int32_datatype(), true);
1379 let v = column_schema.create_default_vector(5).unwrap().unwrap();
1380 assert_eq!(5, v.len());
1381 assert!(v.only_null());
1382
1383 let column_schema = ColumnSchema::new("test", ConcreteDataType::int32_datatype(), true)
1385 .with_default_constraint(Some(ColumnDefaultConstraint::null_value()))
1386 .unwrap();
1387 let v = column_schema.create_default_vector(5).unwrap().unwrap();
1388 assert_eq!(5, v.len());
1389 assert!(v.only_null());
1390 }
1391
1392 #[test]
1393 fn test_column_schema_create_default_null_struct_with_list() {
1394 let list_type =
1395 ConcreteDataType::list_datatype(Arc::new(ConcreteDataType::int32_datatype()));
1396 let struct_type =
1397 ConcreteDataType::struct_datatype(StructType::new(Arc::new(vec![StructField::new(
1398 "values".to_string(),
1399 list_type,
1400 true,
1401 )])));
1402 let column_schema = ColumnSchema::new("test", struct_type, true);
1403
1404 let v = column_schema.create_default_vector(5).unwrap().unwrap();
1405
1406 assert_eq!(5, v.len());
1407 assert!(v.only_null());
1408 }
1409
1410 #[test]
1411 fn test_column_schema_no_default() {
1412 let column_schema = ColumnSchema::new("test", ConcreteDataType::int32_datatype(), false);
1413 assert!(column_schema.create_default_vector(5).unwrap().is_none());
1414 }
1415
1416 #[test]
1417 fn test_create_default_vector_for_padding() {
1418 let column_schema = ColumnSchema::new("test", ConcreteDataType::int32_datatype(), true);
1419 let vector = column_schema.create_default_vector_for_padding(4);
1420 assert!(vector.only_null());
1421 assert_eq!(4, vector.len());
1422
1423 let column_schema = ColumnSchema::new("test", ConcreteDataType::int32_datatype(), false);
1424 let vector = column_schema.create_default_vector_for_padding(4);
1425 assert_eq!(4, vector.len());
1426 let expect: VectorRef = Arc::new(Int32Vector::from_slice([0, 0, 0, 0]));
1427 assert_eq!(expect, vector);
1428 }
1429
1430 #[test]
1431 fn test_column_schema_single_create_default_null() {
1432 let column_schema = ColumnSchema::new("test", ConcreteDataType::int32_datatype(), true);
1434 let v = column_schema.create_default().unwrap().unwrap();
1435 assert!(v.is_null());
1436
1437 let column_schema = ColumnSchema::new("test", ConcreteDataType::int32_datatype(), true)
1439 .with_default_constraint(Some(ColumnDefaultConstraint::null_value()))
1440 .unwrap();
1441 let v = column_schema.create_default().unwrap().unwrap();
1442 assert!(v.is_null());
1443 }
1444
1445 #[test]
1446 fn test_column_schema_single_create_default_not_null() {
1447 let column_schema = ColumnSchema::new("test", ConcreteDataType::int32_datatype(), true)
1448 .with_default_constraint(Some(ColumnDefaultConstraint::Value(Value::Int32(6))))
1449 .unwrap();
1450 let v = column_schema.create_default().unwrap().unwrap();
1451 assert_eq!(v, Value::Int32(6));
1452 }
1453
1454 #[test]
1455 fn test_column_schema_single_no_default() {
1456 let column_schema = ColumnSchema::new("test", ConcreteDataType::int32_datatype(), false);
1457 assert!(column_schema.create_default().unwrap().is_none());
1458 }
1459
1460 #[test]
1461 fn test_debug_for_column_schema() {
1462 let column_schema_int8 =
1463 ColumnSchema::new("test_column_1", ConcreteDataType::int8_datatype(), true);
1464
1465 let column_schema_int32 =
1466 ColumnSchema::new("test_column_2", ConcreteDataType::int32_datatype(), false);
1467
1468 let formatted_int8 = format!("{:?}", column_schema_int8);
1469 let formatted_int32 = format!("{:?}", column_schema_int32);
1470 assert_eq!(formatted_int8, "test_column_1 Int8 null");
1471 assert_eq!(formatted_int32, "test_column_2 Int32 not null");
1472 }
1473
1474 #[test]
1475 fn test_from_field_to_column_schema() {
1476 let field = Field::new("test", ArrowDataType::Int32, true);
1477 let column_schema = ColumnSchema::try_from(&field).unwrap();
1478 assert_eq!("test", column_schema.name);
1479 assert_eq!(ConcreteDataType::int32_datatype(), column_schema.data_type);
1480 assert!(column_schema.is_nullable);
1481 assert!(!column_schema.is_time_index);
1482 assert!(column_schema.default_constraint.is_none());
1483 assert!(column_schema.metadata.is_empty());
1484
1485 let field = Field::new("test", ArrowDataType::Binary, true);
1486 let field = field.with_metadata(Metadata::from([(
1487 TYPE_KEY.to_string(),
1488 ConcreteDataType::json_datatype().name(),
1489 )]));
1490 let column_schema = ColumnSchema::try_from(&field).unwrap();
1491 assert_eq!("test", column_schema.name);
1492 assert_eq!(ConcreteDataType::json_datatype(), column_schema.data_type);
1493 assert!(column_schema.is_nullable);
1494 assert!(!column_schema.is_time_index);
1495 assert!(column_schema.default_constraint.is_none());
1496 assert_eq!(
1497 column_schema.metadata.get(TYPE_KEY).unwrap(),
1498 &ConcreteDataType::json_datatype().name()
1499 );
1500
1501 let field = Field::new("test", ArrowDataType::Binary, true);
1502 let field = field.with_metadata(Metadata::from([(
1503 TYPE_KEY.to_string(),
1504 ConcreteDataType::vector_datatype(3).name(),
1505 )]));
1506 let column_schema = ColumnSchema::try_from(&field).unwrap();
1507 assert_eq!("test", column_schema.name);
1508 assert_eq!(
1509 ConcreteDataType::vector_datatype(3),
1510 column_schema.data_type
1511 );
1512 assert!(column_schema.is_nullable);
1513 assert!(!column_schema.is_time_index);
1514 assert!(column_schema.default_constraint.is_none());
1515 assert_eq!(
1516 column_schema.metadata.get(TYPE_KEY).unwrap(),
1517 &ConcreteDataType::vector_datatype(3).name()
1518 );
1519 }
1520
1521 #[test]
1522 fn test_column_schema_fix_time_index() {
1523 let field = Field::new(
1524 "test",
1525 ArrowDataType::Timestamp(TimeUnit::Second, None),
1526 false,
1527 );
1528 let field = field.with_metadata(Metadata::from([(
1529 TIME_INDEX_KEY.to_string(),
1530 "true".to_string(),
1531 )]));
1532 let column_schema = ColumnSchema::try_from(&field).unwrap();
1533 assert_eq!("test", column_schema.name);
1534 assert_eq!(
1535 ConcreteDataType::timestamp_second_datatype(),
1536 column_schema.data_type
1537 );
1538 assert!(!column_schema.is_nullable);
1539 assert!(column_schema.is_time_index);
1540 assert!(column_schema.default_constraint.is_none());
1541 assert_eq!(1, column_schema.metadata().len());
1542
1543 let field = Field::new("test", ArrowDataType::Int32, false);
1544 let field = field.with_metadata(Metadata::from([(
1545 TIME_INDEX_KEY.to_string(),
1546 "true".to_string(),
1547 )]));
1548 let column_schema = ColumnSchema::try_from(&field).unwrap();
1549 assert_eq!("test", column_schema.name);
1550 assert_eq!(ConcreteDataType::int32_datatype(), column_schema.data_type);
1551 assert!(!column_schema.is_nullable);
1552 assert!(!column_schema.is_time_index);
1553 assert!(column_schema.default_constraint.is_none());
1554 assert!(column_schema.metadata.is_empty());
1555 }
1556
1557 #[test]
1558 fn test_skipping_index_options_deserialization() {
1559 let original_options = "{\"granularity\":1024,\"false-positive-rate-in-10000\":10,\"index-type\":\"BloomFilter\"}";
1560 let options = serde_json::from_str::<SkippingIndexOptions>(original_options).unwrap();
1561 assert_eq!(1024, options.granularity);
1562 assert_eq!(SkippingIndexType::BloomFilter, options.index_type);
1563 assert_eq!(0.001, options.false_positive_rate());
1564
1565 let options_str = serde_json::to_string(&options).unwrap();
1566 assert_eq!(options_str, original_options);
1567 }
1568
1569 #[test]
1570 fn test_skipping_index_options_deserialization_v0_14_to_v0_15() {
1571 let options = "{\"granularity\":10240,\"index-type\":\"BloomFilter\"}";
1572 let options = serde_json::from_str::<SkippingIndexOptions>(options).unwrap();
1573 assert_eq!(10240, options.granularity);
1574 assert_eq!(SkippingIndexType::BloomFilter, options.index_type);
1575 assert_eq!(DEFAULT_FALSE_POSITIVE_RATE, options.false_positive_rate());
1576
1577 let options_str = serde_json::to_string(&options).unwrap();
1578 assert_eq!(
1579 options_str,
1580 "{\"granularity\":10240,\"false-positive-rate-in-10000\":100,\"index-type\":\"BloomFilter\"}"
1581 );
1582 }
1583
1584 #[test]
1585 fn test_fulltext_options_deserialization() {
1586 let original_options = "{\"enable\":true,\"analyzer\":\"English\",\"case-sensitive\":false,\"backend\":\"bloom\",\"granularity\":1024,\"false-positive-rate-in-10000\":10}";
1587 let options = serde_json::from_str::<FulltextOptions>(original_options).unwrap();
1588 assert!(!options.case_sensitive);
1589 assert!(options.enable);
1590 assert_eq!(FulltextBackend::Bloom, options.backend);
1591 assert_eq!(FulltextAnalyzer::default(), options.analyzer);
1592 assert_eq!(1024, options.granularity);
1593 assert_eq!(0.001, options.false_positive_rate());
1594
1595 let options_str = serde_json::to_string(&options).unwrap();
1596 assert_eq!(options_str, original_options);
1597 }
1598
1599 #[test]
1600 fn test_fulltext_options_deserialization_v0_14_to_v0_15() {
1601 let options = "{\"enable\":true,\"analyzer\":\"English\",\"case-sensitive\":false,\"backend\":\"bloom\"}";
1603 let options = serde_json::from_str::<FulltextOptions>(options).unwrap();
1604 assert!(!options.case_sensitive);
1605 assert!(options.enable);
1606 assert_eq!(FulltextBackend::Bloom, options.backend);
1607 assert_eq!(FulltextAnalyzer::default(), options.analyzer);
1608 assert_eq!(DEFAULT_GRANULARITY, options.granularity);
1609 assert_eq!(DEFAULT_FALSE_POSITIVE_RATE, options.false_positive_rate());
1610
1611 let options_str = serde_json::to_string(&options).unwrap();
1612 assert_eq!(
1613 options_str,
1614 "{\"enable\":true,\"analyzer\":\"English\",\"case-sensitive\":false,\"backend\":\"bloom\",\"granularity\":10240,\"false-positive-rate-in-10000\":100}"
1615 );
1616 }
1617}