use crate::ast::*;
use crate::cancel::CancelCheck;
use crate::plan::*;
use crate::result::QueryError;
use powdb_storage::catalog::{Catalog, ExpressionIndexMeta};
use powdb_storage::stored_json_path::StoredJsonPathV1;
use powdb_storage::types::*;
use std::ops::ControlFlow;
use super::compiled::*;
use super::mem_budget;
const CANCELLABLE_SORT_RUN: usize = 2_048;
#[cfg(test)]
thread_local! {
static GENERIC_RID_MATCH_CALLS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
}
#[cfg(test)]
pub(super) fn reset_generic_rid_match_calls() {
GENERIC_RID_MATCH_CALLS.with(|calls| calls.set(0));
}
#[cfg(test)]
pub(super) fn generic_rid_match_calls() -> u64 {
GENERIC_RID_MATCH_CALLS.with(std::cell::Cell::get)
}
pub(super) fn compare_order_values(
left: &Value,
right: &Value,
descending: bool,
) -> std::cmp::Ordering {
use std::cmp::Ordering;
match (left, right) {
(Value::Empty, Value::Empty) => Ordering::Equal,
(Value::Empty, _) => Ordering::Greater,
(_, Value::Empty) => Ordering::Less,
_ if descending => left.cmp(right).reverse(),
_ => left.cmp(right),
}
}
pub(super) fn cooperative_stable_sort_by<T, F>(
values: &mut [T],
memory_limit: usize,
compare: F,
) -> Result<(), QueryError>
where
F: Fn(&T, &T) -> std::cmp::Ordering,
{
crate::cancel::check()?;
let len = values.len();
if len < 2 {
return Ok(());
}
let scratch_bytes = len
.saturating_mul(std::mem::size_of::<usize>())
.saturating_mul(2);
mem_budget::charge(scratch_bytes, memory_limit)?;
let mut order: Vec<usize> = (0..len).collect();
let mut scratch = vec![0usize; len];
for run in order.chunks_mut(CANCELLABLE_SORT_RUN) {
crate::cancel::check()?;
run.sort_by(|&a, &b| compare(&values[a], &values[b]));
crate::cancel::check()?;
}
let mut cancel = CancelCheck::new();
let mut width = CANCELLABLE_SORT_RUN;
while width < len {
let step = width.saturating_mul(2);
let mut start = 0usize;
while start < len {
let mid = start.saturating_add(width).min(len);
let end = start.saturating_add(step).min(len);
let (mut left, mut right, mut out) = (start, mid, start);
while left < mid && right < end {
cancel.tick()?;
if compare(&values[order[left]], &values[order[right]])
!= std::cmp::Ordering::Greater
{
scratch[out] = order[left];
left += 1;
} else {
scratch[out] = order[right];
right += 1;
}
out += 1;
}
while left < mid {
cancel.tick()?;
scratch[out] = order[left];
left += 1;
out += 1;
}
while right < end {
cancel.tick()?;
scratch[out] = order[right];
right += 1;
out += 1;
}
start = start.saturating_add(step);
}
std::mem::swap(&mut order, &mut scratch);
width = step;
}
for (new_position, &old_position) in order.iter().enumerate() {
cancel.tick()?;
scratch[old_position] = new_position;
}
drop(order);
for position in 0..len {
while scratch[position] != position {
cancel.tick()?;
let destination = scratch[position];
values.swap(position, destination);
scratch.swap(position, destination);
}
}
Ok(())
}
pub(super) fn for_each_row_raw_cancellable(
catalog: &Catalog,
table: &str,
mut f: impl FnMut(RowId, &[u8]),
) -> Result<(), QueryError> {
if !crate::cancel::has_active_install() {
return catalog
.for_each_row_raw(table, f)
.map_err(|err| QueryError::StorageError(err.to_string()));
}
let mut cancel = CancelCheck::new();
let mut cancel_err: Option<QueryError> = None;
catalog
.try_for_each_row_raw(table, |rid, data| {
if let Err(err) = cancel.tick() {
cancel_err = Some(err);
return ControlFlow::Break(());
}
f(rid, data);
ControlFlow::Continue(())
})
.map_err(|err| QueryError::StorageError(err.to_string()))?;
match cancel_err {
Some(err) => Err(err),
None => Ok(()),
}
}
fn resolve_expression_index(
catalog: &Catalog,
table: &str,
path: &StoredJsonPathV1,
) -> Option<ExpressionIndexMeta> {
catalog
.expression_index_metadata(table)?
.into_iter()
.find(|metadata| metadata.canonical_version == 1 && metadata.json_path == *path)
}
fn expression_index_fallback(plan: &PlanNode) -> Option<PlanNode> {
match plan {
PlanNode::ExprIndexScan { table, path, key } => Some(PlanNode::Filter {
input: Box::new(PlanNode::SeqScan {
table: table.clone(),
}),
predicate: Expr::BinaryOp(
Box::new(stored_json_path_expr(path)),
BinOp::Eq,
Box::new(key.clone()),
),
}),
PlanNode::ExprRangeScan {
table,
path,
start,
end,
} => Some(PlanNode::Filter {
input: Box::new(PlanNode::SeqScan {
table: table.clone(),
}),
predicate: synthesize_expr_range_predicate(path, start, end),
}),
PlanNode::OrderedExprIndexScan {
table,
path,
descending,
limit,
offset,
} => {
let sorted = PlanNode::Sort {
input: Box::new(PlanNode::SeqScan {
table: table.clone(),
}),
keys: vec![SortKey {
expr: stored_json_path_expr(path),
descending: *descending,
}],
};
let sliced = match offset {
Some(count) => PlanNode::Offset {
input: Box::new(sorted),
count: count.clone(),
},
None => sorted,
};
Some(PlanNode::Limit {
input: Box::new(sliced),
count: limit.clone(),
})
}
_ => None,
}
}
#[derive(Debug)]
pub(crate) struct ProvenanceRows {
pub(super) columns: Vec<String>,
pub(super) rows: Vec<Vec<Value>>,
source_aliases: Vec<String>,
provenance: Vec<Vec<Option<RowId>>>,
}
impl ProvenanceRows {
fn source_index(&self, alias: &str) -> Option<usize> {
self.source_aliases
.iter()
.position(|source| source == alias)
}
}
mod aggregate;
mod dispatch;
mod fast_paths;
mod join;
mod lowering;
mod mutation;
mod scan;
mod validate;
use lowering::{stored_json_path_expr, synthesize_expr_range_predicate};
pub(crate) use aggregate::{
aggregate_rows, aggregate_rows_with_provenance, exec_group_by, exec_group_by_with_provenance,
execute_window,
};
#[cfg(test)]
pub(crate) use aggregate::{compute_group_aggregate, GroupAggregateContext};
#[cfg(test)]
pub(crate) use join::check_nested_loop_pair_limit;
pub(crate) use join::execute_materialized_join;
pub(crate) use lowering::{
format_plan_tree, lower_unindexed_scans, range_matches, synthesize_range_predicate,
};
pub(crate) use validate::{
predicate_column_indices_json, validate_json_path_types, validate_no_stray_aggregates,
};