• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

joaoh82 / rust_sqlite / 25278185540

03 May 2026 11:41AM UTC coverage: 56.503% (-0.4%) from 56.877%
25278185540

Pull #76

github

web-flow
Merge d243178fb into 91ca6317d
Pull Request #76: cleanup(engine): make process_command stdout-clean (drop REPL-only prints)

20 of 30 new or added lines in 4 files covered. (66.67%)

38 existing lines in 3 files now uncovered.

5465 of 9672 relevant lines covered (56.5%)

1.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.0
/src/sql/parser/create.rs
1
use sqlparser::ast::{ColumnOption, CreateTable, DataType, ObjectName, ObjectNamePart, Statement};
2

3
use 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.
9
fn is_vector_type(name: &ObjectName) -> bool {
1✔
10
    name.0.len() == 1
1✔
11
        && match &name.0[0] {
1✔
12
            ObjectNamePart::Identifier(ident) => ident.value.eq_ignore_ascii_case("VECTOR"),
1✔
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.
24
fn parse_vector_dim(args: &[String]) -> std::result::Result<usize, String> {
1✔
25
    match args {
26
        [] => Err("VECTOR requires a dimension, e.g. `VECTOR(384)`".to_string()),
2✔
27
        [single] => {
2✔
28
            let trimmed = single.trim();
1✔
29
            match trimmed.parse::<usize>() {
1✔
30
                Ok(d) if d > 0 => Ok(d),
2✔
31
                Ok(_) => Err(format!("VECTOR dimension must be ≥ 1 (got `{trimmed}`)")),
1✔
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)]
47
pub 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)]
64
pub 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

71
impl CreateQuery {
72
    pub fn new(statement: &Statement) -> Result<CreateQuery> {
3✔
73
        match statement {
2✔
74
            // Confirming the Statement is sqlparser::ast:Statement::CreateTable
75
            Statement::CreateTable(CreateTable {
×
76
                name,
2✔
77
                columns,
2✔
78
                constraints,
2✔
79
                ..
×
80
            }) => {
×
81
                let table_name = name;
2✔
82
                let mut parsed_columns: Vec<ParsedColumn> = vec![];
2✔
83

84
                // Iterating over the columns returned form the Parser::parse:sql
85
                // in the mod sql
86
                for col in columns {
6✔
87
                    let name = col.name.to_string();
4✔
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) {
8✔
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 {
2✔
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(),
4✔
108
                        DataType::Boolean => "Bool".to_string(),
2✔
109
                        DataType::Text => "Text".to_string(),
4✔
110
                        DataType::Varchar(_bytes) => "Text".to_string(),
×
111
                        DataType::Real => "Real".to_string(),
2✔
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(),
2✔
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) => {
2✔
125
                            match parse_vector_dim(args) {
1✔
126
                                Ok(dim) => format!("vector({dim})"),
1✔
127
                                Err(e) => {
1✔
128
                                    return Err(SQLRiteError::General(format!(
2✔
129
                                        "Invalid VECTOR column '{}': {e}",
×
130
                                        col.name
×
131
                                    )));
132
                                }
133
                            }
134
                        }
135
                        other => {
1✔
136
                            eprintln!("not matched on custom type: {other:?}");
2✔
137
                            "Invalid".to_string()
1✔
138
                        }
139
                    };
140

141
                    // checking if column is PRIMARY KEY
142
                    let mut is_pk: bool = false;
2✔
143
                    // chekcing if column is UNIQUE
144
                    let mut is_unique: bool = false;
2✔
145
                    // chekcing if column is NULLABLE
146
                    let mut not_null: bool = false;
2✔
147
                    for column_option in &col.options {
4✔
148
                        match &column_option.option {
2✔
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" {
6✔
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) {
2✔
155
                                        return Err(SQLRiteError::Internal(format!(
×
156
                                            "Table '{}' has more than one primary key",
×
157
                                            &table_name
×
158
                                        )));
159
                                    }
160
                                    is_pk = true;
2✔
161
                                    is_unique = true;
2✔
162
                                    not_null = true;
2✔
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" {
3✔
169
                                    is_unique = true;
1✔
170
                                }
171
                            }
172
                            ColumnOption::NotNull => {
2✔
173
                                not_null = true;
2✔
174
                            }
175
                            _ => (),
×
176
                        };
177
                    }
178

179
                    parsed_columns.push(ParsedColumn {
2✔
180
                        name,
2✔
181
                        datatype: datatype.to_string(),
2✔
182
                        is_pk,
2✔
183
                        not_null,
2✔
184
                        is_unique,
2✔
185
                    });
186
                }
187
                // TODO: handle constraints + default values + check
188
                // constraints + ON DELETE / ON UPDATE referential actions
189
                // properly. They're currently parsed by `sqlparser` and
190
                // dropped on the floor here. (Previously we `println!`-ed
191
                // them to stdout as a debug aid — removed in the
192
                // engine-stdout-pollution cleanup; flip to a `tracing`
193
                // span if we ever want them visible in dev builds.)
NEW
194
                let _ = constraints;
×
195
                Ok(CreateQuery {
2✔
196
                    table_name: table_name.to_string(),
2✔
197
                    columns: parsed_columns,
2✔
198
                })
199
            }
200

201
            _ => Err(SQLRiteError::Internal("Error parsing query".to_string())),
×
202
        }
203
    }
204
}
205

206
#[cfg(test)]
207
mod tests {
208
    use super::*;
209
    use crate::sql::*;
210

211
    #[test]
212
    fn create_table_validate_tablename_test() {
3✔
213
        let sql_input = String::from(
214
            "CREATE TABLE contacts (
215
            id INTEGER PRIMARY KEY,
216
            first_name TEXT NOT NULL,
217
            last_name TEXT NOT NULl,
218
            email TEXT NOT NULL UNIQUE
219
        );",
220
        );
221
        let expected_table_name = String::from("contacts");
1✔
222

223
        let dialect = SQLiteDialect {};
224
        let mut ast = Parser::parse_sql(&dialect, &sql_input).unwrap();
2✔
225

226
        assert!(ast.len() == 1, "ast has more then one Statement");
2✔
227

228
        let query = ast.pop().unwrap();
2✔
229

230
        // Initialy only implementing some basic SQL Statements
231
        if let Statement::CreateTable(_) = query {
1✔
232
            let result = CreateQuery::new(&query);
1✔
233
            match result {
1✔
234
                Ok(payload) => {
1✔
235
                    assert_eq!(payload.table_name, expected_table_name);
2✔
236
                }
237
                Err(_) => panic!("an error occured during parsing CREATE TABLE Statement"),
×
238
            }
239
        }
240
    }
241
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc