use linkrs_core::Value;
use super::Column;
pub const ZONE_MAP_CHUNK_ROWS: usize = 1024;
pub const ZONE_STALE_REBUILD_THRESHOLD: u64 = 1024;
pub fn zone_stale_rebuild_threshold() -> u64 {
ZONE_STALE_REBUILD_THRESHOLD
}
#[derive(Debug, Clone, Default)]
pub struct ZoneBounds {
pub min: Option<Value>,
pub max: Option<Value>,
}
#[derive(Debug, Clone, Default)]
pub struct ComplexZoneSummary {
pub len_min: Option<usize>,
pub len_max: Option<usize>,
pub leaf_min: Option<Value>,
pub leaf_max: Option<Value>,
pub key_fp: u64,
pub count: u64,
}
pub fn complex_len(value: &Value) -> Option<usize> {
match value {
Value::String(s) => Some(s.len()),
Value::FixedString(s) => Some(s.len()),
Value::Blob(b) => Some(b.len()),
Value::List(l) => Some(l.len()),
Value::Map(m) => Some(m.len()),
Value::Set(s) => Some(s.len()),
Value::Vector(v) => Some(v.dimension()),
Value::Json(j) => Some(j.as_str().len()),
Value::JsonB(j) => Some(j.to_json_string().len()),
Value::DataSet(ds) => Some(ds.rows.len()),
Value::Vertex(v) => Some(v.tag.properties.len()),
Value::Edge(e) => Some(e.props.len()),
Value::Path(p) => Some(p.steps.len()),
_ => None,
}
}
pub fn complex_leaf_range(value: &Value) -> Option<(Value, Value)> {
let mut leaves = Vec::new();
collect_leaves(value, &mut leaves);
let mut iter = leaves.into_iter();
let first = iter.next()?;
let mut lo = first.clone();
let mut hi = first;
for leaf in iter {
if compare_values(&leaf, &lo) == std::cmp::Ordering::Less {
lo = leaf.clone();
}
if compare_values(&leaf, &hi) == std::cmp::Ordering::Greater {
hi = leaf;
}
}
Some((lo, hi))
}
pub fn complex_key_fp(value: &Value) -> u64 {
let mut fp = 0u64;
collect_key_fp(value, &mut fp);
fp
}
fn bloom_bits_for_str(name: &str) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
name.hash(&mut hasher);
let hash = hasher.finish();
(1u64 << (hash & 63)) | (1u64 << ((hash >> 7) & 63))
}
fn bloom_bits_for_key(key: &Value) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
key.hash(&mut hasher);
let hash = hasher.finish();
(1u64 << (hash & 63)) | (1u64 << ((hash >> 7) & 63))
}
fn collect_leaves(value: &Value, out: &mut Vec<Value>) {
match value {
Value::List(l) => {
for element in &l.values {
collect_leaves(element, out);
}
}
Value::Array(a) => {
for element in &a.values {
collect_leaves(element, out);
}
}
Value::Map(m) => {
for element in m.values() {
collect_leaves(element, out);
}
}
Value::Set(s) => {
for element in s.iter() {
collect_leaves(element, out);
}
}
Value::Struct(s) => {
for (_, element) in &s.fields {
collect_leaves(element, out);
}
}
Value::Vector(v) => {
for component in v.to_dense() {
out.push(Value::Double(component as f64));
}
}
Value::DataSet(ds) => {
for row in &ds.rows {
for element in row {
collect_leaves(element, out);
}
}
}
Value::Json(j) => {
if let Ok(parsed) = j.to_value() {
collect_json_leaves(&parsed, out);
}
}
Value::JsonB(j) => {
collect_json_leaves(j.as_value(), out);
}
Value::Vertex(v) => {
for element in v.tag.properties.values() {
collect_leaves(element, out);
}
}
Value::Edge(e) => {
for element in e.props.values() {
collect_leaves(element, out);
}
}
Value::String(_) | Value::FixedString(_) | Value::Blob(_) => {
out.push(value.clone());
}
_ => {
out.push(value.clone());
}
}
}
fn collect_json_leaves(value: &serde_json::Value, out: &mut Vec<Value>) {
match value {
serde_json::Value::Number(n) => {
if let Some(number) = n.as_f64() {
out.push(Value::Double(number));
}
}
serde_json::Value::String(s) => {
out.push(Value::string(s));
}
serde_json::Value::Bool(b) => {
out.push(Value::Bool(*b));
}
serde_json::Value::Array(elements) => {
for element in elements {
collect_json_leaves(element, out);
}
}
serde_json::Value::Object(fields) => {
for element in fields.values() {
collect_json_leaves(element, out);
}
}
serde_json::Value::Null => {}
}
}
fn collect_key_fp(value: &Value, fp: &mut u64) {
match value {
Value::Map(m) => {
for key in m.keys() {
*fp |= bloom_bits_for_key(key);
}
for element in m.values() {
collect_key_fp(element, fp);
}
}
Value::Struct(s) => {
for (name, element) in &s.fields {
*fp |= bloom_bits_for_str(name);
collect_key_fp(element, fp);
}
}
Value::Json(j) => {
if let Ok(parsed) = j.to_value() {
collect_json_key_fp(&parsed, fp);
}
}
Value::JsonB(j) => {
collect_json_key_fp(j.as_value(), fp);
}
Value::DataSet(ds) => {
for name in &ds.col_names {
*fp |= bloom_bits_for_str(name);
}
}
Value::Vertex(v) => {
for (name, element) in &v.tag.properties {
*fp |= bloom_bits_for_str(name);
collect_key_fp(element, fp);
}
}
Value::Edge(e) => {
for (name, element) in &e.props {
*fp |= bloom_bits_for_str(name);
collect_key_fp(element, fp);
}
}
Value::List(l) => {
for element in &l.values {
collect_key_fp(element, fp);
}
}
Value::Array(a) => {
for element in &a.values {
collect_key_fp(element, fp);
}
}
Value::Set(s) => {
for element in s.iter() {
collect_key_fp(element, fp);
}
}
_ => {}
}
}
fn collect_json_key_fp(value: &serde_json::Value, fp: &mut u64) {
match value {
serde_json::Value::Object(fields) => {
for (name, element) in fields {
*fp |= bloom_bits_for_str(name);
collect_json_key_fp(element, fp);
}
}
serde_json::Value::Array(elements) => {
for element in elements {
collect_json_key_fp(element, fp);
}
}
_ => {}
}
}
pub fn compare_values(a: &Value, b: &Value) -> std::cmp::Ordering {
fn as_i64(value: &Value) -> Option<i64> {
match value {
Value::SmallInt(v) => Some(*v as i64),
Value::Int(v) => Some(*v as i64),
Value::BigInt(v) => Some(*v),
_ => None,
}
}
fn as_f64(value: &Value) -> Option<f64> {
match value {
Value::SmallInt(v) => Some(*v as f64),
Value::Int(v) => Some(*v as f64),
Value::BigInt(v) => Some(*v as f64),
Value::Float(v) => Some(*v as f64),
Value::Double(v) => Some(*v),
_ => None,
}
}
match (as_i64(a), as_i64(b)) {
(Some(x), Some(y)) => x.cmp(&y),
_ => match (as_f64(a), as_f64(b)) {
(Some(x), Some(y)) => x.partial_cmp(&y).unwrap_or(std::cmp::Ordering::Equal),
_ => Value::cmp(a, b),
},
}
}
#[derive(Debug, Clone, Default)]
pub(super) struct ZoneMaps {
pub maps: Vec<ZoneBounds>,
pub complex: Vec<ComplexZoneSummary>,
}
impl Column {
pub(super) fn update_zone_maps(&self, row_idx: usize, value: Option<&Value>) {
let Some(v) = value else {
return;
};
if v.is_null() {
return;
}
let mut zone = self.zone.write();
let chunk = row_idx / ZONE_MAP_CHUNK_ROWS;
if chunk >= zone.maps.len() {
zone.maps.resize_with(chunk + 1, ZoneBounds::default);
}
if chunk >= zone.complex.len() {
zone.complex
.resize_with(chunk + 1, ComplexZoneSummary::default);
}
let bounds = &mut zone.maps[chunk];
match &bounds.min {
Some(min) if compare_values(min, v) != std::cmp::Ordering::Greater => {}
_ => bounds.min = Some(v.clone()),
}
match &bounds.max {
Some(max) if compare_values(max, v) != std::cmp::Ordering::Less => {}
_ => bounds.max = Some(v.clone()),
}
if let Some(len) = complex_len(v) {
let summary = &mut zone.complex[chunk];
summary.count += 1;
match summary.len_min {
Some(cur) if cur <= len => {}
_ => summary.len_min = Some(len),
}
match summary.len_max {
Some(cur) if cur >= len => {}
_ => summary.len_max = Some(len),
}
if let Some((leaf_lo, leaf_hi)) = complex_leaf_range(v) {
match &summary.leaf_min {
Some(cur) if compare_values(cur, &leaf_lo) != std::cmp::Ordering::Greater => {}
_ => summary.leaf_min = Some(leaf_lo),
}
match &summary.leaf_max {
Some(cur) if compare_values(cur, &leaf_hi) != std::cmp::Ordering::Less => {}
_ => summary.leaf_max = Some(leaf_hi),
}
}
summary.key_fp |= complex_key_fp(v);
}
}
pub fn rebuild_zone_maps(&self) {
for row_idx in 0..self.len() {
let value = self.get(row_idx);
self.update_zone_maps(row_idx, value.as_ref());
}
}
pub fn rebuild_zone_maps_exact(&self) {
self.zone.write().maps.clear();
self.zone.write().complex.clear();
self.zone_stale_writes
.store(0, std::sync::atomic::Ordering::Relaxed);
let total = self.len();
for row_idx in 0..total {
let value = self.get(row_idx);
self.update_zone_maps(row_idx, value.as_ref());
}
let chained: Vec<(usize, Option<Value>)> = self.collect_chained_values();
for (row_idx, value) in chained {
self.update_zone_maps(row_idx, value.as_ref());
}
}
pub fn zone_needs_exact_rebuild(&self) -> bool {
self.zone_stale_writes
.load(std::sync::atomic::Ordering::Relaxed)
>= zone_stale_rebuild_threshold()
}
pub fn maybe_rebuild_zone_maps_exact(&self) -> bool {
if !self.zone_needs_exact_rebuild() {
return false;
}
self.rebuild_zone_maps_exact();
true
}
pub fn zone_maps(&self) -> Vec<ZoneBounds> {
self.zone.read().maps.clone()
}
pub fn zone_for_chunk(&self, chunk: usize) -> Option<ZoneBounds> {
self.zone.read().maps.get(chunk).cloned()
}
pub fn with_zone_for_chunk<T>(
&self,
chunk: usize,
f: impl FnOnce(Option<&ZoneBounds>) -> T,
) -> T {
let guard = self.zone.read();
f(guard.maps.get(chunk))
}
pub fn with_zone_complex_for_chunk<T>(
&self,
chunk: usize,
f: impl FnOnce(Option<&ComplexZoneSummary>) -> T,
) -> T {
let guard = self.zone.read();
f(guard.complex.get(chunk))
}
pub fn complex_len_bounds(&self, chunk: usize) -> Option<(usize, usize)> {
self.zone
.read()
.complex
.get(chunk)
.and_then(|s| match (s.len_min, s.len_max) {
(Some(lo), Some(hi)) => Some((lo, hi)),
_ => None,
})
}
}