Skip to main content

sqlrite/sql/parser/
create.rs

1use sqlparser::ast::{ColumnOption, CreateTable, DataType, Statement};
2
3use crate::error::{Result, SQLRiteError};
4
5/// The schema for each SQL column in every table is represented by
6/// the following structure after parsed and tokenized
7#[derive(PartialEq, Debug)]
8pub struct ParsedColumn {
9    /// Name of the column
10    pub name: String,
11    /// Datatype of the column in String format
12    pub datatype: String,
13    /// Value representing if column is PRIMARY KEY
14    pub is_pk: bool,
15    /// Value representing if column was declared with the NOT NULL Constraint
16    pub not_null: bool,
17    /// Value representing if column was declared with the UNIQUE Constraint
18    pub is_unique: bool,
19}
20
21/// The following structure represents a CREATE TABLE query already parsed
22/// and broken down into name and a Vector of `ParsedColumn` metadata
23///
24#[derive(Debug)]
25pub struct CreateQuery {
26    /// name of table after parking and tokenizing of query
27    pub table_name: String,
28    /// Vector of `ParsedColumn` type with column metadata information
29    pub columns: Vec<ParsedColumn>,
30}
31
32impl CreateQuery {
33    pub fn new(statement: &Statement) -> Result<CreateQuery> {
34        match statement {
35            // Confirming the Statement is sqlparser::ast:Statement::CreateTable
36            Statement::CreateTable(CreateTable {
37                name,
38                columns,
39                constraints,
40                ..
41            }) => {
42                let table_name = name;
43                let mut parsed_columns: Vec<ParsedColumn> = vec![];
44
45                // Iterating over the columns returned form the Parser::parse:sql
46                // in the mod sql
47                for col in columns {
48                    let name = col.name.to_string();
49
50                    // Checks if columm already added to parsed_columns, if so, returns an error
51                    if parsed_columns.iter().any(|col| col.name == name) {
52                        return Err(SQLRiteError::Internal(format!(
53                            "Duplicate column name: {}",
54                            &name
55                        )));
56                    }
57
58                    // Parsing each column for it data type
59                    // For now only accepting basic data types
60                    let datatype = match &col.data_type {
61                        DataType::TinyInt(_)
62                        | DataType::SmallInt(_)
63                        | DataType::Int2(_)
64                        | DataType::Int(_)
65                        | DataType::Int4(_)
66                        | DataType::Int8(_)
67                        | DataType::Integer(_)
68                        | DataType::BigInt(_) => "Integer",
69                        DataType::Boolean => "Bool",
70                        DataType::Text => "Text",
71                        DataType::Varchar(_bytes) => "Text",
72                        DataType::Real => "Real",
73                        DataType::Float(_precision) => "Real",
74                        DataType::Double(_) => "Real",
75                        DataType::Decimal(_) => "Real",
76                        other => {
77                            eprintln!("not matched on custom type: {other:?}");
78                            "Invalid"
79                        }
80                    };
81
82                    // checking if column is PRIMARY KEY
83                    let mut is_pk: bool = false;
84                    // chekcing if column is UNIQUE
85                    let mut is_unique: bool = false;
86                    // chekcing if column is NULLABLE
87                    let mut not_null: bool = false;
88                    for column_option in &col.options {
89                        match &column_option.option {
90                            ColumnOption::PrimaryKey(_) => {
91                                // For now, only Integer and Text types can be PRIMARY KEY and Unique
92                                // Therefore Indexed.
93                                if datatype != "Real" && datatype != "Bool" {
94                                    // Checks if table being created already has a PRIMARY KEY, if so, returns an error
95                                    if parsed_columns.iter().any(|col| col.is_pk) {
96                                        return Err(SQLRiteError::Internal(format!(
97                                            "Table '{}' has more than one primary key",
98                                            &table_name
99                                        )));
100                                    }
101                                    is_pk = true;
102                                    is_unique = true;
103                                    not_null = true;
104                                }
105                            }
106                            ColumnOption::Unique(_) => {
107                                // For now, only Integer and Text types can be UNIQUE
108                                // Therefore Indexed.
109                                if datatype != "Real" && datatype != "Bool" {
110                                    is_unique = true;
111                                }
112                            }
113                            ColumnOption::NotNull => {
114                                not_null = true;
115                            }
116                            _ => (),
117                        };
118                    }
119
120                    parsed_columns.push(ParsedColumn {
121                        name,
122                        datatype: datatype.to_string(),
123                        is_pk,
124                        not_null,
125                        is_unique,
126                    });
127                }
128                // TODO: Handle constraints,
129                // Default value and others.
130                for constraint in constraints {
131                    println!("{constraint:?}");
132                }
133                Ok(CreateQuery {
134                    table_name: table_name.to_string(),
135                    columns: parsed_columns,
136                })
137            }
138
139            _ => Err(SQLRiteError::Internal("Error parsing query".to_string())),
140        }
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use crate::sql::*;
148
149    #[test]
150    fn create_table_validate_tablename_test() {
151        let sql_input = String::from(
152            "CREATE TABLE contacts (
153            id INTEGER PRIMARY KEY,
154            first_name TEXT NOT NULL,
155            last_name TEXT NOT NULl,
156            email TEXT NOT NULL UNIQUE
157        );",
158        );
159        let expected_table_name = String::from("contacts");
160
161        let dialect = SQLiteDialect {};
162        let mut ast = Parser::parse_sql(&dialect, &sql_input).unwrap();
163
164        assert!(ast.len() == 1, "ast has more then one Statement");
165
166        let query = ast.pop().unwrap();
167
168        // Initialy only implementing some basic SQL Statements
169        if let Statement::CreateTable(_) = query {
170            let result = CreateQuery::new(&query);
171            match result {
172                Ok(payload) => {
173                    assert_eq!(payload.table_name, expected_table_name);
174                }
175                Err(_) => panic!("an error occured during parsing CREATE TABLE Statement"),
176            }
177        }
178    }
179}