uqa_sql/schema/indexes/
options.rs1use crate::SQLError;
9#[derive(Default)]
10pub struct IVFIndexOptions {
11 pub nlist: Option<usize>,
12 pub nprobe: Option<usize>,
13 pub train_threshold: Option<usize>,
14}
15#[derive(Default)]
16pub struct HNSWIndexOptions {
17 pub m: Option<usize>,
18 pub ef_construction: Option<usize>,
19 pub ef_search: Option<usize>,
20 pub rebuild_threshold: Option<usize>,
21 pub seed: Option<u64>,
22}
23pub fn index_access_method(statement: &crate::ast::CreateIndex) -> Result<String, SQLError> {
24 let am = statement.access_method.to_ascii_lowercase();
25 if !matches!(am.as_str(), "" | "btree" | "gin" | "ivf" | "hnsw") {
26 return Err(SQLError::Unsupported(format!(
27 "CREATE INDEX access method `{}` is not supported",
28 statement.access_method
29 )));
30 }
31
32 Ok(am)
33}
34pub fn parse_ivf_index_options(options: &[(String, String)]) -> Result<IVFIndexOptions, SQLError> {
35 let mut params = IVFIndexOptions::default();
36 let mut seen = std::collections::BTreeSet::new();
37 for (key, value) in options {
38 if key.eq_ignore_ascii_case("lists") || key.eq_ignore_ascii_case("nlist") {
39 claim_index_option(&mut seen, "nlist", "ivf", key)?;
40 params.nlist = Some(parse_positive_usize_option("ivf", key, value)?);
41 } else if key.eq_ignore_ascii_case("probes") || key.eq_ignore_ascii_case("nprobe") {
42 claim_index_option(&mut seen, "nprobe", "ivf", key)?;
43 params.nprobe = Some(parse_positive_usize_option("ivf", key, value)?);
44 } else if key.eq_ignore_ascii_case("train_threshold")
45 || key.eq_ignore_ascii_case("train-threshold")
46 || key.eq_ignore_ascii_case("min_train")
47 {
48 claim_index_option(&mut seen, "train_threshold", "ivf", key)?;
49 params.train_threshold = Some(parse_positive_usize_option("ivf", key, value)?);
50 } else {
51 return Err(SQLError::Unsupported(format!(
52 "CREATE INDEX USING ivf option `{key}` is not supported"
53 )));
54 }
55 }
56 Ok(params)
57}
58
59pub fn parse_hnsw_index_options(
60 options: &[(String, String)],
61) -> Result<HNSWIndexOptions, SQLError> {
62 let mut params = HNSWIndexOptions::default();
63 let mut seen = std::collections::BTreeSet::new();
64 for (key, value) in options {
65 if key.eq_ignore_ascii_case("m") {
66 claim_index_option(&mut seen, "m", "hnsw", key)?;
67 params.m = Some(parse_positive_usize_option("hnsw", key, value)?);
68 } else if key.eq_ignore_ascii_case("ef_construction")
69 || key.eq_ignore_ascii_case("ef-construction")
70 {
71 claim_index_option(&mut seen, "ef_construction", "hnsw", key)?;
72 params.ef_construction = Some(parse_positive_usize_option("hnsw", key, value)?);
73 } else if key.eq_ignore_ascii_case("ef_search") || key.eq_ignore_ascii_case("ef-search") {
74 claim_index_option(&mut seen, "ef_search", "hnsw", key)?;
75 params.ef_search = Some(parse_positive_usize_option("hnsw", key, value)?);
76 } else if key.eq_ignore_ascii_case("rebuild_threshold")
77 || key.eq_ignore_ascii_case("rebuild-threshold")
78 {
79 claim_index_option(&mut seen, "rebuild_threshold", "hnsw", key)?;
80 params.rebuild_threshold = Some(parse_positive_usize_option("hnsw", key, value)?);
81 } else if key.eq_ignore_ascii_case("seed") {
82 claim_index_option(&mut seen, "seed", "hnsw", key)?;
83 params.seed = Some(value.parse::<u64>().map_err(|_| {
84 SQLError::TypeMismatch(format!(
85 "CREATE INDEX USING hnsw option `{key}` must be an unsigned integer"
86 ))
87 })?);
88 } else {
89 return Err(SQLError::Unsupported(format!(
90 "CREATE INDEX USING hnsw option `{key}` is not supported"
91 )));
92 }
93 }
94 Ok(params)
95}
96
97fn claim_index_option(
98 seen: &mut std::collections::BTreeSet<&'static str>,
99 canonical: &'static str,
100 access_method: &str,
101 source: &str,
102) -> Result<(), SQLError> {
103 if !seen.insert(canonical) {
104 return Err(SQLError::Unsupported(format!(
105 "CREATE INDEX USING {access_method} option `{source}` duplicates `{canonical}`"
106 )));
107 }
108 Ok(())
109}
110
111fn parse_positive_usize_option(
112 access_method: &str,
113 key: &str,
114 value: &str,
115) -> Result<usize, SQLError> {
116 let parsed = value.parse::<usize>().map_err(|_| {
117 SQLError::TypeMismatch(format!(
118 "CREATE INDEX USING {access_method} option `{key}` must be a positive integer"
119 ))
120 })?;
121 if parsed == 0 {
122 return Err(SQLError::TypeMismatch(format!(
123 "CREATE INDEX USING {access_method} option `{key}` must be a positive integer"
124 )));
125 }
126 Ok(parsed)
127}