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}