From 53dd7a468e839c503a7d183d17a63f171713cf59 Mon Sep 17 00:00:00 2001 From: AmrDeveloper Date: Fri, 1 Sep 2023 17:15:49 +0200 Subject: [PATCH] Improve type system implementation and represent values as union not string --- README.md | 2 +- crates/gitql-ast/src/aggregation.rs | 40 +++--- crates/gitql-ast/src/lib.rs | 1 + crates/gitql-ast/src/object.rs | 4 +- crates/gitql-ast/src/transformation.rs | 29 ++-- crates/gitql-ast/src/types.rs | 2 + crates/gitql-ast/src/value.rs | 60 ++++++++ crates/gitql-cli/src/render.rs | 9 +- crates/gitql-engine/src/engine_evaluator.rs | 146 ++++++++++---------- crates/gitql-engine/src/engine_executor.rs | 23 ++- crates/gitql-engine/src/engine_function.rs | 77 ++++++----- crates/gitql-parser/src/lib.rs | 1 - crates/gitql-parser/src/parser.rs | 4 +- docs/function/transformations.md | 4 +- docs/index.md | 2 +- 15 files changed, 236 insertions(+), 168 deletions(-) create mode 100644 crates/gitql-ast/src/value.rs diff --git a/README.md b/README.md index beb607c8..1753deea 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ SELECT name FROM commits GROUP By name SELECT name FROM commits GROUP By name having name = "AmrDeveloper" SELECT * FROM branches -SELECT * FROM branches WHERE ishead = "true" +SELECT * FROM branches WHERE ishead = true SELECT * FROM branches WHERE name ends_with "master" SELECT * FROM branches WHERE name contains "origin" diff --git a/crates/gitql-ast/src/aggregation.rs b/crates/gitql-ast/src/aggregation.rs index 7067328e..cb31443c 100644 --- a/crates/gitql-ast/src/aggregation.rs +++ b/crates/gitql-ast/src/aggregation.rs @@ -1,9 +1,11 @@ -use crate::{object::GQLObject, types::DataType}; +use crate::object::GQLObject; +use crate::types::DataType; +use crate::value::Value; use lazy_static::lazy_static; use std::collections::HashMap; -type Aggregation = fn(&String, &Vec) -> String; +type Aggregation = fn(&String, &Vec) -> Value; pub struct AggregationPrototype { pub parameter: DataType, @@ -64,50 +66,50 @@ lazy_static! { }; } -fn aggregation_max(field_name: &String, objects: &Vec) -> String { +fn aggregation_max(field_name: &String, objects: &Vec) -> Value { let mut max_length: i64 = 0; for object in objects { let field_value = &object.attributes.get(field_name).unwrap(); - let int_value = field_value.parse::().unwrap(); + let int_value = field_value.as_number(); if int_value > max_length { max_length = int_value; } } - return max_length.to_string(); + return Value::Number(max_length); } -fn aggregation_min(field_name: &String, objects: &Vec) -> String { - let mut max_length: i64 = 0; +fn aggregation_min(field_name: &String, objects: &Vec) -> Value { + let mut min_length: i64 = 0; for object in objects { let field_value = &object.attributes.get(field_name).unwrap(); - let int_value = field_value.parse::().unwrap(); - if int_value < max_length { - max_length = int_value; + let int_value = field_value.as_number(); + if int_value < min_length { + min_length = int_value; } } - return max_length.to_string(); + return Value::Number(min_length); } -fn aggregation_sum(field_name: &String, objects: &Vec) -> String { +fn aggregation_sum(field_name: &String, objects: &Vec) -> Value { let mut sum: i64 = 0; for object in objects { let field_value = &object.attributes.get(field_name).unwrap(); - sum += field_value.parse::().unwrap(); + sum += field_value.as_number(); } - return sum.to_string(); + return Value::Number(sum); } -fn aggregation_average(field_name: &String, objects: &Vec) -> String { +fn aggregation_average(field_name: &String, objects: &Vec) -> Value { let mut sum: i64 = 0; let count: i64 = objects.len().try_into().unwrap(); for object in objects { let field_value = &object.attributes.get(field_name).unwrap(); - sum += field_value.parse::().unwrap(); + sum += field_value.as_number(); } let avg = sum / count; - return avg.to_string(); + return Value::Number(avg); } -fn aggregation_count(_field_name: &String, objects: &Vec) -> String { - return objects.len().to_string(); +fn aggregation_count(_field_name: &String, objects: &Vec) -> Value { + return Value::Number(objects.len() as i64); } diff --git a/crates/gitql-ast/src/lib.rs b/crates/gitql-ast/src/lib.rs index 09540299..0cff5b91 100644 --- a/crates/gitql-ast/src/lib.rs +++ b/crates/gitql-ast/src/lib.rs @@ -4,3 +4,4 @@ pub mod object; pub mod statement; pub mod transformation; pub mod types; +pub mod value; diff --git a/crates/gitql-ast/src/object.rs b/crates/gitql-ast/src/object.rs index b967ff0d..994ba682 100644 --- a/crates/gitql-ast/src/object.rs +++ b/crates/gitql-ast/src/object.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; +use crate::value::Value; + #[derive(Clone)] pub struct GQLObject { - pub attributes: HashMap, + pub attributes: HashMap, } diff --git a/crates/gitql-ast/src/transformation.rs b/crates/gitql-ast/src/transformation.rs index ba6cef16..2e6560a2 100644 --- a/crates/gitql-ast/src/transformation.rs +++ b/crates/gitql-ast/src/transformation.rs @@ -1,9 +1,10 @@ use crate::types::DataType; +use crate::value::Value; use lazy_static::lazy_static; use std::collections::HashMap; -type Transformation = fn(String) -> String; +type Transformation = fn(Value) -> Value; pub struct TransformationPrototype { pub parameters: Vec, @@ -13,10 +14,10 @@ pub struct TransformationPrototype { lazy_static! { pub static ref TRANSFORMATIONS: HashMap<&'static str, Transformation> = { let mut map: HashMap<&'static str, Transformation> = HashMap::new(); - map.insert("lower", transformation_lower); - map.insert("upper", transformation_upper); - map.insert("trim", transformtion_trim); - map.insert("length", transformation_length); + map.insert("lower", text_lowercase); + map.insert("upper", text_uppercase); + map.insert("trim", text_trim); + map.insert("len", text_len); map }; } @@ -49,7 +50,7 @@ lazy_static! { ); map.insert( - "length", + "len", TransformationPrototype { parameters: vec![DataType::Text], result: DataType::Text, @@ -59,18 +60,18 @@ lazy_static! { }; } -fn transformation_lower(input: String) -> String { - return input.to_lowercase(); +fn text_lowercase(input: Value) -> Value { + return Value::Text(input.as_text().to_lowercase()); } -fn transformation_upper(input: String) -> String { - return input.to_uppercase(); +fn text_uppercase(input: Value) -> Value { + return Value::Text(input.as_text().to_uppercase()); } -fn transformtion_trim(input: String) -> String { - return input.trim().to_string(); +fn text_trim(input: Value) -> Value { + return Value::Text(input.as_text().trim().to_string()); } -fn transformation_length(input: String) -> String { - return input.len().to_string(); +fn text_len(input: Value) -> Value { + return Value::Number(input.as_text().len() as i64); } diff --git a/crates/gitql-ast/src/types.rs b/crates/gitql-ast/src/types.rs index 718bc82b..35662016 100644 --- a/crates/gitql-ast/src/types.rs +++ b/crates/gitql-ast/src/types.rs @@ -8,6 +8,7 @@ pub enum DataType { Number, Boolean, Date, + Null, } impl DataType { @@ -18,6 +19,7 @@ impl DataType { DataType::Number => "Number", DataType::Boolean => "Boolean", DataType::Date => "Date", + DataType::Null => "Null", }; } } diff --git a/crates/gitql-ast/src/value.rs b/crates/gitql-ast/src/value.rs new file mode 100644 index 00000000..e4ebf487 --- /dev/null +++ b/crates/gitql-ast/src/value.rs @@ -0,0 +1,60 @@ +use crate::types::DataType; + +#[derive(PartialEq, Clone)] +pub enum Value { + Number(i64), + Text(String), + Boolean(bool), + Date(i64), + Null, +} + +impl Value { + pub fn data_type(&self) -> DataType { + return match self { + Value::Number(_) => DataType::Number, + Value::Text(_) => DataType::Text, + Value::Boolean(_) => DataType::Boolean, + Value::Date(_) => DataType::Date, + Value::Null => DataType::Null, + }; + } + + pub fn literal(&self) -> String { + return match self { + Value::Number(i) => i.to_string(), + Value::Text(s) => s.to_string(), + Value::Boolean(b) => b.to_string(), + Value::Date(d) => d.to_string(), + Value::Null => "Null".to_string(), + }; + } + + pub fn as_number(&self) -> i64 { + if let Value::Number(n) = self { + return *n; + } + return 0; + } + + pub fn as_text(&self) -> String { + if let Value::Text(s) = self { + return s.to_string(); + } + return "".to_owned(); + } + + pub fn as_bool(&self) -> bool { + if let Value::Boolean(b) = self { + return *b; + } + return false; + } + + pub fn as_date(&self) -> i64 { + if let Value::Date(d) = self { + return *d; + } + return 0; + } +} diff --git a/crates/gitql-cli/src/render.rs b/crates/gitql-cli/src/render.rs index b68d31e0..85f352b1 100644 --- a/crates/gitql-cli/src/render.rs +++ b/crates/gitql-cli/src/render.rs @@ -27,13 +27,14 @@ pub fn render_objects(groups: &Vec>, hidden_selections: &Vec table_field_max_len { - let wrapped = textwrap::wrap(value, table_field_max_len); + let value = &object.attributes.get(&key as &str).clone().unwrap(); + let value_literal = value.literal(); + if value_literal.len() > table_field_max_len { + let wrapped = textwrap::wrap(value_literal.as_str(), table_field_max_len); let formatted = wrapped.join("\n"); table_row.add_cell(Cell::new(&formatted)); } else { - table_row.add_cell(Cell::new(value)); + table_row.add_cell(Cell::new(value_literal.as_str())); } } table.add_row(table_row); diff --git a/crates/gitql-engine/src/engine_evaluator.rs b/crates/gitql-engine/src/engine_evaluator.rs index ebfd72f1..50f34527 100644 --- a/crates/gitql-engine/src/engine_evaluator.rs +++ b/crates/gitql-engine/src/engine_evaluator.rs @@ -22,6 +22,7 @@ use gitql_ast::expression::SymbolExpression; use gitql_ast::object::GQLObject; use gitql_ast::transformation::TRANSFORMATIONS; use gitql_ast::types::DataType; +use gitql_ast::value::Value; use regex::Regex; use std::string::String; @@ -29,7 +30,7 @@ use std::string::String; pub fn evaluate_expression( expression: &Box, object: &GQLObject, -) -> Result { +) -> Result { match expression.get_expression_kind() { String => { let expr = expression @@ -122,27 +123,23 @@ pub fn evaluate_expression( }; } -fn evaluate_string(expr: &StringExpression) -> Result { - return Ok(expr.value.to_owned()); +fn evaluate_string(expr: &StringExpression) -> Result { + return Ok(Value::Text(expr.value.to_owned())); } -fn evaluate_symbol(expr: &SymbolExpression, object: &GQLObject) -> Result { - return Ok(object.attributes.get(&expr.value).unwrap().to_string()); +fn evaluate_symbol(expr: &SymbolExpression, object: &GQLObject) -> Result { + return Ok(object.attributes.get(&expr.value).unwrap().clone()); } -fn evaluate_number(expr: &NumberExpression) -> Result { - return Ok(expr.value.to_string()); +fn evaluate_number(expr: &NumberExpression) -> Result { + return Ok(Value::Number(expr.value)); } -fn evaluate_boolean(expr: &BooleanExpression) -> Result { - return Ok(if expr.is_true { - "true".to_owned() - } else { - "false".to_owned() - }); +fn evaluate_boolean(expr: &BooleanExpression) -> Result { + return Ok(Value::Boolean(expr.is_true)); } -fn evaluate_prefix_unary(expr: &PrefixUnary, object: &GQLObject) -> Result { +fn evaluate_prefix_unary(expr: &PrefixUnary, object: &GQLObject) -> Result { let value_result = evaluate_expression(&expr.right, object); if value_result.is_err() { return value_result; @@ -150,13 +147,13 @@ fn evaluate_prefix_unary(expr: &PrefixUnary, object: &GQLObject) -> Result().unwrap()).to_string()) + Ok(Value::Number(-rhs.as_number())) }; } -fn evaluate_arithmetic(expr: &ArithmeticExpression, object: &GQLObject) -> Result { +fn evaluate_arithmetic(expr: &ArithmeticExpression, object: &GQLObject) -> Result { let lhs_result = evaluate_expression(&expr.left, object); if lhs_result.is_err() { return lhs_result; @@ -167,12 +164,12 @@ fn evaluate_arithmetic(expr: &ArithmeticExpression, object: &GQLObject) -> Resul return lhs_result; } - let lhs = lhs_result.ok().unwrap().parse::().unwrap(); - let rhs = rhs_result.ok().unwrap().parse::().unwrap(); + let lhs = lhs_result.ok().unwrap().as_number(); + let rhs = rhs_result.ok().unwrap().as_number(); return match expr.operator { - ArithmeticOperator::Plus => Ok((lhs + rhs).to_string()), - ArithmeticOperator::Minus => Ok((lhs - rhs).to_string()), + ArithmeticOperator::Plus => Ok(Value::Number(lhs + rhs)), + ArithmeticOperator::Minus => Ok(Value::Number(lhs - rhs)), ArithmeticOperator::Star => { let mul_result = lhs.overflowing_mul(rhs); if mul_result.1 { @@ -181,14 +178,14 @@ fn evaluate_arithmetic(expr: &ArithmeticExpression, object: &GQLObject) -> Resul lhs, rhs )) } else { - Ok(mul_result.0.to_string()) + Ok(Value::Number(mul_result.0)) } } ArithmeticOperator::Slash => { if rhs == 0 { Err(format!("Attempt to divide `{}` by zero", lhs)) } else { - Ok((lhs / rhs).to_string()) + Ok(Value::Number(lhs / rhs)) } } ArithmeticOperator::Modulus => { @@ -198,13 +195,13 @@ fn evaluate_arithmetic(expr: &ArithmeticExpression, object: &GQLObject) -> Resul lhs )) } else { - Ok((lhs % rhs).to_string()) + Ok(Value::Number(lhs % rhs)) } } }; } -fn evaluate_comparison(expr: &ComparisonExpression, object: &GQLObject) -> Result { +fn evaluate_comparison(expr: &ComparisonExpression, object: &GQLObject) -> Result { let lhs_result = evaluate_expression(&expr.left, object); if lhs_result.is_err() { return lhs_result; @@ -218,27 +215,30 @@ fn evaluate_comparison(expr: &ComparisonExpression, object: &GQLObject) -> Resul let lhs = lhs_result.ok().unwrap(); let rhs = rhs_result.ok().unwrap(); - let is_string_comparison = expr.left.expr_type() == DataType::Text; - let result = if is_string_comparison { - lhs.cmp(&rhs) - } else { - let ilhs = lhs.parse::().unwrap(); - let irhs = rhs.parse::().unwrap(); + let left_type = expr.left.expr_type(); + let comparison_result = if left_type == DataType::Number { + let ilhs = lhs.as_number(); + let irhs = rhs.as_number(); + ilhs.cmp(&irhs) + } else if left_type == DataType::Boolean { + let ilhs = lhs.as_bool(); + let irhs = rhs.as_bool(); ilhs.cmp(&irhs) + } else { + lhs.as_text().cmp(&rhs.as_text()) }; - return Ok(match expr.operator { - ComparisonOperator::Greater => result.is_gt(), - ComparisonOperator::GreaterEqual => result.is_ge(), - ComparisonOperator::Less => result.is_lt(), - ComparisonOperator::LessEqual => result.is_le(), - ComparisonOperator::Equal => result.is_eq(), - ComparisonOperator::NotEqual => !result.is_eq(), - } - .to_string()); + return Ok(Value::Boolean(match expr.operator { + ComparisonOperator::Greater => comparison_result.is_gt(), + ComparisonOperator::GreaterEqual => comparison_result.is_ge(), + ComparisonOperator::Less => comparison_result.is_lt(), + ComparisonOperator::LessEqual => comparison_result.is_le(), + ComparisonOperator::Equal => comparison_result.is_eq(), + ComparisonOperator::NotEqual => !comparison_result.is_eq(), + })); } -fn evaluate_check(expr: &CheckExpression, object: &GQLObject) -> Result { +fn evaluate_check(expr: &CheckExpression, object: &GQLObject) -> Result { let lhs_result = evaluate_expression(&expr.left, object); if lhs_result.is_err() { return lhs_result; @@ -249,38 +249,37 @@ fn evaluate_check(expr: &CheckExpression, object: &GQLObject) -> Result lhs.contains(&rhs), - CheckOperator::StartsWith => lhs.starts_with(&rhs), - CheckOperator::EndsWith => lhs.ends_with(&rhs), + CheckOperator::Contains => Value::Boolean(lhs.contains(&rhs)), + CheckOperator::StartsWith => Value::Boolean(lhs.starts_with(&rhs)), + CheckOperator::EndsWith => Value::Boolean(lhs.ends_with(&rhs)), CheckOperator::Matches => { let regex = Regex::new(&rhs); if regex.is_err() { - return Ok("false".to_owned()); + return Ok(Value::Boolean(false)); } - regex.unwrap().is_match(&lhs) + Value::Boolean(regex.unwrap().is_match(&lhs)) } - } - .to_string()); + }); } -fn evaluate_logical(expr: &LogicalExpression, object: &GQLObject) -> Result { +fn evaluate_logical(expr: &LogicalExpression, object: &GQLObject) -> Result { let lhs_result = evaluate_expression(&expr.left, object); if lhs_result.is_err() { return lhs_result; } - let lhs = lhs_result.ok().unwrap().eq("true"); + let lhs = lhs_result.ok().unwrap().as_bool(); if expr.operator == LogicalOperator::And && !lhs { - return Ok("false".to_owned()); + return Ok(Value::Boolean(false)); } if expr.operator == LogicalOperator::Or && lhs { - return Ok("true".to_owned()); + return Ok(Value::Boolean(true)); } let rhs_result = evaluate_expression(&expr.right, object); @@ -288,17 +287,15 @@ fn evaluate_logical(expr: &LogicalExpression, object: &GQLObject) -> Result lhs && rhs, LogicalOperator::Or => lhs || rhs, LogicalOperator::Xor => lhs ^ rhs, - } - .to_string()); + })); } -fn evaluate_bitwise(expr: &BitwiseExpression, object: &GQLObject) -> Result { +fn evaluate_bitwise(expr: &BitwiseExpression, object: &GQLObject) -> Result { let lhs_result = evaluate_expression(&expr.left, object); if lhs_result.is_err() { return lhs_result; @@ -309,30 +306,30 @@ fn evaluate_bitwise(expr: &BitwiseExpression, object: &GQLObject) -> Result().unwrap(); - let rhs = rhs_result.ok().unwrap().parse::().unwrap(); + let lhs = lhs_result.ok().unwrap().as_number(); + let rhs = rhs_result.ok().unwrap().as_number(); return match expr.operator { - BitwiseOperator::Or => Ok((lhs | rhs).to_string()), - BitwiseOperator::And => Ok((lhs & rhs).to_string()), + BitwiseOperator::Or => Ok(Value::Number(lhs | rhs)), + BitwiseOperator::And => Ok(Value::Number(lhs & rhs)), BitwiseOperator::RightShift => { if rhs >= 64 { Err("Attempt to shift right with overflow".to_string()) } else { - Ok((lhs >> rhs).to_string()) + Ok(Value::Number(lhs >> rhs)) } } BitwiseOperator::LeftShift => { if rhs >= 64 { Err("Attempt to shift left with overflow".to_string()) } else { - Ok((lhs << rhs).to_string()) + Ok(Value::Number(lhs << rhs)) } } }; } -fn evaluate_call(expr: &CallExpression, object: &GQLObject) -> Result { +fn evaluate_call(expr: &CallExpression, object: &GQLObject) -> Result { let lhs_result = evaluate_expression(&expr.callee, object); if lhs_result.is_err() { return lhs_result; @@ -342,7 +339,7 @@ fn evaluate_call(expr: &CallExpression, object: &GQLObject) -> Result Result { +fn evaluate_between(expr: &BetweenExpression, object: &GQLObject) -> Result { let value_result = evaluate_expression(&expr.value, object); if value_result.is_err() { return value_result; @@ -358,14 +355,13 @@ fn evaluate_between(expr: &BetweenExpression, object: &GQLObject) -> Result().unwrap(); - let range_start = range_start_result.ok().unwrap().parse::().unwrap(); - let range_end = range_end_result.ok().unwrap().parse::().unwrap(); - - return Ok((value >= range_start && value <= range_end).to_string()); + let value = value_result.ok().unwrap().as_number(); + let range_start = range_start_result.ok().unwrap().as_number(); + let range_end = range_end_result.ok().unwrap().as_number(); + return Ok(Value::Boolean(value >= range_start && value <= range_end)); } -fn evaluate_case(expr: &CaseExpression, object: &GQLObject) -> Result { +fn evaluate_case(expr: &CaseExpression, object: &GQLObject) -> Result { let conditions = &expr.conditions; let values = &expr.values; @@ -376,7 +372,7 @@ fn evaluate_case(expr: &CaseExpression, object: &GQLObject) -> Result() - .unwrap(); + .as_number(); let other = b .attributes .get(&statement.field_name.to_string()) .unwrap() - .to_string() - .parse::() - .unwrap(); + .as_number(); + first_value.partial_cmp(&other).unwrap() }); } else { @@ -259,7 +256,7 @@ fn execute_order_by_statement( .attributes .get(&statement.field_name.to_string()) .unwrap() - .to_string() + .as_text() }); } @@ -294,14 +291,14 @@ fn execute_group_by_statement( let field_value = object.attributes.get(&statement.field_name).unwrap(); // If there is an existing group for this value, append current object to it - if groups_map.contains_key(field_value) { - let index = *groups_map.get(field_value).unwrap(); + if groups_map.contains_key(&field_value.as_text()) { + let index = *groups_map.get(&field_value.as_text()).unwrap(); let target_group = &mut groups[index]; target_group.push(object.to_owned()); } // Push a new group for this unique value and update the next index else { - groups_map.insert(field_value.to_string(), next_group_index); + groups_map.insert(field_value.as_text(), next_group_index); next_group_index += 1; groups.push(vec![object.to_owned()]); } @@ -339,7 +336,7 @@ fn execute_aggregation_function_statement( for object in group.into_iter() { object .attributes - .insert(result_column_name.to_string(), result.to_string()); + .insert(result_column_name.to_string(), result.to_owned()); } } diff --git a/crates/gitql-engine/src/engine_function.rs b/crates/gitql-engine/src/engine_function.rs index b7cb68df..f8f1d89b 100644 --- a/crates/gitql-engine/src/engine_function.rs +++ b/crates/gitql-engine/src/engine_function.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use gitql_ast::object::GQLObject; +use gitql_ast::{object::GQLObject, value::Value}; pub fn select_gql_objects( repo: &git2::Repository, @@ -39,14 +39,15 @@ fn select_references( } let reference = reference_result.ok().unwrap(); - let mut attributes: HashMap = HashMap::new(); + let mut attributes: HashMap = HashMap::new(); if is_limit_fields_empty || fields.contains(&String::from("name")) { let key = alias_table .get("name") .unwrap_or(&"name".to_string()) .to_string(); - attributes.insert(key, reference.shorthand().unwrap_or("").to_string()); + let name = reference.shorthand().unwrap_or("").to_string(); + attributes.insert(key, Value::Text(name)); } if is_limit_fields_empty || fields.contains(&String::from("full_name")) { @@ -54,7 +55,8 @@ fn select_references( .get("full_name") .unwrap_or(&"full_name".to_string()) .to_string(); - attributes.insert(key, reference.name().unwrap_or("").to_string()); + let full_name = reference.name().unwrap_or("").to_string(); + attributes.insert(key, Value::Text(full_name)); } if is_limit_fields_empty || fields.contains(&String::from("type")) { @@ -64,15 +66,15 @@ fn select_references( .to_string(); if reference.is_branch() { - attributes.insert(key, "branch".to_owned()); + attributes.insert(key, Value::Text("branch".to_owned())); } else if reference.is_remote() { - attributes.insert(key, "remote".to_owned()); + attributes.insert(key, Value::Text("remote".to_owned())); } else if reference.is_tag() { - attributes.insert(key, "tag".to_owned()); + attributes.insert(key, Value::Text("tag".to_owned())); } else if reference.is_note() { - attributes.insert(key, "note".to_owned()); + attributes.insert(key, Value::Text("note".to_owned())); } else { - attributes.insert(key, "other".to_owned()); + attributes.insert(key, Value::Text("other".to_owned())); } } @@ -82,7 +84,7 @@ fn select_references( .unwrap_or(&"repo".to_string()) .to_string(); - attributes.insert(key, repo_path.to_string()); + attributes.insert(key, Value::Text(repo_path.to_string())); } let gql_reference = GQLObject { attributes }; @@ -107,14 +109,14 @@ fn select_commits( for commit_id in revwalk { let commit = repo.find_commit(commit_id.unwrap()).unwrap(); - let mut attributes: HashMap = HashMap::new(); + let mut attributes: HashMap = HashMap::new(); if is_limit_fields_empty || fields.contains(&String::from("commit_id")) { let key = alias_table .get("commit_id") .unwrap_or(&"commit_id".to_string()) .to_string(); - attributes.insert(key, commit.id().to_string()); + attributes.insert(key, Value::Text(commit.id().to_string())); } if is_limit_fields_empty || fields.contains(&String::from("name")) { @@ -122,7 +124,8 @@ fn select_commits( .get("name") .unwrap_or(&"name".to_string()) .to_string(); - attributes.insert(key, commit.author().name().unwrap_or("").to_string()); + let name = commit.author().name().unwrap_or("").to_string(); + attributes.insert(key, Value::Text(name)); } if is_limit_fields_empty || fields.contains(&String::from("email")) { @@ -130,7 +133,8 @@ fn select_commits( .get("email") .unwrap_or(&"email".to_string()) .to_string(); - attributes.insert(key, commit.author().email().unwrap_or("").to_string()); + let email = commit.author().email().unwrap_or("").to_string(); + attributes.insert(key, Value::Text(email)); } if is_limit_fields_empty || fields.contains(&String::from("title")) { @@ -138,7 +142,7 @@ fn select_commits( .get("title") .unwrap_or(&"title".to_string()) .to_string(); - attributes.insert(key, commit.summary().unwrap().to_string()); + attributes.insert(key, Value::Text(commit.summary().unwrap().to_string())); } if is_limit_fields_empty || fields.contains(&String::from("message")) { @@ -146,7 +150,7 @@ fn select_commits( .get("message") .unwrap_or(&"message".to_string()) .to_string(); - attributes.insert(key, commit.message().unwrap_or("").to_string()); + attributes.insert(key, Value::Text(commit.message().unwrap_or("").to_string())); } if is_limit_fields_empty || fields.contains(&String::from("time")) { @@ -154,7 +158,7 @@ fn select_commits( .get("time") .unwrap_or(&"time".to_string()) .to_string(); - attributes.insert(key, commit.time().seconds().to_string()); + attributes.insert(key, Value::Date(commit.time().seconds())); } if is_limit_fields_empty || fields.contains(&String::from("repo")) { @@ -163,7 +167,7 @@ fn select_commits( .unwrap_or(&"repo".to_string()) .to_string(); - attributes.insert(key, repo_path.to_string()); + attributes.insert(key, Value::Text(repo_path.to_string())); } let gql_commit = GQLObject { attributes }; @@ -190,14 +194,14 @@ fn select_diffs( for commit_id in revwalk { let commit = repo.find_commit(commit_id.unwrap()).unwrap(); - let mut attributes: HashMap = HashMap::new(); + let mut attributes: HashMap = HashMap::new(); if is_limit_fields_empty || fields.contains(&String::from("commit_id")) { let key = alias_table .get("commit_id") .unwrap_or(&"commit_id".to_string()) .to_string(); - attributes.insert(key, commit.id().to_string()); + attributes.insert(key, Value::Text(commit.id().to_string())); } if is_limit_fields_empty || fields.contains(&String::from("name")) { @@ -205,7 +209,8 @@ fn select_diffs( .get("name") .unwrap_or(&"name".to_string()) .to_string(); - attributes.insert(key, commit.author().name().unwrap_or("").to_string()); + let name = commit.author().name().unwrap_or("").to_string(); + attributes.insert(key, Value::Text(name)); } if is_limit_fields_empty || fields.contains(&String::from("email")) { @@ -213,7 +218,8 @@ fn select_diffs( .get("email") .unwrap_or(&"email".to_string()) .to_string(); - attributes.insert(key, commit.author().email().unwrap_or("").to_string()); + let email = commit.author().email().unwrap_or("").to_string(); + attributes.insert(key, Value::Text(email)); } if is_limit_fields_empty || fields.contains(&String::from("repo")) { @@ -222,7 +228,7 @@ fn select_diffs( .unwrap_or(&"repo".to_string()) .to_string(); - attributes.insert(key, repo_path.to_string()); + attributes.insert(key, Value::Text(repo_path.to_string())); } if is_limit_fields_empty || select_insertions || select_deletions || select_file_changed { @@ -243,7 +249,7 @@ fn select_diffs( .get("insertions") .unwrap_or(&"insertions".to_string()) .to_string(); - attributes.insert(key, diff_status.insertions().to_string()); + attributes.insert(key, Value::Number(diff_status.insertions() as i64)); } if is_limit_fields_empty || select_deletions { @@ -251,7 +257,7 @@ fn select_diffs( .get("deletions") .unwrap_or(&"deletions".to_string()) .to_string(); - attributes.insert(key, diff_status.deletions().to_string()); + attributes.insert(key, Value::Number(diff_status.deletions() as i64)); } if is_limit_fields_empty || select_file_changed { @@ -259,7 +265,7 @@ fn select_diffs( .get("files_changed") .unwrap_or(&"files_changed".to_string()) .to_string(); - attributes.insert(key, diff_status.files_changed().to_string()); + attributes.insert(key, Value::Number(diff_status.files_changed() as i64)); } } @@ -283,14 +289,15 @@ fn select_branches( for branch in local_branches { let (branch, _) = branch.unwrap(); - let mut attributes: HashMap = HashMap::new(); + let mut attributes: HashMap = HashMap::new(); if is_limit_fields_empty || fields.contains(&String::from("name")) { let key = alias_table .get("name") .unwrap_or(&"name".to_string()) .to_string(); - attributes.insert(key, branch.name().unwrap().unwrap_or("").to_string()); + let branch_name = branch.name().unwrap().unwrap_or("").to_string(); + attributes.insert(key, Value::Text(branch_name)); } if is_limit_fields_empty || fields.contains(&String::from("commit_count")) { @@ -301,7 +308,7 @@ fn select_branches( let branch_ref = branch.get().peel_to_commit().unwrap(); let mut revwalk = repo.revwalk().unwrap(); let _ = revwalk.push(branch_ref.id()); - attributes.insert(key, revwalk.count().to_string()); + attributes.insert(key, Value::Number(revwalk.count() as i64)); } if is_limit_fields_empty || fields.contains(&String::from("is_head")) { @@ -309,7 +316,7 @@ fn select_branches( .get("is_head") .unwrap_or(&"is_head".to_string()) .to_string(); - attributes.insert(key, branch.is_head().to_string()); + attributes.insert(key, Value::Boolean(branch.is_head())); } if is_limit_fields_empty || fields.contains(&String::from("is_remote")) { @@ -317,7 +324,7 @@ fn select_branches( .get("is_remote") .unwrap_or(&"is_remote".to_string()) .to_string(); - attributes.insert(key, branch.get().is_remote().to_string()); + attributes.insert(key, Value::Boolean(branch.get().is_remote())); } if is_limit_fields_empty || fields.contains(&String::from("repo")) { @@ -326,7 +333,7 @@ fn select_branches( .unwrap_or(&"repo".to_string()) .to_string(); - attributes.insert(key, repo_path.to_string()); + attributes.insert(key, Value::Text(repo_path.to_string())); } let gql_branch = GQLObject { attributes }; @@ -349,14 +356,14 @@ fn select_tags( for tag_name in tag_names.iter() { match tag_name { Some(name) => { - let mut attributes: HashMap = HashMap::new(); + let mut attributes: HashMap = HashMap::new(); if is_limit_fields_empty || fields.contains(&String::from("name")) { let key = alias_table .get("name") .unwrap_or(&"name".to_string()) .to_string(); - attributes.insert(key, name.to_string()); + attributes.insert(key, Value::Text(name.to_string())); } if is_limit_fields_empty || fields.contains(&String::from("repo")) { @@ -365,7 +372,7 @@ fn select_tags( .unwrap_or(&"repo".to_string()) .to_string(); - attributes.insert(key, repo_path.to_string()); + attributes.insert(key, Value::Text(repo_path.to_string())); } let gql_tag = GQLObject { attributes }; diff --git a/crates/gitql-parser/src/lib.rs b/crates/gitql-parser/src/lib.rs index e4118d30..bd31c2ce 100644 --- a/crates/gitql-parser/src/lib.rs +++ b/crates/gitql-parser/src/lib.rs @@ -1,4 +1,3 @@ -pub mod aggregation; pub mod diagnostic; pub mod parser; pub mod tokenizer; diff --git a/crates/gitql-parser/src/parser.rs b/crates/gitql-parser/src/parser.rs index 65c7674a..422800d2 100644 --- a/crates/gitql-parser/src/parser.rs +++ b/crates/gitql-parser/src/parser.rs @@ -2,12 +2,12 @@ use lazy_static::lazy_static; use std::collections::HashMap; use std::collections::HashSet; -use crate::aggregation::AGGREGATIONS; -use crate::aggregation::AGGREGATIONS_PROTOS; use crate::diagnostic::GQLError; use crate::tokenizer::Location; use crate::tokenizer::{Token, TokenKind}; +use gitql_ast::aggregation::AGGREGATIONS; +use gitql_ast::aggregation::AGGREGATIONS_PROTOS; use gitql_ast::expression::*; use gitql_ast::statement::*; use gitql_ast::transformation::TRANSFORMATIONS; diff --git a/docs/function/transformations.md b/docs/function/transformations.md index 00d18544..891e1366 100644 --- a/docs/function/transformations.md +++ b/docs/function/transformations.md @@ -21,9 +21,9 @@ Transform String value to String with removes whitespace from the start and eng SELECT * FROM commits where name.trim = "" ``` -### String length +### String len Transform String value to the length of it ```sql -SELECT * FROM commits where name.length > 0 +SELECT * FROM commits where name.len > 0 ``` diff --git a/docs/index.md b/docs/index.md index f7371729..1c559407 100644 --- a/docs/index.md +++ b/docs/index.md @@ -34,7 +34,7 @@ SELECT name FROM commits GROUP By name SELECT name FROM commits GROUP By name having name = "AmrDeveloper" SELECT * FROM branches -SELECT * FROM branches WHERE ishead = "true" +SELECT * FROM branches WHERE is_head = true SELECT * FROM branches WHERE name ends_with "master" SELECT * FROM branches WHERE name contains "origin"