use std::cmp::Ordering;
use gnitz_wire::TypeCode;
use crate::SchemaFacts;
use gnitz_wire::RowSource;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ColumnLocator {
Pk {
byte_off: u8,
size: u8,
type_code: TypeCode,
},
Payload { slot: u8, size: u8, type_code: TypeCode },
}
const _: () = assert!(
std::mem::size_of::<ColumnLocator>() <= 8,
"ColumnLocator must stay packed; a usize coordinate would balloon it to 24 bytes",
);
const _: () = assert!(
gnitz_wire::MAX_PK_BYTES <= u8::MAX as usize,
"ColumnLocator::Pk::byte_off is u8; the PK region no longer fits it",
);
const _: () = assert!(
gnitz_wire::MAX_COLUMNS <= u8::MAX as usize,
"ColumnLocator::Payload::slot is u8; the payload slot space no longer fits it",
);
impl ColumnLocator {
#[inline(always)]
pub fn size(&self) -> usize {
match *self {
ColumnLocator::Pk { size, .. } | ColumnLocator::Payload { size, .. } => size as usize,
}
}
#[inline(always)]
pub fn type_code(&self) -> TypeCode {
match *self {
ColumnLocator::Pk { type_code, .. } | ColumnLocator::Payload { type_code, .. } => type_code,
}
}
#[inline(always)]
pub fn is_null(&self, mb: &impl RowSource, row: usize) -> bool {
match *self {
ColumnLocator::Pk { .. } => false,
ColumnLocator::Payload { .. } => self.is_null_word(mb.get_null_word(row)),
}
}
#[inline(always)]
pub fn is_null_word(&self, null_word: u64) -> bool {
match *self {
ColumnLocator::Pk { .. } => false,
ColumnLocator::Payload { slot, .. } => gnitz_wire::null_word_get(null_word, slot as usize),
}
}
#[inline(always)]
pub fn bytes<'b>(&self, mb: &'b impl RowSource, row: usize) -> &'b [u8] {
match *self {
ColumnLocator::Pk { byte_off, size, .. } => {
let o = byte_off as usize;
&mb.get_pk_bytes(row)[o..o + size as usize]
}
ColumnLocator::Payload { slot, size, .. } => mb.get_col_ptr(row, slot as usize, size as usize),
}
}
#[inline(always)]
pub fn content<'b>(&self, mb: &'b impl RowSource, row: usize) -> &'b [u8] {
gnitz_wire::german_string_content(self.bytes(mb, row), mb.blob())
}
#[inline(always)]
pub fn native_le_bytes<'a, 'b: 'a>(
&self,
mb: &'b impl RowSource,
row: usize,
scratch: &'a mut [u8; 16],
) -> &'a [u8] {
match *self {
ColumnLocator::Pk { size, type_code, .. } => {
let dst = &mut scratch[..size as usize];
gnitz_wire::decode_pk_cell(self.bytes(mb, row), type_code.is_signed_int(), dst);
dst
}
ColumnLocator::Payload { .. } => self.bytes(mb, row),
}
}
#[inline(always)]
pub fn decode_i64(&self, mb: &impl RowSource, row: usize, fi: gnitz_wire::FixedInt) -> i64 {
debug_assert_eq!(gnitz_wire::FixedInt::from_type_code(self.type_code()), Some(fi));
match *self {
ColumnLocator::Pk { .. } => gnitz_wire::decode_opk_i64(self.bytes(mb, row), fi),
ColumnLocator::Payload { .. } => fi.decode_le_i64(self.bytes(mb, row)),
}
}
#[inline(always)]
pub fn cmp_non_null<A: RowSource, B: RowSource>(&self, a: &A, ra: usize, b: &B, rb: usize) -> Ordering {
match *self {
ColumnLocator::Pk { .. } => self.bytes(a, ra).cmp(self.bytes(b, rb)),
ColumnLocator::Payload { type_code, .. } => {
gnitz_wire::cmp_col_window(self.bytes(a, ra), a.blob(), self.bytes(b, rb), b.blob(), type_code)
}
}
}
#[inline(always)]
pub fn opk_image(&self, mb: &impl RowSource, row: usize) -> u128 {
let cell = self.bytes(mb, row);
match *self {
ColumnLocator::Pk { .. } => gnitz_wire::widen_pk_be(cell),
ColumnLocator::Payload { size, type_code, .. } => {
let signed = type_code.is_signed_int();
if size == 16 {
u128::from_le_bytes(cell.try_into().unwrap()) ^ ((signed as u128) << 127)
} else {
(gnitz_wire::read_unsigned_exact(cell) ^ ((signed as u64) << (size * 8 - 1))) as u128
}
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OrderLocator {
pub loc: ColumnLocator,
pub desc: bool,
pub nulls_first: bool,
}
impl OrderLocator {
#[inline]
pub fn of(loc: ColumnLocator, wire: &gnitz_wire::OrderKey) -> Self {
OrderLocator {
loc,
desc: wire.desc,
nulls_first: wire.nulls_first,
}
}
}
pub fn order_locators(order: &[gnitz_wire::OrderKey], schema: &dyn SchemaFacts) -> Vec<OrderLocator> {
let mut keys: Vec<OrderLocator> = order
.iter()
.map(|k| OrderLocator::of(schema.locate(k.col as usize), k))
.collect();
if !keys.is_empty() {
push_identity_tiebreak(&mut keys, schema);
}
keys
}
fn push_identity_tiebreak(keys: &mut Vec<OrderLocator>, schema: &dyn SchemaFacts) {
let asc = |loc| OrderLocator { loc, desc: false, nulls_first: true };
keys.extend(schema.pk_cols().iter().map(|&c| asc(schema.locate(c as usize))));
keys.extend(schema.payload_locators().into_iter().map(asc));
}
#[inline]
pub fn cmp_order_keys<A: RowSource, B: RowSource>(
keys: &[OrderLocator],
a: &A,
ra: usize,
b: &B,
rb: usize,
) -> Ordering {
let na = a.get_null_word(ra);
let nb = b.get_null_word(rb);
for key in keys {
let (xa, xb) = (key.loc.is_null_word(na), key.loc.is_null_word(nb));
if xa != xb {
return if xa == key.nulls_first {
Ordering::Less
} else {
Ordering::Greater
};
}
if xa {
continue;
}
let mut ord = key.loc.cmp_non_null(a, ra, b, rb);
if key.desc {
ord = ord.reverse();
}
if ord != Ordering::Equal {
return ord;
}
}
Ordering::Equal
}
#[cfg(test)]
#[path = "tests/locator.rs"]
mod tests;