Skip to content

Commit

Permalink
feat: Report error where window or aggregate value is used before eva…
Browse files Browse the repository at this point in the history
…luation
  • Loading branch information
AmrDeveloper committed Dec 14, 2024
1 parent 4a6949e commit a2a48a0
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 97 deletions.
4 changes: 2 additions & 2 deletions crates/gitql-ast/src/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ pub enum SymbolFlag {
#[derive(Clone)]
pub struct SymbolExpr {
pub value: String,
pub result_type: Box<dyn DataType>,
pub expr_type: Box<dyn DataType>,
pub flag: SymbolFlag,
}

Expand All @@ -125,7 +125,7 @@ impl Expr for SymbolExpr {
}

fn expr_type(&self) -> Box<dyn DataType> {
self.result_type.clone()
self.expr_type.clone()
}

fn as_any(&self) -> &dyn Any {
Expand Down
8 changes: 7 additions & 1 deletion crates/gitql-ast/src/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,15 @@ pub struct WindowFunction {
pub kind: WindowFunctionKind,
}

#[derive(Clone)]
pub enum WindowValue {
Function(WindowFunction),
Expression(Box<dyn Expr>),
}

#[derive(Clone)]
pub struct WindowFunctionsStatement {
pub functions: HashMap<String, WindowFunction>,
pub window_values: HashMap<String, WindowValue>,
}

impl Statement for WindowFunctionsStatement {
Expand Down
117 changes: 73 additions & 44 deletions crates/gitql-engine/src/engine_window_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use gitql_ast::statement::GroupByStatement;
use gitql_ast::statement::WindowDefinition;
use gitql_ast::statement::WindowFunctionKind;
use gitql_ast::statement::WindowFunctionsStatement;
use gitql_ast::statement::WindowValue;
use gitql_core::environment::Environment;
use gitql_core::object::GitQLObject;

Expand All @@ -29,57 +30,85 @@ pub(crate) fn execute_window_functions_statement(
let main_group = &mut gitql_object.groups[0];
let rows_len = main_group.rows.len();

for (result_column_name, function) in statement.functions.iter() {
let args_len = function.arguments.len();
let column_name = resolve_actual_column_name(alias_table, result_column_name);
let column_index = gitql_object
.titles
.iter()
.position(|r| r.eq(&column_name))
.unwrap();

// Apply window definition to end up with frames
apply_window_definition_on_gitql_object(env, gitql_object, &function.window_definition)?;

// Run window function on each group
for frame_index in 0..gitql_object.len() {
let mut frame_values = Vec::with_capacity(rows_len);
let frame = &mut gitql_object.groups[frame_index];
for row in frame.rows.iter_mut() {
let mut row_selected_values = Vec::with_capacity(args_len);
for argument in function.arguments.iter() {
let argument =
evaluate_expression(env, argument, &gitql_object.titles, &row.values)?;

row_selected_values.push(argument);
}
// Evaluate Window functions
for (result_column_name, window_value) in statement.window_values.iter() {
if let WindowValue::Function(function) = window_value {
let column_name = resolve_actual_column_name(alias_table, result_column_name);
let column_index = gitql_object
.titles
.iter()
.position(|r| r.eq(&column_name))
.unwrap();

// Apply window definition to end up with frames
apply_window_definition_on_gitql_object(
env,
gitql_object,
&function.window_definition,
)?;

// Run window function on each group
let args_len = function.arguments.len();
for frame_index in 0..gitql_object.len() {
let mut frame_values = Vec::with_capacity(rows_len);
let frame = &mut gitql_object.groups[frame_index];
for row in frame.rows.iter_mut() {
let mut row_selected_values = Vec::with_capacity(args_len);
for argument in function.arguments.iter() {
let argument =
evaluate_expression(env, argument, &gitql_object.titles, &row.values)?;

row_selected_values.push(argument);
}

frame_values.push(row_selected_values);
}
frame_values.push(row_selected_values);
}

if frame_values.is_empty() {
continue;
}
if frame_values.is_empty() {
continue;
}

// Evaluate function for this frame
match function.kind {
WindowFunctionKind::AggregatedWindowFunction => {
let aggregation_function =
env.aggregation_function(&function.function_name).unwrap();
let aggregated_value = aggregation_function(&frame_values);
// Evaluate function for this frame
match function.kind {
WindowFunctionKind::AggregatedWindowFunction => {
let aggregation_function =
env.aggregation_function(&function.function_name).unwrap();
let aggregated_value = aggregation_function(&frame_values);

for row in frame.rows.iter_mut() {
row.values[column_index] = aggregated_value.clone();
for row in frame.rows.iter_mut() {
row.values[column_index] = aggregated_value.clone();
}
}
}
WindowFunctionKind::PureWindowFunction => {
let window_function = env.window_function(&function.function_name).unwrap();
let window_values = window_function(&frame_values);
for (index, value) in window_values.iter().enumerate() {
frame.rows[index].values[column_index] = value.clone();
WindowFunctionKind::PureWindowFunction => {
let window_function = env.window_function(&function.function_name).unwrap();
let window_values = window_function(&frame_values);
for (index, value) in window_values.iter().enumerate() {
frame.rows[index].values[column_index] = value.clone();
}
}
};
}
}
}

// Evaluate Expressions that depend on Window Functions evaluation
for (result_column_name, window_value) in statement.window_values.iter() {
if let WindowValue::Expression(expression) = window_value {
let column_name = resolve_actual_column_name(alias_table, result_column_name);
let column_index = gitql_object
.titles
.iter()
.position(|r| r.eq(&column_name))
.unwrap();

for frame_index in 0..gitql_object.len() {
let frame = &mut gitql_object.groups[frame_index];
for row in frame.rows.iter_mut() {
let window_value =
evaluate_expression(env, expression, &gitql_object.titles, &row.values)?;
row.values[column_index] = window_value.clone();
}
};
}
}
}

Expand Down
11 changes: 6 additions & 5 deletions crates/gitql-parser/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::collections::HashMap;

use gitql_ast::statement::{AggregateValue, WindowDefinition, WindowFunction};
use gitql_ast::statement::AggregateValue;
use gitql_ast::statement::WindowDefinition;
use gitql_ast::statement::WindowValue;

use crate::name_generator::NameGenerator;
use crate::token::SourceLocation;
Expand All @@ -9,7 +11,7 @@ use crate::token::SourceLocation;
pub struct ParserContext {
pub aggregations: HashMap<String, AggregateValue>,

pub window_functions: HashMap<String, WindowFunction>,
pub window_functions: HashMap<String, WindowValue>,
pub named_window_clauses: HashMap<String, WindowDefinition>,

pub selected_fields: Vec<String>,
Expand All @@ -22,13 +24,12 @@ pub struct ParserContext {
pub name_alias_table: HashMap<String, String>,
pub name_generator: NameGenerator,

pub has_select_statement: bool,

pub is_single_value_query: bool,
pub has_select_statement: bool,
pub has_group_by_statement: bool,

pub inside_selections: bool,
pub inside_having: bool,
pub inside_order_by: bool,
pub inside_over_clauses: bool,

}
23 changes: 17 additions & 6 deletions crates/gitql-parser/src/parse_function_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use gitql_ast::statement::WindowFunction;
use gitql_ast::statement::WindowFunctionKind;
use gitql_ast::statement::WindowOrderingClause;
use gitql_ast::statement::WindowPartitioningClause;
use gitql_ast::statement::WindowValue;
use gitql_core::environment::Environment;

use crate::context::ParserContext;
Expand Down Expand Up @@ -105,7 +106,7 @@ pub(crate) fn parse_function_call_expression(
function_name_location,
)?;

let column_name = context.name_generator.generate_temp_name();
let column_name = context.name_generator.generate_column_name();
context.hidden_selections.push(column_name.to_string());

let return_type = resolve_dynamic_data_type(
Expand Down Expand Up @@ -145,7 +146,7 @@ pub(crate) fn parse_function_call_expression(

context
.window_functions
.insert(column_name.clone(), function);
.insert(column_name.clone(), WindowValue::Function(function));

flag = SymbolFlag::WindowReference;
} else {
Expand All @@ -156,7 +157,7 @@ pub(crate) fn parse_function_call_expression(
// Return a Symbol that reference to the aggregation function generated name
return Ok(Box::new(SymbolExpr {
value: column_name,
result_type: return_type,
expr_type: return_type,
flag,
}));
}
Expand Down Expand Up @@ -217,7 +218,17 @@ pub(crate) fn parse_function_call_expression(
function_name_location,
)?;

// TODO: Make sure to be used in Order by
// Make sure Window function is called in the right place only
if !(context.inside_selections || context.inside_order_by) {
return Err(Diagnostic::error(
"Window function can only be called inside Select selection or Order by",
)
.add_note("Window functions evaluated later right before `ORDER BY`")
.add_help("You can call Window function in Select selection or Order by")
.with_location(function_name_location)
.as_boxed());
}

let column_name = context.name_generator.generate_column_name();
context.hidden_selections.push(column_name.to_string());

Expand Down Expand Up @@ -249,12 +260,12 @@ pub(crate) fn parse_function_call_expression(

context
.window_functions
.insert(column_name.clone(), function);
.insert(column_name.clone(), WindowValue::Function(function));

// Return a Symbol that reference to the aggregation function generated name
return Ok(Box::new(SymbolExpr {
value: column_name,
result_type: return_type,
expr_type: return_type,
flag: SymbolFlag::WindowReference,
}));
}
Expand Down
Loading

0 comments on commit a2a48a0

Please sign in to comment.