automerge/rust/automerge/src/query/map_range.rs

106 lines
3.7 KiB
Rust

use crate::exid::ExId;
use crate::op_tree::{OpSetMetadata, OpTreeInternal};
use crate::types::{Key, OpId};
use crate::values::ValueIter;
use crate::{Automerge, Value};
use std::fmt::Debug;
use std::ops::RangeBounds;
#[derive(Debug)]
pub(crate) struct MapRange<'a, R: RangeBounds<String>> {
range: R,
index: usize,
last_key: Option<Key>,
next_result: Option<(&'a str, Value<'a>, OpId)>,
index_back: usize,
last_key_back: Option<Key>,
op_tree: &'a OpTreeInternal,
meta: &'a OpSetMetadata,
}
impl<'a, R: RangeBounds<String>> ValueIter<'a> for MapRange<'a, R> {
fn next_value(&mut self, doc: &'a Automerge) -> Option<(Value<'a>, ExId)> {
self.next().map(|(_, val, id)| (val, doc.id_to_exid(id)))
}
}
impl<'a, R: RangeBounds<String>> MapRange<'a, R> {
pub(crate) fn new(range: R, op_tree: &'a OpTreeInternal, meta: &'a OpSetMetadata) -> Self {
Self {
range,
index: 0,
last_key: None,
next_result: None,
index_back: op_tree.len(),
last_key_back: None,
op_tree,
meta,
}
}
}
impl<'a, R: RangeBounds<String>> Iterator for MapRange<'a, R> {
type Item = (&'a str, Value<'a>, OpId);
// FIXME: this is fine if we're scanning everything (see values()) but could be much more efficient
// if we're scanning a narrow range on a map with many keys... we should be able to seek to the starting
// point and stop at the end point and not needless scan all the ops before and after the range
fn next(&mut self) -> Option<Self::Item> {
for i in self.index..self.index_back {
let op = self.op_tree.get(i)?;
self.index += 1;
if op.visible() {
let prop = match op.key {
Key::Map(m) => self.meta.props.get(m),
Key::Seq(_) => return None, // this is a list
};
if self.range.contains(prop) {
let result = self.next_result.replace((prop, op.value(), op.id));
if Some(op.key) != self.last_key {
self.last_key = Some(op.key);
if result.is_some() {
return result;
}
}
}
}
}
self.next_result.take()
}
}
impl<'a, R: RangeBounds<String>> DoubleEndedIterator for MapRange<'a, R> {
fn next_back(&mut self) -> Option<Self::Item> {
for i in (self.index..self.index_back).rev() {
let op = self.op_tree.get(i)?;
self.index_back -= 1;
if Some(op.key) != self.last_key_back && op.visible() {
self.last_key_back = Some(op.key);
let prop = match op.key {
Key::Map(m) => self.meta.props.get(m),
Key::Seq(_) => return None, // this is a list
};
if self.range.contains(prop) {
return Some((prop, op.value(), op.id));
}
}
}
// we're now overlapping the index and index_back so try and take the result from the next query
if let Some((prop, a, b)) = self.next_result.take() {
let last_prop = match self.last_key_back {
None => None,
Some(Key::Map(u)) => Some(self.meta.props.get(u).as_str()),
Some(Key::Seq(_)) => None,
};
// we can only use this result if we haven't ended in the prop's state (to account for
// conflicts).
if Some(prop) != last_prop {
return Some((prop, a, b));
}
}
None
}
}