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