use crate::schema::ColumnTable;
use std::cmp::Ordering;
use gnitz_wire::RowSource;
use gnitz_wire::{KeyRange, NARROW_PK_MAX_BYTES};
use gnitz_wire::{PkBuf, PkListRole};
use crate::schema::{
ColumnLocator, DerivedSchema, SchemaColumn, SchemaDescriptor, SchemaFacts, TypeCode, MAX_PK_COLUMNS,
};
#[inline(always)]
pub fn compare_pk_bytes(a: &[u8], b: &[u8]) -> Ordering {
a.cmp(b)
}
pub fn sort_indices(flat: &[u8], stride: usize, idx: &mut Vec<u32>) {
fn by_image<'a, K: PkSortKey<'a>>(flat: &'a [u8], stride: usize, idx: &mut Vec<u32>) {
let mut pairs: Vec<(K, u32)> = flat.chunks_exact(stride).map(K::from_opk).zip(0..).collect();
pairs.sort_unstable();
idx.extend(pairs.iter().map(|&(_, i)| i));
}
let n = flat.len() / stride;
assert!(n <= u32::MAX as usize, "record count exceeds u32");
idx.clear();
match stride {
0..=8 => by_image::<u64>(flat, stride, idx),
9..=16 => by_image::<u128>(flat, stride, idx),
_ => {
idx.extend(0..n as u32);
idx.sort_unstable_by(|&a, &b| {
let a = a as usize * stride;
let b = b as usize * stride;
compare_pk_bytes(&flat[a..a + stride], &flat[b..b + stride])
});
}
}
}
#[inline(always)]
pub(crate) fn compare_pk_ordering(a: &[u8], b: &[u8]) -> Ordering {
debug_assert_eq!(a.len(), b.len(), "compare_pk_ordering on unequal PK widths");
match pack_pk_be(a).cmp(&pack_pk_be(b)) {
Ordering::Equal if a.len() > NARROW_PK_MAX_BYTES => compare_pk_bytes(a, b),
ord => ord,
}
}
#[inline(always)]
pub fn pk_bytes_eq(a: &[u8], b: &[u8]) -> bool {
compare_pk_ordering(a, b) == Ordering::Equal
}
#[inline]
pub fn pk_in_range(min: &[u8], max: &[u8], key: &[u8]) -> bool {
compare_pk_bytes(min, key) != Ordering::Greater && compare_pk_bytes(key, max) != Ordering::Greater
}
#[inline]
pub fn pk_ranges_overlap(min: &[u8], max: &[u8], lo: &[u8], hi: &[u8]) -> bool {
compare_pk_bytes(max, lo) != Ordering::Less && compare_pk_bytes(min, hi) != Ordering::Greater
}
#[inline(always)]
pub fn pack_pk_be(pk_bytes: &[u8]) -> u128 {
match pk_bytes.len() {
8 => (u64::from_be_bytes(pk_bytes[..8].try_into().unwrap()) as u128) << 64,
len if len >= 16 => u128::from_be_bytes(pk_bytes[..16].try_into().unwrap()),
len @ 9..=15 => {
let hi = u64::from_be_bytes(pk_bytes[..8].try_into().unwrap()) as u128;
let tail = u64::from_be_bytes(pk_bytes[len - 8..len].try_into().unwrap());
(hi << 64) | (tail << (8 * (16 - len))) as u128
}
4 => (u32::from_be_bytes(pk_bytes[..4].try_into().unwrap()) as u128) << 96,
2 => (u16::from_be_bytes(pk_bytes[..2].try_into().unwrap()) as u128) << 112,
1 => (pk_bytes[0] as u128) << 120,
3 => {
let hi = u16::from_be_bytes(pk_bytes[..2].try_into().unwrap()) as u128;
(hi << 112) | ((pk_bytes[2] as u128) << 104)
}
5 => {
let hi = u32::from_be_bytes(pk_bytes[..4].try_into().unwrap()) as u128;
(hi << 96) | ((pk_bytes[4] as u128) << 88)
}
6 => {
let hi = u32::from_be_bytes(pk_bytes[..4].try_into().unwrap()) as u128;
let lo = u16::from_be_bytes(pk_bytes[4..6].try_into().unwrap()) as u128;
(hi << 96) | (lo << 80)
}
7 => {
let hi = u32::from_be_bytes(pk_bytes[..4].try_into().unwrap()) as u128;
let mid = u16::from_be_bytes(pk_bytes[4..6].try_into().unwrap()) as u128;
(hi << 96) | (mid << 80) | ((pk_bytes[6] as u128) << 72)
}
_ => 0,
}
}
#[inline(always)]
pub(crate) fn leading_u64(pk_bytes: &[u8]) -> u64 {
if pk_bytes.len() >= 8 {
u64::from_be_bytes(pk_bytes[..8].try_into().unwrap())
} else {
(pack_pk_be(pk_bytes) >> 64) as u64
}
}
pub(crate) struct NarrowPkOpk {
be: [u8; 16],
stride: usize,
}
impl NarrowPkOpk {
#[inline(always)]
pub(crate) fn new(image: u128, stride: usize) -> Self {
assert!(
stride <= NARROW_PK_MAX_BYTES,
"NarrowPkOpk::new: stride exceeds NARROW_PK_MAX_BYTES"
);
debug_assert!(
stride == 16 || (image >> (stride * 8)) == 0,
"narrow PK image {image} does not fit {stride} bytes",
);
NarrowPkOpk { be: image.to_be_bytes(), stride }
}
#[inline(always)]
pub(crate) fn bytes(&self) -> &[u8] {
&self.be[16 - self.stride..]
}
}
pub(crate) trait PkSortKey<'a>: Ord + Copy {
fn from_opk(opk: &'a [u8]) -> Self;
}
macro_rules! pk_width_dispatch {
($stride:expr, |$k:ident| $body:expr $(,)?) => {
match $stride {
0..=8 => {
type $k<'k> = u64;
$body
}
9..=16 => {
type $k<'k> = u128;
$body
}
17..=32 => {
type $k<'k> = [u128; 2];
$body
}
_ => {
type $k<'k> = &'k [u8];
$body
}
}
};
}
pub(crate) use pk_width_dispatch;
impl PkSortKey<'_> for u64 {
#[inline(always)]
fn from_opk(opk: &[u8]) -> u64 {
leading_u64(opk)
}
}
impl PkSortKey<'_> for u128 {
#[inline(always)]
fn from_opk(opk: &[u8]) -> u128 {
pack_pk_be(opk)
}
}
impl<'a> PkSortKey<'a> for &'a [u8] {
#[inline(always)]
fn from_opk(opk: &'a [u8]) -> Self {
opk
}
}
impl PkSortKey<'_> for [u128; 2] {
#[inline(always)]
fn from_opk(opk: &[u8]) -> [u128; 2] {
let hi = u128::from_be_bytes(opk[..16].try_into().unwrap());
if opk.len() == 32 {
[hi, u128::from_be_bytes(opk[16..32].try_into().unwrap())]
} else {
[hi, pack_pk_be(&opk[16..])]
}
}
}
#[inline]
pub fn probe_key(opk: &[u8]) -> u64 {
gnitz_wire::checksum(opk)
}
#[derive(Clone, Copy)]
pub struct KeySpec {
n: u8,
key_size: u8,
locs: [ColumnLocator; MAX_PK_COLUMNS],
}
impl KeySpec {
fn over(locs: impl IntoIterator<Item = ColumnLocator>) -> Self {
const EMPTY: ColumnLocator = ColumnLocator::Pk {
byte_off: 0,
size: 0,
type_code: TypeCode::U8,
};
let mut spec = KeySpec {
n: 0,
key_size: 0,
locs: [EMPTY; MAX_PK_COLUMNS],
};
for loc in locs {
spec.locs[spec.n as usize] = loc;
spec.n += 1;
spec.key_size += loc.size() as u8;
}
spec
}
pub(crate) fn locators(&self) -> &[ColumnLocator] {
&self.locs[..self.n as usize]
}
pub fn new(cols: &[u32], owner: &SchemaDescriptor) -> Result<Self, String> {
gnitz_wire::validate_pk_tuple(cols, owner.num_columns(), MAX_PK_COLUMNS - owner.pk_cols().len(), |c| {
(owner.columns[c as usize].type_code, false)
})
.map_err(|rule| format!("Index: {}", rule.for_role(PkListRole::ColumnList)))?;
Ok(Self::over(cols.iter().map(|&c| owner.locate(c as usize))))
}
pub(in crate::schema) fn output_schema(&self, source: &SchemaDescriptor) -> SchemaDescriptor {
let mut b = self.key_columns();
b.push_pk_of(source);
b.finish().expect("KeySpec::new bounds the index schema")
}
pub fn span_schema(&self) -> SchemaDescriptor {
self.key_columns()
.finish()
.expect("KeySpec::new admits its key columns")
}
fn key_columns(&self) -> DerivedSchema {
let mut b = DerivedSchema::new();
self.locators()
.iter()
.for_each(|loc| b.push_pk(SchemaColumn::new(loc.type_code(), false)));
b
}
pub(crate) fn for_pk(schema: &SchemaDescriptor) -> Self {
Self::over(schema.pk_columns().map(|(ci, _)| schema.locate(ci)))
}
#[inline]
pub fn key_size(&self) -> usize {
self.key_size as usize
}
pub fn write_span(&self, mb: &impl RowSource, row: usize, dst: &mut [u8]) -> bool {
debug_assert!(dst.len() >= self.key_size(), "write_span: dst shorter than the span");
let mut off = 0;
for loc in self.locators() {
if loc.is_null(mb, row) {
return false;
}
let w = loc.size();
gnitz_wire::store_opk(&mut dst[off..off + w], loc.opk_image(mb, row), false);
off += w;
}
true
}
pub fn split_entry<'a>(&self, entry: &'a [u8]) -> (&'a [u8], &'a [u8]) {
debug_assert!(entry.len() > self.key_size(), "index entry shorter than its span");
entry.split_at(self.key_size())
}
pub fn seek_prefix(&self, images: &[u128]) -> PkBuf {
let k = images.len();
debug_assert!(
k >= 1 && k <= self.n as usize,
"seek_prefix: one image per leading spec column"
);
let mut key = PkBuf::zeroed(0);
for (loc, &image) in self.locs[..k].iter().zip(images) {
key.push(loc.size(), image, false);
}
key
}
pub fn range_keys(&self, stride: usize, range: &KeyRange) -> Option<(PkBuf, Option<PkBuf>)> {
debug_assert!(
range.cols().as_slice().len() <= self.n as usize,
"range_keys: the range lists more columns than the key space"
);
let eq = range.eq_vals();
let n_eq = eq.len();
let mut images = [0u128; MAX_PK_COLUMNS];
images[..n_eq].copy_from_slice(eq);
images[n_eq] = range.start.image;
let start = self.seek_prefix(&images[..=n_eq]);
images[n_eq] = range.end.image;
let end = self.seek_prefix(&images[..=n_eq]);
key_range_between_cuts(
KeyCut::new(start.pk_bytes(), range.start.after),
KeyCut::new(end.pk_bytes(), range.end.after),
stride,
)
}
}
fn increment_key_in_place(p: &mut [u8]) -> bool {
for b in p.iter_mut().rev() {
*b = b.wrapping_add(1);
if *b != 0 {
return true;
}
}
false
}
fn decrement_key_in_place(p: &mut [u8]) {
for b in p.iter_mut().rev() {
*b = b.wrapping_sub(1);
if *b != 0xFF {
return;
}
}
}
#[derive(Clone, Copy)]
pub struct KeyCut<'a> {
group: &'a [u8],
above: bool,
}
impl<'a> KeyCut<'a> {
pub(crate) fn new(group: &'a [u8], above: bool) -> Self {
KeyCut { group, above }
}
pub fn min_of(group: &'a [u8]) -> Self {
KeyCut::new(group, false)
}
pub fn above(group: &'a [u8]) -> Self {
KeyCut::new(group, true)
}
fn key(&self, stride: usize) -> Option<PkBuf> {
let mut k = PkBuf::from_bytes(self.group);
let exists = !self.above || increment_key_in_place(k.pk_bytes_mut());
exists.then(|| k.widened(stride))
}
}
pub fn key_range_between_cuts(start: KeyCut, end: KeyCut, stride: usize) -> Option<(PkBuf, Option<PkBuf>)> {
let start = start.key(stride)?;
let end = end.key(stride);
if end.as_ref().is_some_and(|e| start.pk_bytes() >= e.pk_bytes()) {
return None;
}
Some((start, end))
}
pub(crate) fn range_shares_prefix(start: &PkBuf, end: Option<&PkBuf>, prefix: usize) -> bool {
let last = match end {
Some(e) => {
let mut l = *e;
decrement_key_in_place(l.pk_bytes_mut());
l
}
None => PkBuf::max(start.pk_bytes().len()),
};
start.pk_bytes()[..prefix] == last.pk_bytes()[..prefix]
}
impl SchemaDescriptor {
pub fn pk_range_keys(&self, r: &KeyRange) -> Option<(PkBuf, Option<PkBuf>)> {
KeySpec::for_pk(self).range_keys(self.pk_stride(), r)
}
}
#[cfg(test)]
#[path = "tests/key.rs"]
mod tests;
#[cfg(test)]
#[path = "benches/key.rs"]
mod bench;