use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::fmt;
use std::io::{self, Write};
use std::mem;
use tempfile::TempDir;
use crate::run::RunReader;
use crate::sorter::Chunk;
use crate::{Error, Result};
pub(crate) enum Source {
Run(RunReader),
Memory { chunk: Chunk, next: usize },
}
impl Source {
fn read_into(&mut self, key: &mut Vec<u8>, value: &mut Vec<u8>) -> io::Result<bool> {
match self {
Source::Run(reader) => reader.read_into(key, value),
Source::Memory { chunk, next } => {
if *next == chunk.len() {
return Ok(false);
}
let (record_key, record_value) = chunk.record(*next);
key.clear();
key.extend_from_slice(record_key);
value.clear();
value.extend_from_slice(record_value);
*next += 1;
Ok(true)
}
}
}
}
struct Head {
key: Vec<u8>,
value: Vec<u8>,
source: usize,
}
impl Ord for Head {
fn cmp(&self, other: &Self) -> Ordering {
other
.key
.cmp(&self.key)
.then(other.source.cmp(&self.source))
}
}
impl PartialOrd for Head {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Head {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for Head {}
pub struct Merged {
sources: Vec<Source>,
heap: BinaryHeap<Head>,
current: Option<Head>,
remaining: u64,
dir: Option<TempDir>,
failed: bool,
}
impl Merged {
pub(crate) fn new(sources: Vec<Source>, dir: Option<TempDir>, records: u64) -> Result<Self> {
let mut merged = Merged {
heap: BinaryHeap::with_capacity(sources.len()),
sources,
current: None,
remaining: records,
dir,
failed: false,
};
for source in 0..merged.sources.len() {
let mut head = Head {
key: Vec::new(),
value: Vec::new(),
source,
};
if merged.sources[source].read_into(&mut head.key, &mut head.value)? {
merged.heap.push(head);
}
}
if merged.heap.is_empty() {
merged.release();
}
Ok(merged)
}
pub fn next_record(&mut self) -> Result<Option<(&[u8], &[u8])>> {
if self.failed {
return Err(Error::Failed);
}
if let Err(error) = self.advance() {
self.failed = true;
self.heap.clear();
self.current = None;
self.release();
return Err(error.into());
}
Ok(self
.current
.as_ref()
.map(|head| (head.key.as_slice(), head.value.as_slice())))
}
pub fn write_values(&mut self, mut out: impl Write) -> Result<u64> {
let mut written = 0;
while let Some((_, value)) = self.next_record()? {
out.write_all(value)?;
written += 1;
}
out.flush()?;
Ok(written)
}
pub fn len(&self) -> u64 {
self.remaining
}
pub fn is_empty(&self) -> bool {
self.remaining == 0
}
fn advance(&mut self) -> io::Result<()> {
let Some(mut head) = self.current.take() else {
self.pop();
return Ok(());
};
if !self.sources[head.source].read_into(&mut head.key, &mut head.value)? {
self.pop();
return Ok(());
}
if let Some(mut top) = self.heap.peek_mut()
&& *top > head
{
mem::swap(&mut *top, &mut head);
}
self.current = Some(head);
self.remaining -= 1;
Ok(())
}
fn pop(&mut self) {
self.current = self.heap.pop();
match self.current {
Some(_) => self.remaining -= 1,
None => self.release(),
}
}
fn release(&mut self) {
self.sources.clear();
self.dir = None;
}
}
impl Iterator for Merged {
type Item = Result<(Vec<u8>, Vec<u8>)>;
fn next(&mut self) -> Option<Self::Item> {
if self.failed {
return None;
}
self.next_record()
.map(|record| record.map(|(key, value)| (key.to_vec(), value.to_vec())))
.transpose()
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = usize::try_from(self.remaining).unwrap_or(usize::MAX);
(remaining, Some(remaining))
}
}
impl fmt::Debug for Merged {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Merged")
.field("remaining", &self.remaining)
.field(
"sources",
&(self.heap.len() + usize::from(self.current.is_some())),
)
.finish_non_exhaustive()
}
}