use super::{
field_resolution::resolve_runtime_field,
values::{runtime_partial_cmp, text_abbrev_key},
};
use crate::query::unified::UnifiedRecord;
use reddb_rql::ast::{Expr, OrderByClause};
use reddb_types::Value;
use std::cmp::Ordering;
pub trait OrderingHost {
fn evaluate_expression(&self, expression: &Expr, record: &UnifiedRecord) -> Option<Value>;
fn secret_query_input(&self, value: Value) -> Option<Value>;
fn record_identity_key(&self, record: &UnifiedRecord) -> String;
fn topk_disabled(&self) -> bool;
}
pub fn compare_runtime_order_with_host<H: OrderingHost>(
host: &H,
left: &UnifiedRecord,
right: &UnifiedRecord,
clauses: &[OrderByClause],
table_name: Option<&str>,
table_alias: Option<&str>,
) -> Ordering {
for clause in clauses {
let (left_value, right_value) = if let Some(ref expr) = clause.expr {
(
host.evaluate_expression(expr, left),
host.evaluate_expression(expr, right),
)
} else {
(
resolve_runtime_field(left, &clause.field, table_name, table_alias),
resolve_runtime_field(right, &clause.field, table_name, table_alias),
)
};
let ordering = compare_runtime_optional_values(
&|value| host.secret_query_input(value),
left_value.as_ref(),
right_value.as_ref(),
clause.nulls_first,
);
if ordering != Ordering::Equal {
return if clause.ascending {
ordering
} else {
ordering.reverse()
};
}
}
host.record_identity_key(left)
.cmp(&host.record_identity_key(right))
}
struct SortKey {
value: Option<Value>,
abbrev: Option<u64>,
}
impl SortKey {
fn new(value: Option<Value>) -> Self {
let abbrev = match &value {
Some(Value::Text(s)) => Some(text_abbrev_key(s)),
_ => None,
};
SortKey { value, abbrev }
}
}
pub fn sort_records_by_order_by_with_host<H: OrderingHost>(
host: &H,
records: &mut Vec<UnifiedRecord>,
order_by: &[OrderByClause],
table_name: Option<&str>,
table_alias: Option<&str>,
) {
if order_by.is_empty() || records.len() < 2 {
return;
}
let n = records.len();
let k = order_by.len();
let mut keys_flat: Vec<SortKey> = Vec::with_capacity(n * k);
for rec in records.iter() {
for clause in order_by.iter() {
let v = if let Some(ref expr) = clause.expr {
host.evaluate_expression(expr, rec)
} else {
resolve_runtime_field(rec, &clause.field, table_name, table_alias)
};
keys_flat.push(SortKey::new(v));
}
}
let mut idxs: Vec<usize> = (0..n).collect();
let cmp_keys = |a: usize, b: usize| -> Ordering {
let la = a * k;
let lb = b * k;
for (j, clause) in order_by.iter().enumerate() {
let lk = &keys_flat[la + j];
let rk = &keys_flat[lb + j];
let ord = match (&lk.abbrev, &rk.abbrev, &lk.value, &rk.value) {
(Some(la_a), Some(ra), Some(Value::Text(ls)), Some(Value::Text(rs))) => {
match la_a.cmp(ra) {
Ordering::Equal => ls.as_ref().cmp(rs.as_ref()),
other => other,
}
}
_ => compare_runtime_optional_values(
&|value| host.secret_query_input(value),
lk.value.as_ref(),
rk.value.as_ref(),
clause.nulls_first,
),
};
if ord != Ordering::Equal {
return if clause.ascending { ord } else { ord.reverse() };
}
}
Ordering::Equal
};
idxs.sort_by(|&a, &b| cmp_keys(a, b));
let orig: Vec<_> = std::mem::take(records);
let mut out = Vec::with_capacity(n);
for i in idxs {
out.push(orig[i].clone());
}
*records = out;
}
pub fn sort_or_top_k_records_with_host<H: OrderingHost>(
host: &H,
records: &mut Vec<UnifiedRecord>,
order_by: &[OrderByClause],
offset: Option<u64>,
limit: Option<u64>,
table_name: Option<&str>,
table_alias: Option<&str>,
) {
if order_by.is_empty() {
return;
}
let effective_k = match (offset, limit) {
(_, None) => None,
(off, Some(lim)) => {
Some(usize::try_from(off.unwrap_or(0).saturating_add(lim)).unwrap_or(usize::MAX))
}
};
if !host.topk_disabled() {
if let Some(k) = effective_k {
if k > 0 && records.len() > k.saturating_mul(2) {
top_k_records_by_order_by_with_host(
host,
records,
order_by,
k,
table_name,
table_alias,
);
return;
}
}
}
sort_records_by_order_by_with_host(host, records, order_by, table_name, table_alias);
}
pub fn top_k_records_by_order_by_with_host<H: OrderingHost>(
host: &H,
records: &mut Vec<UnifiedRecord>,
order_by: &[OrderByClause],
k: usize,
table_name: Option<&str>,
table_alias: Option<&str>,
) {
if k == 0 {
records.clear();
return;
}
if order_by.is_empty() {
records.truncate(k);
return;
}
let n = records.len();
if n <= k {
sort_records_by_order_by_with_host(host, records, order_by, table_name, table_alias);
return;
}
let kc = order_by.len();
let mut keys_flat: Vec<SortKey> = Vec::with_capacity(n * kc);
for rec in records.iter() {
for clause in order_by.iter() {
let v = if let Some(ref expr) = clause.expr {
host.evaluate_expression(expr, rec)
} else {
resolve_runtime_field(rec, &clause.field, table_name, table_alias)
};
keys_flat.push(SortKey::new(v));
}
}
let row_cmp = |a: usize, b: usize| -> Ordering {
let la = a * kc;
let lb = b * kc;
for (j, clause) in order_by.iter().enumerate() {
let lk = &keys_flat[la + j];
let rk = &keys_flat[lb + j];
let ord = match (&lk.abbrev, &rk.abbrev, &lk.value, &rk.value) {
(Some(la_a), Some(ra), Some(Value::Text(ls)), Some(Value::Text(rs))) => {
match la_a.cmp(ra) {
Ordering::Equal => ls.as_ref().cmp(rs.as_ref()),
other => other,
}
}
_ => compare_runtime_optional_values(
&|value| host.secret_query_input(value),
lk.value.as_ref(),
rk.value.as_ref(),
clause.nulls_first,
),
};
if ord != Ordering::Equal {
return if clause.ascending { ord } else { ord.reverse() };
}
}
Ordering::Equal
};
let mut idxs: Vec<usize> = (0..n).collect();
idxs.select_nth_unstable_by(k - 1, |&a, &b| row_cmp(a, b).then_with(|| a.cmp(&b)));
idxs.truncate(k);
idxs.sort_by(|&a, &b| row_cmp(a, b).then_with(|| a.cmp(&b)));
let orig: Vec<_> = std::mem::take(records);
let mut out = Vec::with_capacity(k);
for i in idxs {
out.push(orig[i].clone());
}
*records = out;
}
pub fn compare_runtime_optional_values(
secret_input: &impl Fn(Value) -> Option<Value>,
left: Option<&Value>,
right: Option<&Value>,
nulls_first: bool,
) -> Ordering {
match (left, right) {
(None, None) => Ordering::Equal,
(None, Some(_)) => {
if nulls_first {
Ordering::Less
} else {
Ordering::Greater
}
}
(Some(_), None) => {
if nulls_first {
Ordering::Greater
} else {
Ordering::Less
}
}
(Some(Value::Null), Some(Value::Null)) => Ordering::Equal,
(Some(Value::Null), Some(_)) => {
if nulls_first {
Ordering::Less
} else {
Ordering::Greater
}
}
(Some(_), Some(Value::Null)) => {
if nulls_first {
Ordering::Greater
} else {
Ordering::Less
}
}
(Some(left), Some(right)) => {
runtime_partial_cmp(left, right, secret_input).unwrap_or(Ordering::Equal)
}
}
}