use std::cmp::Ordering;
use std::sync::Mutex;
use rudb_common::{Error, Field, LogicalType, Memory, Reservation, Result, Session, Value};
use rudb_functions::resolve;
use rudb_kernels::Accumulator;
use rudb_pipeline::{Progress, Sink};
use rudb_plan::{
ColumnBinding, Expr, ExprRef, Plan, Slice, SortKey, WindowBound, WindowExclude, WindowFrame,
WindowUnit,
};
use rudb_vector::Chunk;
use crate::buffer::Buffered;
use crate::prepared::{Prepared, Scratch};
use crate::rows;
use crate::schema::Schema;
use crate::sort::{Arrival, Place, compare};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Reads {
Frame,
Position(Ranking),
Picked(Picks),
Shifted(Looks),
Filled,
}
impl Reads {
fn of(name: &str) -> Self {
if let Some(ranking) = Ranking::of(name) {
return Self::Position(ranking);
}
match name {
"first_value" => Self::Picked(Picks::First),
"last_value" => Self::Picked(Picks::Last),
"nth_value" => Self::Picked(Picks::Nth),
"lag" => Self::Shifted(Looks::Back),
"lead" => Self::Shifted(Looks::Forward),
"fill" => Self::Filled,
_ => Self::Frame,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Picks {
First,
Last,
Nth,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Looks {
Back,
Forward,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Ranking {
RowNumber,
Rank,
DenseRank,
PercentRank,
CumeDist,
Ntile,
}
impl Ranking {
fn of(name: &str) -> Option<Self> {
Some(match name {
"row_number" => Self::RowNumber,
"rank" => Self::Rank,
"dense_rank" | "rank_dense" => Self::DenseRank,
"percent_rank" => Self::PercentRank,
"cume_dist" => Self::CumeDist,
"ntile" => Self::Ntile,
_ => return None,
})
}
}
#[derive(Debug)]
struct Call {
name: String,
reads: Reads,
returns: LogicalType,
args_at: usize,
args: usize,
filter_at: Option<usize>,
distinct: bool,
ignore_nulls: bool,
}
type Windowed = (Vec<Value>, Vec<Value>, Arrival);
#[derive(Debug, Clone, Copy)]
struct Offsets {
start: Option<usize>,
end: Option<usize>,
}
#[derive(Debug, Clone)]
struct Moved {
name: &'static str,
key: LogicalType,
offset: LogicalType,
returns: LogicalType,
}
#[derive(Debug, Clone, Default)]
struct Ranged {
start: Option<Moved>,
end: Option<Moved>,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Written {
pub(crate) index: u32,
pub(crate) partition: Slice,
pub(crate) order: Slice,
pub(crate) frame: WindowFrame,
pub(crate) expressions: Slice,
}
#[derive(Debug)]
pub(crate) struct Window {
values: Prepared,
partitions: usize,
order: Vec<SortKey>,
sorting: Vec<SortKey>,
calls: Vec<Call>,
frame: WindowFrame,
offsets: Offsets,
ranged: Ranged,
types: Vec<LogicalType>,
schema: Schema,
memory: Memory,
rows: Mutex<Vec<Windowed>>,
charged: Mutex<Vec<Reservation>>,
held: Mutex<Reservation>,
out: Buffered,
}
#[derive(Debug)]
pub(crate) struct Gathered {
rows: Vec<Windowed>,
scratch: Scratch,
charged: Reservation,
place: Place,
}
impl Window {
#[must_use]
pub(crate) fn in_session(mut self, session: &Session) -> Self {
self.values = self.values.in_session(session);
self
}
pub(crate) fn schema(&self) -> &Schema {
&self.schema
}
pub(crate) fn new(
plan: &Plan,
input: &Schema,
written: &Written,
memory: &Memory,
) -> Result<(Self, Buffered)> {
let Written { index, partition, order, frame, expressions } = *written;
let order = plan.sort_key_list(order).to_vec();
let mut gathered: Vec<ExprRef> = plan.expr_list(partition).to_vec();
let partitions = gathered.len();
let mut sorting: Vec<SortKey> = gathered
.iter()
.map(|&expr| SortKey { expr, descending: false, nulls_first: false })
.collect();
sorting.extend(order.iter().copied());
gathered.extend(order.iter().map(|key| key.expr));
let mut calls = Vec::new();
for &expr in plan.expr_list(expressions) {
let Expr::Window { name, args, distinct, filter, ignore_nulls } = plan.expr(expr)
else {
return Err(Error::internal("a window node listing an expression that is not one"));
};
let arguments = plan.expr_list(*args).to_vec();
let args_at = gathered.len();
gathered.extend(arguments.iter().copied());
let filter_at = filter.map(|predicate| {
gathered.push(predicate);
gathered.len() - 1
});
let written = plan.string(*name);
calls.push(Call {
reads: Reads::of(written),
name: written.to_string(),
returns: plan.expr_type(expr).clone(),
args_at,
args: arguments.len(),
filter_at,
distinct: *distinct,
ignore_nulls: *ignore_nulls,
});
}
let offsets = Offsets {
start: distance(frame.start).map(|expr| {
gathered.push(expr);
gathered.len() - 1
}),
end: distance(frame.end).map(|expr| {
gathered.push(expr);
gathered.len() - 1
}),
};
let mut fields = input.fields().to_vec();
let mut bindings = input.bindings().to_vec();
for (at, call) in calls.iter().enumerate() {
fields.push(Field::new(call.name.clone(), call.returns.clone()));
let at = u32::try_from(at).expect("a window node this wide cannot be built");
bindings.push(ColumnBinding::new(index, at));
}
let schema = Schema::new(fields, bindings)?;
let mut types = input.types();
types.extend(calls.iter().map(|call| call.returns.clone()));
let ranged = if frame.unit == WindowUnit::Range {
let key = order.first().map(|key| plan.expr_type(key.expr).clone());
Ranged {
start: moved(plan, key.as_ref(), frame.start, &order)?,
end: moved(plan, key.as_ref(), frame.end, &order)?,
}
} else {
Ranged::default()
};
let out = Buffered::new();
let window = Self {
values: Prepared::new(plan, &gathered, input)?,
partitions,
order,
sorting,
calls,
frame,
offsets,
ranged,
types,
schema,
memory: memory.clone(),
rows: Mutex::new(Vec::new()),
charged: Mutex::new(Vec::new()),
held: Mutex::new(memory.reservation()),
out: out.clone(),
};
Ok((window, out))
}
fn same_partition(&self, left: &Windowed, right: &Windowed) -> Result<bool> {
for at in 0..self.partitions {
if rudb_kernels::order_with_nulls(&left.0[at], &right.0[at], false)? != Ordering::Equal
{
return Ok(false);
}
}
Ok(true)
}
fn peers(&self, left: &Windowed, right: &Windowed) -> Result<bool> {
for at in 0..self.order.len() {
let at = self.partitions + at;
if rudb_kernels::order_with_nulls(&left.0[at], &right.0[at], false)? != Ordering::Equal
{
return Ok(false);
}
}
Ok(true)
}
}
fn negative(offset: &Value) -> bool {
offset.as_i64().is_some_and(|written| written < 0)
}
fn distance(bound: WindowBound) -> Option<ExprRef> {
match bound {
WindowBound::Preceding(expr) | WindowBound::Following(expr) => Some(expr),
_ => None,
}
}
fn moved(
plan: &Plan,
key: Option<&LogicalType>,
bound: WindowBound,
order: &[SortKey],
) -> Result<Option<Moved>> {
let (offset, back) = match bound {
WindowBound::Preceding(offset) => (offset, true),
WindowBound::Following(offset) => (offset, false),
_ => return Ok(None),
};
let (Some(key), Some(sort)) = (key, order.first()) else {
return Err(Error::internal("a RANGE distance with no single order key"));
};
let name = if back == sort.descending { "+" } else { "-" };
let offset = plan.expr_type(offset).clone();
let resolved = resolve(name, &[key.clone(), offset])?;
let [key, offset] = resolved.arguments.as_slice() else {
return Err(Error::internal("an arithmetic overload that does not take two arguments"));
};
Ok(Some(Moved {
name: resolved.name,
key: key.clone(),
offset: offset.clone(),
returns: resolved.returns,
}))
}
impl Sink for Window {
type Local = Gathered;
fn local(&self) -> Gathered {
Gathered {
rows: Vec::new(),
scratch: self.values.scratch(),
charged: self.memory.reservation(),
place: Place::default(),
}
}
fn at(&self, morsel: &rudb_pipeline::Morsel, local: &mut Gathered) -> Result<()> {
local.place.start(morsel.index());
Ok(())
}
fn sink(&self, chunk: &Chunk, local: &mut Gathered) -> Result<Progress> {
let mut gathered = Vec::new();
self.values.evaluate(chunk, &mut local.scratch, &mut gathered)?;
let mut taken = 0;
for row in 0..chunk.len() {
let held: Vec<Value> =
gathered.iter().map(|column| column.try_value_at(row)).collect::<Result<_>>()?;
let values: Vec<Value> = (0..chunk.width())
.map(|column| chunk.try_value_at(row, column))
.collect::<Result<_>>()?;
taken += rows::footprint(&held) + rows::footprint(&values);
local.rows.push((held, values, local.place.of(row)));
}
local.place.past(chunk.len());
local.charged.grow(taken)?;
Ok(Progress::More)
}
fn combine(&self, local: Gathered) -> Result<()> {
let mut rows = self.rows.lock().map_err(poisoned)?;
rows.extend(local.rows);
self.charged.lock().map_err(poisoned)?.push(local.charged);
Ok(())
}
fn finalize(&self) -> Result<()> {
let mut gathered = std::mem::take(&mut *self.rows.lock().map_err(poisoned)?);
let keys = self.sorting.len();
let mut failure: Option<Error> = None;
gathered.sort_by(|left, right| {
let ordering = compare(&self.sorting, &left.0[..keys], &right.0[..keys], &mut failure);
match ordering {
Ordering::Equal => left.2.cmp(&right.2),
ordering => ordering,
}
});
if let Some(error) = failure {
return Err(error);
}
let mut answered: Vec<Vec<Value>> = Vec::with_capacity(gathered.len());
let mut start = 0;
while start < gathered.len() {
let mut end = start + 1;
while end < gathered.len() && self.same_partition(&gathered[start], &gathered[end])? {
end += 1;
}
self.over(&gathered[start..end], &mut answered)?;
start = end;
}
let mut held = self.held.lock().map_err(poisoned)?;
let chunks = rows::chunks(&self.types, &answered, &mut held)?;
self.out.fill(chunks)?;
self.charged.lock().map_err(poisoned)?.clear();
Ok(())
}
}
impl Window {
fn over(&self, rows: &[Windowed], answered: &mut Vec<Vec<Value>>) -> Result<()> {
let peers = self.peer_groups(rows)?;
let filled: Vec<Option<Vec<Value>>> = self
.calls
.iter()
.map(|call| (call.reads == Reads::Filled).then(|| self.filling(call, rows)))
.collect();
for at in 0..rows.len() {
let frame = self.frame_of(rows, &peers, at)?;
let mut row = rows[at].1.clone();
for (which, call) in self.calls.iter().enumerate() {
match &filled[which] {
Some(column) => row.push(column[at].clone()),
None => row.push(self.answer(call, rows, &peers, at, frame.clone())?),
}
}
answered.push(row);
}
Ok(())
}
fn peer_groups(&self, rows: &[Windowed]) -> Result<Vec<usize>> {
let mut groups = Vec::with_capacity(rows.len());
let mut group = 0;
for at in 0..rows.len() {
if at > 0 && !self.peers(&rows[at - 1], &rows[at])? {
group += 1;
}
groups.push(group);
}
Ok(groups)
}
fn frame_of(
&self,
rows: &[Windowed],
peers: &[usize],
at: usize,
) -> Result<std::ops::Range<usize>> {
let last = rows.len();
let from = match self.frame.start {
WindowBound::UnboundedPreceding => 0,
WindowBound::CurrentRow => match self.frame.unit {
WindowUnit::Rows => at,
_ => first_of(peers, peers[at]),
},
WindowBound::Preceding(_) | WindowBound::Following(_)
if self.frame.unit == WindowUnit::Range =>
{
self.reached(rows, peers, at, self.offsets.start, &self.ranged.start, false)?
}
WindowBound::Preceding(_) => {
self.away(rows, peers, at, self.offsets.start, true, false)?
}
WindowBound::Following(_) => {
self.away(rows, peers, at, self.offsets.start, false, false)?
}
WindowBound::UnboundedFollowing => {
return Err(Error::internal("a frame starting after every row"));
}
};
let to = match self.frame.end {
WindowBound::UnboundedFollowing => last,
WindowBound::CurrentRow => match self.frame.unit {
WindowUnit::Rows => at + 1,
_ => last_of(peers, peers[at]) + 1,
},
WindowBound::Preceding(_) | WindowBound::Following(_)
if self.frame.unit == WindowUnit::Range =>
{
self.reached(rows, peers, at, self.offsets.end, &self.ranged.end, true)?
}
WindowBound::Preceding(_) => {
self.away(rows, peers, at, self.offsets.end, true, true)?
}
WindowBound::Following(_) => {
self.away(rows, peers, at, self.offsets.end, false, true)?
}
WindowBound::UnboundedPreceding => {
return Err(Error::internal("a frame ending before every row"));
}
};
Ok(from..to.min(last).max(from))
}
fn reached(
&self,
rows: &[Windowed],
peers: &[usize],
at: usize,
column: Option<usize>,
moved: &Option<Moved>,
after: bool,
) -> Result<usize> {
let sort = *self
.sorting
.get(self.partitions)
.ok_or_else(|| Error::internal("a RANGE distance with no order key to measure from"))?;
let key = &rows[at].0[self.partitions];
if key.is_null() {
return Ok(if after {
last_of(peers, peers[at]) + 1
} else {
first_of(peers, peers[at])
});
}
let moved =
moved.as_ref().ok_or_else(|| Error::internal("a RANGE distance with no arithmetic"))?;
let column =
column.ok_or_else(|| Error::internal("a frame distance the window did not gather"))?;
let offset = &rows[at].0[column];
if offset.is_null() {
return Err(Error::binder("Window RANGE expressions cannot be NULL"));
}
if negative(offset) {
let written = if after { self.frame.end } else { self.frame.start };
let end = match written {
WindowBound::Preceding(_) => "PRECEDING",
_ => "FOLLOWING",
};
return Err(Error::out_of_range(format!("Invalid RANGE {end} value")));
}
let args = [
rudb_kernels::cast_value(key, &moved.key, false)?,
rudb_kernels::cast_value(offset, &moved.offset, false)?,
];
let wanted = rudb_kernels::call_values(moved.name, &args, &moved.returns, None)?;
let mut low = 0;
let mut high = rows.len();
while low < high {
let middle = low + (high - low) / 2;
let held = &rows[middle].0[self.partitions];
let ordering = crate::sort::rank(held, &wanted, sort)?;
let before =
if after { ordering != Ordering::Greater } else { ordering == Ordering::Less };
if before {
low = middle + 1;
} else {
high = middle;
}
}
Ok(low)
}
fn away(
&self,
rows: &[Windowed],
peers: &[usize],
at: usize,
column: Option<usize>,
back: bool,
after: bool,
) -> Result<usize> {
let offset = self.distance_at(rows, at, column, back)?;
let signed = if back { offset.saturating_neg() } else { offset };
let landed = |from: usize| -> Option<usize> {
let from = i64::try_from(from).unwrap_or(i64::MAX);
usize::try_from(from.saturating_add(signed)).ok()
};
Ok(match self.frame.unit {
WindowUnit::Rows => {
let Some(landed) = landed(at) else { return Ok(0) };
if after { landed.saturating_add(1) } else { landed }
}
_ => {
let Some(group) = landed(peers[at]) else { return Ok(0) };
if after {
peers.iter().rposition(|&held| held <= group).map_or(0, |end| end + 1)
} else {
peers.iter().position(|&held| held >= group).unwrap_or(rows.len())
}
}
})
}
fn distance_at(
&self,
rows: &[Windowed],
at: usize,
column: Option<usize>,
back: bool,
) -> Result<i64> {
let column =
column.ok_or_else(|| Error::internal("a frame distance the window did not gather"))?;
let value = &rows[at].0[column];
let named = || {
let unit = match self.frame.unit {
WindowUnit::Rows => "ROWS",
WindowUnit::Range => "RANGE",
WindowUnit::Groups => "GROUPS",
};
let end = if back { "PRECEDING" } else { "FOLLOWING" };
format!("Window {unit} {end} expression")
};
if value.is_null() {
return Err(Error::invalid_input(format!("{} cannot be NULL", named())));
}
value.as_i64().ok_or_else(|| Error::invalid_input(format!("{} must be a number", named())))
}
fn answer(
&self,
call: &Call,
rows: &[Windowed],
peers: &[usize],
at: usize,
frame: std::ops::Range<usize>,
) -> Result<Value> {
match call.reads {
Reads::Position(ranking) => return ranked(ranking, call, rows, peers, at),
Reads::Picked(pick) => return self.picked(pick, call, rows, peers, at, frame),
Reads::Shifted(look) => return shifted(look, call, rows, at),
Reads::Filled => return Err(Error::internal("fill is answered a partition at a time")),
Reads::Frame => {}
}
let mut accumulator = Accumulator::new(&call.name, &call.returns)?;
let mut seen: Vec<Vec<Value>> = Vec::new();
for row in frame {
if self.excluded(peers, at, row) {
continue;
}
if let Some(filter) = call.filter_at {
if rows[row].0[filter].as_bool() != Some(true) {
continue;
}
}
let args: Vec<Value> = rows[row].0[call.args_at..call.args_at + call.args].to_vec();
if call.ignore_nulls && args.iter().any(Value::is_null) {
continue;
}
if call.distinct {
if seen.contains(&args) {
continue;
}
seen.push(args.clone());
}
accumulator.update(&args)?;
}
accumulator.finish()
}
fn picked(
&self,
pick: Picks,
call: &Call,
rows: &[Windowed],
peers: &[usize],
at: usize,
frame: std::ops::Range<usize>,
) -> Result<Value> {
let wanted = match pick {
Picks::First => Some(1),
Picks::Last => None,
Picks::Nth => {
let written = &rows[at].0[call.args_at + 1];
if written.is_null() {
return Ok(Value::Null);
}
let count = written.as_i64().ok_or_else(|| {
Error::invalid_input("Argument for nth_value must be a number")
})?;
if count <= 0 {
return Ok(Value::Null);
}
Some(usize::try_from(count).unwrap_or(usize::MAX))
}
};
let mut seen = 0_usize;
let mut last = Value::Null;
for row in frame {
if self.excluded(peers, at, row) {
continue;
}
let value = rows[row].0[call.args_at].clone();
if call.ignore_nulls && value.is_null() {
continue;
}
seen += 1;
if wanted == Some(seen) {
return Ok(value);
}
last = value;
}
Ok(if wanted.is_none() { last } else { Value::Null })
}
fn filling(&self, call: &Call, rows: &[Windowed]) -> Vec<Value> {
let mut out: Vec<Value> = rows.iter().map(|row| row.0[call.args_at].clone()).collect();
let sorted = self.partitions;
let keys: Vec<Option<f64>> = rows.iter().map(|row| placement(&row.0[sorted])).collect();
let Some(first) = keys.iter().position(Option::is_some) else {
return out;
};
let last =
keys[first..].iter().position(Option::is_none).map_or(keys.len(), |past| first + past);
let anchors: Vec<(usize, f64, f64)> = (first..last)
.filter_map(|at| Some((at, keys[at]?, placement(&rows[at].0[call.args_at])?)))
.collect();
if anchors.is_empty() {
return out;
}
let mut behind = 0;
for at in first..last {
while behind < anchors.len() && anchors[behind].0 <= at {
behind += 1;
}
if !out[at].is_null() {
continue;
}
let (from, to) = match (behind.checked_sub(1), behind < anchors.len()) {
(Some(before), true) => (before, behind),
(Some(before), false) => (before.saturating_sub(1), before),
(None, _) => (0, usize::min(1, anchors.len() - 1)),
};
let (_, x0, y0) = anchors[from];
let (_, x1, y1) = anchors[to];
out[at] = blended(y0, y1, gradient(keys[at].unwrap_or(x0), x0, x1), &call.returns);
}
out
}
fn excluded(&self, peers: &[usize], at: usize, row: usize) -> bool {
match self.frame.exclude {
WindowExclude::NoOthers => false,
WindowExclude::CurrentRow => row == at,
WindowExclude::Group => peers[row] == peers[at],
WindowExclude::Ties => peers[row] == peers[at] && row != at,
}
}
}
fn ranked(
ranking: Ranking,
call: &Call,
rows: &[Windowed],
peers: &[usize],
at: usize,
) -> Result<Value> {
let total = rows.len();
let first = first_of(peers, peers[at]);
let last = last_of(peers, peers[at]);
let count = |held: usize| {
i64::try_from(held).map_err(|_| Error::internal("a partition longer than a BIGINT"))
};
Ok(match ranking {
Ranking::RowNumber => Value::BigInt(count(at + 1)?),
Ranking::Rank => Value::BigInt(count(first + 1)?),
Ranking::DenseRank => Value::BigInt(count(peers[at] + 1)?),
Ranking::PercentRank => {
Value::Double(if total <= 1 { 0.0 } else { first as f64 / (total - 1) as f64 })
}
Ranking::CumeDist => Value::Double((last + 1) as f64 / total as f64),
Ranking::Ntile => ntile(call, rows, at, total)?,
})
}
fn ntile(call: &Call, rows: &[Windowed], at: usize, total: usize) -> Result<Value> {
let written = &rows[at].0[call.args_at];
if written.is_null() {
return Ok(Value::Null);
}
let buckets = written
.as_i64()
.ok_or_else(|| Error::invalid_input("Argument for ntile must be a number"))?;
if buckets <= 0 {
return Err(Error::invalid_input("Argument for ntile must be greater than zero"));
}
let buckets = usize::try_from(buckets).unwrap_or(total).min(total.max(1));
let each = total / buckets;
let wide = total % buckets;
let bucket = if at < wide * (each + 1) {
at / (each + 1)
} else {
wide + (at - wide * (each + 1)) / each.max(1)
};
let bucket = i64::try_from(bucket + 1).map_err(|_| Error::internal("too many buckets"))?;
Ok(Value::BigInt(bucket))
}
fn shifted(look: Looks, call: &Call, rows: &[Windowed], at: usize) -> Result<Value> {
let held = &rows[at].0[call.args_at..call.args_at + call.args];
let count = match held.get(1) {
None => 1,
Some(value) if value.is_null() => return Ok(Value::Null),
Some(value) => value.as_i64().ok_or_else(|| {
Error::invalid_input(format!("Argument for {} must be a number", call.name))
})?,
};
let back = match look {
Looks::Back => count >= 0,
Looks::Forward => count < 0,
};
let steps = usize::try_from(count.unsigned_abs()).unwrap_or(usize::MAX);
let landed = if call.ignore_nulls {
away_over_nulls(call, rows, at, back, steps)
} else if back {
at.checked_sub(steps)
} else {
at.checked_add(steps).filter(|&row| row < rows.len())
};
Ok(match landed {
Some(row) => rows[row].0[call.args_at].clone(),
None => held.get(2).cloned().unwrap_or(Value::Null),
})
}
fn away_over_nulls(
call: &Call,
rows: &[Windowed],
at: usize,
back: bool,
steps: usize,
) -> Option<usize> {
let mut left = steps;
let mut row = at;
while left > 0 {
row = if back {
row.checked_sub(1)?
} else {
row.checked_add(1).filter(|&row| row < rows.len())?
};
if rows[row].0[call.args_at].is_null() {
continue;
}
left -= 1;
}
Some(row)
}
fn placement(value: &Value) -> Option<f64> {
let number = match *value {
Value::TinyInt(held) => f64::from(held),
Value::SmallInt(held) => f64::from(held),
Value::Integer(held) => f64::from(held),
Value::BigInt(held) => held as f64,
Value::HugeInt(held) => held as f64,
Value::UTinyInt(held) => f64::from(held),
Value::USmallInt(held) => f64::from(held),
Value::UInteger(held) => f64::from(held),
Value::UBigInt(held) => held as f64,
Value::UHugeInt(held) => held as f64,
Value::Float(held) => f64::from(held),
Value::Double(held) => held,
Value::Decimal { unscaled, .. } => unscaled as f64,
Value::Date(held) => f64::from(held),
Value::Time(held)
| Value::TimeTz(held)
| Value::Timestamp(held)
| Value::TimestampTz(held) => held as f64,
_ => return None,
};
number.is_finite().then_some(number)
}
fn gradient(x: f64, x0: f64, x1: f64) -> f64 {
let mut den = x1 - x0;
if den == 0.0 {
return 0.0;
}
let mut num = x - x0;
if !den.is_finite() {
let scale = x0.abs().max(x1.abs());
num = x / scale - x0 / scale;
den = x1 / scale - x0 / scale;
}
num / den
}
fn blended(y0: f64, y1: f64, d: f64, returns: &LogicalType) -> Value {
if matches!(*returns, LogicalType::Float | LogicalType::Double) {
let number = y0 * (1.0 - d) + y1 * d;
return match *returns {
LogicalType::Float => Value::Float(number as f32),
_ => Value::Double(number),
};
}
let delta = y1 - y0;
let number = if (0.0..=1.0).contains(&d) {
(y0 + delta * d).trunc()
} else {
let offset = (delta.abs() * d.abs()).round();
if (delta >= 0.0) == (d >= 0.0) { y0 + offset } else { y0 - offset }
};
seated(number, returns)
}
fn seated(number: f64, returns: &LogicalType) -> Value {
if !number.is_finite() || number.abs() >= 1.701_411_834_604_692_3e38 {
return Value::Null;
}
let whole = number as i128;
let fits = |low: i128, high: i128| (low..=high).contains(&whole);
match *returns {
LogicalType::TinyInt if fits(i128::from(i8::MIN), i128::from(i8::MAX)) => {
Value::TinyInt(whole as i8)
}
LogicalType::SmallInt if fits(i128::from(i16::MIN), i128::from(i16::MAX)) => {
Value::SmallInt(whole as i16)
}
LogicalType::Integer if fits(i128::from(i32::MIN), i128::from(i32::MAX)) => {
Value::Integer(whole as i32)
}
LogicalType::BigInt if fits(i128::from(i64::MIN), i128::from(i64::MAX)) => {
Value::BigInt(whole as i64)
}
LogicalType::HugeInt => Value::HugeInt(whole),
LogicalType::UTinyInt if fits(0, i128::from(u8::MAX)) => Value::UTinyInt(whole as u8),
LogicalType::USmallInt if fits(0, i128::from(u16::MAX)) => Value::USmallInt(whole as u16),
LogicalType::UInteger if fits(0, i128::from(u32::MAX)) => Value::UInteger(whole as u32),
LogicalType::UBigInt if fits(0, i128::from(u64::MAX)) => Value::UBigInt(whole as u64),
LogicalType::UHugeInt if whole >= 0 => Value::UHugeInt(whole as u128),
LogicalType::Decimal { width, scale } if digits(whole) <= u32::from(width) => {
Value::Decimal { unscaled: whole, width, scale }
}
LogicalType::Date if fits(i128::from(i32::MIN), i128::from(i32::MAX)) => {
Value::Date(whole as i32)
}
_ if !fits(i128::from(i64::MIN), i128::from(i64::MAX)) => Value::Null,
LogicalType::Time => Value::Time(whole as i64),
LogicalType::TimeTz => Value::TimeTz(whole as i64),
LogicalType::Timestamp
| LogicalType::TimestampS
| LogicalType::TimestampMs
| LogicalType::TimestampNs => Value::Timestamp(whole as i64),
LogicalType::TimestampTz => Value::TimestampTz(whole as i64),
_ => Value::Null,
}
}
fn digits(unscaled: i128) -> u32 {
let mut left = unscaled.unsigned_abs();
let mut counted = 1;
while left >= 10 {
left /= 10;
counted += 1;
}
counted
}
fn first_of(peers: &[usize], group: usize) -> usize {
peers.iter().position(|&held| held == group).unwrap_or(0)
}
fn last_of(peers: &[usize], group: usize) -> usize {
peers.iter().rposition(|&held| held == group).unwrap_or(0)
}
fn poisoned<T>(_: std::sync::PoisonError<T>) -> Error {
Error::internal("a window lock a panicking thread left behind")
}