Skip to main content

senax_mysql_parser/
create.rs

1use nom::branch::alt;
2use nom::bytes::complete::{tag, tag_no_case, take_until};
3use nom::character::complete::{digit1, multispace0, multispace1};
4use nom::combinator::{map, opt};
5use nom::multi::{many0, many1};
6use nom::sequence::{delimited, preceded, terminated};
7use nom::{IResult, Parser};
8use serde::Deserialize;
9use serde::Serialize;
10use std::fmt;
11use std::str;
12use std::str::FromStr;
13
14use super::column::{Column, ColumnConstraint, ColumnSpecification};
15use super::common::{
16    Literal, Real, SqlType, TableKey, column_identifier_no_alias, column_identifier_query,
17    parse_comment, reference_option, schema_table_reference, sql_identifier, statement_terminator,
18    type_identifier, ws_sep_comma,
19};
20use super::create_table_options::table_options;
21use super::keywords::escape;
22use super::order::{OrderType, order_type};
23use crate::common::{string_literal, take_until_unbalanced};
24use crate::create_table_options::TableOption;
25
26#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
27pub struct CreateTableStatement {
28    pub table: String,
29    pub fields: Vec<ColumnSpecification>,
30    pub keys: Option<Vec<TableKey>>,
31    pub options: Vec<TableOption>,
32}
33
34impl fmt::Display for CreateTableStatement {
35    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36        write!(f, "CREATE TABLE {} ", escape(&self.table))?;
37        write!(f, "(")?;
38        write!(
39            f,
40            "{}",
41            self.fields
42                .iter()
43                .map(|field| format!("{}", field))
44                .collect::<Vec<_>>()
45                .join(", ")
46        )?;
47        if let Some(ref keys) = self.keys {
48            write!(
49                f,
50                ", {}",
51                keys.iter()
52                    .map(|key| format!("{}", key))
53                    .collect::<Vec<_>>()
54                    .join(", ")
55            )?;
56        }
57        write!(f, ")")?;
58        for option in &self.options {
59            write!(f, "\n{}", option)?;
60        }
61        write!(f, ";")
62    }
63}
64
65// MySQL grammar element for index column definition (ยง13.1.18, index_col_name)
66pub fn index_col_name(i: &[u8]) -> IResult<&[u8], (Column, Option<OrderType>)> {
67    let (remaining_input, (mut column, order)) = (
68        terminated(
69            alt((column_identifier_no_alias, column_identifier_query)),
70            multispace0,
71        ),
72        opt(order_type),
73    )
74        .parse(i)?;
75    column.desc = order == Some(OrderType::OrderDescending);
76    Ok((remaining_input, (column, order)))
77}
78
79// Helper for list of index columns
80pub fn index_col_list(i: &[u8]) -> IResult<&[u8], Vec<Column>> {
81    many0(map(
82        terminated(index_col_name, opt(ws_sep_comma)),
83        // XXX(malte): ignores length and order
84        |e| e.0,
85    ))
86    .parse(i)
87}
88
89// Parse rule for an individual key specification.
90pub fn key_specification(i: &[u8]) -> IResult<&[u8], TableKey> {
91    alt((
92        full_text_key,
93        primary_key,
94        unique,
95        key_or_index,
96        spatial,
97        check_constraint,
98        constraint,
99    ))
100    .parse(i)
101}
102
103// Matches a parenthesized expression with balanced parentheses,
104// skipping over string/identifier literals ('...', "...", `...`).
105fn balanced_parens(i: &[u8]) -> IResult<&[u8], &[u8]> {
106    if i.first() != Some(&b'(') {
107        return Err(nom::Err::Error(nom::error::Error::new(
108            i,
109            nom::error::ErrorKind::Char,
110        )));
111    }
112    let mut depth = 0usize;
113    let mut quote: Option<u8> = None;
114    let mut idx = 0usize;
115    while idx < i.len() {
116        let c = i[idx];
117        if let Some(q) = quote {
118            if c == b'\\' && q != b'`' {
119                idx += 1; // skip escaped character
120            } else if c == q {
121                if i.get(idx + 1) == Some(&q) {
122                    idx += 1; // doubled quote
123                } else {
124                    quote = None;
125                }
126            }
127        } else {
128            match c {
129                b'(' => depth += 1,
130                b')' => {
131                    depth -= 1;
132                    if depth == 0 {
133                        return Ok((&i[idx + 1..], &i[..=idx]));
134                    }
135                }
136                b'\'' | b'"' | b'`' => quote = Some(c),
137                _ => {}
138            }
139        }
140        idx += 1;
141    }
142    Err(nom::Err::Error(nom::error::Error::new(
143        i,
144        nom::error::ErrorKind::Eof,
145    )))
146}
147
148// CONSTRAINT `name` CHECK ((expr)) [NOT ENFORCED | /*!80016 NOT ENFORCED */]
149fn check_constraint(i: &[u8]) -> IResult<&[u8], TableKey> {
150    let (remaining_input, (_, _, name, _, _, _, clause, _, _)) = (
151        tag_no_case("CONSTRAINT"),
152        multispace1,
153        sql_identifier,
154        multispace1,
155        tag_no_case("CHECK"),
156        multispace0,
157        balanced_parens,
158        opt((multispace1, opt(tag_no_case("NOT ")), tag_no_case("ENFORCED"))),
159        opt((
160            multispace0,
161            tag("/*!80016"),
162            take_until("*/"),
163            tag("*/"),
164        )),
165    )
166        .parse(i)?;
167
168    let name = String::from_utf8(name.to_vec()).unwrap().replace("``", "`");
169    let clause = String::from_utf8(clause.to_vec()).unwrap();
170    Ok((
171        remaining_input,
172        TableKey::CheckConstraint(name, clause),
173    ))
174}
175
176fn full_text_key(i: &[u8]) -> IResult<&[u8], TableKey> {
177    let (remaining_input, (_, _, _, _, name, _, columns, _, parser, _)) = (
178        tag_no_case("fulltext"),
179        multispace1,
180        alt((tag_no_case("key"), tag_no_case("index"))),
181        multispace1,
182        sql_identifier,
183        multispace0,
184        delimited(
185            tag("("),
186            delimited(multispace0, index_col_list, multispace0),
187            tag(")"),
188        ),
189        multispace0,
190        opt(delimited(
191            tag("/*!50100 WITH PARSER"),
192            delimited(multispace0, sql_identifier, multispace0),
193            tag("*/"),
194        )),
195        multispace0,
196    )
197        .parse(i)?;
198
199    let name = String::from_utf8(name.to_vec()).unwrap().replace("``", "`");
200    let parser = parser.map(|v| String::from_utf8(v.to_vec()).unwrap());
201    Ok((
202        remaining_input,
203        TableKey::FulltextKey(name, columns, parser),
204    ))
205}
206
207fn primary_key(i: &[u8]) -> IResult<&[u8], TableKey> {
208    let (remaining_input, (_, _, columns, _, _, _)) = (
209        tag_no_case("primary key"),
210        multispace0,
211        delimited(
212            tag("("),
213            delimited(multispace0, index_col_list, multispace0),
214            tag(")"),
215        ),
216        opt(map(
217            preceded(multispace1, tag_no_case("auto_increment")),
218            |_| (),
219        )),
220        multispace0,
221        opt(tag_no_case("USING BTREE")),
222    )
223        .parse(i)?;
224
225    Ok((remaining_input, TableKey::PrimaryKey(columns)))
226}
227
228fn unique(i: &[u8]) -> IResult<&[u8], TableKey> {
229    // TODO: add branching to correctly parse whitespace after `unique`
230    let (remaining_input, (_, _, _, name, _, columns, _, _)) = (
231        tag_no_case("unique"),
232        opt(preceded(
233            multispace1,
234            alt((tag_no_case("key"), tag_no_case("index"))),
235        )),
236        multispace0,
237        sql_identifier,
238        multispace0,
239        delimited(
240            tag("("),
241            delimited(multispace0, index_col_list, multispace0),
242            tag(")"),
243        ),
244        multispace0,
245        opt(tag_no_case("USING BTREE")),
246    )
247        .parse(i)?;
248
249    let n = String::from_utf8(name.to_vec()).unwrap().replace("``", "`");
250    Ok((remaining_input, TableKey::UniqueKey(n, columns)))
251}
252
253fn key_or_index(i: &[u8]) -> IResult<&[u8], TableKey> {
254    let (remaining_input, (_, _, name, _, columns, _, _)) = (
255        alt((tag_no_case("key"), tag_no_case("index"))),
256        multispace0,
257        sql_identifier,
258        multispace0,
259        delimited(
260            tag("("),
261            delimited(multispace0, index_col_list, multispace0),
262            tag(")"),
263        ),
264        multispace0,
265        opt(tag_no_case("USING BTREE")),
266    )
267        .parse(i)?;
268
269    let n = String::from_utf8(name.to_vec()).unwrap().replace("``", "`");
270    Ok((remaining_input, TableKey::Key(n, columns)))
271}
272
273fn spatial(i: &[u8]) -> IResult<&[u8], TableKey> {
274    let (remaining_input, (_, _, _, name, _, columns)) = (
275        tag_no_case("spatial"),
276        opt(preceded(
277            multispace1,
278            alt((tag_no_case("key"), tag_no_case("index"))),
279        )),
280        multispace0,
281        sql_identifier,
282        multispace0,
283        delimited(
284            tag("("),
285            delimited(multispace0, index_col_list, multispace0),
286            tag(")"),
287        ),
288    )
289        .parse(i)?;
290
291    let n = String::from_utf8(name.to_vec()).unwrap().replace("``", "`");
292    Ok((remaining_input, TableKey::SpatialKey(n, columns)))
293}
294
295fn constraint(i: &[u8]) -> IResult<&[u8], TableKey> {
296    let (
297        remaining_input,
298        (
299            _,
300            _,
301            name,
302            _,
303            _,
304            _,
305            columns,
306            _,
307            _,
308            _,
309            table,
310            _,
311            foreign,
312            on_delete,
313            on_update,
314            on_delete2,
315        ),
316    ) = (
317        tag_no_case("CONSTRAINT"),
318        multispace1,
319        sql_identifier,
320        multispace1,
321        tag_no_case("FOREIGN KEY"),
322        multispace0,
323        delimited(
324            tag("("),
325            delimited(multispace0, index_col_list, multispace0),
326            tag(")"),
327        ),
328        multispace1,
329        tag_no_case("REFERENCES"),
330        multispace1,
331        sql_identifier,
332        multispace0,
333        delimited(
334            tag("("),
335            delimited(multispace0, index_col_list, multispace0),
336            tag(")"),
337        ),
338        opt((
339            multispace1,
340            tag_no_case("ON DELETE"),
341            multispace1,
342            reference_option,
343        )),
344        opt((
345            multispace1,
346            tag_no_case("ON UPDATE"),
347            multispace1,
348            reference_option,
349        )),
350        opt((
351            multispace1,
352            tag_no_case("ON DELETE"),
353            multispace1,
354            reference_option,
355        )),
356    )
357        .parse(i)?;
358
359    let name = String::from_utf8(name.to_vec()).unwrap().replace("``", "`");
360    let table = String::from_utf8(table.to_vec())
361        .unwrap()
362        .replace("``", "`");
363    let on_delete = if let Some(on_delete) = on_delete {
364        let (_, _, _, on_delete) = on_delete;
365        Some(on_delete)
366    } else if let Some(on_delete) = on_delete2 {
367        let (_, _, _, on_delete) = on_delete;
368        Some(on_delete)
369    } else {
370        None
371    };
372    let on_update = if let Some(on_update) = on_update {
373        let (_, _, _, on_update) = on_update;
374        Some(on_update)
375    } else {
376        None
377    };
378    Ok((
379        remaining_input,
380        TableKey::Constraint(name, columns, table, foreign, on_delete, on_update),
381    ))
382}
383
384// Parse rule for a comma-separated list.
385pub fn key_specification_list(i: &[u8]) -> IResult<&[u8], Vec<TableKey>> {
386    many1(terminated(key_specification, opt(ws_sep_comma))).parse(i)
387}
388
389fn field_specification(i: &[u8]) -> IResult<&[u8], ColumnSpecification> {
390    let (remaining_input, (column, field_type, constraints, comment, _)) = (
391        column_identifier_no_alias,
392        opt(delimited(multispace1, type_identifier, multispace0)),
393        many0(column_constraint),
394        opt(parse_comment),
395        opt(ws_sep_comma),
396    )
397        .parse(i)?;
398
399    let sql_type = match field_type {
400        None => SqlType::Text,
401        Some(ref t) => t.clone(),
402    };
403    Ok((
404        remaining_input,
405        ColumnSpecification {
406            column,
407            sql_type,
408            constraints: constraints.into_iter().flatten().collect(),
409            comment,
410        },
411    ))
412}
413
414// Parse rule for a comma-separated list.
415pub fn field_specification_list(i: &[u8]) -> IResult<&[u8], Vec<ColumnSpecification>> {
416    many1(field_specification).parse(i)
417}
418
419// Parse rule for a column definition constraint.
420pub fn column_constraint(i: &[u8]) -> IResult<&[u8], Option<ColumnConstraint>> {
421    let not_null = map(
422        delimited(multispace0, tag_no_case("not null"), multispace0),
423        |_| Some(ColumnConstraint::NotNull),
424    );
425    let null = map(
426        delimited(multispace0, tag_no_case("null"), multispace0),
427        |_| None,
428    );
429    let auto_increment = map(
430        delimited(multispace0, tag_no_case("auto_increment"), multispace0),
431        |_| Some(ColumnConstraint::AutoIncrement),
432    );
433    let primary_key = map(
434        delimited(multispace0, tag_no_case("primary key"), multispace0),
435        |_| Some(ColumnConstraint::PrimaryKey),
436    );
437    let unique = map(
438        delimited(multispace0, tag_no_case("unique"), multispace0),
439        |_| Some(ColumnConstraint::Unique),
440    );
441    let character_set = map(
442        preceded(
443            delimited(multispace0, tag_no_case("character set"), multispace1),
444            sql_identifier,
445        ),
446        |cs| {
447            let char_set = str::from_utf8(cs).unwrap().to_owned();
448            Some(ColumnConstraint::CharacterSet(char_set))
449        },
450    );
451    let collate = map(
452        preceded(
453            delimited(multispace0, tag_no_case("collate"), multispace1),
454            sql_identifier,
455        ),
456        |c| {
457            let collation = str::from_utf8(c).unwrap().to_owned();
458            Some(ColumnConstraint::Collation(collation))
459        },
460    );
461    let srid = map(
462        (
463            multispace0,
464            tag_no_case("/*!80003 SRID "),
465            digit1,
466            tag_no_case(" */"),
467            multispace0,
468        ),
469        |t| Some(ColumnConstraint::Srid(super::common::len_as_u32(t.2))),
470    );
471
472    let generated = map(
473        (
474            multispace0,
475            tag_no_case("GENERATED ALWAYS AS"),
476            multispace1,
477            tag("("),
478            take_until_unbalanced('(', ')'),
479            tag(")"),
480            multispace1,
481            alt((tag_no_case("VIRTUAL"), tag_no_case("STORED"))),
482            multispace0,
483        ),
484        |t| {
485            let query = str::from_utf8(t.4).unwrap().to_owned();
486            let stored = str::from_utf8(t.7).unwrap().eq_ignore_ascii_case("STORED");
487            Some(ColumnConstraint::Generated(query, stored))
488        },
489    );
490
491    alt((
492        not_null,
493        null,
494        auto_increment,
495        default,
496        primary_key,
497        unique,
498        character_set,
499        collate,
500        srid,
501        generated,
502    ))
503    .parse(i)
504}
505
506fn fixed_point(i: &[u8]) -> IResult<&[u8], Literal> {
507    let (remaining_input, (i, _, f)) = (digit1, tag("."), digit1).parse(i)?;
508
509    Ok((
510        remaining_input,
511        Literal::FixedPoint(Real {
512            integral: i32::from_str(str::from_utf8(i).unwrap()).unwrap(),
513            fractional: i32::from_str(str::from_utf8(f).unwrap()).unwrap(),
514        }),
515    ))
516}
517
518fn default(i: &[u8]) -> IResult<&[u8], Option<ColumnConstraint>> {
519    let (remaining_input, (_, _, _, def, _)) = (
520        multispace0,
521        tag_no_case("default"),
522        multispace1,
523        alt((
524            map(tag("''"), |_| Literal::String(String::from(""))),
525            string_literal,
526            fixed_point,
527            map(digit1, |d| {
528                let d_i64 = i64::from_str(str::from_utf8(d).unwrap()).unwrap();
529                Literal::Integer(d_i64)
530            }),
531            map(tag_no_case("null"), |_| Literal::Null),
532            map(
533                tag_no_case("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
534                |_| Literal::CurrentTimestamp,
535            ),
536            map(tag_no_case("current_timestamp"), |_| {
537                Literal::CurrentTimestamp
538            }),
539            map(tag_no_case("(now())"), |_| Literal::CurrentTimestamp),
540        )),
541        multispace0,
542    )
543        .parse(i)?;
544    if def == Literal::Null {
545        return Ok((remaining_input, None));
546    }
547    Ok((remaining_input, Some(ColumnConstraint::DefaultValue(def))))
548}
549
550// Parse rule for a SQL CREATE TABLE query.
551pub fn creation(i: &[u8]) -> IResult<&[u8], CreateTableStatement> {
552    let (remaining_input, (_, _, _, _, table, _, _, _, fields, _, keys, _, _, _, options, _)) = (
553        tag_no_case("create"),
554        multispace1,
555        tag_no_case("table"),
556        multispace1,
557        schema_table_reference,
558        multispace0,
559        tag("("),
560        multispace0,
561        field_specification_list,
562        multispace0,
563        opt(key_specification_list),
564        multispace0,
565        tag(")"),
566        multispace0,
567        table_options,
568        statement_terminator,
569    )
570        .parse(i)?;
571    Ok((
572        remaining_input,
573        CreateTableStatement {
574            table,
575            fields,
576            keys,
577            options,
578        },
579    ))
580}