use std::collections::HashMap;
use nodedb_types::result::QueryResult;
use nodedb_types::value::Value;
use crate::error::LiteError;
use crate::query::engine::LiteQueryEngine;
use crate::storage::engine::StorageEngine;
use super::common::{maps_to_result, merge_rows, scan_collection};
use crate::query::query_ops::aggregate::value_cmp;
pub async fn execute_sort_merge_join<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
left_collection: &str,
right_collection: &str,
on: &[(String, String)],
join_type: &str,
limit: usize,
pre_sorted: bool,
) -> Result<QueryResult, LiteError> {
let mut left_rows = scan_collection(engine, left_collection).await?;
let mut right_rows = scan_collection(engine, right_collection).await?;
let left_keys: Vec<String> = on.iter().map(|(l, _)| l.clone()).collect();
let right_keys: Vec<String> = on.iter().map(|(_, r)| r.clone()).collect();
if !pre_sorted {
left_rows.sort_by(|a, b| compare_row_keys(a, b, &left_keys));
right_rows.sort_by(|a, b| compare_row_keys(a, b, &right_keys));
}
let effective_limit = if limit == 0 { usize::MAX } else { limit };
let output = merge_sorted(
left_rows,
right_rows,
&left_keys,
&right_keys,
join_type,
effective_limit,
);
Ok(maps_to_result(output))
}
fn row_key(row: &HashMap<String, Value>, keys: &[String]) -> Vec<Value> {
keys.iter()
.map(|k| row.get(k).cloned().unwrap_or(Value::Null))
.collect()
}
fn compare_row_keys(
a: &HashMap<String, Value>,
b: &HashMap<String, Value>,
keys: &[String],
) -> std::cmp::Ordering {
for k in keys {
let av = a.get(k).unwrap_or(&Value::Null);
let bv = b.get(k).unwrap_or(&Value::Null);
let ord = value_cmp(av, bv);
if ord != std::cmp::Ordering::Equal {
return ord;
}
}
std::cmp::Ordering::Equal
}
fn key_cmp(a: &[Value], b: &[Value]) -> std::cmp::Ordering {
for (av, bv) in a.iter().zip(b.iter()) {
let ord = value_cmp(av, bv);
if ord != std::cmp::Ordering::Equal {
return ord;
}
}
std::cmp::Ordering::Equal
}
fn merge_sorted(
left: Vec<HashMap<String, Value>>,
right: Vec<HashMap<String, Value>>,
left_keys: &[String],
right_keys: &[String],
join_type: &str,
limit: usize,
) -> Vec<HashMap<String, Value>> {
let mut output = Vec::new();
let mut li = 0usize;
let mut ri = 0usize;
let mut right_matched = vec![false; right.len()];
while li < left.len() && output.len() < limit {
let lk = row_key(&left[li], left_keys);
while ri < right.len() {
let rk = row_key(&right[ri], right_keys);
if key_cmp(&rk, &lk) != std::cmp::Ordering::Less {
break;
}
ri += 1;
}
let mut match_ri = ri;
let mut matched = false;
while match_ri < right.len() {
let rk = row_key(&right[match_ri], right_keys);
if key_cmp(&rk, &lk) != std::cmp::Ordering::Equal {
break;
}
matched = true;
right_matched[match_ri] = true;
match join_type {
"semi" => {
if output.is_empty()
|| !output
.last()
.map(|r: &HashMap<String, Value>| row_key(r, left_keys) == lk)
.unwrap_or(false)
{
output.push(left[li].clone());
}
}
"anti" => {}
_ => {
let merged = merge_rows(&left[li], &right[match_ri], None);
output.push(merged);
if output.len() >= limit {
return output;
}
}
}
match_ri += 1;
}
if !matched {
match join_type {
"left" | "full" => {
output.push(left[li].clone());
}
"anti" => {
output.push(left[li].clone());
}
_ => {}
}
}
li += 1;
}
if join_type == "right" || join_type == "full" {
for (idx, right_row) in right.iter().enumerate() {
if !right_matched[idx] {
output.push(right_row.clone());
if output.len() >= limit {
break;
}
}
}
}
output
}
#[cfg(test)]
mod tests {
use super::*;
use nodedb_types::value::Value;
use std::collections::HashMap;
fn row(fields: &[(&str, Value)]) -> HashMap<String, Value> {
fields
.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect()
}
#[test]
fn sort_merge_pre_sorted_both_sides() {
let left = vec![
row(&[("id", Value::Integer(1)), ("lv", Value::Integer(10))]),
row(&[("id", Value::Integer(2)), ("lv", Value::Integer(20))]),
row(&[("id", Value::Integer(3)), ("lv", Value::Integer(30))]),
];
let right = vec![
row(&[("rid", Value::Integer(1)), ("rv", Value::Integer(100))]),
row(&[("rid", Value::Integer(3)), ("rv", Value::Integer(300))]),
];
let on = vec![("id".to_string(), "rid".to_string())];
let result = merge_sorted(
left,
right,
&["id".into()],
&["rid".into()],
"inner",
usize::MAX,
);
assert_eq!(result.len(), 2);
let vals: Vec<i64> = result
.iter()
.map(|r| {
if let Value::Integer(n) = r["rv"] {
n
} else {
0
}
})
.collect();
assert!(vals.contains(&100));
assert!(vals.contains(&300));
let _ = on;
}
}