use std::num::NonZeroU64;
use crate::{ColType, PkListRole, PkRule, TypeCode, WireProbeMode};
pub struct WireSysCol {
pub name: &'static str,
pub type_code: TypeCode,
}
pub(crate) const fn col(name: &'static str, type_code: TypeCode) -> WireSysCol {
WireSysCol { name, type_code }
}
#[inline]
pub const fn unpack_pair_pk(pk: u128) -> (u64, u64) {
((pk >> 64) as u64, pk as u64)
}
pub const RELTAB_PAY_SCHEMA_ID: usize = {
let i = crate::sys_rows::TableTabSlot::schema_id as usize;
assert!(
i == crate::sys_rows::ViewTabSlot::schema_id as usize,
"TABLE_TAB and VIEW_TAB disagree on schema_id"
);
i
};
pub const RELTAB_PAY_NAME: usize = {
let i = crate::sys_rows::TableTabSlot::name as usize;
assert!(
i == crate::sys_rows::ViewTabSlot::name as usize,
"TABLE_TAB and VIEW_TAB disagree on name"
);
i
};
const FNV_PRIME: u64 = 0x100_0000_01b3;
const fn fnv_bytes(mut h: u64, b: &[u8]) -> u64 {
let mut i = 0;
while i < b.len() {
h = (h ^ b[i] as u64).wrapping_mul(FNV_PRIME);
i += 1;
}
h
}
const fn fold_family(mut h: u64, f: &WireSysFamily) -> u64 {
let (cols, pk) = (f.cols, f.pk_cols);
h = (h ^ f.id).wrapping_mul(FNV_PRIME);
h = fnv_bytes(h, f.name.as_bytes());
let mut i = 0;
while i < cols.len() {
h = fnv_bytes(h, cols[i].name.as_bytes());
h = (h ^ cols[i].type_code as u64).wrapping_mul(FNV_PRIME);
i += 1;
}
let mut k = 0;
while k < pk.len() {
h = (h ^ pk[k] as u64).wrapping_mul(FNV_PRIME);
k += 1;
}
h
}
pub const SYS_SCHEMA_DIGEST: u64 = {
let mut h = 0xcbf2_9ce4_8422_2325;
let mut i = 0;
while i < SYS_FAMILIES.len() {
h = fold_family(h, &SYS_FAMILIES[i]);
i += 1;
}
h = (h ^ crate::circuit::CIRCUIT_VERSION as u64).wrapping_mul(FNV_PRIME);
h = (h ^ EXPR_BLOB_VERSION as u64).wrapping_mul(FNV_PRIME);
h
};
pub const EXPR_BLOB_VERSION: u8 = 7;
pub const SCHEMA_TAB: u64 = 1;
pub const TABLE_TAB: u64 = 2;
pub const VIEW_TAB: u64 = 3;
pub const COL_TAB: u64 = 4;
pub const IDX_TAB: u64 = 5;
pub const SEQ_TAB: u64 = 7;
pub const CIRCUIT_TAB: u64 = 11;
pub struct WireSysFamily {
pub id: u64,
pub name: &'static str,
pub cols: &'static [WireSysCol],
pub pk_cols: &'static [u32],
}
pub(crate) struct SysShape {
pub(crate) cols: &'static [WireSysCol],
pub(crate) key_len: usize,
}
const fn fam(id: u64, name: &'static str, shape: SysShape) -> WireSysFamily {
const LEADING: &[u32] = &[0, 1];
WireSysFamily {
id,
name,
cols: shape.cols,
pk_cols: LEADING.split_at(shape.key_len).0,
}
}
pub const SYS_FAMILIES: &[WireSysFamily] = &[
fam(SCHEMA_TAB, "_schemas", crate::sys_rows::SCHEMA_TAB_SHAPE),
fam(COL_TAB, "_columns", crate::sys_rows::COL_TAB_SHAPE),
fam(CIRCUIT_TAB, "_circuits", crate::sys_rows::CIRCUIT_TAB_SHAPE),
fam(TABLE_TAB, "_tables", crate::sys_rows::TABLE_TAB_SHAPE),
fam(VIEW_TAB, "_views", crate::sys_rows::VIEW_TAB_SHAPE),
fam(IDX_TAB, "_indices", crate::sys_rows::IDX_TAB_SHAPE),
fam(SEQ_TAB, "_sequences", crate::sys_rows::SEQ_TAB_SHAPE),
];
pub const fn sys_family_index(id: u64) -> Option<usize> {
let mut i = 0;
while i < SYS_FAMILIES.len() {
if SYS_FAMILIES[i].id == id {
return Some(i);
}
i += 1;
}
None
}
pub const FIRST_USER_TABLE_ID: u64 = 16;
pub const CATALOG_ID_CEILING: u64 = 1 << 31;
pub fn is_valid_ident_char(ch: u8) -> bool {
ch.is_ascii_alphanumeric() || ch == b'_'
}
pub fn validate_user_identifier(name: &str) -> Result<(), String> {
if name.is_empty() {
return Err("Identifier cannot be empty".into());
}
if name.as_bytes()[0] == b'_' {
return Err(format!(
"User identifiers cannot start with '_' (reserved for system prefix): {name}"
));
}
for &ch in name.as_bytes() {
if !is_valid_ident_char(ch) {
return Err(format!("Identifier contains invalid characters: {name}"));
}
}
Ok(())
}
pub fn canonical_identifier(name: &str) -> Result<String, String> {
validate_user_identifier(name)?;
Ok(name.to_ascii_lowercase())
}
pub fn qualified_key(schema_name: &str, name: &str) -> String {
let mut q = String::with_capacity(schema_name.len() + 1 + name.len());
q.push_str(schema_name);
q.push('.');
q.push_str(name);
q
}
pub const MAX_COLUMNS: usize = 65;
pub const fn payload_slot(pk: &[u32], ci: usize) -> Option<usize> {
if contains_col(pk, ci) {
return None;
}
let mut below = 0;
let mut k = 0;
while k < pk.len() {
below += ((pk[k] as usize) < ci) as usize;
k += 1;
}
Some(ci - below)
}
pub const fn payload_col_idx(pk: &[u32], pi: usize) -> usize {
let mut ci = 0;
let mut slot = 0;
loop {
if !contains_col(pk, ci) {
if slot == pi {
return ci;
}
slot += 1;
}
ci += 1;
}
}
const fn contains_col(cols: &[u32], ci: usize) -> bool {
let mut k = 0;
while k < cols.len() {
if cols[k] as usize == ci {
return true;
}
k += 1;
}
false
}
pub const MAX_PK_COLUMNS: usize = PK_LIST_MAX_COLS + 1;
pub const MAX_PK_BYTES: usize = MAX_PK_COLUMNS * 16;
pub const PK_LIST_MAX_COLS: usize = 4;
const PK_LIST_COUNT_BITS: u32 = 4;
const PK_LIST_COL_BITS: u32 = 7;
const PK_LIST_COL_MAX: u32 = (1 << PK_LIST_COL_BITS) - 1;
const _: () = assert!(PK_LIST_MAX_COLS >= 1);
const _: () = assert!(
PK_LIST_MAX_COLS < (1 << PK_LIST_COUNT_BITS),
"PK_LIST_MAX_COLS overflows the packed count field"
);
const _: () = assert!(
PK_LIST_COUNT_BITS as usize + PK_LIST_COL_BITS as usize * PK_LIST_MAX_COLS <= 62,
"PK_LIST_MAX_COLS overflows the packed u64 column region" );
const _: () = assert!(
MAX_COLUMNS <= 1 << PK_LIST_COL_BITS,
"a schema column index no longer fits the packed 7-bit field"
);
pub const PK_LIST_PACKED_FLAG: u64 = 1 << 63;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Probe {
Pk,
PkColumn(u32),
Index(PkColList, NonZeroU64),
}
impl Probe {
pub fn wire(self) -> (WireProbeMode, u64, u64) {
match self {
Probe::Pk => (WireProbeMode::Pk, 0, 0),
Probe::PkColumn(col) => (WireProbeMode::PkColumn, col as u64, 0),
Probe::Index(cols, cap) => (WireProbeMode::Index, cap.get(), cols.pack()),
}
}
pub fn from_wire(mode: WireProbeMode, arg0: u64, arg1: u64) -> Result<Self, String> {
let refused = || format!("no probe is carried as ({mode:?}, {arg0}, {arg1:#x})");
match (mode, arg0, arg1) {
(WireProbeMode::Pk, 0, 0) => Ok(Probe::Pk),
(WireProbeMode::PkColumn, col, 0) => Ok(Probe::PkColumn(u32::try_from(col).map_err(|_| refused())?)),
(WireProbeMode::Index, cap, cols) => {
let cap = NonZeroU64::new(cap).ok_or_else(refused)?;
let cols = PkColList::unpack(cols).map_err(|e| e.for_role(PkListRole::ColumnList))?;
Ok(Probe::Index(cols, cap))
}
_ => Err(refused()),
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct PkColList {
cols: [u32; PK_LIST_MAX_COLS],
len: usize,
}
impl PkColList {
pub fn checked(cols: &[u32], ncols: usize) -> Result<Self, PkRule> {
crate::validate_pk_indices(cols, ncols.min(MAX_COLUMNS), PK_LIST_MAX_COLS)?;
let mut arr = [0u32; PK_LIST_MAX_COLS];
arr[..cols.len()].copy_from_slice(cols);
Ok(PkColList { cols: arr, len: cols.len() })
}
pub fn from_slice(cols: &[u32]) -> Self {
Self::checked(cols, MAX_COLUMNS).unwrap_or_else(|rule| {
panic!(
"PkColList::from_slice({cols:?}): {}",
rule.for_role(PkListRole::ColumnList)
)
})
}
pub fn as_slice(&self) -> &[u32] {
&self.cols[..self.len]
}
pub fn pack(self) -> u64 {
let mut w = PK_LIST_PACKED_FLAG | self.len as u64;
for (i, &c) in self.as_slice().iter().enumerate() {
w |= (c as u64) << (PK_LIST_COUNT_BITS + PK_LIST_COL_BITS * i as u32);
}
w
}
pub fn unpack(word: u64) -> Result<Self, PkRule> {
if word & PK_LIST_PACKED_FLAG == 0 {
return Err(PkRule::NotPacked);
}
let n = (word & ((1 << PK_LIST_COUNT_BITS) - 1)) as usize;
if n > PK_LIST_MAX_COLS {
return Err(PkRule::TooManyColumns { count: n, max: PK_LIST_MAX_COLS });
}
let mut cols = [0u32; PK_LIST_MAX_COLS];
for (i, slot) in cols[..n].iter_mut().enumerate() {
*slot = ((word >> (PK_LIST_COUNT_BITS + PK_LIST_COL_BITS * i as u32)) & PK_LIST_COL_MAX as u64) as u32;
}
Self::checked(&cols[..n], MAX_COLUMNS)
}
}
const TABLE_FLAG_REPLICATED: u64 = 1;
const TABLE_FLAG_STREAM: u64 = 1 << 1;
const TABLE_FLAG_SERIAL: u64 = 1 << 2;
const TABLE_FLAG_DIST_SHIFT: u32 = 8;
const TABLE_FLAG_DIST_MASK: u64 = 0xFF;
const TABLE_FLAGS_DEFINED: u64 =
TABLE_FLAG_REPLICATED | TABLE_FLAG_STREAM | TABLE_FLAG_SERIAL | (TABLE_FLAG_DIST_MASK << TABLE_FLAG_DIST_SHIFT);
pub fn bool_word(w: u64) -> Result<bool, String> {
match w {
0 => Ok(false),
1 => Ok(true),
_ => Err(format!("boolean word {w} is neither 0 nor 1")),
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum TableDistribution {
Keyed { prefix_len: u8 },
Replicated,
}
impl Default for TableDistribution {
fn default() -> Self {
TableDistribution::Keyed { prefix_len: 0 }
}
}
#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
pub struct TableProps {
pub stream: bool,
pub serial: bool,
pub distribution: TableDistribution,
}
impl TableProps {
#[inline]
pub fn pack(self) -> u64 {
let dist = match self.distribution {
TableDistribution::Keyed { prefix_len } => (prefix_len as u64) << TABLE_FLAG_DIST_SHIFT,
TableDistribution::Replicated => TABLE_FLAG_REPLICATED,
};
dist | if self.stream { TABLE_FLAG_STREAM } else { 0 } | if self.serial { TABLE_FLAG_SERIAL } else { 0 }
}
pub fn from_flags(flags: u64) -> Result<TableProps, String> {
if flags & !TABLE_FLAGS_DEFINED != 0 {
return Err(format!("table flags {flags:#x} carry unknown bits"));
}
let prefix_len = ((flags >> TABLE_FLAG_DIST_SHIFT) & TABLE_FLAG_DIST_MASK) as u8;
let distribution = if flags & TABLE_FLAG_REPLICATED == 0 {
TableDistribution::Keyed { prefix_len }
} else if prefix_len == 0 {
TableDistribution::Replicated
} else {
return Err(replicated_with_prefix(prefix_len));
};
Ok(TableProps {
stream: flags & TABLE_FLAG_STREAM != 0,
serial: flags & TABLE_FLAG_SERIAL != 0,
distribution,
})
}
pub fn validate(&self, pk_len: usize) -> Result<(), String> {
if let TableDistribution::Keyed { prefix_len } = self.distribution {
if prefix_len as usize > pk_len {
return Err(format!(
"distribution prefix length {prefix_len} exceeds PK column count {pk_len}"
));
}
}
if self.serial && self.stream {
return Err("a stream cannot be SERIAL: it holds no rows to seed the generator from".to_string());
}
if self.serial && pk_len != 1 {
return Err("a SERIAL table's primary key is its one SERIAL column".to_string());
}
Ok(())
}
}
pub fn validate_serial_key<'a>(pk: impl IntoIterator<Item = (&'a str, ColType)>) -> Result<(), String> {
match pk.into_iter().find(|(_, ty)| crate::FixedInt::exact(ty.tc).is_none()) {
Some((name, ty)) => Err(format!(
"SERIAL primary key column '{name}' is {ty}; SERIAL needs an integer of at most 8 bytes"
)),
None => Ok(()),
}
}
pub fn replicated_with_prefix(prefix_len: u8) -> String {
format!(
"REPLICATED and CLUSTER BY are mutually exclusive: a replicated table keeps \
a full copy on every worker, so a hash-distribution prefix (k={prefix_len}) is meaningless"
)
}
#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
pub enum ViewProps {
#[default]
Plain,
Bounded { capacity_bytes: NonZeroU64 },
Fed { delta_bytes: NonZeroU64 },
}
impl ViewProps {
pub fn from_budgets(
capacity_bytes: Option<NonZeroU64>,
delta_bytes: Option<NonZeroU64>,
) -> Result<ViewProps, String> {
match (capacity_bytes, delta_bytes) {
(None, None) => Ok(ViewProps::Plain),
(Some(capacity_bytes), None) => Ok(ViewProps::Bounded { capacity_bytes }),
(None, Some(delta_bytes)) => Ok(ViewProps::Fed { delta_bytes }),
(Some(_), Some(_)) => Err("a capacity-bounded view cannot carry a delta feed: its bootstrap read \
hydrates from the source relation's live store, which no tick round governs"
.to_string()),
}
}
pub fn from_row(capacity: u64, delta: u64) -> Result<ViewProps, String> {
Self::from_budgets(NonZeroU64::new(capacity), NonZeroU64::new(delta))
}
pub fn row_words(self) -> (u64, u64) {
(self.capacity_bytes().unwrap_or(0), self.delta_bytes().unwrap_or(0))
}
pub fn capacity_bytes(self) -> Option<u64> {
match self {
ViewProps::Bounded { capacity_bytes } => Some(capacity_bytes.get()),
_ => None,
}
}
pub fn delta_bytes(self) -> Option<u64> {
match self {
ViewProps::Fed { delta_bytes } => Some(delta_bytes.get()),
_ => None,
}
}
}
#[cfg(test)]
#[path = "tests/catalog.rs"]
mod tests;