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

joaoh82 / rust_sqlite / 24967676994

26 Apr 2026 09:38PM UTC coverage: 67.568% (+0.6%) from 66.922%
24967676994

Pull #42

github

web-flow
Merge 34c3ebad6 into f4d231254
Pull Request #42: Phase 7a: VECTOR(N) column type (storage only)

245 of 312 new or added lines in 7 files covered. (78.53%)

7 existing lines in 1 file now uncovered.

4246 of 6284 relevant lines covered (67.57%)

1.24 hits per line

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

67.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.
NEW
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✔
NEW
32
                Err(_) => Err(format!(
×
33
                    "VECTOR dimension must be a positive integer (got `{trimmed}`)"
34
                )),
35
            }
36
        }
NEW
37
        many => Err(format!(
×
38
            "VECTOR takes exactly one dimension argument (got {})",
NEW
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> {
2✔
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✔
NEW
108
                        DataType::Boolean => "Bool".to_string(),
×
109
                        DataType::Text => "Text".to_string(),
4✔
NEW
110
                        DataType::Varchar(_bytes) => "Text".to_string(),
×
111
                        DataType::Real => "Real".to_string(),
2✔
NEW
112
                        DataType::Float(_precision) => "Real".to_string(),
×
NEW
113
                        DataType::Double(_) => "Real".to_string(),
×
NEW
114
                        DataType::Decimal(_) => "Real".to_string(),
×
115
                        // Phase 7a — `VECTOR(N)` parses as Custom("VECTOR", ["N"]).
116
                        // sqlparser's SQLite dialect doesn't have a built-in
117
                        // Vector variant; Custom is what unrecognized type
118
                        // names + their parenthesized args fall through to.
119
                        DataType::Custom(name, args) if is_vector_type(name) => {
2✔
120
                            match parse_vector_dim(args) {
1✔
121
                                Ok(dim) => format!("vector({dim})"),
1✔
122
                                Err(e) => {
1✔
123
                                    return Err(SQLRiteError::General(format!(
2✔
NEW
124
                                        "Invalid VECTOR column '{}': {e}",
×
NEW
125
                                        col.name
×
126
                                    )));
127
                                }
128
                            }
129
                        }
130
                        other => {
1✔
131
                            eprintln!("not matched on custom type: {other:?}");
2✔
132
                            "Invalid".to_string()
1✔
133
                        }
134
                    };
135

136
                    // checking if column is PRIMARY KEY
137
                    let mut is_pk: bool = false;
2✔
138
                    // chekcing if column is UNIQUE
139
                    let mut is_unique: bool = false;
2✔
140
                    // chekcing if column is NULLABLE
141
                    let mut not_null: bool = false;
2✔
142
                    for column_option in &col.options {
4✔
143
                        match &column_option.option {
2✔
144
                            ColumnOption::PrimaryKey(_) => {
×
145
                                // For now, only Integer and Text types can be PRIMARY KEY and Unique
146
                                // Therefore Indexed.
147
                                if datatype != "Real" && datatype != "Bool" {
6✔
148
                                    // Checks if table being created already has a PRIMARY KEY, if so, returns an error
149
                                    if parsed_columns.iter().any(|col| col.is_pk) {
2✔
150
                                        return Err(SQLRiteError::Internal(format!(
×
151
                                            "Table '{}' has more than one primary key",
×
152
                                            &table_name
×
153
                                        )));
154
                                    }
155
                                    is_pk = true;
2✔
156
                                    is_unique = true;
2✔
157
                                    not_null = true;
2✔
158
                                }
159
                            }
160
                            ColumnOption::Unique(_) => {
×
161
                                // For now, only Integer and Text types can be UNIQUE
162
                                // Therefore Indexed.
163
                                if datatype != "Real" && datatype != "Bool" {
3✔
164
                                    is_unique = true;
1✔
165
                                }
166
                            }
167
                            ColumnOption::NotNull => {
2✔
168
                                not_null = true;
2✔
169
                            }
170
                            _ => (),
×
171
                        };
172
                    }
173

174
                    parsed_columns.push(ParsedColumn {
2✔
175
                        name,
2✔
176
                        datatype: datatype.to_string(),
2✔
177
                        is_pk,
2✔
178
                        not_null,
2✔
179
                        is_unique,
2✔
180
                    });
181
                }
182
                // TODO: Handle constraints,
183
                // Default value and others.
184
                for constraint in constraints {
2✔
185
                    println!("{constraint:?}");
×
186
                }
187
                Ok(CreateQuery {
2✔
188
                    table_name: table_name.to_string(),
2✔
189
                    columns: parsed_columns,
2✔
190
                })
191
            }
192

193
            _ => Err(SQLRiteError::Internal("Error parsing query".to_string())),
×
194
        }
195
    }
196
}
197

198
#[cfg(test)]
199
mod tests {
200
    use super::*;
201
    use crate::sql::*;
202

203
    #[test]
204
    fn create_table_validate_tablename_test() {
3✔
205
        let sql_input = String::from(
206
            "CREATE TABLE contacts (
207
            id INTEGER PRIMARY KEY,
208
            first_name TEXT NOT NULL,
209
            last_name TEXT NOT NULl,
210
            email TEXT NOT NULL UNIQUE
211
        );",
212
        );
213
        let expected_table_name = String::from("contacts");
1✔
214

215
        let dialect = SQLiteDialect {};
216
        let mut ast = Parser::parse_sql(&dialect, &sql_input).unwrap();
2✔
217

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

220
        let query = ast.pop().unwrap();
2✔
221

222
        // Initialy only implementing some basic SQL Statements
223
        if let Statement::CreateTable(_) = query {
1✔
224
            let result = CreateQuery::new(&query);
1✔
225
            match result {
1✔
226
                Ok(payload) => {
1✔
227
                    assert_eq!(payload.table_name, expected_table_name);
2✔
228
                }
229
                Err(_) => panic!("an error occured during parsing CREATE TABLE Statement"),
×
230
            }
231
        }
232
    }
233
}
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