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::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        constraint,
98    ))
99    .parse(i)
100}
101
102fn full_text_key(i: &[u8]) -> IResult<&[u8], TableKey> {
103    let (remaining_input, (_, _, _, _, name, _, columns, _, parser, _)) = (
104        tag_no_case("fulltext"),
105        multispace1,
106        alt((tag_no_case("key"), tag_no_case("index"))),
107        multispace1,
108        sql_identifier,
109        multispace0,
110        delimited(
111            tag("("),
112            delimited(multispace0, index_col_list, multispace0),
113            tag(")"),
114        ),
115        multispace0,
116        opt(delimited(
117            tag("/*!50100 WITH PARSER"),
118            delimited(multispace0, sql_identifier, multispace0),
119            tag("*/"),
120        )),
121        multispace0,
122    )
123        .parse(i)?;
124
125    let name = String::from_utf8(name.to_vec()).unwrap();
126    let parser = parser.map(|v| String::from_utf8(v.to_vec()).unwrap());
127    Ok((
128        remaining_input,
129        TableKey::FulltextKey(name, columns, parser),
130    ))
131}
132
133fn primary_key(i: &[u8]) -> IResult<&[u8], TableKey> {
134    let (remaining_input, (_, _, columns, _, _, _)) = (
135        tag_no_case("primary key"),
136        multispace0,
137        delimited(
138            tag("("),
139            delimited(multispace0, index_col_list, multispace0),
140            tag(")"),
141        ),
142        opt(map(
143            preceded(multispace1, tag_no_case("auto_increment")),
144            |_| (),
145        )),
146        multispace0,
147        opt(tag_no_case("USING BTREE")),
148    )
149        .parse(i)?;
150
151    Ok((remaining_input, TableKey::PrimaryKey(columns)))
152}
153
154fn unique(i: &[u8]) -> IResult<&[u8], TableKey> {
155    // TODO: add branching to correctly parse whitespace after `unique`
156    let (remaining_input, (_, _, _, name, _, columns, _, _)) = (
157        tag_no_case("unique"),
158        opt(preceded(
159            multispace1,
160            alt((tag_no_case("key"), tag_no_case("index"))),
161        )),
162        multispace0,
163        sql_identifier,
164        multispace0,
165        delimited(
166            tag("("),
167            delimited(multispace0, index_col_list, multispace0),
168            tag(")"),
169        ),
170        multispace0,
171        opt(tag_no_case("USING BTREE")),
172    )
173        .parse(i)?;
174
175    let n = String::from_utf8(name.to_vec()).unwrap();
176    Ok((remaining_input, TableKey::UniqueKey(n, columns)))
177}
178
179fn key_or_index(i: &[u8]) -> IResult<&[u8], TableKey> {
180    let (remaining_input, (_, _, name, _, columns, _, _)) = (
181        alt((tag_no_case("key"), tag_no_case("index"))),
182        multispace0,
183        sql_identifier,
184        multispace0,
185        delimited(
186            tag("("),
187            delimited(multispace0, index_col_list, multispace0),
188            tag(")"),
189        ),
190        multispace0,
191        opt(tag_no_case("USING BTREE")),
192    )
193        .parse(i)?;
194
195    let n = String::from_utf8(name.to_vec()).unwrap();
196    Ok((remaining_input, TableKey::Key(n, columns)))
197}
198
199fn spatial(i: &[u8]) -> IResult<&[u8], TableKey> {
200    let (remaining_input, (_, _, _, name, _, columns)) = (
201        tag_no_case("spatial"),
202        opt(preceded(
203            multispace1,
204            alt((tag_no_case("key"), tag_no_case("index"))),
205        )),
206        multispace0,
207        sql_identifier,
208        multispace0,
209        delimited(
210            tag("("),
211            delimited(multispace0, index_col_list, multispace0),
212            tag(")"),
213        ),
214    )
215        .parse(i)?;
216
217    let n = String::from_utf8(name.to_vec()).unwrap();
218    Ok((remaining_input, TableKey::SpatialKey(n, columns)))
219}
220
221fn constraint(i: &[u8]) -> IResult<&[u8], TableKey> {
222    let (
223        remaining_input,
224        (
225            _,
226            _,
227            name,
228            _,
229            _,
230            _,
231            columns,
232            _,
233            _,
234            _,
235            table,
236            _,
237            foreign,
238            on_delete,
239            on_update,
240            on_delete2,
241        ),
242    ) = (
243        tag_no_case("CONSTRAINT"),
244        multispace1,
245        sql_identifier,
246        multispace1,
247        tag_no_case("FOREIGN KEY"),
248        multispace0,
249        delimited(
250            tag("("),
251            delimited(multispace0, index_col_list, multispace0),
252            tag(")"),
253        ),
254        multispace1,
255        tag_no_case("REFERENCES"),
256        multispace1,
257        sql_identifier,
258        multispace0,
259        delimited(
260            tag("("),
261            delimited(multispace0, index_col_list, multispace0),
262            tag(")"),
263        ),
264        opt((
265            multispace1,
266            tag_no_case("ON DELETE"),
267            multispace1,
268            reference_option,
269        )),
270        opt((
271            multispace1,
272            tag_no_case("ON UPDATE"),
273            multispace1,
274            reference_option,
275        )),
276        opt((
277            multispace1,
278            tag_no_case("ON DELETE"),
279            multispace1,
280            reference_option,
281        )),
282    )
283        .parse(i)?;
284
285    let name = String::from_utf8(name.to_vec()).unwrap();
286    let table = String::from_utf8(table.to_vec()).unwrap();
287    let on_delete = if let Some(on_delete) = on_delete {
288        let (_, _, _, on_delete) = on_delete;
289        Some(on_delete)
290    } else if let Some(on_delete) = on_delete2 {
291        let (_, _, _, on_delete) = on_delete;
292        Some(on_delete)
293    } else {
294        None
295    };
296    let on_update = if let Some(on_update) = on_update {
297        let (_, _, _, on_update) = on_update;
298        Some(on_update)
299    } else {
300        None
301    };
302    Ok((
303        remaining_input,
304        TableKey::Constraint(name, columns, table, foreign, on_delete, on_update),
305    ))
306}
307
308// Parse rule for a comma-separated list.
309pub fn key_specification_list(i: &[u8]) -> IResult<&[u8], Vec<TableKey>> {
310    many1(terminated(key_specification, opt(ws_sep_comma))).parse(i)
311}
312
313fn field_specification(i: &[u8]) -> IResult<&[u8], ColumnSpecification> {
314    let (remaining_input, (column, field_type, constraints, comment, _)) = (
315        column_identifier_no_alias,
316        opt(delimited(multispace1, type_identifier, multispace0)),
317        many0(column_constraint),
318        opt(parse_comment),
319        opt(ws_sep_comma),
320    )
321        .parse(i)?;
322
323    let sql_type = match field_type {
324        None => SqlType::Text,
325        Some(ref t) => t.clone(),
326    };
327    Ok((
328        remaining_input,
329        ColumnSpecification {
330            column,
331            sql_type,
332            constraints: constraints.into_iter().flatten().collect(),
333            comment,
334        },
335    ))
336}
337
338// Parse rule for a comma-separated list.
339pub fn field_specification_list(i: &[u8]) -> IResult<&[u8], Vec<ColumnSpecification>> {
340    many1(field_specification).parse(i)
341}
342
343// Parse rule for a column definition constraint.
344pub fn column_constraint(i: &[u8]) -> IResult<&[u8], Option<ColumnConstraint>> {
345    let not_null = map(
346        delimited(multispace0, tag_no_case("not null"), multispace0),
347        |_| Some(ColumnConstraint::NotNull),
348    );
349    let null = map(
350        delimited(multispace0, tag_no_case("null"), multispace0),
351        |_| None,
352    );
353    let auto_increment = map(
354        delimited(multispace0, tag_no_case("auto_increment"), multispace0),
355        |_| Some(ColumnConstraint::AutoIncrement),
356    );
357    let primary_key = map(
358        delimited(multispace0, tag_no_case("primary key"), multispace0),
359        |_| Some(ColumnConstraint::PrimaryKey),
360    );
361    let unique = map(
362        delimited(multispace0, tag_no_case("unique"), multispace0),
363        |_| Some(ColumnConstraint::Unique),
364    );
365    let character_set = map(
366        preceded(
367            delimited(multispace0, tag_no_case("character set"), multispace1),
368            sql_identifier,
369        ),
370        |cs| {
371            let char_set = str::from_utf8(cs).unwrap().to_owned();
372            Some(ColumnConstraint::CharacterSet(char_set))
373        },
374    );
375    let collate = map(
376        preceded(
377            delimited(multispace0, tag_no_case("collate"), multispace1),
378            sql_identifier,
379        ),
380        |c| {
381            let collation = str::from_utf8(c).unwrap().to_owned();
382            Some(ColumnConstraint::Collation(collation))
383        },
384    );
385    let srid = map(
386        (
387            multispace0,
388            tag_no_case("/*!80003 SRID "),
389            digit1,
390            tag_no_case(" */"),
391            multispace0,
392        ),
393        |t| Some(ColumnConstraint::Srid(super::common::len_as_u32(t.2))),
394    );
395
396    let generated = map(
397        (
398            multispace0,
399            tag_no_case("GENERATED ALWAYS AS"),
400            multispace1,
401            tag("("),
402            take_until_unbalanced('(', ')'),
403            tag(")"),
404            multispace1,
405            alt((tag_no_case("VIRTUAL"), tag_no_case("STORED"))),
406            multispace0,
407        ),
408        |t| {
409            let query = str::from_utf8(t.4).unwrap().to_owned();
410            let stored = str::from_utf8(t.7).unwrap().eq_ignore_ascii_case("STORED");
411            Some(ColumnConstraint::Generated(query, stored))
412        },
413    );
414
415    alt((
416        not_null,
417        null,
418        auto_increment,
419        default,
420        primary_key,
421        unique,
422        character_set,
423        collate,
424        srid,
425        generated,
426    ))
427    .parse(i)
428}
429
430fn fixed_point(i: &[u8]) -> IResult<&[u8], Literal> {
431    let (remaining_input, (i, _, f)) = (digit1, tag("."), digit1).parse(i)?;
432
433    Ok((
434        remaining_input,
435        Literal::FixedPoint(Real {
436            integral: i32::from_str(str::from_utf8(i).unwrap()).unwrap(),
437            fractional: i32::from_str(str::from_utf8(f).unwrap()).unwrap(),
438        }),
439    ))
440}
441
442fn default(i: &[u8]) -> IResult<&[u8], Option<ColumnConstraint>> {
443    let (remaining_input, (_, _, _, def, _)) = (
444        multispace0,
445        tag_no_case("default"),
446        multispace1,
447        alt((
448            map(
449                delimited(tag("'"), take_until("'"), tag("'")),
450                |s: &[u8]| Literal::String(String::from_utf8(s.to_vec()).unwrap()),
451            ),
452            fixed_point,
453            map(digit1, |d| {
454                let d_i64 = i64::from_str(str::from_utf8(d).unwrap()).unwrap();
455                Literal::Integer(d_i64)
456            }),
457            map(tag("''"), |_| Literal::String(String::from(""))),
458            map(tag_no_case("null"), |_| Literal::Null),
459            map(
460                tag_no_case("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
461                |_| Literal::CurrentTimestamp,
462            ),
463            map(tag_no_case("current_timestamp"), |_| {
464                Literal::CurrentTimestamp
465            }),
466            map(tag_no_case("(now())"), |_| Literal::CurrentTimestamp),
467        )),
468        multispace0,
469    )
470        .parse(i)?;
471    if def == Literal::Null {
472        return Ok((remaining_input, None));
473    }
474    Ok((remaining_input, Some(ColumnConstraint::DefaultValue(def))))
475}
476
477// Parse rule for a SQL CREATE TABLE query.
478pub fn creation(i: &[u8]) -> IResult<&[u8], CreateTableStatement> {
479    let (remaining_input, (_, _, _, _, table, _, _, _, fields, _, keys, _, _, _, options, _)) = (
480        tag_no_case("create"),
481        multispace1,
482        tag_no_case("table"),
483        multispace1,
484        schema_table_reference,
485        multispace0,
486        tag("("),
487        multispace0,
488        field_specification_list,
489        multispace0,
490        opt(key_specification_list),
491        multispace0,
492        tag(")"),
493        multispace0,
494        table_options,
495        statement_terminator,
496    )
497        .parse(i)?;
498    Ok((
499        remaining_input,
500        CreateTableStatement {
501            table,
502            fields,
503            keys,
504            options,
505        },
506    ))
507}