use std::borrow::Cow;
use revision::optimised::IndexedMapWalker;
use revision::{
BorrowedReader, DeserializeRevisioned, Error as RevisionError, SerializeRevisioned,
WalkRevisioned,
};
use surrealdb_strand::Strand;
use wire_skip::{rev2_optimised_payload_unchecked, skip_value_wire};
use crate::catalog::Record;
use crate::val::Value;
mod tests;
pub(crate) mod wire_skip;
#[derive(Debug, Clone)]
pub(crate) struct PathSegment {
utf8: Strand,
wire: Box<[u8]>,
}
impl PathSegment {
pub(crate) fn new(utf8: impl Into<Strand>) -> Self {
let utf8: Strand = utf8.into();
let mut wire = Vec::with_capacity(utf8.len() + 4);
<Strand as SerializeRevisioned>::serialize_revisioned(&utf8, &mut wire)
.expect("Vec writer never errors");
Self {
utf8,
wire: wire.into_boxed_slice(),
}
}
#[inline]
pub(crate) fn as_str(&self) -> &str {
self.utf8.as_str()
}
#[inline]
pub(crate) fn as_bytes(&self) -> &[u8] {
self.utf8.as_bytes()
}
#[inline]
pub(crate) fn as_strand(&self) -> &Strand {
&self.utf8
}
#[inline]
pub(crate) fn wire(&self) -> &[u8] {
&self.wire
}
}
impl From<&str> for PathSegment {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<String> for PathSegment {
fn from(s: String) -> Self {
Self::new(s)
}
}
impl From<Strand> for PathSegment {
fn from(s: Strand) -> Self {
Self::new(s)
}
}
impl PartialEq for PathSegment {
fn eq(&self, other: &Self) -> bool {
self.utf8 == other.utf8
}
}
impl Eq for PathSegment {}
impl PartialOrd for PathSegment {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PathSegment {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.utf8.cmp(&other.utf8)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub(crate) enum WalkLeafErr {
Missing,
Bail,
}
#[derive(Debug)]
pub(crate) enum Extracted {
Found(Value),
Missing,
Bail,
}
#[inline]
fn rev2_record_data_bytes(record_bytes: &[u8]) -> Option<&[u8]> {
let (&rev, rest) = record_bytes.split_first()?;
if rev != 2 {
return None;
}
let len_bytes: [u8; 4] = rest.get(..4)?.try_into().ok()?;
let payload_len = u32::from_le_bytes(len_bytes) as usize;
let payload = rest.get(4..4 + payload_len)?;
let off_bytes: [u8; 4] = payload.get(4..8)?.try_into().ok()?;
let data_off = u32::from_le_bytes(off_bytes) as usize;
payload.get(data_off..)
}
#[inline]
pub(crate) fn record_data_bytes(record_bytes: &[u8]) -> Result<Cow<'_, [u8]>, RevisionError> {
if let Some(fast) = rev2_record_data_bytes(record_bytes) {
return Ok(Cow::Borrowed(fast));
}
let mut reader: &[u8] = record_bytes;
let data = Record::walk_revisioned(&mut reader).and_then(|w| w.into_data_bytes())?;
Ok(Cow::Owned(data.into_owned()))
}
pub(crate) fn extract_field_from_record_bytes(
record_bytes: &[u8],
path: &[PathSegment],
depth_limit: u32,
) -> Extracted {
extract_field_from_record_bytes_parts(record_bytes, &[], path, depth_limit)
}
pub(crate) fn extract_field_from_record_bytes_parts(
record_bytes: &[u8],
prefix: &[&PathSegment],
path: &[PathSegment],
depth_limit: u32,
) -> Extracted {
if prefix.is_empty() && path.is_empty() {
return Extracted::Bail;
}
let data_bytes = match record_data_bytes(record_bytes) {
Ok(b) => b,
Err(_) => return Extracted::Bail,
};
let mut reader: &[u8] = &data_bytes;
let value_walker = match Value::walk_revisioned(&mut reader) {
Ok(w) => w,
Err(_) => return Extracted::Bail,
};
descend_value_path_parts(value_walker, prefix, path, depth_limit)
}
fn indexed_map_walker_from_object_bytes(
object_wire: &[u8],
) -> Result<IndexedMapWalker<'_, Strand, Value>, RevisionError> {
let payload = rev2_optimised_payload_unchecked(object_wire)?;
IndexedMapWalker::<'_, Strand, Value>::from_payload_unvalidated(payload)
}
pub(crate) trait NeedleKey {
fn key_utf8(&self) -> &[u8];
fn key_wire(&self) -> Option<&[u8]> {
None
}
}
impl NeedleKey for &[u8] {
#[inline]
fn key_utf8(&self) -> &[u8] {
self
}
}
pub(crate) fn strand_wire_bytes_from_utf8(utf8: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(utf8.len() + 4);
<usize as SerializeRevisioned>::serialize_revisioned(&utf8.len(), &mut out)
.expect("Vec writer never errors");
out.extend_from_slice(utf8);
out
}
fn lookup_value_bytes_in_map<'p>(
map_walker: &IndexedMapWalker<'p, Strand, Value>,
needle: &PathSegment,
) -> Result<Option<&'p [u8]>, RevisionError> {
let needle_utf8 = needle.as_bytes();
if map_walker.is_indexed() {
let needle_wire = needle.wire();
return map_walker.find_value_bytes(|kb: &[u8]| kb.cmp(needle_wire));
}
let Some(body) = map_walker.legacy_body() else {
return Ok(None);
};
let len = map_walker.len();
let mut reader: &[u8] = body;
for _ in 0..len {
let key_len = <usize as DeserializeRevisioned>::deserialize_revisioned(&mut reader)?;
if reader.len() < key_len {
return Err(RevisionError::Deserialize(
"legacy indexed-map body: key length exceeds remaining bytes".into(),
));
}
let key_bytes = &reader[..key_len];
reader = &reader[key_len..];
if key_bytes == needle_utf8 {
let v_start = body.len() - reader.len();
let mut probe: &[u8] = reader;
skip_value_wire(&mut probe)?;
let v_end = body.len() - probe.len();
return Ok(Some(&body[v_start..v_end]));
}
skip_value_wire(&mut reader)?;
}
Ok(None)
}
fn descend_value_path_parts<'r, R: BorrowedReader>(
value_walker: <Value as WalkRevisioned>::Walker<'r, R>,
prefix: &[&PathSegment],
path: &[PathSegment],
depth_limit: u32,
) -> Extracted {
let total = prefix.len() + path.len();
if total == 0 || total > depth_limit as usize {
return Extracted::Bail;
}
descend_value_recursive(value_walker, prefix, path)
}
fn descend_value_recursive<'r, R: BorrowedReader>(
walker: <Value as WalkRevisioned>::Walker<'r, R>,
prefix: &[&PathSegment],
path: &[PathSegment],
) -> Extracted {
let needle: &PathSegment = if let Some(&s) = prefix.first() {
s
} else if let Some(s) = path.first() {
s
} else {
return Extracted::Bail;
};
if !walker.is_object() {
return Extracted::Bail;
}
let view = match walker.object_view() {
Ok(v) => v,
Err(_) => return Extracted::Bail,
};
let map_walker = match indexed_map_walker_from_object_bytes(view.as_bytes()) {
Ok(w) => w,
Err(_) => return Extracted::Bail,
};
let value_bytes = match lookup_value_bytes_in_map(&map_walker, needle) {
Ok(Some(b)) => b,
Ok(None) => return Extracted::Missing,
Err(_) => return Extracted::Bail,
};
let (next_prefix, next_path): (&[&PathSegment], &[PathSegment]) = if prefix.is_empty() {
(&[], &path[1..])
} else {
(&prefix[1..], path)
};
if next_prefix.is_empty() && next_path.is_empty() {
let mut leaf_reader: &[u8] = value_bytes;
return match <Value as DeserializeRevisioned>::deserialize_revisioned(&mut leaf_reader) {
Ok(v) => Extracted::Found(v),
Err(_) => Extracted::Bail,
};
}
let mut value_reader: &[u8] = value_bytes;
let inner_walker = match <Value as WalkRevisioned>::walk_revisioned(&mut value_reader) {
Ok(w) => w,
Err(_) => return Extracted::Bail,
};
descend_value_recursive(inner_walker, next_prefix, next_path)
}
pub(crate) enum DescendResult<T> {
Found(T),
Missing,
Bail,
}
pub(crate) fn descend_to_value_walker<T, F>(
value_walker: <Value as WalkRevisioned>::Walker<'_, &[u8]>,
path: &[&PathSegment],
depth_limit: u32,
consume: F,
) -> DescendResult<T>
where
F: FnOnce(&[u8]) -> T,
{
descend_to_value_walker_parts(value_walker, path, &[], depth_limit, consume)
}
pub(crate) fn descend_to_value_walker_parts<T, F>(
walker: <Value as WalkRevisioned>::Walker<'_, &[u8]>,
prefix: &[&PathSegment],
path: &[PathSegment],
depth_limit: u32,
consume: F,
) -> DescendResult<T>
where
F: FnOnce(&[u8]) -> T,
{
let total = prefix.len() + path.len();
if total == 0 || total > depth_limit as usize {
return DescendResult::Bail;
}
descend_walker_recursive(walker, prefix, path, consume)
}
fn descend_walker_recursive<'r, R, T, F>(
walker: <Value as WalkRevisioned>::Walker<'r, R>,
prefix: &[&PathSegment],
path: &[PathSegment],
consume: F,
) -> DescendResult<T>
where
R: BorrowedReader,
F: FnOnce(&[u8]) -> T,
{
let needle: &PathSegment = if let Some(&s) = prefix.first() {
s
} else if let Some(s) = path.first() {
s
} else {
return DescendResult::Bail;
};
if !walker.is_object() {
return DescendResult::Bail;
}
let view = match walker.object_view() {
Ok(v) => v,
Err(_) => return DescendResult::Bail,
};
let map_walker = match indexed_map_walker_from_object_bytes(view.as_bytes()) {
Ok(w) => w,
Err(_) => return DescendResult::Bail,
};
let value_bytes = match lookup_value_bytes_in_map(&map_walker, needle) {
Ok(Some(b)) => b,
Ok(None) => return DescendResult::Missing,
Err(_) => return DescendResult::Bail,
};
let (next_prefix, next_path): (&[&PathSegment], &[PathSegment]) = if prefix.is_empty() {
(&[], &path[1..])
} else {
(&prefix[1..], path)
};
if next_prefix.is_empty() && next_path.is_empty() {
return DescendResult::Found(consume(value_bytes));
}
let mut value_reader: &[u8] = value_bytes;
let inner_walker = match <Value as WalkRevisioned>::walk_revisioned(&mut value_reader) {
Ok(w) => w,
Err(_) => return DescendResult::Bail,
};
descend_walker_recursive(inner_walker, next_prefix, next_path, consume)
}
#[derive(Debug)]
pub(crate) enum SlotScanResult<T> {
Found(T),
Missing,
Bail,
}
pub(crate) fn scan_record_object_at_path_with_slots<K, F, T>(
record_bytes: &[u8],
path: &[&PathSegment],
needles_sorted: &[K],
depth_limit: u32,
on_slots: F,
) -> SlotScanResult<T>
where
K: NeedleKey,
F: FnOnce(&[Option<&[u8]>]) -> T,
{
if needles_sorted.is_empty() {
return SlotScanResult::Found(on_slots(&[]));
}
debug_assert!(
needles_sorted.windows(2).all(|w| w[0].key_utf8() < w[1].key_utf8()),
"needles_sorted must be strictly increasing in UTF-8 byte order",
);
let data_bytes = match record_data_bytes(record_bytes) {
Ok(b) => b,
Err(_) => return SlotScanResult::Bail,
};
let mut reader: &[u8] = &data_bytes;
let value_walker = match Value::walk_revisioned(&mut reader) {
Ok(w) => w,
Err(_) => return SlotScanResult::Bail,
};
if path.is_empty() {
match scan_value_object_with_slots(value_walker, needles_sorted, on_slots) {
Some(t) => SlotScanResult::Found(t),
None => SlotScanResult::Bail,
}
} else {
let result = descend_to_value_walker(value_walker, path, depth_limit, |value_bytes| {
let mut reader: &[u8] = value_bytes;
let walker = <Value as WalkRevisioned>::walk_revisioned(&mut reader).ok()?;
scan_value_object_with_slots(walker, needles_sorted, on_slots)
});
match result {
DescendResult::Found(Some(t)) => SlotScanResult::Found(t),
DescendResult::Found(None) => SlotScanResult::Bail,
DescendResult::Missing => SlotScanResult::Missing,
DescendResult::Bail => SlotScanResult::Bail,
}
}
}
fn scan_value_object_with_slots<'r, R, K, F, T>(
value_walker: <Value as WalkRevisioned>::Walker<'r, R>,
needles_sorted: &[K],
on_slots: F,
) -> Option<T>
where
R: BorrowedReader,
K: NeedleKey,
F: FnOnce(&[Option<&[u8]>]) -> T,
{
if !value_walker.is_object() {
return None;
}
let object_view = value_walker.object_view().ok()?;
let map_walker = indexed_map_walker_from_object_bytes(object_view.as_bytes()).ok()?;
let n = needles_sorted.len();
const STACK_SLOT_CAP: usize = 8;
let mut stack_slots: [Option<&[u8]>; STACK_SLOT_CAP] = [None; STACK_SLOT_CAP];
let mut heap_slots: Vec<Option<&[u8]>> = Vec::new();
let slots: &mut [Option<&[u8]>] = if n <= STACK_SLOT_CAP {
&mut stack_slots[..n]
} else {
heap_slots.resize(n, None);
heap_slots.as_mut_slice()
};
let mut remaining = n;
if map_walker.is_indexed() {
let m = needles_sorted.len();
let n = map_walker.len();
if m > 0 && 4 * m < n {
for (i, needle) in needles_sorted.iter().enumerate() {
let owned_wire;
let needle_wire: &[u8] = match needle.key_wire() {
Some(w) => w,
None => {
owned_wire = strand_wire_bytes_from_utf8(needle.key_utf8());
&owned_wire
}
};
match map_walker.find_value_bytes(|kb: &[u8]| kb.cmp(needle_wire)) {
Ok(Some(vb)) => slots[i] = Some(vb),
Ok(None) => {}
Err(_) => return None,
}
}
} else {
for (kb_wire, vb) in map_walker.entries()? {
if remaining == 0 {
break;
}
let mut kr: &[u8] = kb_wire;
let key_len =
<usize as DeserializeRevisioned>::deserialize_revisioned(&mut kr).ok()?;
if kr.len() != key_len {
return None;
}
if let Ok(idx) = needles_sorted.binary_search_by(|n| n.key_utf8().cmp(kr))
&& slots[idx].is_none()
{
slots[idx] = Some(vb);
remaining -= 1;
}
}
}
} else {
let body = map_walker.legacy_body()?;
let len = map_walker.len();
let mut reader: &[u8] = body;
for _ in 0..len {
if remaining == 0 {
break;
}
let key_len =
<usize as DeserializeRevisioned>::deserialize_revisioned(&mut reader).ok()?;
if reader.len() < key_len {
return None;
}
let kb_utf8 = &reader[..key_len];
reader = &reader[key_len..];
if let Ok(idx) = needles_sorted.binary_search_by(|n| n.key_utf8().cmp(kb_utf8)) {
let v_start = body.len() - reader.len();
let mut probe: &[u8] = reader;
skip_value_wire(&mut probe).ok()?;
let v_end = body.len() - probe.len();
if slots[idx].is_none() {
slots[idx] = Some(&body[v_start..v_end]);
remaining -= 1;
}
reader = probe;
} else {
skip_value_wire(&mut reader).ok()?;
}
}
}
Some(on_slots(slots))
}
#[cfg(test)]
pub(crate) const TEST_DEPTH_LIMIT: u32 = 256;
#[cfg(test)]
pub(crate) fn descend_record_value_path(record_bytes: &[u8], path: &[PathSegment]) -> Extracted {
extract_field_from_record_bytes(record_bytes, path, TEST_DEPTH_LIMIT)
}
#[cfg(test)]
pub(crate) fn descend_value_slice_path(value_wire: &[u8], path: &[PathSegment]) -> Extracted {
if path.is_empty() {
return Extracted::Bail;
}
let mut reader = value_wire;
let walker = match Value::walk_revisioned(&mut reader) {
Ok(w) => w,
Err(_) => return Extracted::Bail,
};
descend_value_path_parts(walker, &[], path, TEST_DEPTH_LIMIT)
}