mirror of
https://github.com/neondatabase/neon.git
synced 2026-01-14 17:02:56 +00:00
persistent_range_query: first draft
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -2255,6 +2255,13 @@ version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "persistent_range_query"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"workspace_hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.2"
|
||||
|
||||
9
libs/persistent_range_query/Cargo.toml
Normal file
9
libs/persistent_range_query/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "persistent_range_query"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
workspace_hack = { version = "0.1", path = "../../workspace_hack" }
|
||||
68
libs/persistent_range_query/src/lib.rs
Normal file
68
libs/persistent_range_query/src/lib.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use std::ops::Range;
|
||||
|
||||
pub mod naive;
|
||||
pub mod ops;
|
||||
|
||||
/// Should be a monoid:
|
||||
/// * Identity element: for all a: combine(new_for_empty_range(), a) = combine(a, new_for_empty_range()) = a
|
||||
/// * Associativity: for all a, b, c: combine(combine(a, b), c) == combine(a, combine(b, c))
|
||||
pub trait RangeQueryResult<Key>: Sized {
|
||||
fn new_for_empty_range() -> Self;
|
||||
|
||||
// Contract: left_range.end == right_range.start
|
||||
// left_range.start == left_range.end == right_range.start == right_range.end is still possible
|
||||
fn combine(left: &Self, left_range: &Range<Key>, right: &Self, right_range: &Range<Key>) -> Self
|
||||
where
|
||||
Self: Clone,
|
||||
{
|
||||
let mut left = left.clone();
|
||||
Self::add(&mut left, left_range, right, right_range);
|
||||
left
|
||||
}
|
||||
|
||||
// TODO: does it work with non-Clone?
|
||||
fn add(left: &mut Self, left_range: &Range<Key>, right: &Self, right_range: &Range<Key>)
|
||||
where
|
||||
Self: Clone,
|
||||
{
|
||||
*left = Self::combine(left, left_range, right, right_range);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LazyRangeInitializer<Key, R> {
|
||||
fn get(&self, range: &Range<Key>) -> R;
|
||||
}
|
||||
|
||||
/// Should be a monoid:
|
||||
/// * Identity element: for all op: compose(no_op(), op) == compose(op, no_op()) == op
|
||||
/// * Associativity: for all op_1, op_2, op_3: compose(compose(op_1, op_2), op_3) == compose(op_1, compose(op_2, op_3))
|
||||
///
|
||||
/// Should left act on Result:
|
||||
/// * Identity operation: for all r: no_op().apply(r) == r
|
||||
/// * Compatibility: for all op_1, op_2, r: op_1.apply(op_2.apply(r)) == compose(op_1, op_2).apply(r)
|
||||
pub trait RangeModification<Result, Key> {
|
||||
fn no_op() -> Self;
|
||||
fn apply<'a>(&self, result: &mut Result, range: &Range<Key>);
|
||||
fn compose(later: &Self, earlier: &mut Self);
|
||||
}
|
||||
|
||||
pub trait VecVersion<
|
||||
Key,
|
||||
Result: RangeQueryResult<Key>,
|
||||
Modification: RangeModification<Result, Key>,
|
||||
>: Clone
|
||||
{
|
||||
fn get(&self, keys: Range<Key>) -> Result;
|
||||
fn modify(&mut self, keys: Range<Key>, modification: Modification);
|
||||
}
|
||||
|
||||
pub trait PersistentVecStorage<
|
||||
Key,
|
||||
Result: RangeQueryResult<Key>,
|
||||
Initializer: LazyRangeInitializer<Key, Result>,
|
||||
Modification: RangeModification<Result, Key>,
|
||||
>
|
||||
{
|
||||
type Version: VecVersion<Key, Result, Modification>;
|
||||
fn new(all_keys: Range<Key>, initializer: Initializer) -> Self::Version;
|
||||
}
|
||||
93
libs/persistent_range_query/src/naive.rs
Normal file
93
libs/persistent_range_query/src/naive.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use crate::{
|
||||
LazyRangeInitializer, PersistentVecStorage, RangeModification, RangeQueryResult, VecVersion,
|
||||
};
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Range;
|
||||
|
||||
pub struct NaiveVecVersion<
|
||||
Key: Clone,
|
||||
Result: RangeQueryResult<Key> + Clone,
|
||||
Modification: RangeModification<Result, Key>,
|
||||
> {
|
||||
all_keys: Range<Key>,
|
||||
values: Vec<Result>,
|
||||
_modification: PhantomData<Modification>,
|
||||
}
|
||||
|
||||
impl<
|
||||
Key: Clone,
|
||||
Result: RangeQueryResult<Key> + Clone,
|
||||
Modification: RangeModification<Result, Key>,
|
||||
> Clone for NaiveVecVersion<Key, Result, Modification>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
all_keys: self.all_keys.clone(),
|
||||
values: self.values.clone(),
|
||||
_modification: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IndexableKey: Clone {
|
||||
fn index(all_keys: &Range<Self>, key: &Self) -> usize;
|
||||
fn element_range(all_keys: &Range<Self>, index: usize) -> Range<Self>;
|
||||
}
|
||||
|
||||
impl<
|
||||
Key: IndexableKey,
|
||||
Result: Clone + RangeQueryResult<Key>,
|
||||
Modification: RangeModification<Result, Key>,
|
||||
> VecVersion<Key, Result, Modification> for NaiveVecVersion<Key, Result, Modification>
|
||||
{
|
||||
fn get(&self, keys: Range<Key>) -> Result {
|
||||
let mut result = Result::new_for_empty_range();
|
||||
let mut result_range = keys.start.clone()..keys.start.clone();
|
||||
for index in IndexableKey::index(&self.all_keys, &keys.start)
|
||||
..IndexableKey::index(&self.all_keys, &keys.end)
|
||||
{
|
||||
let element_range = IndexableKey::element_range(&self.all_keys, index);
|
||||
Result::add(
|
||||
&mut result,
|
||||
&result_range,
|
||||
&self.values[index],
|
||||
&element_range,
|
||||
);
|
||||
result_range.end = element_range.end;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn modify(&mut self, keys: Range<Key>, modification: Modification) {
|
||||
for index in IndexableKey::index(&self.all_keys, &keys.start)
|
||||
..IndexableKey::index(&self.all_keys, &keys.end)
|
||||
{
|
||||
let element_range = IndexableKey::element_range(&self.all_keys, index);
|
||||
modification.apply(&mut self.values[index], &element_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NaiveVecStorage;
|
||||
|
||||
impl<
|
||||
Key: IndexableKey,
|
||||
Result: Clone + RangeQueryResult<Key>,
|
||||
Initializer: LazyRangeInitializer<Key, Result>,
|
||||
Modification: RangeModification<Result, Key>,
|
||||
> PersistentVecStorage<Key, Result, Initializer, Modification> for NaiveVecStorage
|
||||
{
|
||||
type Version = NaiveVecVersion<Key, Result, Modification>;
|
||||
|
||||
fn new(all_keys: Range<Key>, initializer: Initializer) -> Self::Version {
|
||||
let mut values = Vec::with_capacity(IndexableKey::index(&all_keys, &all_keys.end));
|
||||
for index in 0..values.capacity() {
|
||||
values.push(initializer.get(&IndexableKey::element_range(&all_keys, index)));
|
||||
}
|
||||
NaiveVecVersion {
|
||||
all_keys,
|
||||
values,
|
||||
_modification: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
14
libs/persistent_range_query/src/ops/mod.rs
Normal file
14
libs/persistent_range_query/src/ops/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
pub mod rsq;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct SameElementsInitializer<T> {
|
||||
initial_element_value: T,
|
||||
}
|
||||
|
||||
impl<T> SameElementsInitializer<T> {
|
||||
pub fn new(initial_element_value: T) -> Self {
|
||||
SameElementsInitializer {
|
||||
initial_element_value,
|
||||
}
|
||||
}
|
||||
}
|
||||
85
libs/persistent_range_query/src/ops/rsq.rs
Normal file
85
libs/persistent_range_query/src/ops/rsq.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
//! # Range Sum Query
|
||||
|
||||
use crate::ops::SameElementsInitializer;
|
||||
use crate::{LazyRangeInitializer, RangeModification, RangeQueryResult};
|
||||
use std::borrow::Borrow;
|
||||
use std::ops::{AddAssign, Mul, Range, Sub};
|
||||
|
||||
// TODO: commutative Add
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SumResult<T> {
|
||||
sum: T,
|
||||
}
|
||||
|
||||
impl<T> SumResult<T> {
|
||||
pub fn sum(&self) -> &T {
|
||||
&self.sum
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: for<'a> AddAssign<&'a T> + From<u8>, Key> RangeQueryResult<Key> for SumResult<T> {
|
||||
fn new_for_empty_range() -> Self {
|
||||
SumResult { sum: 0.into() }
|
||||
}
|
||||
|
||||
fn add(left: &mut Self, _left_range: &Range<Key>, right: &Self, _right_range: &Range<Key>) {
|
||||
left.sum += &right.sum
|
||||
}
|
||||
}
|
||||
|
||||
impl<Key, T, TR: Borrow<T>, KeyDiff> LazyRangeInitializer<Key, SumResult<T>>
|
||||
for SameElementsInitializer<TR>
|
||||
where
|
||||
for<'a> &'a T: Mul<KeyDiff, Output = T>,
|
||||
for<'b> &'b Key: Sub<Output = KeyDiff>,
|
||||
{
|
||||
fn get(&self, range: &Range<Key>) -> SumResult<T> {
|
||||
SumResult {
|
||||
sum: self.initial_element_value.borrow() * (&range.end - &range.start),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum AddAssignModification<T> {
|
||||
None,
|
||||
Add(T),
|
||||
Assign(T),
|
||||
}
|
||||
|
||||
impl<T: Clone + for<'a> AddAssign<&'a T>, Key> RangeModification<SumResult<T>, Key>
|
||||
for AddAssignModification<T>
|
||||
where
|
||||
for<'a> SameElementsInitializer<&'a T>: LazyRangeInitializer<Key, SumResult<T>>,
|
||||
{
|
||||
fn no_op() -> Self {
|
||||
AddAssignModification::None
|
||||
}
|
||||
|
||||
fn apply<'a>(&self, result: &'a mut SumResult<T>, range: &Range<Key>) {
|
||||
use AddAssignModification::*;
|
||||
match self {
|
||||
None => {}
|
||||
Add(x) | Assign(x) => {
|
||||
let to_add = SameElementsInitializer::new(x).get(range).sum;
|
||||
if let Assign(_) = self {
|
||||
result.sum = to_add;
|
||||
} else {
|
||||
result.sum += &to_add;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compose(later: &Self, earlier: &mut Self) {
|
||||
use AddAssignModification::*;
|
||||
match (later, earlier) {
|
||||
(_, e @ None) => *e = later.clone(),
|
||||
(None, _) => {}
|
||||
(Assign(_), e) => *e = later.clone(),
|
||||
(Add(x), Add(y)) => *y += x,
|
||||
(Add(x), Assign(value)) => *value += x,
|
||||
}
|
||||
}
|
||||
}
|
||||
56
libs/persistent_range_query/tests/rsq_test.rs
Normal file
56
libs/persistent_range_query/tests/rsq_test.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use persistent_range_query::naive::*;
|
||||
use persistent_range_query::ops::rsq::*;
|
||||
use persistent_range_query::ops::SameElementsInitializer;
|
||||
use persistent_range_query::{PersistentVecStorage, VecVersion};
|
||||
use std::ops::{Mul, Range, Sub};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct K(u8);
|
||||
|
||||
impl IndexableKey for K {
|
||||
fn index(all_keys: &Range<Self>, key: &Self) -> usize {
|
||||
(key.0 as usize) - (all_keys.start.0 as usize)
|
||||
}
|
||||
|
||||
fn element_range(all_keys: &Range<Self>, index: usize) -> Range<Self> {
|
||||
K(all_keys.start.0 + index as u8)..K(all_keys.start.0 + index as u8 + 1)
|
||||
}
|
||||
}
|
||||
|
||||
struct KDiff(i16);
|
||||
|
||||
impl Sub<&K> for &K {
|
||||
type Output = KDiff;
|
||||
|
||||
fn sub(self, rhs: &K) -> Self::Output {
|
||||
KDiff((self.0 as i16) - (rhs.0 as i16))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul<KDiff> for &'a i32 {
|
||||
type Output = i32;
|
||||
|
||||
fn mul(self, rhs: KDiff) -> Self::Output {
|
||||
self * (rhs.0 as i32)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_naive() {
|
||||
let mut s: NaiveVecVersion<_, _, AddAssignModification<_>> =
|
||||
NaiveVecStorage::new(K(0)..K(12), SameElementsInitializer::new(0i32));
|
||||
assert_eq!(*s.get(K(0)..K(12)).sum(), 0);
|
||||
|
||||
s.modify(K(2)..K(5), AddAssignModification::Add(3));
|
||||
assert_eq!(*s.get(K(0)..K(12)).sum(), 3 + 3 + 3);
|
||||
let s_old = s.clone();
|
||||
|
||||
s.modify(K(3)..K(6), AddAssignModification::Assign(10));
|
||||
assert_eq!(*s.get(K(0)..K(12)).sum(), 3 + 10 + 10 + 10);
|
||||
|
||||
s.modify(K(4)..K(7), AddAssignModification::Add(2));
|
||||
assert_eq!(*s.get(K(0)..K(12)).sum(), 3 + 10 + 12 + 12 + 2);
|
||||
|
||||
assert_eq!(*s.get(K(4)..K(6)).sum(), 12 + 12);
|
||||
assert_eq!(*s_old.get(K(4)..K(6)).sum(), 3);
|
||||
}
|
||||
Reference in New Issue
Block a user