use std::collections::BTreeSet;
use std::sync::Arc;
use super::cow::{route, CowMap, FastHashMap};
use super::id_set::IdSet;
use crate::types::PropertyValue;
use crate::{LoraBinary, ZoneId};
#[derive(Debug, Default, Clone, PartialEq)]
pub(super) struct PropertyValueBuckets {
exact: CowMap<PropertyIndexKey, IdSet>,
single: CowMap<u64, u64>,
}
#[derive(Clone, Copy)]
pub(super) enum Hits<'a> {
Set(&'a IdSet),
One(u64),
}
impl<'a> Hits<'a> {
pub(super) fn is_empty(self) -> bool {
match self {
Hits::Set(ids) => ids.is_empty(),
Hits::One(_) => false,
}
}
pub(super) fn to_vec(self) -> Vec<u64> {
match self {
Hits::Set(ids) => ids.to_vec(),
Hits::One(id) => vec![id],
}
}
pub(super) fn iter(self) -> impl Iterator<Item = u64> + 'a {
let (set, one) = match self {
Hits::Set(ids) => (Some(ids.iter()), None),
Hits::One(id) => (None, Some(id)),
};
set.into_iter().flatten().chain(one)
}
}
fn digest(value: &PropertyIndexKey) -> u64 {
use std::hash::BuildHasher;
match value {
PropertyIndexKey::String(s) => digest_str(s),
other => std::hash::BuildHasherDefault::<super::cow::KeyHasher>::default().hash_one(other),
}
}
fn digest_str(value: &str) -> u64 {
use std::hash::BuildHasher;
std::hash::BuildHasherDefault::<super::cow::KeyHasher>::default().hash_one(value)
}
pub(super) enum Probe<'a> {
Str(&'a str),
Key(PropertyIndexKey),
}
impl<'a> Probe<'a> {
pub(super) fn of(value: &'a PropertyValue) -> Option<Self> {
match value {
PropertyValue::String(s) => Some(Probe::Str(s)),
other => PropertyIndexKey::from_value(other).map(Probe::Key),
}
}
pub(super) fn of_ref(value: crate::ValueRef<'a>) -> Option<Self> {
match value {
crate::ValueRef::String(s) => Some(Probe::Str(s)),
other => PropertyIndexKey::from_value_ref(other).map(Probe::Key),
}
}
fn key(&self) -> std::borrow::Cow<'_, PropertyIndexKey> {
match self {
Probe::Str(s) => std::borrow::Cow::Owned(PropertyIndexKey::String(Arc::from(*s))),
Probe::Key(key) => std::borrow::Cow::Borrowed(key),
}
}
fn digest(&self) -> Option<u64> {
match self {
Probe::Str(s) => Some(digest_str(s)),
Probe::Key(key) => key.has_heap_image().then(|| digest(key)),
}
}
fn matches(&self, value: crate::ValueRef<'_>) -> bool {
match self {
Probe::Str(s) => matches!(value, crate::ValueRef::String(v) if v == *s),
Probe::Key(key) => key.matches(value),
}
}
}
impl PropertyValueBuckets {
pub(super) fn is_empty(&self) -> bool {
self.exact.is_empty() && self.single.is_empty()
}
fn with_shape(exact: Vec<u64>, single: Vec<u64>) -> Self {
Self {
exact: CowMap::with_shape(exact),
single: CowMap::with_shape(single),
}
}
fn shape_route(value: &Probe<'_>) -> (bool, u64) {
match value.digest() {
Some(d) => (true, route(&d)),
None => (false, route(&*value.key())),
}
}
pub(super) fn insert(&mut self, id: u64, value: &Probe<'_>, holds: &dyn Fn(u64) -> bool) {
let Some(d) = value.digest() else {
self.exact.upsert(
value.key().into_owned(),
|| IdSet::new(id),
|ids| {
ids.insert(id);
},
);
return;
};
if !self.exact.is_empty() {
if let Some(ids) = self.exact.get_mut(&*value.key()) {
ids.insert(id);
return;
}
}
match self.single.get(&d).copied() {
None => {
self.single.insert(d, id);
}
Some(other) if other == id => {}
Some(other) => {
let mut ids = IdSet::new(id);
if holds(other) {
self.single.remove(&d);
ids.insert(other);
}
self.exact.insert(value.key().into_owned(), ids);
}
}
}
pub(super) fn remove(&mut self, id: u64, value: &Probe<'_>) {
let digest = value.digest();
if !self.exact.is_empty() {
let key = value.key();
if let Some(ids) = self.exact.get_mut(&*key) {
if ids.remove(id) {
self.exact.remove(&*key);
} else if let (1, Some(d)) = (ids.len(), digest) {
let last = ids.iter().next().expect("one id left");
if !self.single.contains_key(&d) {
self.exact.remove(&*key);
self.single.insert(d, last);
}
}
}
}
if let Some(d) = digest {
if self.single.get(&d) == Some(&id) {
self.single.remove(&d);
}
}
}
fn lists(&self, id: u64, value: &Probe<'_>) -> bool {
(!self.exact.is_empty() && self.exact.contains_key(&*value.key()))
|| value
.digest()
.is_some_and(|d| self.single.get(&d) == Some(&id))
}
pub(super) fn get(&self, value: &Probe<'_>, holds: impl Fn(u64) -> bool) -> Option<Hits<'_>> {
if !self.exact.is_empty() {
if let Some(ids) = self.exact.get(&*value.key()) {
return Some(Hits::Set(ids));
}
}
let id = *self.single.get(&value.digest()?)?;
holds(id).then_some(Hits::One(id))
}
pub(super) fn entries(&self) -> impl Iterator<Item = (Option<&PropertyIndexKey>, u64)> + '_ {
self.exact
.iter()
.flat_map(|(value, ids)| ids.iter().map(move |id| (Some(value), id)))
.chain(self.single.iter().map(|(_, id)| (None, *id)))
}
pub(super) fn exact(&self) -> &CowMap<PropertyIndexKey, IdSet> {
&self.exact
}
pub(super) fn single(&self) -> &CowMap<u64, u64> {
&self.single
}
}
pub(super) type PropertyIndex = FastHashMap<Arc<str>, PropertyValueBuckets>;
pub(super) type ScopedPropertyIndex = FastHashMap<Arc<str>, Arc<PropertyIndex>>;
#[derive(Default)]
pub(super) struct PropertyIndexRegistry {
pub(super) node_properties: PropertyIndexState,
pub(super) relationship_properties: PropertyIndexState,
}
impl std::fmt::Debug for PropertyIndexRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PropertyIndexRegistry")
.field("node_properties", &self.node_properties)
.field("relationship_properties", &self.relationship_properties)
.finish()
}
}
impl Clone for PropertyIndexRegistry {
fn clone(&self) -> Self {
Self {
node_properties: self.node_properties.clone(),
relationship_properties: self.relationship_properties.clone(),
}
}
}
#[derive(Debug, Default, Clone)]
pub(super) struct PropertyIndexState {
pub(super) active_keys: Arc<BTreeSet<String>>,
pub(super) scoped_values: Arc<ScopedPropertyIndex>,
pub(super) any_scope_keys: Arc<BTreeSet<String>>,
pub(super) any_scope: Arc<PropertyIndex>,
}
pub(super) const UNLABELLED: &str = "";
fn or_unlabelled<'a>(scopes: impl IntoIterator<Item = &'a str>) -> impl Iterator<Item = &'a str> {
let mut scopes = scopes.into_iter().peekable();
let unlabelled = scopes.peek().is_none().then_some(UNLABELLED);
scopes.chain(unlabelled)
}
impl PropertyIndexState {
pub(super) fn is_active(&self, key: &str) -> bool {
self.active_keys.contains(key)
}
pub(super) fn activate(&mut self, key: &str) -> bool {
if self.active_keys.contains(key) {
return false;
}
Arc::make_mut(&mut self.active_keys).insert(key.to_string())
}
pub(super) fn deactivate(&mut self, key: &str) -> bool {
if !self.active_keys.contains(key) {
return false;
}
Arc::make_mut(&mut self.active_keys).remove(key);
if self.any_scope_keys.contains(key) {
Arc::make_mut(&mut self.any_scope_keys).remove(key);
Arc::make_mut(&mut self.any_scope).remove(key);
}
let holds_key: Vec<Arc<str>> = self
.scoped_values
.iter()
.filter(|(_, values)| values.contains_key(key))
.map(|(scope, _)| scope.clone())
.collect();
for scope in holds_key {
let scoped = Arc::make_mut(&mut self.scoped_values);
let values = Arc::make_mut(scoped.get_mut(&*scope).expect("scope listed above"));
values.remove(key);
if values.is_empty() {
scoped.remove(&*scope);
}
}
true
}
pub(super) fn any_scope_is_active(&self, key: &str) -> bool {
self.any_scope_keys.contains(key)
}
pub(super) fn activate_any_scope(&mut self, key: &str, records: &dyn EntityValues) {
if self.any_scope_keys.contains(key) {
return;
}
let mut merged = PropertyValueBuckets::default();
for values in self.scoped_values.values() {
let Some(buckets) = values.get(key) else {
continue;
};
for (value, id) in buckets.entries() {
let value = match value {
Some(value) => value.clone(),
None => match records.value(id, key) {
Some(value) => value,
None => continue,
},
};
let probe = Probe::Key(value);
merged.insert(id, &probe, &|other| {
records.get(other, key).is_some_and(|v| probe.matches(v))
});
}
}
if !merged.is_empty() {
Arc::make_mut(&mut self.any_scope).insert(Arc::from(key), merged);
}
Arc::make_mut(&mut self.any_scope_keys).insert(key.to_string());
}
fn scope_mut<'s>(
scoped: &'s mut Arc<ScopedPropertyIndex>,
scope: &str,
) -> &'s mut PropertyIndex {
let scoped = Arc::make_mut(scoped);
if !scoped.contains_key(scope) {
scoped.insert(Arc::from(scope), Arc::default());
}
Arc::make_mut(scoped.get_mut(scope).expect("scope inserted above"))
}
fn insert_value(
values: &mut PropertyIndex,
entity_id: u64,
key: &str,
value: &Probe<'_>,
records: &dyn EntityValues,
) {
let buckets = match values.get_mut(key) {
Some(buckets) => buckets,
None => values.entry(Arc::from(key)).or_default(),
};
buckets.insert(entity_id, value, &|other| {
records.get(other, key).is_some_and(|v| value.matches(v))
});
}
pub(super) fn insert_bulk<'a, I, S>(
&mut self,
key: &str,
entries: impl Fn() -> I,
records: &dyn EntityValues,
) where
I: Iterator<Item = (u64, S, crate::ValueRef<'a>)>,
S: IntoIterator<Item = &'a str>,
{
type Routes = (Vec<u64>, Vec<u64>);
let mut scoped: FastHashMap<&'a str, Routes> = FastHashMap::default();
for (_, scopes, value) in entries() {
let Some(probe) = Probe::of_ref(value) else {
continue;
};
let (single, route) = PropertyValueBuckets::shape_route(&probe);
for scope in or_unlabelled(scopes) {
let routes = scoped.entry(scope).or_default();
if single {
routes.1.push(route);
} else {
routes.0.push(route);
}
}
}
for (scope, (exact, single)) in scoped {
let values = Self::scope_mut(&mut self.scoped_values, scope);
if !values.contains_key(key) {
values.insert(
Arc::from(key),
PropertyValueBuckets::with_shape(exact, single),
);
}
}
for (entity_id, scopes, value) in entries() {
let Some(probe) = Probe::of_ref(value) else {
continue;
};
self.insert_probe_with_scopes(entity_id, scopes, key, &probe, records);
}
}
fn lists(values: &PropertyIndex, entity_id: u64, key: &str, value: &Probe<'_>) -> bool {
values
.get(key)
.is_some_and(|buckets| buckets.lists(entity_id, value))
}
fn remove_value(values: &mut PropertyIndex, entity_id: u64, key: &str, value: &Probe<'_>) {
let mut remove_key = false;
if let Some(buckets) = values.get_mut(key) {
buckets.remove(entity_id, value);
remove_key = buckets.is_empty();
}
if remove_key {
values.remove(key);
}
}
pub(super) fn insert_scoped(
&mut self,
entity_id: u64,
scope: &str,
key: &str,
value: &PropertyValue,
records: &dyn EntityValues,
) {
let Some(probe) = Probe::of(value) else {
return;
};
let scoped = Self::scope_mut(&mut self.scoped_values, scope);
Self::insert_value(scoped, entity_id, key, &probe, records);
}
pub(super) fn insert_with_scopes<'a>(
&mut self,
entity_id: u64,
scopes: impl IntoIterator<Item = &'a str>,
key: &str,
value: &PropertyValue,
records: &dyn EntityValues,
) {
let Some(probe) = Probe::of(value) else {
return;
};
self.insert_probe_with_scopes(entity_id, scopes, key, &probe, records);
}
fn insert_probe_with_scopes<'a>(
&mut self,
entity_id: u64,
scopes: impl IntoIterator<Item = &'a str>,
key: &str,
probe: &Probe<'_>,
records: &dyn EntityValues,
) {
if self.any_scope_keys.contains(key) {
Self::insert_value(
Arc::make_mut(&mut self.any_scope),
entity_id,
key,
probe,
records,
);
}
for scope in or_unlabelled(scopes) {
let scoped = Self::scope_mut(&mut self.scoped_values, scope);
Self::insert_value(scoped, entity_id, key, probe, records);
}
}
fn remove_from_scope(&mut self, entity_id: u64, scope: &str, key: &str, value: &Probe<'_>) {
if !self
.scoped_values
.get(scope)
.is_some_and(|values| Self::lists(values, entity_id, key, value))
{
return;
}
let scoped = Arc::make_mut(&mut self.scoped_values);
let mut remove_scope = false;
if let Some(values) = scoped.get_mut(scope) {
let values = Arc::make_mut(values);
Self::remove_value(values, entity_id, key, value);
remove_scope = values.is_empty();
}
if remove_scope {
scoped.remove(scope);
}
}
pub(super) fn remove_scoped(
&mut self,
entity_id: u64,
scope: &str,
key: &str,
value: &PropertyValue,
) {
let Some(probe) = Probe::of(value) else {
return;
};
self.remove_from_scope(entity_id, scope, key, &probe);
}
pub(super) fn remove_with_scopes<'a>(
&mut self,
entity_id: u64,
scopes: impl IntoIterator<Item = &'a str>,
key: &str,
value: &PropertyValue,
) {
let Some(probe) = Probe::of(value) else {
return;
};
if Self::lists(&self.any_scope, entity_id, key, &probe) {
Self::remove_value(Arc::make_mut(&mut self.any_scope), entity_id, key, &probe);
}
for scope in or_unlabelled(scopes) {
self.remove_from_scope(entity_id, scope, key, &probe);
}
}
pub(super) fn ids_for(
&self,
key: &str,
value: &PropertyValue,
records: &dyn EntityValues,
) -> Option<Hits<'_>> {
debug_assert!(
self.any_scope_keys.contains(key),
"lookup of `{key}` across scopes before its map was built"
);
let probe = Probe::of(value)?;
self.any_scope.get(key)?.get(&probe, |id| {
records.get(id, key).is_some_and(|v| probe.matches(v))
})
}
pub(super) fn scoped_ids_for(
&self,
scope: &str,
key: &str,
value: &PropertyValue,
records: &dyn EntityValues,
) -> Option<Hits<'_>> {
if scope == UNLABELLED {
return None;
}
let probe = Probe::of(value)?;
self.scoped_values.get(scope)?.get(key)?.get(&probe, |id| {
records.get(id, key).is_some_and(|v| probe.matches(v))
})
}
}
pub(super) trait EntityValues {
fn get(&self, id: u64, key: &str) -> Option<crate::ValueRef<'_>>;
fn value(&self, id: u64, key: &str) -> Option<PropertyIndexKey> {
PropertyIndexKey::from_value_ref(self.get(id, key)?)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(super) enum PropertyIndexKey {
Null,
Bool(bool),
Int(i64),
Float(u64),
String(std::sync::Arc<str>),
Binary(Box<LoraBinary>),
List(Box<[PropertyIndexKey]>),
Map(Box<[(String, PropertyIndexKey)]>),
Temporal {
kind: TemporalKind,
nanos: Nanos,
offset: i32,
zone: Option<ZoneId>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(super) struct Nanos {
high: i64,
low: u64,
}
impl From<i128> for Nanos {
fn from(nanos: i128) -> Self {
Self {
high: (nanos >> 64) as i64,
low: nanos as u64,
}
}
}
#[cfg(target_pointer_width = "64")]
const _: () = assert!(std::mem::size_of::<PropertyIndexKey>() == 24);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(super) enum TemporalKind {
Date,
LocalTime,
Time,
LocalDateTime,
DateTime,
}
impl PartialOrd for PropertyIndexKey {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PropertyIndexKey {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering;
let tag = |k: &PropertyIndexKey| match k {
PropertyIndexKey::Null => 0,
PropertyIndexKey::Bool(_) => 1,
PropertyIndexKey::Int(_) | PropertyIndexKey::Float(_) => 2,
PropertyIndexKey::String(_) => 4,
PropertyIndexKey::Binary(_) => 5,
PropertyIndexKey::List(_) => 6,
PropertyIndexKey::Map(_) => 7,
PropertyIndexKey::Temporal { .. } => 8,
};
match tag(self).cmp(&tag(other)) {
Ordering::Equal => match (self, other) {
(PropertyIndexKey::Null, PropertyIndexKey::Null) => Ordering::Equal,
(PropertyIndexKey::Bool(a), PropertyIndexKey::Bool(b)) => a.cmp(b),
(PropertyIndexKey::Int(a), PropertyIndexKey::Int(b)) => a.cmp(b),
(PropertyIndexKey::Float(a), PropertyIndexKey::Float(b)) => a.cmp(b),
(PropertyIndexKey::Int(a), PropertyIndexKey::Float(b)) => {
int_float_cmp(*a, float_from_sortable_bits(*b)).then(Ordering::Less)
}
(PropertyIndexKey::Float(a), PropertyIndexKey::Int(b)) => {
int_float_cmp(*b, float_from_sortable_bits(*a))
.reverse()
.then(Ordering::Greater)
}
(PropertyIndexKey::String(a), PropertyIndexKey::String(b)) => a.cmp(b),
(PropertyIndexKey::Binary(a), PropertyIndexKey::Binary(b)) => {
let aa: Vec<u8> = a.segments().iter().flatten().copied().collect();
let bb: Vec<u8> = b.segments().iter().flatten().copied().collect();
aa.cmp(&bb)
}
(PropertyIndexKey::List(a), PropertyIndexKey::List(b)) => a.cmp(b),
(PropertyIndexKey::Map(a), PropertyIndexKey::Map(b)) => a.cmp(b),
(
PropertyIndexKey::Temporal {
kind: ak,
nanos: an,
offset: ao,
zone: az,
},
PropertyIndexKey::Temporal {
kind: bk,
nanos: bn,
offset: bo,
zone: bz,
},
) => ak
.cmp(bk)
.then(an.cmp(bn))
.then(ao.cmp(bo))
.then(az.cmp(bz)),
_ => Ordering::Equal, },
ord => ord,
}
}
}
impl PropertyIndexKey {
#[inline]
pub(super) fn has_heap_image(&self) -> bool {
matches!(
self,
Self::String(_) | Self::Binary(_) | Self::List(_) | Self::Map(_)
)
}
#[inline]
pub(super) fn matches(&self, value: crate::ValueRef<'_>) -> bool {
match (self, value) {
(Self::String(key), crate::ValueRef::String(value)) => **key == *value,
(Self::String(_), _) => false,
(key, value) => Self::from_value_ref(value).as_ref() == Some(key),
}
}
pub(super) fn from_value_ref(value: crate::ValueRef<'_>) -> Option<Self> {
use crate::ValueRef;
match value {
ValueRef::Null => Some(Self::Null),
ValueRef::Bool(v) => Some(Self::Bool(v)),
ValueRef::Int(v) => Some(Self::Int(v)),
ValueRef::Float(v) => Self::from_value(&PropertyValue::Float(v)),
ValueRef::String(v) => Some(Self::String(std::sync::Arc::from(v))),
ValueRef::Other(v) => Self::from_value(&v.get()),
}
}
pub(super) fn from_value(value: &PropertyValue) -> Option<Self> {
match value {
PropertyValue::Null => Some(Self::Null),
PropertyValue::Bool(v) => Some(Self::Bool(*v)),
PropertyValue::Int(v) => Some(Self::Int(*v)),
PropertyValue::Float(v) => {
if v.is_nan() {
None
} else {
Some(Self::Float(sortable_f64_bits(*v)))
}
}
PropertyValue::String(v) => Some(Self::String(std::sync::Arc::from(v.as_str()))),
PropertyValue::Binary(v) => Some(Self::Binary(Box::new(v.clone()))),
PropertyValue::List(values) => values
.iter()
.map(Self::from_value)
.collect::<Option<Box<[_]>>>()
.map(Self::List),
PropertyValue::Map(values) => values
.iter()
.map(|(k, v)| Self::from_value(v).map(|indexed| (k.clone(), indexed)))
.collect::<Option<Box<[_]>>>()
.map(Self::Map),
PropertyValue::Date(v) => Some(Self::temporal(TemporalKind::Date, v.order_nanos(), 0)),
PropertyValue::LocalTime(v) => {
Some(Self::temporal(TemporalKind::LocalTime, v.order_nanos(), 0))
}
PropertyValue::Time(v) => Some(Self::temporal(
TemporalKind::Time,
v.order_nanos(),
v.offset_seconds,
)),
PropertyValue::LocalDateTime(v) => Some(Self::temporal(
TemporalKind::LocalDateTime,
v.order_nanos(),
0,
)),
PropertyValue::DateTime(v) => Some(Self::Temporal {
kind: TemporalKind::DateTime,
nanos: v.order_nanos().into(),
offset: v.offset_seconds,
zone: v.zone,
}),
PropertyValue::Duration(_) | PropertyValue::Point(_) | PropertyValue::Vector(_) => None,
}
}
fn temporal(kind: TemporalKind, nanos: impl Into<Nanos>, offset: i32) -> Self {
Self::Temporal {
kind,
nanos: nanos.into(),
offset,
zone: None,
}
}
pub(super) fn range_lower(value: &PropertyValue) -> Option<Self> {
match Self::from_value(value)? {
Self::Temporal { kind, nanos, .. } => Some(Self::temporal(kind, nanos, i32::MIN)),
Self::Float(bits) => {
let f = float_from_sortable_bits(bits);
let whole = f as i64;
Some(if int_float_cmp(whole, f) == std::cmp::Ordering::Equal {
Self::Int(whole)
} else {
Self::Float(bits)
})
}
key => Some(key),
}
}
pub(super) fn range_upper(value: &PropertyValue) -> Option<Self> {
match Self::from_value(value)? {
Self::Temporal { kind, nanos, .. } => Some(Self::temporal(kind, nanos, i32::MAX)),
Self::Int(i) => {
let f = i as f64;
Some(if int_float_cmp(i, f) == std::cmp::Ordering::Equal {
Self::Float(sortable_f64_bits(f))
} else {
Self::Int(i)
})
}
key => Some(key),
}
}
pub(super) fn kind_floor(&self) -> Option<Self> {
match self {
Self::Temporal { kind, .. } => Some(Self::temporal(*kind, i128::MIN, i32::MIN)),
Self::Int(_) | Self::Float(_) => {
Some(Self::Float(sortable_f64_bits(f64::NEG_INFINITY)))
}
_ => None,
}
}
pub(super) fn kind_ceiling(&self) -> Option<Self> {
match self {
Self::Temporal { kind, .. } => Some(Self::temporal(*kind, i128::MAX, i32::MAX)),
Self::Int(_) | Self::Float(_) => Some(Self::Float(sortable_f64_bits(f64::INFINITY))),
_ => None,
}
}
pub(super) fn is_temporal(&self) -> bool {
matches!(self, Self::Temporal { .. })
}
pub(super) fn all_temporals() -> (Self, Self) {
(
Self::temporal(TemporalKind::Date, i128::MIN, i32::MIN),
Self::temporal(TemporalKind::DateTime, i128::MAX, i32::MAX),
)
}
}
fn sortable_f64_bits(value: f64) -> u64 {
let bits = if value == 0.0 {
0.0f64.to_bits()
} else {
value.to_bits()
};
if bits & (1 << 63) == 0 {
bits | (1 << 63)
} else {
!bits
}
}
fn float_from_sortable_bits(bits: u64) -> f64 {
f64::from_bits(if bits & (1 << 63) != 0 {
bits & !(1 << 63)
} else {
!bits
})
}
fn int_float_cmp(i: i64, f: f64) -> std::cmp::Ordering {
use std::cmp::Ordering;
const TWO_63: f64 = 9_223_372_036_854_775_808.0;
if f >= TWO_63 {
return Ordering::Less;
}
if f < -TWO_63 {
return Ordering::Greater;
}
let whole = f.trunc();
i.cmp(&(whole as i64))
.then_with(|| 0.0f64.partial_cmp(&(f - whole)).unwrap_or(Ordering::Equal))
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeMap;
use std::hash::{BuildHasher, BuildHasherDefault};
use super::super::cow::KeyHasher;
fn key(value: PropertyValue) -> PropertyIndexKey {
PropertyIndexKey::from_value(&value).expect("indexable")
}
#[test]
fn list_and_map_keys_order_like_their_values() {
let lists: Vec<Vec<i64>> = vec![vec![], vec![0], vec![1, 2], vec![1, 3], vec![2]];
let maps: Vec<BTreeMap<String, i64>> = [
&[][..],
&[("a", 1)],
&[("a", 1), ("b", 0)],
&[("a", 1), ("b", 3)],
&[("a", 2)],
&[("b", 1)],
]
.iter()
.map(|entries| entries.iter().map(|&(k, v)| (k.to_string(), v)).collect())
.collect();
let list_value =
|l: &Vec<i64>| PropertyValue::List(l.iter().map(|&v| PropertyValue::Int(v)).collect());
let map_value = |m: &BTreeMap<String, i64>| {
PropertyValue::Map(
m.iter()
.map(|(k, &v)| (k.clone(), PropertyValue::Int(v)))
.collect(),
)
};
let hash = |k: &PropertyIndexKey| BuildHasherDefault::<KeyHasher>::default().hash_one(k);
for a in &lists {
for b in &lists {
let (ka, kb) = (key(list_value(a)), key(list_value(b)));
assert_eq!(ka.cmp(&kb), a.cmp(b), "{a:?} vs {b:?}");
assert_eq!(ka == kb, hash(&ka) == hash(&kb));
}
}
for a in &maps {
for b in &maps {
let (ka, kb) = (key(map_value(a)), key(map_value(b)));
assert_eq!(ka.cmp(&kb), a.cmp(b), "{a:?} vs {b:?}");
assert_eq!(ka == kb, hash(&ka) == hash(&kb));
}
}
}
fn ids(hits: Option<Hits<'_>>) -> Vec<u64> {
hits.map(Hits::to_vec).unwrap_or_default()
}
fn string(s: &str) -> Probe<'_> {
Probe::Str(s)
}
#[test]
fn a_shared_value_moves_between_the_digest_and_exact_tiers() {
let mut b = PropertyValueBuckets::default();
let same = |_: u64| true;
b.insert(1, &string("a"), &same);
b.insert(2, &string("b"), &same);
assert_eq!((b.exact.len(), b.single.len()), (0, 2));
assert_eq!(ids(b.get(&string("a"), same)), vec![1]);
b.insert(3, &string("a"), &same);
assert_eq!((b.exact.len(), b.single.len()), (1, 1));
assert_eq!(ids(b.get(&string("a"), |_| false)), vec![1, 3]);
b.remove(1, &string("a"));
assert_eq!((b.exact.len(), b.single.len()), (0, 2));
assert_eq!(ids(b.get(&string("a"), same)), vec![3]);
b.remove(3, &string("a"));
b.remove(2, &string("b"));
assert!(b.is_empty());
}
#[test]
fn a_digest_entry_is_confirmed_by_the_record() {
let mut b = PropertyValueBuckets::default();
b.insert(7, &string("k"), &|_| true);
assert_eq!(ids(b.get(&string("k"), |id| id == 7)), vec![7]);
assert!(b.get(&string("k"), |_| false).is_none());
}
#[test]
fn a_digest_collision_keeps_both_values() {
let mut b = PropertyValueBuckets::default();
b.insert(1, &string("x"), &|_| true);
b.insert(2, &string("x"), &|_| false);
assert_eq!((b.exact.len(), b.single.len()), (1, 1));
assert_eq!(ids(b.get(&string("x"), |_| true)), vec![2]);
b.remove(2, &string("x"));
assert_eq!((b.exact.len(), b.single.len()), (0, 1));
b.remove(1, &string("x"));
assert!(b.is_empty());
}
#[test]
fn inline_values_stay_exact() {
let mut b = PropertyValueBuckets::default();
let int = Probe::Key(PropertyIndexKey::Int(5));
b.insert(1, &int, &|_| unreachable!("no digest entry to confirm"));
assert_eq!((b.exact.len(), b.single.len()), (1, 0));
assert_eq!(ids(b.get(&int, |_| unreachable!())), vec![1]);
}
#[test]
fn string_probes_and_keys_share_a_digest() {
let key = PropertyIndexKey::String(Arc::from("same"));
assert_eq!(Probe::Key(key.clone()).digest(), string("same").digest());
assert_eq!(digest(&key), digest_str("same"));
}
#[test]
fn a_unique_string_key_costs_a_digest_entry() {
let mut b = PropertyValueBuckets::default();
let keys: Vec<String> = (0..1000)
.map(|i| format!("musicbrainz:release-group:{i}"))
.collect();
for (i, k) in keys.iter().enumerate() {
b.insert(i as u64, &string(k), &|_| true);
}
assert_eq!((b.exact.len(), b.single.len()), (0, 1000));
assert_eq!(
std::mem::size_of::<(u64, u64)>(),
16,
"a digest entry is a digest and an id"
);
}
}