Skip to main content

sqlrite/sql/parser/
create.rs

1use sqlparser::ast::{ColumnOption, CreateTable, DataType, ObjectName, ObjectNamePart, Statement};
2
3use crate::error::{Result, SQLRiteError};
4
5/// True when an `ObjectName` resolves to a single identifier `VECTOR`
6/// (case-insensitive). Phase 7a adds the `VECTOR(N)` column type as a
7/// sqlparser `DataType::Custom` — the engine recognizes it via this
8/// helper so the regular DataType match arm above stays uncluttered.
9fn is_vector_type(name: &ObjectName) -> bool {
10    name.0.len() == 1
11        && match &name.0[0] {
12            ObjectNamePart::Identifier(ident) => ident.value.eq_ignore_ascii_case("VECTOR"),
13            // Function-form ObjectNamePart shouldn't appear in a CREATE TABLE
14            // column type position. If it ever does, treat it as not-a-vector
15            // and the outer match falls through to the "Invalid" arm.
16            _ => false,
17        }
18}
19
20/// Parses the dimension out of the `Custom` args for `VECTOR(N)`.
21/// `args` is the `Vec<String>` sqlparser hands back for parenthesized
22/// type arguments — for `VECTOR(384)` that's `["384"]`. Validates that
23/// exactly one positive-integer argument was supplied.
24fn parse_vector_dim(args: &[String]) -> std::result::Result<usize, String> {
25    match args {
26        [] => Err("VECTOR requires a dimension, e.g. `VECTOR(384)`".to_string()),
27        [single] => {
28            let trimmed = single.trim();
29            match trimmed.parse::<usize>() {
30                Ok(d) if d > 0 => Ok(d),
31                Ok(_) => Err(format!("VECTOR dimension must be ≥ 1 (got `{trimmed}`)")),
32                Err(_) => Err(format!(
33                    "VECTOR dimension must be a positive integer (got `{trimmed}`)"
34                )),
35            }
36        }
37        many => Err(format!(
38            "VECTOR takes exactly one dimension argument (got {})",
39            many.len()
40        )),
41    }
42}
43
44/// The schema for each SQL column in every table is represented by
45/// the following structure after parsed and tokenized
46#[derive(PartialEq, Debug)]
47pub struct ParsedColumn {
48    /// Name of the column
49    pub name: String,
50    /// Datatype of the column in String format
51    pub datatype: String,
52    /// Value representing if column is PRIMARY KEY
53    pub is_pk: bool,
54    /// Value representing if column was declared with the NOT NULL Constraint
55    pub not_null: bool,
56    /// Value representing if column was declared with the UNIQUE Constraint
57    pub is_unique: bool,
58}
59
60/// The following structure represents a CREATE TABLE query already parsed
61/// and broken down into name and a Vector of `ParsedColumn` metadata
62///
63#[derive(Debug)]
64pub struct CreateQuery {
65    /// name of table after parking and tokenizing of query
66    pub table_name: String,
67    /// Vector of `ParsedColumn` type with column metadata information
68    pub columns: Vec<ParsedColumn>,
69}
70
71impl CreateQuery {
72    pub fn new(statement: &Statement) -> Result<CreateQuery> {
73        match statement {
74            // Confirming the Statement is sqlparser::ast:Statement::CreateTable
75            Statement::CreateTable(CreateTable {
76                name,
77                columns,
78                constraints,
79                ..
80            }) => {
81                let table_name = name;
82                let mut parsed_columns: Vec<ParsedColumn> = vec![];
83
84                // Iterating over the columns returned form the Parser::parse:sql
85                // in the mod sql
86                for col in columns {
87                    let name = col.name.to_string();
88
89                    // Checks if columm already added to parsed_columns, if so, returns an error
90                    if parsed_columns.iter().any(|col| col.name == name) {
91                        return Err(SQLRiteError::Internal(format!(
92                            "Duplicate column name: {}",
93                            &name
94                        )));
95                    }
96
97                    // Parsing each column for it data type
98                    // For now only accepting basic data types
99                    let datatype: String = match &col.data_type {
100                        DataType::TinyInt(_)
101                        | DataType::SmallInt(_)
102                        | DataType::Int2(_)
103                        | DataType::Int(_)
104                        | DataType::Int4(_)
105                        | DataType::Int8(_)
106                        | DataType::Integer(_)
107                        | DataType::BigInt(_) => "Integer".to_string(),
108                        DataType::Boolean => "Bool".to_string(),
109                        DataType::Text => "Text".to_string(),
110                        DataType::Varchar(_bytes) => "Text".to_string(),
111                        DataType::Real => "Real".to_string(),
112                        DataType::Float(_precision) => "Real".to_string(),
113                        DataType::Double(_) => "Real".to_string(),
114                        DataType::Decimal(_) => "Real".to_string(),
115                        // Phase 7e — `JSON` parses as a unit variant in
116                        // sqlparser's DataType enum. JSONB is treated as
117                        // an alias (matches PostgreSQL's permissive
118                        // behaviour); both store as text under the hood.
119                        DataType::JSON | DataType::JSONB => "Json".to_string(),
120                        // Phase 7a — `VECTOR(N)` parses as Custom("VECTOR", ["N"]).
121                        // sqlparser's SQLite dialect doesn't have a built-in
122                        // Vector variant; Custom is what unrecognized type
123                        // names + their parenthesized args fall through to.
124                        DataType::Custom(name, args) if is_vector_type(name) => {
125                            match parse_vector_dim(args) {
126                                Ok(dim) => format!("vector({dim})"),
127                                Err(e) => {
128                                    return Err(SQLRiteError::General(format!(
129                                        "Invalid VECTOR column '{}': {e}",
130                                        col.name
131                                    )));
132                                }
133                            }
134                        }
135                        other => {
136                            eprintln!("not matched on custom type: {other:?}");
137                            "Invalid".to_string()
138                        }
139                    };
140
141                    // checking if column is PRIMARY KEY
142                    let mut is_pk: bool = false;
143                    // chekcing if column is UNIQUE
144                    let mut is_unique: bool = false;
145                    // chekcing if column is NULLABLE
146                    let mut not_null: bool = false;
147                    for column_option in &col.options {
148                        match &column_option.option {
149                            ColumnOption::PrimaryKey(_) => {
150                                // For now, only Integer and Text types can be PRIMARY KEY and Unique
151                                // Therefore Indexed.
152                                if datatype != "Real" && datatype != "Bool" {
153                                    // Checks if table being created already has a PRIMARY KEY, if so, returns an error
154                                    if parsed_columns.iter().any(|col| col.is_pk) {
155                                        return Err(SQLRiteError::Internal(format!(
156                                            "Table '{}' has more than one primary key",
157                                            &table_name
158                                        )));
159                                    }
160                                    is_pk = true;
161                                    is_unique = true;
162                                    not_null = true;
163                                }
164                            }
165                            ColumnOption::Unique(_) => {
166                                // For now, only Integer and Text types can be UNIQUE
167                                // Therefore Indexed.
168                                if datatype != "Real" && datatype != "Bool" {
169                                    is_unique = true;
170                                }
171                            }
172                            ColumnOption::NotNull => {
173                                not_null = true;
174                            }
175                            _ => (),
176                        };
177                    }
178
179                    parsed_columns.push(ParsedColumn {
180                        name,
181                        datatype: datatype.to_string(),
182                        is_pk,
183                        not_null,
184                        is_unique,
185                    });
186                }
187                // TODO: Handle constraints,
188                // Default value and others.
189                for constraint in constraints {
190                    println!("{constraint:?}");
191                }
192                Ok(CreateQuery {
193                    table_name: table_name.to_string(),
194                    columns: parsed_columns,
195                })
196            }
197
198            _ => Err(SQLRiteError::Internal("Error parsing query".to_string())),
199        }
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206    use crate::sql::*;
207
208    #[test]
209    fn create_table_validate_tablename_test() {
210        let sql_input = String::from(
211            "CREATE TABLE contacts (
212            id INTEGER PRIMARY KEY,
213            first_name TEXT NOT NULL,
214            last_name TEXT NOT NULl,
215            email TEXT NOT NULL UNIQUE
216        );",
217        );
218        let expected_table_name = String::from("contacts");
219
220        let dialect = SQLiteDialect {};
221        let mut ast = Parser::parse_sql(&dialect, &sql_input).unwrap();
222
223        assert!(ast.len() == 1, "ast has more then one Statement");
224
225        let query = ast.pop().unwrap();
226
227        // Initialy only implementing some basic SQL Statements
228        if let Statement::CreateTable(_) = query {
229            let result = CreateQuery::new(&query);
230            match result {
231                Ok(payload) => {
232                    assert_eq!(payload.table_name, expected_table_name);
233                }
234                Err(_) => panic!("an error occured during parsing CREATE TABLE Statement"),
235            }
236        }
237    }
238}