formatting
This commit is contained in:
parent
ad98cfafb2
commit
c25c6edc6a
29 changed files with 886 additions and 571 deletions
|
|
@ -1,6 +1,12 @@
|
|||
use crate::syntax::RawQuerySyntax;
|
||||
use minisql::{interpreter2::DbSchema, operation::Operation};
|
||||
use nom::{branch::alt, character::complete::{multispace0, char}, multi::many1, sequence::{delimited, terminated}, IResult};
|
||||
use nom::{
|
||||
branch::alt,
|
||||
character::complete::{char, multispace0},
|
||||
multi::many1,
|
||||
sequence::{delimited, terminated},
|
||||
IResult,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -35,7 +41,10 @@ fn parse_statement(input: &str) -> IResult<&str, RawQuerySyntax> {
|
|||
/// Parse one or more statements
|
||||
#[allow(dead_code)]
|
||||
fn parse_statement1(input: &str) -> IResult<&str, Vec<RawQuerySyntax>> {
|
||||
many1(terminated(parse_statement, delimited(multispace0, char(';'), multispace0)))(input)
|
||||
many1(terminated(
|
||||
parse_statement,
|
||||
delimited(multispace0, char(';'), multispace0),
|
||||
))(input)
|
||||
}
|
||||
|
||||
pub fn parse_and_validate(str_query: String, db_schema: &DbSchema) -> Result<Operation, Error> {
|
||||
|
|
@ -53,11 +62,9 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_parse_two_select() {
|
||||
let (rest, sntx) = parse_statement1("SELECT * FROM users ; SELECT * FROM cities ; ").expect("should parse");
|
||||
assert_eq!(
|
||||
sntx.len(),
|
||||
2
|
||||
);
|
||||
let (rest, sntx) = parse_statement1("SELECT * FROM users ; SELECT * FROM cities ; ")
|
||||
.expect("should parse");
|
||||
assert_eq!(sntx.len(), 2);
|
||||
assert_eq!(rest, "");
|
||||
}
|
||||
|
||||
|
|
@ -68,11 +75,10 @@ mod test {
|
|||
SELECT * FROM users ;
|
||||
INSERT INTO table1 (id, data) VALUES (u4, 30) ;
|
||||
INSERT INTO table1 (id, data) VALUES (u5, 40) ;
|
||||
"#).expect("should parse");
|
||||
assert_eq!(
|
||||
sntx.len(),
|
||||
4
|
||||
);
|
||||
"#,
|
||||
)
|
||||
.expect("should parse");
|
||||
assert_eq!(sntx.len(), 4);
|
||||
assert_eq!(rest, "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,19 @@
|
|||
use minisql::type_system::DbType;
|
||||
use nom::{
|
||||
branch::alt, bytes::complete::{tag, take_while}, character::{complete::{alphanumeric1, anychar, char, multispace0, multispace1}, is_alphanumeric}, combinator::peek, error::make_error, sequence::{delimited, terminated}, IResult, Parser
|
||||
branch::alt,
|
||||
bytes::complete::{tag, take_while},
|
||||
character::{
|
||||
complete::{alphanumeric1, anychar, char, multispace0, multispace1},
|
||||
is_alphanumeric,
|
||||
},
|
||||
combinator::peek,
|
||||
error::make_error,
|
||||
sequence::{delimited, terminated},
|
||||
IResult, Parser,
|
||||
};
|
||||
|
||||
use crate::syntax::Condition;
|
||||
use super::literal::parse_literal;
|
||||
use crate::syntax::Condition;
|
||||
|
||||
pub fn parse_table_name(input: &str) -> IResult<&str, &str> {
|
||||
alt((
|
||||
|
|
@ -16,11 +25,9 @@ pub fn parse_table_name(input: &str) -> IResult<&str, &str> {
|
|||
pub fn parse_identifier(input: &str) -> IResult<&str, &str> {
|
||||
let (_, first) = peek(anychar)(input)?;
|
||||
if first.is_alphabetic() || first == '_' {
|
||||
take_while(|c: char| {
|
||||
match c {
|
||||
'a'..='z' | 'A'..='Z' | '_' | '0'..='9' => true,
|
||||
_ => false
|
||||
}
|
||||
take_while(|c: char| match c {
|
||||
'a'..='z' | 'A'..='Z' | '_' | '0'..='9' => true,
|
||||
_ => false,
|
||||
})(input)
|
||||
} else {
|
||||
Err(nom::Err::Error(make_error(
|
||||
|
|
@ -35,22 +42,13 @@ pub fn parse_column_name(input: &str) -> IResult<&str, String> {
|
|||
}
|
||||
|
||||
pub fn parse_db_type(input: &str) -> IResult<&str, DbType> {
|
||||
let (input, db_type) = alt(
|
||||
(
|
||||
tag("STRING")
|
||||
.map(|_| DbType::String),
|
||||
tag("INT")
|
||||
.map(|_| DbType::Int),
|
||||
tag("NUMBER")
|
||||
.map(|_| DbType::Number),
|
||||
tag("UUID")
|
||||
.map(|_| DbType::Uuid),
|
||||
delimited(tag("Option("), parse_db_type, tag(")"))
|
||||
.map(|ty| {
|
||||
DbType::Option(Box::new(ty))
|
||||
})
|
||||
)
|
||||
)(input)?;
|
||||
let (input, db_type) = alt((
|
||||
tag("STRING").map(|_| DbType::String),
|
||||
tag("INT").map(|_| DbType::Int),
|
||||
tag("NUMBER").map(|_| DbType::Number),
|
||||
tag("UUID").map(|_| DbType::Uuid),
|
||||
delimited(tag("Option("), parse_db_type, tag(")")).map(|ty| DbType::Option(Box::new(ty))),
|
||||
))(input)?;
|
||||
Ok((input, db_type))
|
||||
}
|
||||
|
||||
|
|
@ -122,10 +120,7 @@ mod tests {
|
|||
parse_identifier("_variable__Test").expect("should parse").1,
|
||||
"_variable__Test"
|
||||
);
|
||||
assert!(matches!(
|
||||
parse_identifier("123_variable__Test"),
|
||||
Err(_)
|
||||
));
|
||||
assert!(matches!(parse_identifier("123_variable__Test"), Err(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -139,8 +134,12 @@ mod tests {
|
|||
#[test]
|
||||
fn test_parse_nested_option_int_type() {
|
||||
assert_eq!(
|
||||
parse_db_type("Option(Option(Option(INT)))").expect("should parse").1,
|
||||
DbType::Option(Box::new(DbType::Option(Box::new(DbType::Option(Box::new(DbType::Int))))))
|
||||
parse_db_type("Option(Option(Option(INT)))")
|
||||
.expect("should parse")
|
||||
.1,
|
||||
DbType::Option(Box::new(DbType::Option(Box::new(DbType::Option(
|
||||
Box::new(DbType::Int)
|
||||
)))))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,8 +77,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_parse_create_no_quotes_table_name() {
|
||||
parse_create("CREATE TABLE Table1(id UUID PRIMARY KEY,column1 INT)")
|
||||
.expect("should parse");
|
||||
parse_create("CREATE TABLE Table1(id UUID PRIMARY KEY,column1 INT)").expect("should parse");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -91,8 +90,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_parse_create() {
|
||||
let (_, create) = parse_create("CREATE TABLE \"Table1\"( id UUID , column1 INT )")
|
||||
.expect("should parse");
|
||||
let (_, create) =
|
||||
parse_create("CREATE TABLE \"Table1\"( id UUID , column1 INT )").expect("should parse");
|
||||
assert!(matches!(create, RawQuerySyntax::CreateTable(_)));
|
||||
match create {
|
||||
RawQuerySyntax::CreateTable(schema) => {
|
||||
|
|
@ -114,11 +113,13 @@ mod tests {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_parse_create_option() {
|
||||
let (_, create) = parse_create("CREATE TABLE games (id UUID PRIMARY KEY, name STRING, year Option(INT), price NUMBER)")
|
||||
.expect("should parse");
|
||||
let (_, create) = parse_create(
|
||||
"CREATE TABLE games (id UUID PRIMARY KEY, name STRING, year Option(INT), price NUMBER)",
|
||||
)
|
||||
.expect("should parse");
|
||||
assert!(matches!(create, RawQuerySyntax::CreateTable(_)));
|
||||
match create {
|
||||
RawQuerySyntax::CreateTable(schema) => {
|
||||
|
|
@ -139,16 +140,12 @@ mod tests {
|
|||
assert_eq!(column1_column.type_, DbType::String);
|
||||
|
||||
let column = schema.get_column(&"year".to_string());
|
||||
let Some(column) = column else {
|
||||
panic!()
|
||||
};
|
||||
let Some(column) = column else { panic!() };
|
||||
assert_eq!(column.column_name, "year".to_string());
|
||||
assert_eq!(column.type_, DbType::Option(Box::new(DbType::Int)));
|
||||
|
||||
let column = schema.get_column(&"price".to_string());
|
||||
let Some(column) = column else {
|
||||
panic!()
|
||||
};
|
||||
let Some(column) = column else { panic!() };
|
||||
assert_eq!(column.column_name, "price".to_string());
|
||||
assert_eq!(column.type_, DbType::Number);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,22 +29,19 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_parse_delete() {
|
||||
let (_, sntx) =
|
||||
parse_delete("DELETE FROM \"T1\" WHERE id = 1").expect("should parse");
|
||||
let (_, sntx) = parse_delete("DELETE FROM \"T1\" WHERE id = 1").expect("should parse");
|
||||
assert!(matches!(sntx, RawQuerySyntax::Delete(_, _)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_delete_with_spaces() {
|
||||
let (_, sntx) =
|
||||
parse_delete("DELETE FROM T1 WHERE id = 1").expect("should parse");
|
||||
let (_, sntx) = parse_delete("DELETE FROM T1 WHERE id = 1").expect("should parse");
|
||||
assert!(matches!(sntx, RawQuerySyntax::Delete(_, _)))
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_parse_delete_none() {
|
||||
let (_, sntx) =
|
||||
parse_delete("DELETE FROM games WHERE year = None").expect("should parse");
|
||||
let (_, sntx) = parse_delete("DELETE FROM games WHERE year = None").expect("should parse");
|
||||
if let RawQuerySyntax::Delete(tname, Some(Condition::Eq(column_name, lit))) = sntx {
|
||||
assert_eq!(tname, "games".to_string());
|
||||
assert_eq!(column_name, "year".to_string());
|
||||
|
|
|
|||
|
|
@ -68,10 +68,7 @@ mod tests {
|
|||
insertion_values,
|
||||
vec![
|
||||
("id".to_string(), Literal::Int(1)),
|
||||
(
|
||||
"data".to_string(),
|
||||
Literal::String("Text".to_string())
|
||||
)
|
||||
("data".to_string(), Literal::String("Text".to_string()))
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -83,8 +80,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_parse_insert_with_spaces() {
|
||||
let sql =
|
||||
"INSERT INTO \"MyTable\" ( id, data ) VALUES ( 1, \"Text\" )";
|
||||
let sql = "INSERT INTO \"MyTable\" ( id, data ) VALUES ( 1, \"Text\" )";
|
||||
let operation = parse_insert(sql).expect("should parse");
|
||||
match operation {
|
||||
("", RawQuerySyntax::Insert(table_name, insertion_values)) => {
|
||||
|
|
@ -93,10 +89,7 @@ mod tests {
|
|||
insertion_values,
|
||||
vec![
|
||||
("id".to_string(), Literal::Int(1)),
|
||||
(
|
||||
"data".to_string(),
|
||||
Literal::String("Text".to_string())
|
||||
)
|
||||
("data".to_string(), Literal::String("Text".to_string()))
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -117,18 +110,12 @@ mod tests {
|
|||
insertion_values,
|
||||
vec![
|
||||
("id".to_string(), Literal::Uuid(12345)),
|
||||
(
|
||||
"name".to_string(),
|
||||
Literal::String("Doom".to_string())
|
||||
),
|
||||
("name".to_string(), Literal::String("Doom".to_string())),
|
||||
(
|
||||
"year".to_string(),
|
||||
Literal::Some(Box::new(Literal::Int(1993)))
|
||||
),
|
||||
(
|
||||
"price".to_string(),
|
||||
Literal::Number(6.5)
|
||||
)
|
||||
("price".to_string(), Literal::Number(6.5))
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
use nom::{
|
||||
branch::alt, bytes::complete::tag, character::complete::{char, digit1, none_of, u64}, combinator::opt, error::make_error, multi::many0, sequence::{delimited, pair, preceded}, IResult, Parser
|
||||
branch::alt,
|
||||
bytes::complete::tag,
|
||||
character::complete::{char, digit1, none_of, u64},
|
||||
combinator::opt,
|
||||
error::make_error,
|
||||
multi::many0,
|
||||
sequence::{delimited, pair, preceded},
|
||||
IResult, Parser,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
|
@ -13,7 +20,13 @@ pub enum Literal {
|
|||
}
|
||||
|
||||
pub fn parse_literal(input: &str) -> IResult<&str, Literal> {
|
||||
alt((parse_option, parse_string, parse_number, parse_int, parse_uuid))(input)
|
||||
alt((
|
||||
parse_option,
|
||||
parse_string,
|
||||
parse_number,
|
||||
parse_int,
|
||||
parse_uuid,
|
||||
))(input)
|
||||
}
|
||||
|
||||
pub fn parse_number(input: &str) -> IResult<&str, Literal> {
|
||||
|
|
@ -92,16 +105,16 @@ pub fn parse_string(input: &str) -> IResult<&str, Literal> {
|
|||
}
|
||||
|
||||
pub fn parse_uuid(input: &str) -> IResult<&str, Literal> {
|
||||
let (input, value) = pair(char('u'), u64)(input)
|
||||
.map(|(input, (_, v))| (input, Literal::Uuid(v)))?;
|
||||
let (input, value) =
|
||||
pair(char('u'), u64)(input).map(|(input, (_, v))| (input, Literal::Uuid(v)))?;
|
||||
Ok((input, value))
|
||||
}
|
||||
|
||||
pub fn parse_option(input: &str) -> IResult<&str, Literal> {
|
||||
let (input, inner) = alt((tag("None")
|
||||
.map(|_| Literal::None), delimited(tag("Some("), parse_literal, tag(")")).map(|v| {
|
||||
Literal::Some(Box::new(v))
|
||||
})))(input)?;
|
||||
let (input, inner) = alt((
|
||||
tag("None").map(|_| Literal::None),
|
||||
delimited(tag("Some("), parse_literal, tag(")")).map(|v| Literal::Some(Box::new(v))),
|
||||
))(input)?;
|
||||
Ok((input, inner))
|
||||
}
|
||||
|
||||
|
|
@ -114,24 +127,15 @@ mod tests {
|
|||
fn test_string_parser() {
|
||||
assert_eq!(
|
||||
parse_string(r#""simple""#),
|
||||
Ok((
|
||||
"",
|
||||
Literal::String(String::from("simple"))
|
||||
))
|
||||
Ok(("", Literal::String(String::from("simple"))))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_string(r#""\"\t\r\n\\""#),
|
||||
Ok((
|
||||
"",
|
||||
Literal::String(String::from("\"\t\r\n\\"))
|
||||
))
|
||||
Ok(("", Literal::String(String::from("\"\t\r\n\\"))))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_string(r#""name is \"John\".""#),
|
||||
Ok((
|
||||
"",
|
||||
Literal::String(String::from("name is \"John\"."))
|
||||
))
|
||||
Ok(("", Literal::String(String::from("name is \"John\"."))))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -147,13 +151,11 @@ mod tests {
|
|||
|
||||
let (_, _) = parse_literal("\"STRING\"").expect("should parse");
|
||||
let (input, value) =
|
||||
parse_literal("\"abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ \"").expect("should parse");
|
||||
parse_literal("\"abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ \"").expect("should parse");
|
||||
assert_eq!(input, "");
|
||||
assert_eq!(
|
||||
value,
|
||||
Literal::String(
|
||||
"abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ ".to_string()
|
||||
)
|
||||
Literal::String("abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ ".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -192,18 +194,12 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_parse_int() {
|
||||
assert_eq!(
|
||||
parse_literal("5134616"),
|
||||
Ok(("", Literal::Int(5134616)))
|
||||
);
|
||||
assert_eq!(parse_literal("5134616"), Ok(("", Literal::Int(5134616))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_uuid() {
|
||||
assert_eq!(
|
||||
parse_uuid("u131515"),
|
||||
Ok(("", Literal::Uuid(131515)))
|
||||
)
|
||||
assert_eq!(parse_uuid("u131515"), Ok(("", Literal::Uuid(131515))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -214,7 +210,10 @@ mod tests {
|
|||
);
|
||||
assert_eq!(
|
||||
parse_option("Some(Some(3))"),
|
||||
Ok(("", Literal::Some(Box::new(Literal::Some(Box::new(Literal::Int(3)))))))
|
||||
Ok((
|
||||
"",
|
||||
Literal::Some(Box::new(Literal::Some(Box::new(Literal::Int(3)))))
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_option("Some(None)"),
|
||||
|
|
|
|||
|
|
@ -42,7 +42,9 @@ pub fn try_parse_column_selection(input: &str) -> IResult<&str, ColumnSelection>
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::parsing::{
|
||||
common::{parse_column_name, parse_table_name}, literal::Literal, select::parse_select
|
||||
common::{parse_column_name, parse_table_name},
|
||||
literal::Literal,
|
||||
select::parse_select,
|
||||
};
|
||||
use crate::syntax::{ColumnSelection, RawQuerySyntax};
|
||||
|
||||
|
|
@ -137,7 +139,7 @@ mod tests {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_parse_select_option_none() {
|
||||
use crate::syntax::Condition;
|
||||
|
|
|
|||
|
|
@ -71,4 +71,3 @@ impl RawTableSchema {
|
|||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ pub enum ValidationError {
|
|||
expected_type: DbType,
|
||||
},
|
||||
#[error("Expected type {expected_type:?}, received None")]
|
||||
UnexpectedNoneValue{ expected_type: DbType },
|
||||
UnexpectedNoneValue { expected_type: DbType },
|
||||
#[error("values for required columns {0:?} are missing")]
|
||||
RequiredColumnsAreMissing(Vec<ColumnName>),
|
||||
}
|
||||
|
|
@ -284,7 +284,10 @@ fn validate_condition(
|
|||
)?;
|
||||
let value_type: DbType = type_from_literal_with_type_hint(&value, &expected_type)?;
|
||||
if expected_type.eq(&value_type) {
|
||||
Ok(Some(operation::Condition::Eq(column, literal_to_value(value, &expected_type))))
|
||||
Ok(Some(operation::Condition::Eq(
|
||||
column,
|
||||
literal_to_value(value, &expected_type),
|
||||
)))
|
||||
} else {
|
||||
Err(ValidationError::TypeMismatch {
|
||||
column_name: column_name.to_string(),
|
||||
|
|
@ -339,10 +342,7 @@ where
|
|||
None
|
||||
}
|
||||
|
||||
fn get_table_schema(
|
||||
db_schema: &DbSchema,
|
||||
table_name: &TableName,
|
||||
) -> Option<Arc<TableSchema>> {
|
||||
fn get_table_schema(db_schema: &DbSchema, table_name: &TableName) -> Option<Arc<TableSchema>> {
|
||||
let (_, _, table_schema) = db_schema
|
||||
.iter()
|
||||
.find(|(tname, _, _)| table_name.eq(tname))?;
|
||||
|
|
@ -360,15 +360,18 @@ fn literal_to_value(lit: Literal, hint: &DbType) -> Value {
|
|||
if let DbType::Option(t) = hint {
|
||||
Value::None(*t.clone())
|
||||
} else {
|
||||
// By the time calling current function, hopefully we should be sure about the
|
||||
// By the time calling current function, hopefully we should be sure about the
|
||||
// type we want from the literal
|
||||
panic!()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn type_from_literal_with_type_hint(lit: &Literal, hint: &DbType) -> Result<DbType, ValidationError> {
|
||||
fn type_from_literal_with_type_hint(
|
||||
lit: &Literal,
|
||||
hint: &DbType,
|
||||
) -> Result<DbType, ValidationError> {
|
||||
Ok(match lit {
|
||||
Literal::Number(_) => DbType::Number,
|
||||
Literal::String(_) => DbType::String,
|
||||
|
|
@ -379,7 +382,9 @@ fn type_from_literal_with_type_hint(lit: &Literal, hint: &DbType) -> Result<DbTy
|
|||
if matches!(hint, DbType::Option(_)) {
|
||||
hint.clone()
|
||||
} else {
|
||||
return Err(ValidationError::UnexpectedNoneValue { expected_type: hint.clone() })
|
||||
return Err(ValidationError::UnexpectedNoneValue {
|
||||
expected_type: hint.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue