mod exec;
mod parser;
use super::super::{FunctionMeta, Registry};
use super::check_arity;
use crate::eval::functions::lookup::array_utils::flatten_to_rows;
use crate::types::{ErrorKind, Value};
pub fn query_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 2, 3) {
return err;
}
let query_str = match &args[1] {
Value::Text(s) => s.clone(),
_ => return Value::Error(ErrorKind::Value),
};
let headers: usize = match args.get(2) {
None | Some(Value::Empty) => 0,
Some(Value::Number(n)) if *n >= 0.0 => *n as usize,
_ => return Value::Error(ErrorKind::Value),
};
let rows = flatten_to_rows(&args[0]);
if rows.is_empty() || headers > rows.len() {
return Value::Error(ErrorKind::Value);
}
let ncols = rows.iter().map(|r| r.len()).max().unwrap_or(0);
if ncols == 0 {
return Value::Error(ErrorKind::Value);
}
let pad = |mut r: Vec<Value>| -> Vec<Value> {
r.resize(ncols, Value::Empty);
r
};
let mut all_rows: Vec<Vec<Value>> = rows.into_iter().map(pad).collect();
let data_rows: Vec<Vec<Value>> = all_rows.split_off(headers);
let header_row: Option<Vec<Value>> = if headers >= 1 { all_rows.into_iter().next() } else { None };
let parsed = match parser::parse(&query_str, ncols) {
Ok(p) => p,
Err(e) => return e,
};
let result_rows = match exec::execute(&parsed, &data_rows) {
Ok(r) => r,
Err(e) => return e,
};
let emit_header = headers >= 1 || !parsed.labels.is_empty();
let mut out_rows: Vec<Value> = Vec::with_capacity(result_rows.len() + 1);
if emit_header {
out_rows.push(Value::Array(exec::build_header(&parsed, header_row.as_deref())));
}
out_rows.extend(result_rows.into_iter().map(Value::Array));
if out_rows.is_empty() {
return Value::Error(ErrorKind::NA);
}
Value::Array(out_rows)
}
#[cfg(test)]
mod tests;
pub fn register_query(registry: &mut Registry) {
registry.register_eager(
"QUERY",
query_fn,
FunctionMeta {
category: "query",
signature: "QUERY(data, query, [headers])",
description: "Run a pseudo-SQL query (select/where/group by/order by/limit/label) over a range",
},
);
}