use std::sync::Mutex;
use rudb_common::{Cancel, Error, LogicalType, Memory, Reservation, Result, Value};
use rudb_kernels::{Connective, combine, is_true};
use rudb_pipeline::{Progress, Sink, Stream};
use rudb_plan::{ExprRef, JoinKind, Plan, Slice};
use rudb_vector::{Chunk, Vector};
use crate::buffer::Buffered;
use crate::expr::evaluate_all;
use crate::gather::{self, Gathering, Rows};
use crate::rows;
use crate::schema::Schema;
#[derive(Debug)]
pub(crate) struct Join<'a> {
plan: &'a Plan,
kind: JoinKind,
conditions: Vec<ExprRef>,
left_schema: Schema,
right_schema: Schema,
combined: Schema,
schema: Schema,
memory: Memory,
cancel: Cancel,
right: Rows,
left: Mutex<Vec<Vec<Value>>>,
charged: Mutex<Vec<Reservation>>,
held: Mutex<Reservation>,
out: Buffered,
}
pub(crate) struct Gathered<'s> {
pub(crate) schema: &'s Schema,
pub(crate) rows: Rows,
}
impl<'a> Join<'a> {
pub(crate) fn new(
plan: &'a Plan,
left: &Schema,
right: Gathered<'_>,
kind: JoinKind,
conditions: Slice,
cancel: &Cancel,
memory: &Memory,
) -> (Self, Buffered) {
let combined = Schema::concat(left, right.schema);
let schema = match kind {
JoinKind::Semi | JoinKind::Anti => left.clone(),
_ => combined.clone(),
};
let out = Buffered::new();
let join = Self {
plan,
kind,
conditions: plan.expr_list(conditions).to_vec(),
left_schema: left.clone(),
right_schema: right.schema.clone(),
combined,
schema,
memory: memory.clone(),
cancel: cancel.clone(),
right: right.rows,
left: Mutex::new(Vec::new()),
charged: Mutex::new(Vec::new()),
held: Mutex::new(memory.reservation()),
out: out.clone(),
};
(join, out)
}
pub(crate) fn schema(&self) -> &Schema {
&self.schema
}
fn joined(
&self,
left_rows: &[Vec<Value>],
right_rows: &[Vec<Value>],
) -> Result<Vec<Vec<Value>>> {
let mut scratch = self.memory.reservation();
let left_types = self.left_schema.types();
let right_types = self.right_schema.types();
if self.kind == JoinKind::Positional {
return Ok(positional(left_rows, right_rows, left_types.len(), right_types.len()));
}
let right_chunks = rows::chunks(&right_types, right_rows, &mut scratch)?;
let mut matched = vec![false; right_rows.len()];
scratch.grow(u64::try_from(right_rows.len()).unwrap_or(u64::MAX))?;
let mut out: Vec<Vec<Value>> = Vec::new();
for left_row in left_rows {
self.cancel.check()?;
let before = out.len();
let hits = self.matching(left_row, &left_types, &right_chunks)?;
for &hit in &hits {
matched[hit] = true;
}
match self.kind {
JoinKind::Semi => {
if !hits.is_empty() {
out.push(left_row.clone());
}
}
JoinKind::Anti => {
if hits.is_empty() {
out.push(left_row.clone());
}
}
JoinKind::Single => {
if hits.len() > 1 {
return Err(Error::invalid_input(
"More than one row returned by a subquery used as an expression"
.to_string(),
));
}
match hits.first() {
Some(&hit) => out.push(pair(left_row, &right_rows[hit])),
None => out.push(pad_right(left_row, right_types.len())),
}
}
JoinKind::Left | JoinKind::Full if hits.is_empty() => {
out.push(pad_right(left_row, right_types.len()));
}
_ => {
for &hit in &hits {
out.push(pair(left_row, &right_rows[hit]));
}
}
}
scratch.grow(out[before..].iter().map(|row| rows::footprint(row)).sum())?;
}
if matches!(self.kind, JoinKind::Right | JoinKind::Full) {
for (at, seen) in matched.iter().enumerate() {
if !seen {
out.push(pad_left(left_types.len(), &right_rows[at]));
}
}
}
Ok(out)
}
fn matching(
&self,
left_row: &[Value],
left_types: &[LogicalType],
right_chunks: &[Chunk],
) -> Result<Vec<usize>> {
let mut hits = Vec::new();
let mut base = 0;
for chunk in right_chunks {
let rows = chunk.len();
if self.conditions.is_empty() {
hits.extend(base..base + rows);
} else {
let combined = widen(left_row, left_types, chunk)?;
let flags = evaluate_all(self.plan, &self.conditions, &self.combined, &combined)?;
let merged = combine(Connective::And, &flags)?;
for row in 0..rows {
if is_true(&merged.value_at(row)) {
hits.push(base + row);
}
}
}
base += rows;
}
Ok(hits)
}
}
impl Sink for Join<'_> {
type Local = Gathering;
fn local(&self) -> Gathering {
gather::gathering(&self.memory)
}
fn sink(&self, chunk: &Chunk, local: &mut Gathering) -> Result<Progress> {
gather::take(chunk, local)?;
Ok(Progress::More)
}
fn combine(&self, local: Gathering) -> Result<()> {
let (rows, charged) = gather::into_parts(local);
self.left.lock().map_err(poisoned)?.extend(rows);
self.charged.lock().map_err(poisoned)?.push(charged);
Ok(())
}
fn finalize(&self) -> Result<()> {
let left_rows = std::mem::take(&mut *self.left.lock().map_err(poisoned)?);
let right_rows = self.right.take()?;
let out = self.joined(&left_rows, &right_rows)?;
drop(left_rows);
drop(right_rows);
let mut held = self.held.lock().map_err(poisoned)?;
let chunks = rows::chunks(&self.schema.types(), &out, &mut held)?;
self.out.fill(chunks)?;
self.charged.lock().map_err(poisoned)?.clear();
Ok(())
}
}
fn poisoned<T>(_: T) -> Error {
Error::internal("a thread panicked while holding the rows a join gathered")
}
#[derive(Debug)]
pub(crate) struct CrossProduct {
left_types: Vec<LogicalType>,
types: Vec<LogicalType>,
schema: Schema,
right: Buffered,
}
#[derive(Debug)]
pub(crate) struct Crossing {
left: Option<Chunk>,
row: usize,
at: usize,
}
impl CrossProduct {
pub(crate) fn new(left: &Schema, right_schema: &Schema, right: Buffered) -> Self {
let schema = Schema::concat(left, right_schema);
Self { left_types: left.types(), types: schema.types(), schema, right }
}
pub(crate) fn schema(&self) -> &Schema {
&self.schema
}
}
impl Stream for CrossProduct {
type Local = Crossing;
fn local(&self) -> Crossing {
Crossing { left: None, row: 0, at: 0 }
}
fn push(&self, chunk: &mut Chunk, local: &mut Crossing) -> Result<Progress> {
let stored = self.right.len()?;
let left = match local.left.take() {
Some(left) => left,
None => {
local.row = 0;
local.at = 0;
chunk.clone()
}
};
if stored == 0 || left.is_empty() {
*chunk = Chunk::empty(&self.types);
return Ok(Progress::More);
}
let right = self
.right
.at(local.at)?
.ok_or_else(|| Error::internal("a cross product asked for a chunk nobody kept"))?;
let row: Vec<Value> = left.row(local.row).collect();
*chunk = widen(&row, &self.left_types, &right)?;
local.at += 1;
if local.at >= stored {
local.at = 0;
local.row += 1;
}
if local.row >= left.len() {
return Ok(Progress::More);
}
local.left = Some(left);
Ok(Progress::Again)
}
}
fn pair(left: &[Value], right: &[Value]) -> Vec<Value> {
let mut row = left.to_vec();
row.extend(right.iter().cloned());
row
}
fn pad_right(left: &[Value], width: usize) -> Vec<Value> {
let mut row = left.to_vec();
row.extend(std::iter::repeat_n(Value::Null, width));
row
}
fn pad_left(width: usize, right: &[Value]) -> Vec<Value> {
let mut row = vec![Value::Null; width];
row.extend(right.iter().cloned());
row
}
fn positional(
left: &[Vec<Value>],
right: &[Vec<Value>],
left_width: usize,
right_width: usize,
) -> Vec<Vec<Value>> {
let rows = left.len().max(right.len());
(0..rows)
.map(|at| match (left.get(at), right.get(at)) {
(Some(left), Some(right)) => pair(left, right),
(Some(left), None) => pad_right(left, right_width),
(None, Some(right)) => pad_left(left_width, right),
(None, None) => Vec::new(),
})
.collect()
}
fn widen(left_row: &[Value], left_types: &[LogicalType], right: &Chunk) -> Result<Chunk> {
let rows = right.len();
let mut columns: Vec<Vector> = left_row
.iter()
.zip(left_types)
.map(|(value, ty)| Vector::constant(ty.clone(), value.clone(), rows))
.collect();
columns.extend(right.columns().iter().cloned());
Chunk::with_rows(columns, rows)
}
#[cfg(test)]
mod tests {
use rudb_common::{Cancel, Field, LogicalType, Memory, Value};
use rudb_plan::{JoinKind, Plan, Slice};
use rudb_vector::{Data, Vector};
use super::{Buffered, Chunk, CrossProduct, Gathered, Join, Progress, Schema, Sink, Stream};
use crate::gather::{Gather, Keep, Rows};
fn chunk(values: &[i32]) -> Chunk {
let column = Vector::flat(LogicalType::Integer, Data::Int32(values.to_vec().into()))
.expect("integers are an i32 layout");
Chunk::new(vec![column]).expect("one column is one length")
}
fn schema(name: &str, table: u32) -> Schema {
Schema::numbered(vec![Field::new(name, LogicalType::Integer)], table)
}
fn gathered(memory: &Memory, values: &[i32]) -> (Gather, Rows) {
let (gather, rows) = Gather::new(memory);
let mut local = gather.local();
if !values.is_empty() {
gather.sink(&chunk(values), &mut local).expect("the right rows");
}
gather.combine(local).expect("the one instance");
gather.finalize().expect("nothing to do");
(gather, rows)
}
fn run(join: &Join<'_>, left: &[i32]) {
let mut local = join.local();
if !left.is_empty() {
join.sink(&chunk(left), &mut local).expect("the left rows");
}
join.combine(local).expect("the one instance");
join.finalize().expect("the answer");
}
fn rows(out: &Buffered, width: usize) -> Vec<Vec<Value>> {
let Some(chunk) = out.at(0).expect("readable") else { return Vec::new() };
(0..chunk.len())
.map(|row| (0..width).map(|column| chunk.value_at(row, column)).collect())
.collect()
}
#[test]
fn every_left_row_meets_every_right_row_when_there_is_no_condition() {
let plan = Plan::new();
let memory = Memory::unlimited();
let (_gather, right) = gathered(&memory, &[10, 20]);
let (join, out) = Join::new(
&plan,
&schema("a", 0),
Gathered { schema: &schema("b", 1), rows: right },
JoinKind::Inner,
Slice::EMPTY,
&Cancel::new(),
&memory,
);
run(&join, &[1, 2]);
assert_eq!(
rows(&out, 2),
[
vec![Value::Integer(1), Value::Integer(10)],
vec![Value::Integer(1), Value::Integer(20)],
vec![Value::Integer(2), Value::Integer(10)],
vec![Value::Integer(2), Value::Integer(20)],
]
);
}
#[test]
fn an_anti_join_against_nothing_keeps_every_left_row() {
let plan = Plan::new();
let memory = Memory::unlimited();
let (_gather, right) = gathered(&memory, &[]);
let (join, out) = Join::new(
&plan,
&schema("a", 0),
Gathered { schema: &schema("b", 1), rows: right },
JoinKind::Anti,
Slice::EMPTY,
&Cancel::new(),
&memory,
);
run(&join, &[1, 2]);
assert_eq!(rows(&out, 1), [vec![Value::Integer(1)], vec![Value::Integer(2)]]);
}
#[test]
fn a_positional_join_pads_the_shorter_side() {
let plan = Plan::new();
let memory = Memory::unlimited();
let (_gather, right) = gathered(&memory, &[10]);
let (join, out) = Join::new(
&plan,
&schema("a", 0),
Gathered { schema: &schema("b", 1), rows: right },
JoinKind::Positional,
Slice::EMPTY,
&Cancel::new(),
&memory,
);
run(&join, &[1, 2]);
assert_eq!(
rows(&out, 2),
[vec![Value::Integer(1), Value::Integer(10)], vec![Value::Integer(2), Value::Null],]
);
}
fn crossed(cross: &CrossProduct, left: &[i32]) -> Vec<Vec<Value>> {
let mut local = cross.local();
let mut chunk = chunk(left);
let mut out = Vec::new();
loop {
let progress = cross.push(&mut chunk, &mut local).expect("a chunk");
out.extend(
(0..chunk.len()).map(|row| vec![chunk.value_at(row, 0), chunk.value_at(row, 1)]),
);
if progress != Progress::Again {
return out;
}
chunk = Chunk::empty(&[]);
}
}
fn kept(memory: &Memory, first: &[i32], second: &[i32]) -> (Keep, Buffered) {
let (keep, out) = Keep::new(memory);
let mut local = keep.local();
keep.sink(&chunk(first), &mut local).expect("the first right chunk");
keep.sink(&chunk(second), &mut local).expect("the second");
keep.combine(local).expect("the one instance");
keep.finalize().expect("the chunks");
(keep, out)
}
#[test]
fn a_cross_product_pairs_one_left_row_with_one_right_chunk_at_a_time() {
let memory = Memory::unlimited();
let (_keep, right) = kept(&memory, &[10, 20], &[30]);
let cross = CrossProduct::new(&schema("a", 0), &schema("b", 1), right);
assert_eq!(
crossed(&cross, &[1, 2]),
[
vec![Value::Integer(1), Value::Integer(10)],
vec![Value::Integer(1), Value::Integer(20)],
vec![Value::Integer(1), Value::Integer(30)],
vec![Value::Integer(2), Value::Integer(10)],
vec![Value::Integer(2), Value::Integer(20)],
vec![Value::Integer(2), Value::Integer(30)],
]
);
}
#[test]
fn a_cross_product_with_nothing_on_the_right_produces_nothing() {
let memory = Memory::unlimited();
let (keep, right) = Keep::new(&memory);
keep.combine(keep.local()).expect("an instance that saw nothing");
keep.finalize().expect("no chunks");
let cross = CrossProduct::new(&schema("a", 0), &schema("b", 1), right);
let mut local = cross.local();
let mut chunk = chunk(&[1, 2]);
assert_eq!(cross.push(&mut chunk, &mut local).expect("no rows"), Progress::More);
assert!(chunk.is_empty());
}
}