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 if matches!(&parser.token, Token::Ident(_, _))
241            && matches!(parser.peek(), Token::LParen)
242            && parser.options.dialect.is_postgresql()
243        {
244            // Function call expression like upper(col) or func(a, b)
245            IndexColExpr::Expression(parse_expression_unreserved(parser, PRIORITY_MAX)?)
246        } else {
247            // Regular column name
248            let name = parser.consume_plain_identifier_unreserved()?;
249            IndexColExpr::Column(name)
250        };
251
252        let size = if parser.skip_token(Token::LParen).is_some() {
253            let size = parser.recovered("')'", &|t| t == &Token::RParen, |parser| {
254                parser.consume_int()
255            })?;
256            parser.consume_token(Token::RParen)?;
257            Some(size)
258        } else {
259            None
260        };
261
262        // Parse optional operator class (PostgreSQL)
263        let opclass = parse_operator_class(parser)?;
264
265        // Parse optional ASC | DESC
266        let asc = parser.skip_keyword(Keyword::ASC);
267        let desc = if asc.is_none() {
268            parser.skip_keyword(Keyword::DESC)
269        } else {
270            None
271        };
272
273        column_names.push(IndexCol {
274            expr,
275            size,
276            opclass,
277            asc,
278            desc,
279        });
280
281        if parser.skip_token(Token::Comma).is_none() {
282            break;
283        }
284    }
285
286    let r_paren_span = parser.consume_token(Token::RParen)?;
287
288    // PostgreSQL: INCLUDE clause
289    let include_clause = if let Some(include_span) = parser.skip_keyword(Keyword::INCLUDE) {
290        let l_paren = parser.consume_token(Token::LParen)?;
291        let mut include_cols = Vec::new();
292        loop {
293            include_cols.push(parser.consume_plain_identifier_unreserved()?);
294            if parser.skip_token(Token::Comma).is_none() {
295                break;
296            }
297        }
298        let r_paren = parser.consume_token(Token::RParen)?;
299        parser.postgres_only(&include_span);
300        Some(IncludeClause {
301            include_span,
302            l_paren_span: l_paren,
303            columns: include_cols,
304            r_paren_span: r_paren,
305        })
306    } else {
307        None
308    };
309
310    // PostgreSQL: WITH (storage_parameters)
311    let with_options = if let Some(with_span) = parser.skip_keyword(Keyword::WITH) {
312        parser.postgres_only(&with_span);
313        parser.consume_token(Token::LParen)?;
314        let mut opts = Vec::new();
315        parser.recovered("')'", &|t| t == &Token::RParen, |parser| {
316            loop {
317                let name = parser.consume_plain_identifier_unreserved()?;
318                let eq_span = parser.consume_token(Token::Eq)?;
319                let value = parse_expression_unreserved(parser, PRIORITY_MAX)?;
320                opts.push(WithOption {
321                    name,
322                    eq_span,
323                    value,
324                });
325                if parser.skip_token(Token::Comma).is_none() {
326                    break;
327                }
328            }
329            Ok(())
330        })?;
331        parser.consume_token(Token::RParen)?;
332        Some((with_span, opts))
333    } else {
334        None
335    };
336
337    // Parse index options after column list (MySQL/MariaDB)
338    loop {
339        match &parser.token {
340            Token::Ident(_, Keyword::USING) => {
341                let using_span = parser.consume_keyword(Keyword::USING)?;
342                let using_index_method = parse_using_index_method(parser, using_span)?;
343                index_options.push(CreateIndexOption::UsingIndex(using_index_method));
344            }
345            Token::Ident(_, Keyword::ALGORITHM) => {
346                let algorithm_span = parser.consume_keyword(Keyword::ALGORITHM)?;
347                parser.skip_token(Token::Eq); // Optional =
348                if matches!(&parser.token, Token::Ident(_, Keyword::DEFAULT)) {
349                    let default_span = parser.consume_keyword(Keyword::DEFAULT)?;
350                    index_options.push(CreateIndexOption::AlgorithmDefault {
351                        algorithm_span,
352                        default_span,
353                    });
354                } else {
355                    let algorithm_value = parser.consume_plain_identifier_unreserved()?;
356                    index_options.push(CreateIndexOption::Algorithm(
357                        algorithm_span,
358                        algorithm_value,
359                    ));
360                }
361            }
362            Token::Ident(_, Keyword::LOCK) => {
363                let lock_span = parser.consume_keyword(Keyword::LOCK)?;
364                parser.skip_token(Token::Eq); // Optional =
365                let lock_value = parser.consume_plain_identifier_unreserved()?;
366                index_options.push(CreateIndexOption::Lock(lock_span, lock_value));
367            }
368            _ => break,
369        }
370    }
371
372    let mut where_ = None;
373    if let Some(where_span) = parser.skip_keyword(Keyword::WHERE) {
374        let where_expr = parse_expression_unreserved(parser, PRIORITY_MAX)?;
375        if parser.options.dialect.is_maria() {
376            parser.err(
377                "Partial indexes not supported",
378                &where_span.join_span(&where_expr),
379            );
380        }
381        where_ = Some((where_span, where_expr));
382    }
383
384    // PostgreSQL: NULLS [NOT] DISTINCT
385    let nulls_distinct = if let Some(nulls_span) = parser.skip_keyword(Keyword::NULLS) {
386        let not_span = parser.skip_keyword(Keyword::NOT);
387        let distinct_span = parser.consume_keyword(Keyword::DISTINCT)?;
388        parser.postgres_only(&nulls_span.join_span(&distinct_span));
389        Some((nulls_span, not_span))
390    } else {
391        None
392    };
393
394    Ok(CreateIndex {
395        create_span,
396        create_options,
397        index_span,
398        index_name,
399        if_not_exists,
400        on_span,
401        table_name,
402        index_options,
403        l_paren_span,
404        column_names,
405        r_paren_span,
406        include_clause,
407        with_options,
408        where_,
409        nulls_distinct,
410    })
411}