Skip to content

Commit

Permalink
Implement In Expression
Browse files Browse the repository at this point in the history
  • Loading branch information
AmrDeveloper committed Sep 8, 2023
1 parent fba142c commit 0d93e27
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 54 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ GQL is a query language with a syntax very similar to SQL with a tiny engine to
SELECT 1
SELECT 1 + 2
SELECT LEN("Git Query Language")
SELECT "One" IN ("One", "Two", "Three")

SELECT name, COUNT(name) AS commit_num FROM commits GROUP BY name ORDER BY commit_num DESC LIMIT 10
SELECT commit_count FROM branches WHERE commit_count BETWEEN 0 .. 10
Expand Down
21 changes: 21 additions & 0 deletions crates/gitql-ast/src/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub enum ExpressionKind {
Call,
Between,
Case,
In,
}

pub trait Expression {
Expand Down Expand Up @@ -340,3 +341,23 @@ impl Expression for CaseExpression {
self
}
}

pub struct InExpression {
pub argument: Box<dyn Expression>,
pub values: Vec<Box<dyn Expression>>,
pub values_type: DataType,
}

impl Expression for InExpression {
fn get_expression_kind(&self) -> ExpressionKind {
ExpressionKind::In
}

fn expr_type(&self, _scope: &Scope) -> DataType {
return self.values_type.clone();
}

fn as_any(&self) -> &dyn Any {
self
}
}
4 changes: 2 additions & 2 deletions crates/gitql-ast/src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,14 @@ lazy_static! {
Prototype {
parameters: vec![DataType::Text],
result: DataType::Number,
},
},
);
map.insert(
"char",
Prototype {
parameters: vec![DataType::Number],
result: DataType::Text,
},
},
);
map
};
Expand Down
16 changes: 16 additions & 0 deletions crates/gitql-ast/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ pub enum Value {
}

impl Value {
pub fn eq(&self, other: &Value) -> bool {
if self.data_type() != other.data_type() {
return false;
}

return match self.data_type() {
DataType::Any => true,
DataType::Text => self.as_text() == other.as_text(),
DataType::Number => self.as_number() == other.as_number(),
DataType::Boolean => self.as_bool() == other.as_bool(),
DataType::Date => self.as_date() == other.as_date(),
DataType::Undefined => true,
DataType::Null => true,
};
}

pub fn data_type(&self) -> DataType {
return match self {
Value::Number(_) => DataType::Number,
Expand Down
16 changes: 16 additions & 0 deletions crates/gitql-engine/src/engine_evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use gitql_ast::expression::ComparisonExpression;
use gitql_ast::expression::ComparisonOperator;
use gitql_ast::expression::Expression;
use gitql_ast::expression::ExpressionKind::*;
use gitql_ast::expression::InExpression;
use gitql_ast::expression::LogicalExpression;
use gitql_ast::expression::LogicalOperator;
use gitql_ast::expression::NumberExpression;
Expand Down Expand Up @@ -120,6 +121,10 @@ pub fn evaluate_expression(
.unwrap();
return evaluate_case(expr, object);
}
In => {
let expr = expression.as_any().downcast_ref::<InExpression>().unwrap();
return evaluate_in(expr, object);
}
};
}

Expand Down Expand Up @@ -351,3 +356,14 @@ fn evaluate_case(expr: &CaseExpression, object: &HashMap<String, Value>) -> Resu
_ => Err("Invalid case statement".to_owned()),
};
}

fn evaluate_in(expr: &InExpression, object: &HashMap<String, Value>) -> Result<Value, String> {
let argument = evaluate_expression(&expr.argument, object)?;
for value_expr in &expr.values {
let value = evaluate_expression(value_expr, object)?;
if argument.eq(&value) {
return Ok(Value::Boolean(true));
}
}
return Ok(Value::Boolean(false));
}
178 changes: 126 additions & 52 deletions crates/gitql-parser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,61 @@ fn parse_expression(
tokens: &Vec<Token>,
position: &mut usize,
) -> Result<Box<dyn Expression>, GQLError> {
return parse_between_expression(context, tokens, position);
return parse_in_expression(context, tokens, position);
}

fn parse_in_expression(
context: &mut ParserContext,
tokens: &Vec<Token>,
position: &mut usize,
) -> Result<Box<dyn Expression>, GQLError> {
let expression = parse_between_expression(context, tokens, position)?;
if *position < tokens.len() && tokens[*position].kind == TokenKind::In {
let in_location = tokens[*position].location;

// Consume `IN` keyword
*position += 1;

if consume_kind(tokens, *position, TokenKind::LeftParen).is_err() {
return Err(GQLError {
message: "Expects values between `(` and `)` after `IN` keyword".to_owned(),
location: in_location,
});
}

// Check that all values has the same type
let values = parse_arguments_expressions(context, tokens, position)?;
if values.is_empty() {
return Err(GQLError {
message: "Values of `IN` expression can't be empty".to_owned(),
location: in_location,
});
}

let values_type_result = check_all_values_are_same_type(context, &values);
if values_type_result.is_err() {
return Err(GQLError {
message: "Expects values between `(` and `)` to have the same type".to_owned(),
location: in_location,
});
}

// Check that argument and values has the same type
let values_type = values_type_result.ok().unwrap();
if expression.expr_type(&context.symbol_table) != values_type {
return Err(GQLError {
message: "Argument and Values of In Expression must have the same type".to_owned(),
location: in_location,
});
}

return Ok(Box::new(InExpression {
argument: expression,
values,
values_type,
}));
}
return Ok(expression);
}

fn parse_between_expression(
Expand All @@ -538,7 +592,7 @@ fn parse_between_expression(
if *position < tokens.len() && tokens[*position].kind == TokenKind::Between {
let between_location = tokens[*position].location;

// Consume Between keyword
// Consume `BETWEEN` keyword
*position += 1;

if expression.expr_type(&context.symbol_table) != DataType::Number {
Expand Down Expand Up @@ -1153,7 +1207,7 @@ fn parse_function_call_expression(
// Make sure it's valid function name
let function_name = &symbol_expression.unwrap().value.to_lowercase();
if FUNCTIONS.contains_key(function_name.as_str()) {
let arguments = parse_call_arguments_expressions(context, tokens, position)?;
let arguments = parse_arguments_expressions(context, tokens, position)?;
let prototype = PROTOTYPES.get(function_name.as_str()).unwrap();
let parameters = &prototype.parameters;
let return_type = prototype.result.clone();
Expand All @@ -1177,7 +1231,7 @@ fn parse_function_call_expression(
is_aggregation: false,
}));
} else if AGGREGATIONS.contains_key(function_name.as_str()) {
let arguments = parse_call_arguments_expressions(context, tokens, position)?;
let arguments = parse_arguments_expressions(context, tokens, position)?;
let prototype = AGGREGATIONS_PROTOS.get(function_name.as_str()).unwrap();
let parameters = &vec![prototype.parameter.clone()];
let return_type = prototype.result.clone();
Expand Down Expand Up @@ -1220,54 +1274,7 @@ fn parse_function_call_expression(
return Ok(expression);
}

fn check_function_call_arguments(
context: &mut ParserContext,
arguments: &Vec<Box<dyn Expression>>,
parameters: &Vec<DataType>,
function_name: String,
location: Location,
) -> Result<(), GQLError> {
let arguments_len = arguments.len();
let parameters_len = parameters.len();

// Make sure number of arguments and parameters are the same
if arguments_len != parameters_len {
let message = format!(
"Function `{}` expects `{}` arguments but got `{}`",
function_name, parameters_len, arguments_len
);
return Err(GQLError { message, location });
}

// Check each argument vs parameter type
for index in 0..arguments_len {
let argument_type = arguments
.get(index)
.unwrap()
.expr_type(&context.symbol_table);

let parameter_type = parameters.get(index).unwrap();

if argument_type == DataType::Any || *parameter_type == DataType::Any {
continue;
}

if argument_type != *parameter_type {
let message = format!(
"Function `{}` argument number {} with type `{}` don't match expected type `{}`",
function_name,
index,
argument_type.literal(),
parameter_type.literal()
);
return Err(GQLError { message, location });
}
}

return Ok(());
}

fn parse_call_arguments_expressions(
fn parse_arguments_expressions(
context: &mut ParserContext,
tokens: &Vec<Token>,
position: &mut usize,
Expand Down Expand Up @@ -1484,6 +1491,73 @@ fn parse_case_expression(
}));
}

fn check_function_call_arguments(
context: &mut ParserContext,
arguments: &Vec<Box<dyn Expression>>,
parameters: &Vec<DataType>,
function_name: String,
location: Location,
) -> Result<(), GQLError> {
let arguments_len = arguments.len();
let parameters_len = parameters.len();

// Make sure number of arguments and parameters are the same
if arguments_len != parameters_len {
let message = format!(
"Function `{}` expects `{}` arguments but got `{}`",
function_name, parameters_len, arguments_len
);
return Err(GQLError { message, location });
}

// Check each argument vs parameter type
for index in 0..arguments_len {
let argument_type = arguments
.get(index)
.unwrap()
.expr_type(&context.symbol_table);

let parameter_type = parameters.get(index).unwrap();

if argument_type == DataType::Any || *parameter_type == DataType::Any {
continue;
}

if argument_type != *parameter_type {
let message = format!(
"Function `{}` argument number {} with type `{}` don't match expected type `{}`",
function_name,
index,
argument_type.literal(),
parameter_type.literal()
);
return Err(GQLError { message, location });
}
}

return Ok(());
}

fn check_all_values_are_same_type(
context: &ParserContext,
arguments: &Vec<Box<dyn Expression>>,
) -> Result<DataType, ()> {
let arguments_count = arguments.len();
if arguments_count == 0 {
return Ok(DataType::Any);
}

let data_type = arguments[0].expr_type(&context.symbol_table);
for index in 1..arguments_count {
let expr_type = arguments[index].expr_type(&context.symbol_table);
if data_type != expr_type {
return Err(());
}
}

return Ok(data_type);
}

fn type_check_selected_fields(
symbol_table: &Scope,
table_name: &str,
Expand Down
2 changes: 2 additions & 0 deletions crates/gitql-parser/src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub enum TokenKind {
Offset,
Order,
By,
In,

Case,
When,
Expand Down Expand Up @@ -818,6 +819,7 @@ fn resolve_symbol_kind(literal: String) -> TokenKind {
"else" => TokenKind::Else,
"end" => TokenKind::End,
"between" => TokenKind::Between,
"in" => TokenKind::In,

// Logical Operators
"or" => TokenKind::LogicalOr,
Expand Down
7 changes: 7 additions & 0 deletions docs/expression/binary.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,11 @@ Used to check if value is between range start and end included

```SQL
SELECT commit_count FROM branches WHERE commit_count BETWEEN 2 .. 30000
```

### In Expression
Returns true if any values are equal to the argument

```SQL
SELECT "One" IN ("One", "Two", "Three")
```

0 comments on commit 0d93e27

Please sign in to comment.