Skip to main content

qusql_parse/
create_index.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5// http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12use crate::{
13    Expression, Identifier, QualifiedName, Span, Spanned,
14    alter_table::{IndexCol, IndexColExpr, parse_operator_class},
15    create_option::CreateOption,
16    expression::{PRIORITY_MAX, parse_expression_unreserved},
17    keywords::Keyword,
18    lexer::Token,
19    parser::{ParseError, Parser},
20    qualified_name::parse_qualified_name_unreserved,
21};
22
23/// A single `key = value` option inside a `WITH (...)` clause on `CREATE INDEX`.
24#[derive(Clone, Debug)]
25pub struct WithOption<'a> {
26    pub name: Identifier<'a>,
27    pub eq_span: Span,
28    pub value: Expression<'a>,
29}
30
31impl<'a> Spanned for WithOption<'a> {
32    fn span(&self) -> Span {
33        self.name.join_span(&self.value)
34    }
35}
36use alloc::vec::Vec;
37
38#[derive(Clone, Debug)]
39pub enum UsingIndexMethod {
40    Gist(Span),
41    Bloom(Span),
42    Brin(Span),
43    Hnsw(Span),
44    Gin(Span),
45    BTree(Span),
46    Hash(Span),
47    RTree(Span),
48}
49
50impl Spanned for UsingIndexMethod {
51    fn span(&self) -> Span {
52        match self {
53            UsingIndexMethod::Gist(s) => s.clone(),
54            UsingIndexMethod::Bloom(s) => s.clone(),
55            UsingIndexMethod::Brin(s) => s.clone(),
56            UsingIndexMethod::Hnsw(s) => s.clone(),
57            UsingIndexMethod::BTree(s) => s.clone(),
58            UsingIndexMethod::Hash(s) => s.clone(),
59            UsingIndexMethod::RTree(s) => s.clone(),
60            UsingIndexMethod::Gin(s) => s.clone(),
61        }
62    }
63}
64
65pub(crate) fn parse_using_index_method<'a>(
66    parser: &mut Parser<'a, '_>,
67    using_span: Span,
68) -> Result<UsingIndexMethod, ParseError> {
69    match &parser.token {
70        Token::Ident(_, Keyword::GIST) => {
71            let gist_span = parser.consume_keyword(Keyword::GIST)?;
72            Ok(UsingIndexMethod::Gist(using_span.join_span(&gist_span)))
73        }
74        Token::Ident(_, Keyword::BLOOM) => {
75            let bloom_span = parser.consume_keyword(Keyword::BLOOM)?;
76            Ok(UsingIndexMethod::Bloom(using_span.join_span(&bloom_span)))
77        }
78        Token::Ident(_, Keyword::BRIN) => {
79            let brin_span = parser.consume_keyword(Keyword::BRIN)?;
80            Ok(UsingIndexMethod::Brin(using_span.join_span(&brin_span)))
81        }
82        Token::Ident(_, Keyword::HNSW) => {
83            let hnsw_span = parser.consume_keyword(Keyword::HNSW)?;
84            Ok(UsingIndexMethod::Hnsw(using_span.join_span(&hnsw_span)))
85        }
86        Token::Ident(_, Keyword::GIN) => {
87            let gin_span = parser.consume_keyword(Keyword::GIN)?;
88            Ok(UsingIndexMethod::Gin(using_span.join_span(&gin_span)))
89        }
90        Token::Ident(_, Keyword::BTREE) => {
91            let btree_span = parser.consume_keyword(Keyword::BTREE)?;
92            Ok(UsingIndexMethod::BTree(using_span.join_span(&btree_span)))
93        }
94        Token::Ident(_, Keyword::HASH) => {
95            let hash_span = parser.consume_keyword(Keyword::HASH)?;
96            Ok(UsingIndexMethod::Hash(using_span.join_span(&hash_span)))
97        }
98        Token::Ident(_, Keyword::RTREE) => {
99            let rtree_span = parser.consume_keyword(Keyword::RTREE)?;
100            Ok(UsingIndexMethod::RTree(using_span.join_span(&rtree_span)))
101        }
102        _ => Err(parser
103            .err_here("Expected GIST, BLOOM, BRIN, HNSW, BTREE, HASH, or RTREE after USING")?),
104    }
105}
106
107#[derive(Clone, Debug)]
108pub enum CreateIndexOption<'a> {
109    UsingIndex(UsingIndexMethod),
110    Algorithm(Span, Identifier<'a>),
111    AlgorithmDefault {
112        algorithm_span: Span,
113        default_span: Span,
114    },
115    Lock(Span, Identifier<'a>),
116}
117
118impl<'a> Spanned for CreateIndexOption<'a> {
119    fn span(&self) -> Span {
120        match self {
121            CreateIndexOption::UsingIndex(method) => method.span(),
122            CreateIndexOption::Algorithm(s, i) => s.join_span(i),
123            CreateIndexOption::AlgorithmDefault {
124                algorithm_span,
125                default_span,
126            } => algorithm_span.join_span(default_span),
127            CreateIndexOption::Lock(s, i) => s.join_span(i),
128        }
129    }
130}
131
132#[derive(Clone, Debug)]
133pub struct IncludeClause<'a> {
134    pub include_span: Span,
135    pub l_paren_span: Span,
136    pub columns: Vec<Identifier<'a>>,
137    pub r_paren_span: Span,
138}
139
140impl<'a> Spanned for IncludeClause<'a> {
141    fn span(&self) -> Span {
142        self.include_span
143            .join_span(&self.l_paren_span)
144            .join_span(&self.columns)
145            .join_span(&self.r_paren_span)
146    }
147}
148
149#[derive(Clone, Debug)]
150pub struct CreateIndex<'a> {
151    pub create_span: Span,
152    pub create_options: Vec<CreateOption<'a>>,
153    pub index_span: Span,
154    pub index_name: Option<Identifier<'a>>,
155    pub if_not_exists: Option<Span>,
156    pub on_span: Span,
157    pub table_name: QualifiedName<'a>,
158    pub index_options: Vec<CreateIndexOption<'a>>,
159    pub l_paren_span: Span,
160    pub column_names: Vec<IndexCol<'a>>,
161    pub r_paren_span: Span,
162    pub include_clause: Option<IncludeClause<'a>>,
163    pub with_options: Option<(Span, Vec<WithOption<'a>>)>,
164    pub where_: Option<(Span, Expression<'a>)>,
165    pub nulls_distinct: Option<(Span, Option<Span>)>,
166}
167
168impl<'a> Spanned for CreateIndex<'a> {
169    fn span(&self) -> Span {
170        self.create_span
171            .join_span(&self.create_options)
172            .join_span(&self.index_span)
173            .join_span(&self.index_name)
174            .join_span(&self.on_span)
175            .join_span(&self.table_name)
176            .join_span(&self.index_options)
177            .join_span(&self.l_paren_span)
178            .join_span(&self.column_names)
179            .join_span(&self.r_paren_span)
180            .join_span(&self.include_clause)
181            .join_span(&self.with_options.as_ref().map(|(s, c)| s.join_span(c)))
182            .join_span(&self.where_)
183            .join_span(&self.nulls_distinct)
184    }
185}
186
187pub(crate) fn parse_create_index<'a>(
188    parser: &mut Parser<'a, '_>,
189    create_span: Span,
190    mut create_options: Vec<CreateOption<'a>>,
191) -> Result<CreateIndex<'a>, ParseError> {
192    let index_span = parser.consume_keyword(Keyword::INDEX)?;
193
194    // PostgreSQL: CONCURRENTLY
195    if let Some(concurrently_span) = parser.skip_keyword(Keyword::CONCURRENTLY) {
196        parser.postgres_only(&concurrently_span);
197        create_options.push(CreateOption::Concurrently(concurrently_span));
198    }
199
200    let if_not_exists = if let Some(s) = parser.skip_keyword(Keyword::IF) {
201        Some(s.join_span(&parser.consume_keywords(&[Keyword::NOT, Keyword::EXISTS])?))
202    } else {
203        None
204    };
205
206    // PostgreSQL: index name is optional, ON can come directly after INDEX
207    let index_name = if let Token::Ident(_, Keyword::ON) = &parser.token {
208        // Unnamed index
209        None
210    } else {
211        // Named index
212        Some(parser.consume_plain_identifier_unreserved()?)
213    };
214
215    // MySQL/MariaDB require index names
216    if index_name.is_none() && parser.options.dialect.is_maria() {
217        parser.err("Index name required", &index_span);
218    }
219
220    let on_span = parser.consume_keyword(Keyword::ON)?;
221    let table_name = parse_qualified_name_unreserved(parser)?;
222
223    // PostgreSQL: USING (GIST|BLOOM|BRIN|HNSW) before column list
224    let mut index_options = Vec::new();
225    if let Some(using_span) = parser.skip_keyword(Keyword::USING) {
226        let using_index_method = parse_using_index_method(parser, using_span)?;
227        index_options.push(CreateIndexOption::UsingIndex(using_index_method));
228    }
229
230    let l_paren_span = parser.consume_token(Token::LParen)?;
231    let mut column_names = Vec::new();
232    loop {
233        // Check if this is a functional index expression (starts with '(')
234        let expr = if parser.token == Token::LParen {
235            // Functional index: parse expression
236            parser.consume_token(Token::LParen)?;
237            let expression = parse_expression_unreserved(parser, PRIORITY_MAX)?;
238            parser.consume_token(Token::RParen)?;
239            IndexColExpr::Expression(expression)
240        } else {
241            // Regular column name
242            let name = parser.consume_plain_identifier_unreserved()?;
243            IndexColExpr::Column(name)
244        };
245
246        let size = if parser.skip_token(Token::LParen).is_some() {
247            let size = parser.recovered("')'", &|t| t == &Token::RParen, |parser| {
248                parser.consume_int()
249            })?;
250            parser.consume_token(Token::RParen)?;
251            Some(size)
252        } else {
253            None
254        };
255
256        // Parse optional operator class (PostgreSQL)
257        let opclass = parse_operator_class(parser)?;
258
259        // Parse optional ASC | DESC
260        let asc = parser.skip_keyword(Keyword::ASC);
261        let desc = if asc.is_none() {
262            parser.skip_keyword(Keyword::DESC)
263        } else {
264            None
265        };
266
267        column_names.push(IndexCol {
268            expr,
269            size,
270            opclass,
271            asc,
272            desc,
273        });
274
275        if parser.skip_token(Token::Comma).is_none() {
276            break;
277        }
278    }
279
280    let r_paren_span = parser.consume_token(Token::RParen)?;
281
282    // PostgreSQL: INCLUDE clause
283    let include_clause = if let Some(include_span) = parser.skip_keyword(Keyword::INCLUDE) {
284        let l_paren = parser.consume_token(Token::LParen)?;
285        let mut include_cols = Vec::new();
286        loop {
287            include_cols.push(parser.consume_plain_identifier_unreserved()?);
288            if parser.skip_token(Token::Comma).is_none() {
289                break;
290            }
291        }
292        let r_paren = parser.consume_token(Token::RParen)?;
293        parser.postgres_only(&include_span);
294        Some(IncludeClause {
295            include_span,
296            l_paren_span: l_paren,
297            columns: include_cols,
298            r_paren_span: r_paren,
299        })
300    } else {
301        None
302    };
303
304    // PostgreSQL: WITH (storage_parameters)
305    let with_options = if let Some(with_span) = parser.skip_keyword(Keyword::WITH) {
306        parser.postgres_only(&with_span);
307        parser.consume_token(Token::LParen)?;
308        let mut opts = Vec::new();
309        parser.recovered("')'", &|t| t == &Token::RParen, |parser| {
310            loop {
311                let name = parser.consume_plain_identifier_unreserved()?;
312                let eq_span = parser.consume_token(Token::Eq)?;
313                let value = parse_expression_unreserved(parser, PRIORITY_MAX)?;
314                opts.push(WithOption {
315                    name,
316                    eq_span,
317                    value,
318                });
319                if parser.skip_token(Token::Comma).is_none() {
320                    break;
321                }
322            }
323            Ok(())
324        })?;
325        parser.consume_token(Token::RParen)?;
326        Some((with_span, opts))
327    } else {
328        None
329    };
330
331    // Parse index options after column list (MySQL/MariaDB)
332    loop {
333        match &parser.token {
334            Token::Ident(_, Keyword::USING) => {
335                let using_span = parser.consume_keyword(Keyword::USING)?;
336                let using_index_method = parse_using_index_method(parser, using_span)?;
337                index_options.push(CreateIndexOption::UsingIndex(using_index_method));
338            }
339            Token::Ident(_, Keyword::ALGORITHM) => {
340                let algorithm_span = parser.consume_keyword(Keyword::ALGORITHM)?;
341                parser.skip_token(Token::Eq); // Optional =
342                if matches!(&parser.token, Token::Ident(_, Keyword::DEFAULT)) {
343                    let default_span = parser.consume_keyword(Keyword::DEFAULT)?;
344                    index_options.push(CreateIndexOption::AlgorithmDefault {
345                        algorithm_span,
346                        default_span,
347                    });
348                } else {
349                    let algorithm_value = parser.consume_plain_identifier_unreserved()?;
350                    index_options.push(CreateIndexOption::Algorithm(
351                        algorithm_span,
352                        algorithm_value,
353                    ));
354                }
355            }
356            Token::Ident(_, Keyword::LOCK) => {
357                let lock_span = parser.consume_keyword(Keyword::LOCK)?;
358                parser.skip_token(Token::Eq); // Optional =
359                let lock_value = parser.consume_plain_identifier_unreserved()?;
360                index_options.push(CreateIndexOption::Lock(lock_span, lock_value));
361            }
362            _ => break,
363        }
364    }
365
366    let mut where_ = None;
367    if let Some(where_span) = parser.skip_keyword(Keyword::WHERE) {
368        let where_expr = parse_expression_unreserved(parser, PRIORITY_MAX)?;
369        if parser.options.dialect.is_maria() {
370            parser.err(
371                "Partial indexes not supported",
372                &where_span.join_span(&where_expr),
373            );
374        }
375        where_ = Some((where_span, where_expr));
376    }
377
378    // PostgreSQL: NULLS [NOT] DISTINCT
379    let nulls_distinct = if let Some(nulls_span) = parser.skip_keyword(Keyword::NULLS) {
380        let not_span = parser.skip_keyword(Keyword::NOT);
381        let distinct_span = parser.consume_keyword(Keyword::DISTINCT)?;
382        parser.postgres_only(&nulls_span.join_span(&distinct_span));
383        Some((nulls_span, not_span))
384    } else {
385        None
386    };
387
388    Ok(CreateIndex {
389        create_span,
390        create_options,
391        index_span,
392        index_name,
393        if_not_exists,
394        on_span,
395        table_name,
396        index_options,
397        l_paren_span,
398        column_names,
399        r_paren_span,
400        include_clause,
401        with_options,
402        where_,
403        nulls_distinct,
404    })
405}