reddb_rql/parser/
index_ddl.rs1use super::error::ParseError;
4use super::Parser;
5use crate::ast::{CreateIndexQuery, DropIndexQuery, IndexMethod, QueryExpr};
6use crate::lexer::Token;
7
8impl<'a> Parser<'a> {
9 pub fn parse_create_index_query(&mut self) -> Result<QueryExpr, ParseError> {
14 let unique = self.consume(&Token::Unique)?;
17
18 self.expect(Token::Index)?;
19
20 let if_not_exists = self.match_if_not_exists()?;
21
22 let name = self.expect_ident()?;
23
24 self.expect(Token::On)?;
25
26 let table = self.expect_ident()?;
27
28 self.expect(Token::LParen)?;
30 let mut columns = Vec::new();
31 loop {
32 columns.push(self.parse_index_column_path()?);
33 if !self.consume(&Token::Comma)? {
34 break;
35 }
36 }
37 self.expect(Token::RParen)?;
38
39 let method = if self.consume(&Token::Using)? {
41 self.parse_index_method()?
42 } else {
43 IndexMethod::BTree };
45
46 Ok(QueryExpr::CreateIndex(CreateIndexQuery {
47 name,
48 table,
49 columns,
50 method,
51 unique,
52 if_not_exists,
53 }))
54 }
55
56 pub fn parse_drop_index_query(&mut self) -> Result<QueryExpr, ParseError> {
60 self.expect(Token::Index)?;
63
64 let if_exists = self.match_if_exists()?;
65
66 let name = self.expect_ident()?;
67
68 self.expect(Token::On)?;
69
70 let table = self.expect_ident()?;
71
72 Ok(QueryExpr::DropIndex(DropIndexQuery {
73 name,
74 table,
75 if_exists,
76 }))
77 }
78
79 fn parse_index_method(&mut self) -> Result<IndexMethod, ParseError> {
85 let peeked = self.peek().clone();
86 if matches!(peeked, Token::Hash) {
87 self.advance()?;
88 return Ok(IndexMethod::Hash);
89 }
90 match peeked {
91 Token::Ident(ref name) => {
92 let method = match name.to_ascii_uppercase().as_str() {
93 "HASH" => IndexMethod::Hash,
94 "BTREE" => IndexMethod::BTree,
95 "BITMAP" => IndexMethod::Bitmap,
96 "RTREE" => IndexMethod::RTree,
97 _ => {
98 return Err(ParseError::new(
99 format!(
100 "unknown index method '{}', expected HASH, BTREE, BITMAP, or RTREE",
101 name
102 ),
103 self.position(),
104 ));
105 }
106 };
107 self.advance()?;
108 Ok(method)
109 }
110 other => Err(ParseError::expected(
111 vec!["HASH", "BTREE", "BITMAP", "RTREE"],
112 &other,
113 self.position(),
114 )),
115 }
116 }
117
118 fn parse_index_column_path(&mut self) -> Result<String, ParseError> {
119 let mut column = self.expect_ident_or_keyword()?;
120 while self.consume(&Token::Dot)? {
121 column.push('.');
122 column.push_str(&self.expect_ident_or_keyword()?);
123 }
124 Ok(column)
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 fn parser(input: &str) -> Parser<'_> {
133 Parser::new(input).unwrap_or_else(|err| panic!("failed to lex {input:?}: {err:?}"))
134 }
135
136 fn parse_create_index(input: &str) -> CreateIndexQuery {
137 let mut parser = parser(input);
138 parser.expect(Token::Create).expect("CREATE");
139 let QueryExpr::CreateIndex(query) = parser
140 .parse_create_index_query()
141 .unwrap_or_else(|err| panic!("failed to parse {input:?}: {err:?}"))
142 else {
143 panic!("Expected CreateIndexQuery");
144 };
145 assert!(
146 matches!(parser.peek(), Token::Eof),
147 "CREATE INDEX parse did not consume all input: {input:?}"
148 );
149 query
150 }
151
152 fn parse_drop_index(input: &str) -> DropIndexQuery {
153 let mut parser = parser(input);
154 parser.expect(Token::Drop).expect("DROP");
155 let QueryExpr::DropIndex(query) = parser
156 .parse_drop_index_query()
157 .unwrap_or_else(|err| panic!("failed to parse {input:?}: {err:?}"))
158 else {
159 panic!("Expected DropIndexQuery");
160 };
161 assert!(
162 matches!(parser.peek(), Token::Eof),
163 "DROP INDEX parse did not consume all input: {input:?}"
164 );
165 query
166 }
167
168 #[test]
169 fn parse_create_index_defaults_and_options() {
170 let query = parse_create_index("CREATE INDEX IF NOT EXISTS idx ON users (email, type)");
171 assert_eq!(query.name, "idx");
172 assert_eq!(query.table, "users");
173 assert_eq!(query.columns, vec!["email", "type"]);
174 assert_eq!(query.method, IndexMethod::BTree);
175 assert!(query.if_not_exists);
176 assert!(!query.unique);
177
178 let query = parse_create_index("CREATE UNIQUE INDEX idx_orders ON orders (id) USING HASH");
179 assert!(query.unique);
180 assert_eq!(query.method, IndexMethod::Hash);
181 }
182
183 #[test]
184 fn parse_create_index_on_document_path() {
185 let query = parse_create_index("CREATE INDEX idx_docs_tier ON docs (body.service.tier)");
186 assert_eq!(query.name, "idx_docs_tier");
187 assert_eq!(query.table, "docs");
188 assert_eq!(query.columns, vec!["body.service.tier"]);
189 assert_eq!(query.method, IndexMethod::BTree);
190 }
191
192 #[test]
193 fn parse_index_method_accepts_all_ident_variants() {
194 for (method, expected) in [
195 ("BTREE", IndexMethod::BTree),
196 ("BITMAP", IndexMethod::Bitmap),
197 ("RTREE", IndexMethod::RTree),
198 ("hash", IndexMethod::Hash),
199 ] {
200 let query = parse_create_index(&format!(
201 "CREATE INDEX idx_{method} ON users (email) USING {method}"
202 ));
203 assert_eq!(query.method, expected);
204 }
205 }
206
207 #[test]
208 fn parse_drop_index_body_covers_if_exists() {
209 let query = parse_drop_index("DROP INDEX IF EXISTS idx_email ON users");
210 assert_eq!(query.name, "idx_email");
211 assert_eq!(query.table, "users");
212 assert!(query.if_exists);
213
214 let query = parse_drop_index("DROP INDEX idx_email ON users");
215 assert!(!query.if_exists);
216 }
217
218 #[test]
219 fn parse_index_method_reports_unknown_ident_and_non_ident_errors() {
220 let mut p = parser("CREATE INDEX idx ON users (email) USING GIN");
221 p.expect(Token::Create).expect("CREATE");
222 let err = p.parse_create_index_query().unwrap_err();
223 assert!(
224 err.to_string().contains("unknown index method 'GIN'"),
225 "{err}"
226 );
227
228 let mut p = parser("CREATE INDEX idx ON users (email) USING 42");
229 p.expect(Token::Create).expect("CREATE");
230 let err = p.parse_create_index_query().unwrap_err();
231 assert!(
232 err.to_string()
233 .contains("expected: HASH, BTREE, BITMAP, RTREE"),
234 "{err}"
235 );
236 }
237}