reifydb-core 0.10.1

Core database interfaces and data structures for ReifyDB
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 ReifyDB

use std::mem::take;

use postcard::{to_extend, to_stdvec};
use reifydb_codec::{
	key::serializer::KeySerializer,
	tag::{TypeTag, ValueKind},
};
use reifydb_value::{
	Result,
	util::hash::{Hash128, xxh3_128},
	value::{
		Value,
		column_view::{ColumnView, ViewData},
		container::{
			decimal_array::decimal_get_value,
			dictionary_array,
			temporal_array::{dates, datetimes, durations, times},
			uuid_array::{identity_ids, uuid4s, uuid7s},
			varlen_array, wide_int_array,
		},
		to_value::ToValue,
		value_type::ValueType,
	},
};
use serde::Serialize;

const UTF8_VARIANT: u8 = 9;
const BLOB_VARIANT: u8 = 22;

enum Cell<'a> {
	None,
	Value(Value),
	Utf8(&'a str),
	Blob(&'a [u8]),
}

pub fn extend_keys(view: &ColumnView, rows: &mut [KeySerializer]) -> Result<()> {
	match &view.data {
		ViewData::Float4(array) => {
			let none = none_bytes(view);
			for (index, row) in rows.iter_mut().enumerate() {
				let value = array.value(index);
				if view.none_at(index) || value.is_nan() {
					row.extend_raw(&none);
				} else {
					row.extend_kind(ValueKind::Float4).extend_f32(if value == 0.0 {
						0.0
					} else {
						value
					});
				}
			}
		}
		ViewData::Float8(array) => {
			let none = none_bytes(view);
			for (index, row) in rows.iter_mut().enumerate() {
				let value = array.value(index);
				if view.none_at(index) || value.is_nan() {
					row.extend_raw(&none);
				} else {
					row.extend_kind(ValueKind::Float8).extend_f64(if value == 0.0 {
						0.0
					} else {
						value
					});
				}
			}
		}
		_ => {
			let none = Value::None {
				inner: view.base_type(),
			};
			for_each_cell(view, rows.len(), |index, cell| {
				let row = &mut rows[index];
				match cell {
					Cell::None => {
						row.try_extend_value(&none)?;
					}
					Cell::Value(value) => {
						row.try_extend_value(&value)?;
					}
					Cell::Utf8(text) => {
						row.extend_kind(ValueKind::Utf8).extend_str(text);
					}
					Cell::Blob(bytes) => {
						row.extend_kind(ValueKind::Blob).extend_bytes(bytes);
					}
				}
				Ok(())
			})?;
		}
	}
	Ok(())
}

pub fn extend_value_bytes(view: &ColumnView, rows: &mut [Vec<u8>]) -> Result<()> {
	let none = to_stdvec(&Value::None {
		inner: view.base_type(),
	})
	.expect("value postcard is total");
	for_each_cell(view, rows.len(), |index, cell| {
		let row = &mut rows[index];
		match cell {
			Cell::None => row.extend_from_slice(&none),
			Cell::Value(value) => postcard_into(row, &value),
			Cell::Utf8(text) => {
				row.push(UTF8_VARIANT);
				postcard_into(row, text);
			}
			Cell::Blob(bytes) => {
				row.push(BLOB_VARIANT);
				postcard_into(row, bytes);
			}
		}
		Ok(())
	})
}

pub fn value_key_hashes(views: &[ColumnView], rows: usize) -> Result<Vec<Option<Hash128>>> {
	let mut bytes = vec![Vec::new(); rows];
	let mut none = vec![false; rows];
	for view in views {
		extend_value_bytes(view, &mut bytes)?;
		for_each_cell(view, rows, |index, cell| {
			none[index] |= matches!(cell, Cell::None | Cell::Value(Value::None { .. }));
			Ok(())
		})?;
	}
	Ok(bytes.iter().zip(none).map(|(row, none)| (!none).then(|| xxh3_128(row))).collect())
}

fn postcard_into<T: Serialize + ?Sized>(row: &mut Vec<u8>, value: &T) {
	*row = to_extend(value, take(row)).expect("value postcard is total");
}

fn for_each_cell<'a>(
	view: &ColumnView<'a>,
	rows: usize,
	mut visit: impl FnMut(usize, Cell<'a>) -> Result<()>,
) -> Result<()> {
	match &view.data {
		ViewData::Bool(array) => cells(view, rows, &mut visit, |i| Cell::Value(Value::Boolean(array.value(i)))),
		ViewData::Float4(array) => cells(view, rows, &mut visit, |i| Cell::Value(array.value(i).to_value())),
		ViewData::Float8(array) => cells(view, rows, &mut visit, |i| Cell::Value(array.value(i).to_value())),
		ViewData::Int1(array) => cells(view, rows, &mut visit, |i| Cell::Value(array.value(i).to_value())),
		ViewData::Int2(array) => cells(view, rows, &mut visit, |i| Cell::Value(array.value(i).to_value())),
		ViewData::Int4(array) => cells(view, rows, &mut visit, |i| Cell::Value(array.value(i).to_value())),
		ViewData::Int8(array) => cells(view, rows, &mut visit, |i| Cell::Value(array.value(i).to_value())),
		ViewData::Int16(array) => {
			cells(view, rows, &mut visit, |i| Cell::Value(wide_int_array::get_value::<i128>(array, i)))
		}
		ViewData::Uint1(array) => cells(view, rows, &mut visit, |i| Cell::Value(array.value(i).to_value())),
		ViewData::Uint2(array) => cells(view, rows, &mut visit, |i| Cell::Value(array.value(i).to_value())),
		ViewData::Uint4(array) => cells(view, rows, &mut visit, |i| Cell::Value(array.value(i).to_value())),
		ViewData::Uint8(array) => cells(view, rows, &mut visit, |i| Cell::Value(array.value(i).to_value())),
		ViewData::Uint16(array) => {
			cells(view, rows, &mut visit, |i| Cell::Value(wide_int_array::get_value::<u128>(array, i)))
		}
		ViewData::Utf8 {
			container,
			..
		} => {
			let container = *container;
			cells(view, rows, &mut visit, move |i| match varlen_array::get(container, i) {
				Some(text) => Cell::Utf8(text),
				None => Cell::Value(Value::none_of(ValueType::Utf8)),
			})
		}
		ViewData::Blob {
			container,
			..
		} => {
			let container = *container;
			cells(view, rows, &mut visit, move |i| match varlen_array::get(container, i) {
				Some(bytes) => Cell::Blob(bytes),
				None => Cell::Value(Value::none_of(ValueType::Blob)),
			})
		}
		ViewData::Date(array) => {
			let values = dates(array);
			cells(view, rows, &mut visit, |i| Cell::Value(values[i].to_value()))
		}
		ViewData::DateTime(array) => {
			let values = datetimes(array);
			cells(view, rows, &mut visit, |i| Cell::Value(values[i].to_value()))
		}
		ViewData::Time(array) => {
			let values = times(array);
			cells(view, rows, &mut visit, |i| Cell::Value(values[i].to_value()))
		}
		ViewData::Duration(array) => {
			let values = durations(array);
			cells(view, rows, &mut visit, |i| Cell::Value(values[i].to_value()))
		}
		ViewData::IdentityId(array) => {
			let values = identity_ids(array);
			cells(view, rows, &mut visit, |i| Cell::Value(Value::IdentityId(values[i])))
		}
		ViewData::Uuid4(array) => {
			let values = uuid4s(array);
			cells(view, rows, &mut visit, |i| Cell::Value(values[i].to_value()))
		}
		ViewData::Uuid7(array) => {
			let values = uuid7s(array);
			cells(view, rows, &mut visit, |i| Cell::Value(values[i].to_value()))
		}
		ViewData::Decimal(decimals) => {
			cells(view, rows, &mut visit, |i| Cell::Value(decimal_get_value(decimals, i)))
		}
		ViewData::DictionaryId {
			container,
			..
		} => cells(view, rows, &mut visit, |i| Cell::Value(dictionary_array::get_value(container, i))),
		_ => cells(view, rows, &mut visit, |i| Cell::Value(view.get_value(i))),
	}
}

fn cells<'a>(
	view: &ColumnView<'a>,
	rows: usize,
	visit: &mut impl FnMut(usize, Cell<'a>) -> Result<()>,
	cell: impl Fn(usize) -> Cell<'a>,
) -> Result<()> {
	let nulls = view.logical_nulls();
	for index in 0..rows {
		match nulls.as_ref().is_some_and(|nulls| !(index < nulls.len() && nulls.is_valid(index))) {
			true => visit(index, Cell::None)?,
			false => visit(index, cell(index))?,
		}
	}
	Ok(())
}

fn none_bytes(view: &ColumnView) -> [u8; 2] {
	[
		ValueKind::None.byte(),
		TypeTag::of_type(&view.base_type())
			.expect("option nesting in a key none inner exceeds the supported depth")
			.byte(),
	]
}