alopex_sql/parser/
ddl.rs

1use crate::ast::ddl::{
2    ColumnConstraint, ColumnDef, CreateIndex, CreateTable, DataType, DropIndex, DropTable,
3    IndexMethod, IndexOption, TableConstraint, VectorMetric,
4};
5use crate::ast::span::{Span, Spanned};
6use crate::error::{ParserError, Result};
7use crate::tokenizer::keyword::Keyword;
8use crate::tokenizer::token::{Token, Word};
9
10use super::Parser;
11
12impl<'a> Parser<'a> {
13    pub fn parse_create_table(&mut self) -> Result<CreateTable> {
14        let start_span = self.expect_keyword("CREATE", Keyword::CREATE)?;
15        self.expect_keyword("TABLE", Keyword::TABLE)?;
16        let if_not_exists = if self.consume_keyword(Keyword::IF) {
17            self.expect_keyword("NOT", Keyword::NOT)?;
18            self.expect_keyword("EXISTS", Keyword::EXISTS)?;
19            true
20        } else {
21            false
22        };
23
24        let (name, _name_span) = self.parse_identifier()?;
25        self.expect_token("'('", |t| matches!(t, Token::LParen))?;
26
27        let mut columns = Vec::new();
28        let mut constraints = Vec::new();
29
30        loop {
31            if self.consume_keyword(Keyword::PRIMARY) {
32                self.expect_keyword("KEY", Keyword::KEY)?;
33                self.expect_token("'('", |t| matches!(t, Token::LParen))?;
34                let mut cols = Vec::new();
35                loop {
36                    let (col, _) = self.parse_identifier()?;
37                    cols.push(col);
38                    if matches!(self.peek().token, Token::Comma) {
39                        self.advance();
40                        continue;
41                    }
42                    break;
43                }
44                let pk_end = self
45                    .expect_token("')'", |t| matches!(t, Token::RParen))?
46                    .span;
47                constraints.push(TableConstraint::PrimaryKey {
48                    columns: cols,
49                    span: start_span.union(&pk_end),
50                });
51            } else {
52                let (col_name, col_span) = self.parse_identifier()?;
53                let (data_type, dt_span) = self.parse_data_type()?;
54                let mut col_constraints = Vec::new();
55                while let Token::Word(Word { keyword, .. }) = &self.peek().token {
56                    let constraint = match keyword {
57                        Keyword::NOT => {
58                            let start = self.peek().span;
59                            self.advance();
60                            self.expect_keyword("NULL", Keyword::NULL)?;
61                            ColumnConstraint::WithSpan {
62                                kind: Box::new(ColumnConstraint::NotNull),
63                                span: start.union(&self.prev().unwrap().span),
64                            }
65                        }
66                        Keyword::NULL => {
67                            let s = self.advance().span;
68                            ColumnConstraint::WithSpan {
69                                kind: Box::new(ColumnConstraint::Null),
70                                span: s,
71                            }
72                        }
73                        Keyword::PRIMARY => {
74                            let start = self.advance().span;
75                            self.expect_keyword("KEY", Keyword::KEY)?;
76                            ColumnConstraint::WithSpan {
77                                kind: Box::new(ColumnConstraint::PrimaryKey),
78                                span: start.union(&self.prev().unwrap().span),
79                            }
80                        }
81                        Keyword::UNIQUE => {
82                            let s = self.advance().span;
83                            ColumnConstraint::WithSpan {
84                                kind: Box::new(ColumnConstraint::Unique),
85                                span: s,
86                            }
87                        }
88                        Keyword::DEFAULT => {
89                            let start = self.advance().span;
90                            let expr = self.parse_expr()?;
91                            let span = start.union(&expr.span());
92                            ColumnConstraint::WithSpan {
93                                kind: Box::new(ColumnConstraint::Default(expr)),
94                                span,
95                            }
96                        }
97                        _ => break,
98                    };
99                    col_constraints.push(constraint);
100                }
101
102                let end_span = if let Some(last) = col_constraints.last() {
103                    last.span()
104                } else {
105                    dt_span
106                };
107
108                columns.push(ColumnDef {
109                    name: col_name,
110                    data_type,
111                    constraints: col_constraints,
112                    span: col_span.union(&end_span),
113                });
114            }
115
116            if matches!(self.peek().token, Token::Comma) {
117                self.advance();
118                continue;
119            }
120            break;
121        }
122
123        let end_span = self
124            .expect_token("')'", |t| matches!(t, Token::RParen))?
125            .span;
126
127        // Optional WITH clause for table options
128        let mut with_options = Vec::new();
129        let mut span = start_span.union(&end_span);
130        if self.consume_keyword(Keyword::WITH) {
131            let with_start = self
132                .expect_token("'('", |t| matches!(t, Token::LParen))?
133                .span;
134            loop {
135                let (key, key_span) = self.parse_identifier()?;
136                self.expect_token("'='", |t| matches!(t, Token::Eq))?;
137                let val_tok = self.expect_token("option value", |t| {
138                    matches!(
139                        t,
140                        Token::Number(_) | Token::Word(_) | Token::SingleQuotedString(_)
141                    )
142                })?;
143                let value = match val_tok.token {
144                    Token::Number(n) => n,
145                    Token::Word(Word { value, .. }) => value,
146                    Token::SingleQuotedString(s) => s,
147                    _ => unreachable!(),
148                };
149                let _span = key_span.union(&val_tok.span);
150                with_options.push((key, value));
151
152                if matches!(self.peek().token, Token::Comma) {
153                    self.advance();
154                    continue;
155                }
156                break;
157            }
158            let with_end = self
159                .expect_token("')'", |t| matches!(t, Token::RParen))?
160                .span;
161            span = span.union(&with_start).union(&with_end);
162        }
163
164        Ok(CreateTable {
165            if_not_exists,
166            name,
167            columns,
168            constraints,
169            with_options,
170            span,
171        })
172    }
173
174    pub fn parse_drop_table(&mut self) -> Result<DropTable> {
175        let start_span = self.expect_keyword("DROP", Keyword::DROP)?;
176        self.expect_keyword("TABLE", Keyword::TABLE)?;
177        let if_exists = if self.consume_keyword(Keyword::IF) {
178            self.expect_keyword("EXISTS", Keyword::EXISTS)?;
179            true
180        } else {
181            false
182        };
183        let (name, name_span) = self.parse_identifier()?;
184        let span = start_span.union(&name_span);
185        Ok(DropTable {
186            if_exists,
187            name,
188            span,
189        })
190    }
191
192    pub fn parse_create_index(&mut self) -> Result<CreateIndex> {
193        let start_span = self.expect_keyword("CREATE", Keyword::CREATE)?;
194        self.expect_keyword("INDEX", Keyword::INDEX)?;
195        let if_not_exists = if self.consume_keyword(Keyword::IF) {
196            self.expect_keyword("NOT", Keyword::NOT)?;
197            self.expect_keyword("EXISTS", Keyword::EXISTS)?;
198            true
199        } else {
200            false
201        };
202
203        let (name, _name_span) = self.parse_identifier()?;
204        self.expect_keyword("ON", Keyword::ON)?;
205        let (table, _table_span) = self.parse_identifier()?;
206        self.expect_token("'('", |t| matches!(t, Token::LParen))?;
207        let (column, _col_span) = self.parse_identifier()?;
208        let end_paren = self
209            .expect_token("')'", |t| matches!(t, Token::RParen))?
210            .span;
211
212        let mut method = None;
213        if self.consume_keyword(Keyword::USING) {
214            let meth = self
215                .expect_keyword("index method", Keyword::HNSW)
216                .or_else(|_| self.expect_keyword("index method", Keyword::BTREE))?;
217            method = Some(match self.prev().unwrap().token {
218                Token::Word(Word {
219                    keyword: Keyword::HNSW,
220                    ..
221                }) => IndexMethod::Hnsw,
222                _ => IndexMethod::BTree,
223            });
224            // Use meth span to extend end
225            let _ = meth;
226        }
227
228        let mut options = Vec::new();
229        if self.consume_keyword(Keyword::WITH) {
230            self.expect_token("'('", |t| matches!(t, Token::LParen))?;
231            loop {
232                let (key, key_span) = self.parse_identifier()?;
233                self.expect_token("'='", |t| matches!(t, Token::Eq))?;
234                let val_tok = self.expect_token("option value", |t| {
235                    matches!(
236                        t,
237                        Token::Number(_) | Token::Word(_) | Token::SingleQuotedString(_)
238                    )
239                })?;
240                let value = match val_tok.token {
241                    Token::Number(n) => n,
242                    Token::Word(Word { value, .. }) => value,
243                    Token::SingleQuotedString(s) => s,
244                    _ => unreachable!(),
245                };
246                let span = key_span.union(&val_tok.span);
247                options.push(IndexOption { key, value, span });
248
249                if matches!(self.peek().token, Token::Comma) {
250                    self.advance();
251                    continue;
252                }
253                break;
254            }
255            let _ = self.expect_token("')'", |t| matches!(t, Token::RParen))?;
256        }
257
258        let span = start_span.union(&end_paren);
259        Ok(CreateIndex {
260            if_not_exists,
261            name,
262            table,
263            column,
264            method,
265            options,
266            span,
267        })
268    }
269
270    pub fn parse_drop_index(&mut self) -> Result<DropIndex> {
271        let start_span = self.expect_keyword("DROP", Keyword::DROP)?;
272        self.expect_keyword("INDEX", Keyword::INDEX)?;
273        let if_exists = if self.consume_keyword(Keyword::IF) {
274            self.expect_keyword("EXISTS", Keyword::EXISTS)?;
275            true
276        } else {
277            false
278        };
279        let (name, name_span) = self.parse_identifier()?;
280        let span = start_span.union(&name_span);
281        Ok(DropIndex {
282            if_exists,
283            name,
284            span,
285        })
286    }
287
288    fn parse_data_type(&mut self) -> Result<(DataType, Span)> {
289        let tok = self.peek().clone();
290        let (dtype, end_span) = match &tok.token {
291            Token::Word(Word { keyword, .. }) => match keyword {
292                Keyword::INTEGER => {
293                    self.advance();
294                    (DataType::Integer, tok.span)
295                }
296                Keyword::INT => {
297                    self.advance();
298                    (DataType::Int, tok.span)
299                }
300                Keyword::BIGINT => {
301                    self.advance();
302                    (DataType::BigInt, tok.span)
303                }
304                Keyword::FLOAT => {
305                    self.advance();
306                    (DataType::Float, tok.span)
307                }
308                Keyword::DOUBLE => {
309                    self.advance();
310                    (DataType::Double, tok.span)
311                }
312                Keyword::TEXT => {
313                    self.advance();
314                    (DataType::Text, tok.span)
315                }
316                Keyword::BLOB => {
317                    self.advance();
318                    (DataType::Blob, tok.span)
319                }
320                Keyword::BOOLEAN => {
321                    self.advance();
322                    (DataType::Boolean, tok.span)
323                }
324                Keyword::BOOL => {
325                    self.advance();
326                    (DataType::Bool, tok.span)
327                }
328                Keyword::TIMESTAMP => {
329                    self.advance();
330                    (DataType::Timestamp, tok.span)
331                }
332                Keyword::VECTOR => {
333                    self.advance();
334                    self.expect_token("'('", |t| matches!(t, Token::LParen))?;
335                    let dim_tok =
336                        self.expect_token("dimension", |t| matches!(t, Token::Number(_)))?;
337                    let dimension: u32 = match dim_tok.token {
338                        Token::Number(ref n) => {
339                            n.parse().map_err(|_| ParserError::InvalidVector {
340                                line: dim_tok.span.start.line,
341                                column: dim_tok.span.start.column,
342                            })?
343                        }
344                        _ => unreachable!(),
345                    };
346                    let mut metric = None;
347                    let mut last_span = dim_tok.span;
348                    if matches!(self.peek().token, Token::Comma) {
349                        self.advance();
350                        let m_tok = self.expect_token("metric", |t| {
351                            matches!(
352                                t,
353                                Token::Word(Word {
354                                    keyword: Keyword::COSINE | Keyword::L2 | Keyword::INNER,
355                                    ..
356                                })
357                            )
358                        })?;
359                        metric = Some(match m_tok.token {
360                            Token::Word(Word {
361                                keyword: Keyword::COSINE,
362                                ..
363                            }) => VectorMetric::Cosine,
364                            Token::Word(Word {
365                                keyword: Keyword::L2,
366                                ..
367                            }) => VectorMetric::L2,
368                            _ => VectorMetric::Inner,
369                        });
370                        last_span = m_tok.span;
371                    }
372                    let end = self
373                        .expect_token("')'", |t| matches!(t, Token::RParen))?
374                        .span;
375                    let span = tok.span.union(&end);
376                    (
377                        DataType::Vector { dimension, metric },
378                        span.union(&last_span),
379                    )
380                }
381                _ => {
382                    return Err(ParserError::UnexpectedToken {
383                        line: tok.span.start.line,
384                        column: tok.span.start.column,
385                        expected: "data type".into(),
386                        found: format!("{:?}", tok.token),
387                    });
388                }
389            },
390            _ => {
391                return Err(ParserError::UnexpectedToken {
392                    line: tok.span.start.line,
393                    column: tok.span.start.column,
394                    expected: "data type".into(),
395                    found: format!("{:?}", tok.token),
396                });
397            }
398        };
399
400        Ok((dtype, end_span))
401    }
402}