senax_mysql_parser/
create.rs

1use nom::branch::alt;
2use nom::bytes::complete::{tag, tag_no_case};
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        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().replace("``", "`");
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().replace("``", "`");
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().replace("``", "`");
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().replace("``", "`");
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().replace("``", "`");
286    let table = String::from_utf8(table.to_vec())
287        .unwrap()
288        .replace("``", "`");
289    let on_delete = if let Some(on_delete) = on_delete {
290        let (_, _, _, on_delete) = on_delete;
291        Some(on_delete)
292    } else if let Some(on_delete) = on_delete2 {
293        let (_, _, _, on_delete) = on_delete;
294        Some(on_delete)
295    } else {
296        None
297    };
298    let on_update = if let Some(on_update) = on_update {
299        let (_, _, _, on_update) = on_update;
300        Some(on_update)
301    } else {
302        None
303    };
304    Ok((
305        remaining_input,
306        TableKey::Constraint(name, columns, table, foreign, on_delete, on_update),
307    ))
308}
309
310// Parse rule for a comma-separated list.
311pub fn key_specification_list(i: &[u8]) -> IResult<&[u8], Vec<TableKey>> {
312    many1(terminated(key_specification, opt(ws_sep_comma))).parse(i)
313}
314
315fn field_specification(i: &[u8]) -> IResult<&[u8], ColumnSpecification> {
316    let (remaining_input, (column, field_type, constraints, comment, _)) = (
317        column_identifier_no_alias,
318        opt(delimited(multispace1, type_identifier, multispace0)),
319        many0(column_constraint),
320        opt(parse_comment),
321        opt(ws_sep_comma),
322    )
323        .parse(i)?;
324
325    let sql_type = match field_type {
326        None => SqlType::Text,
327        Some(ref t) => t.clone(),
328    };
329    Ok((
330        remaining_input,
331        ColumnSpecification {
332            column,
333            sql_type,
334            constraints: constraints.into_iter().flatten().collect(),
335            comment,
336        },
337    ))
338}
339
340// Parse rule for a comma-separated list.
341pub fn field_specification_list(i: &[u8]) -> IResult<&[u8], Vec<ColumnSpecification>> {
342    many1(field_specification).parse(i)
343}
344
345// Parse rule for a column definition constraint.
346pub fn column_constraint(i: &[u8]) -> IResult<&[u8], Option<ColumnConstraint>> {
347    let not_null = map(
348        delimited(multispace0, tag_no_case("not null"), multispace0),
349        |_| Some(ColumnConstraint::NotNull),
350    );
351    let null = map(
352        delimited(multispace0, tag_no_case("null"), multispace0),
353        |_| None,
354    );
355    let auto_increment = map(
356        delimited(multispace0, tag_no_case("auto_increment"), multispace0),
357        |_| Some(ColumnConstraint::AutoIncrement),
358    );
359    let primary_key = map(
360        delimited(multispace0, tag_no_case("primary key"), multispace0),
361        |_| Some(ColumnConstraint::PrimaryKey),
362    );
363    let unique = map(
364        delimited(multispace0, tag_no_case("unique"), multispace0),
365        |_| Some(ColumnConstraint::Unique),
366    );
367    let character_set = map(
368        preceded(
369            delimited(multispace0, tag_no_case("character set"), multispace1),
370            sql_identifier,
371        ),
372        |cs| {
373            let char_set = str::from_utf8(cs).unwrap().to_owned();
374            Some(ColumnConstraint::CharacterSet(char_set))
375        },
376    );
377    let collate = map(
378        preceded(
379            delimited(multispace0, tag_no_case("collate"), multispace1),
380            sql_identifier,
381        ),
382        |c| {
383            let collation = str::from_utf8(c).unwrap().to_owned();
384            Some(ColumnConstraint::Collation(collation))
385        },
386    );
387    let srid = map(
388        (
389            multispace0,
390            tag_no_case("/*!80003 SRID "),
391            digit1,
392            tag_no_case(" */"),
393            multispace0,
394        ),
395        |t| Some(ColumnConstraint::Srid(super::common::len_as_u32(t.2))),
396    );
397
398    let generated = map(
399        (
400            multispace0,
401            tag_no_case("GENERATED ALWAYS AS"),
402            multispace1,
403            tag("("),
404            take_until_unbalanced('(', ')'),
405            tag(")"),
406            multispace1,
407            alt((tag_no_case("VIRTUAL"), tag_no_case("STORED"))),
408            multispace0,
409        ),
410        |t| {
411            let query = str::from_utf8(t.4).unwrap().to_owned();
412            let stored = str::from_utf8(t.7).unwrap().eq_ignore_ascii_case("STORED");
413            Some(ColumnConstraint::Generated(query, stored))
414        },
415    );
416
417    alt((
418        not_null,
419        null,
420        auto_increment,
421        default,
422        primary_key,
423        unique,
424        character_set,
425        collate,
426        srid,
427        generated,
428    ))
429    .parse(i)
430}
431
432fn fixed_point(i: &[u8]) -> IResult<&[u8], Literal> {
433    let (remaining_input, (i, _, f)) = (digit1, tag("."), digit1).parse(i)?;
434
435    Ok((
436        remaining_input,
437        Literal::FixedPoint(Real {
438            integral: i32::from_str(str::from_utf8(i).unwrap()).unwrap(),
439            fractional: i32::from_str(str::from_utf8(f).unwrap()).unwrap(),
440        }),
441    ))
442}
443
444fn default(i: &[u8]) -> IResult<&[u8], Option<ColumnConstraint>> {
445    let (remaining_input, (_, _, _, def, _)) = (
446        multispace0,
447        tag_no_case("default"),
448        multispace1,
449        alt((
450            map(tag("''"), |_| Literal::String(String::from(""))),
451            string_literal,
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_no_case("null"), |_| Literal::Null),
458            map(
459                tag_no_case("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
460                |_| Literal::CurrentTimestamp,
461            ),
462            map(tag_no_case("current_timestamp"), |_| {
463                Literal::CurrentTimestamp
464            }),
465            map(tag_no_case("(now())"), |_| Literal::CurrentTimestamp),
466        )),
467        multispace0,
468    )
469        .parse(i)?;
470    if def == Literal::Null {
471        return Ok((remaining_input, None));
472    }
473    Ok((remaining_input, Some(ColumnConstraint::DefaultValue(def))))
474}
475
476// Parse rule for a SQL CREATE TABLE query.
477pub fn creation(i: &[u8]) -> IResult<&[u8], CreateTableStatement> {
478    let (remaining_input, (_, _, _, _, table, _, _, _, fields, _, keys, _, _, _, options, _)) = (
479        tag_no_case("create"),
480        multispace1,
481        tag_no_case("table"),
482        multispace1,
483        schema_table_reference,
484        multispace0,
485        tag("("),
486        multispace0,
487        field_specification_list,
488        multispace0,
489        opt(key_specification_list),
490        multispace0,
491        tag(")"),
492        multispace0,
493        table_options,
494        statement_terminator,
495    )
496        .parse(i)?;
497    Ok((
498        remaining_input,
499        CreateTableStatement {
500            table,
501            fields,
502            keys,
503            options,
504        },
505    ))
506}