use std::cmp::Ordering;
use std::sync::{Arc, RwLock};
use crate::exec::OperatorMetrics;
use crate::exec::field_path::{FieldPath, FieldPathPart};
use crate::exec::operators::scan::pipeline::FieldState;
use crate::exec::operators::{SortDirection, SortKey, compare_values};
use crate::exec::pre_decode_filter::{PreDecodeFilterReason, field_state_blocks_raw_read};
use crate::val::Value;
use crate::val::object_extract::{Extracted, PathSegment, extract_field_from_record_bytes};
#[derive(Debug, Default)]
pub(crate) struct TopKThresholdCell {
value: RwLock<Option<Arc<Value>>>,
}
impl TopKThresholdCell {
pub(crate) fn publish(&self, v: Value) {
let v = Some(Arc::new(v));
match self.value.write() {
Ok(mut guard) => *guard = v,
Err(poisoned) => *poisoned.into_inner() = v,
}
}
pub(crate) fn snapshot(&self) -> Option<Arc<Value>> {
match self.value.read() {
Ok(guard) => guard.clone(),
Err(poisoned) => poisoned.into_inner().clone(),
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct TopKThresholdProbe {
path: Arc<[PathSegment]>,
direction: SortDirection,
single_key: bool,
cell: Arc<TopKThresholdCell>,
depth_limit: u32,
metrics: Option<Arc<OperatorMetrics>>,
}
impl TopKThresholdProbe {
pub(crate) fn new(
path: Vec<PathSegment>,
direction: SortDirection,
single_key: bool,
cell: Arc<TopKThresholdCell>,
depth_limit: u32,
) -> Self {
Self {
path: path.into(),
direction,
single_key,
cell,
depth_limit,
metrics: None,
}
}
pub(crate) fn root_field(&self) -> &str {
self.path[0].as_str()
}
pub(crate) fn snapshot(&self) -> Option<Arc<Value>> {
self.cell.snapshot()
}
pub(crate) fn metrics(&self) -> Option<&Arc<OperatorMetrics>> {
self.metrics.as_ref()
}
pub(crate) fn rejects(&self, threshold: &Value, record_bytes: &[u8]) -> bool {
let candidate =
match extract_field_from_record_bytes(record_bytes, &self.path, self.depth_limit) {
Extracted::Found(v) => v,
Extracted::Missing => Value::None,
Extracted::Bail => return false,
};
let ord = compare_values(&candidate, threshold, false, false);
let ord = match self.direction {
SortDirection::Asc => ord,
SortDirection::Desc => ord.reverse(),
};
match ord {
Ordering::Greater => true,
Ordering::Equal => self.single_key,
Ordering::Less => false,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub(crate) enum TopKPushdownReason {
UnsupportedOrder,
LimitTooLarge,
Tempfiles,
ComputedFields,
FieldPermissions,
}
#[derive(Debug, Clone)]
pub(crate) enum TopKPushdownStatus {
NotApplicable,
Active(Arc<TopKThresholdProbe>),
Deferred(Arc<TopKThresholdProbe>),
Ineligible(TopKPushdownReason),
}
impl TopKPushdownStatus {
pub(crate) fn explain_text(&self) -> Option<&'static str> {
match self {
Self::NotApplicable => None,
Self::Active(_) => Some("yes"),
Self::Deferred(_) => Some("deferred (runtime field state)"),
Self::Ineligible(TopKPushdownReason::UnsupportedOrder) => {
Some("no (unsupported order)")
}
Self::Ineligible(TopKPushdownReason::LimitTooLarge) => Some("no (limit too large)"),
Self::Ineligible(TopKPushdownReason::Tempfiles) => Some("no (tempfiles)"),
Self::Ineligible(TopKPushdownReason::ComputedFields) => Some("no (computed fields)"),
Self::Ineligible(TopKPushdownReason::FieldPermissions) => {
Some("no (field permissions)")
}
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct TopKPushdownHandle {
pub(crate) cell: Arc<TopKThresholdCell>,
pub(crate) expected_first_key: SortKey,
pub(crate) expected_key_count: usize,
}
pub(crate) fn field_path_wire_segments(path: &FieldPath) -> Option<Vec<PathSegment>> {
if path.is_empty() {
return None;
}
let mut segments = Vec::with_capacity(path.len());
for part in &path.0 {
match part {
FieldPathPart::Field(name) => segments.push(PathSegment::new(name.as_str())),
_ => return None,
}
}
if segments[0].as_str() == "id" {
return None;
}
Some(segments)
}
fn map_reason(reason: PreDecodeFilterReason) -> TopKPushdownReason {
match reason {
PreDecodeFilterReason::ComputedFields => TopKPushdownReason::ComputedFields,
PreDecodeFilterReason::FieldPermissions => TopKPushdownReason::FieldPermissions,
PreDecodeFilterReason::UnsupportedPredicate => TopKPushdownReason::UnsupportedOrder,
}
}
pub(crate) fn topk_pushdown_status_at_plan_time(
probe: Arc<TopKThresholdProbe>,
field_state: Option<&FieldState>,
) -> TopKPushdownStatus {
match field_state {
Some(fs) => match field_state_blocks_raw_read(fs, probe.root_field(), true) {
None => TopKPushdownStatus::Active(probe),
Some(PreDecodeFilterReason::FieldPermissions) => {
match field_state_blocks_raw_read(fs, probe.root_field(), false) {
None => TopKPushdownStatus::Deferred(probe),
Some(reason) => TopKPushdownStatus::Ineligible(map_reason(reason)),
}
}
Some(reason) => TopKPushdownStatus::Ineligible(map_reason(reason)),
},
None => TopKPushdownStatus::Deferred(probe),
}
}
pub(crate) fn topk_probe_for_execute(
status: &TopKPushdownStatus,
field_state: &FieldState,
check_perms: bool,
metrics: &Arc<OperatorMetrics>,
) -> Option<Arc<TopKThresholdProbe>> {
let probe = match status {
TopKPushdownStatus::NotApplicable | TopKPushdownStatus::Ineligible(_) => return None,
TopKPushdownStatus::Active(p) => p,
TopKPushdownStatus::Deferred(p) => {
if field_state_blocks_raw_read(field_state, p.root_field(), check_perms).is_some() {
return None;
}
p
}
};
if metrics.is_enabled() && !check_perms {
Some(Arc::new(TopKThresholdProbe {
metrics: Some(Arc::clone(metrics)),
..probe.as_ref().clone()
}))
} else {
Some(Arc::clone(probe))
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use chrono::{TimeZone, Utc};
use revision::SerializeRevisioned;
use surrealdb_strand::Strand;
use super::*;
use crate::catalog::Record;
use crate::val::{Datetime, Number, Object};
const TEST_DEPTH_LIMIT: u32 = 256;
fn wire_record(obj: Object) -> Vec<u8> {
let rec = Record {
metadata: None,
data: Value::Object(obj),
};
let mut out = Vec::new();
rec.serialize_revisioned(&mut out).unwrap();
out
}
fn record_with_int(field: &str, n: i64) -> Vec<u8> {
wire_record(Object::from(BTreeMap::from([(
Strand::from(field),
Value::Number(Number::Int(n)),
)])))
}
fn probe(direction: SortDirection, single_key: bool, path: &[&str]) -> TopKThresholdProbe {
TopKThresholdProbe::new(
path.iter().map(|s| PathSegment::new(*s)).collect(),
direction,
single_key,
Arc::new(TopKThresholdCell::default()),
TEST_DEPTH_LIMIT,
)
}
#[test]
fn cell_starts_empty_publishes_and_overwrites() {
let cell = TopKThresholdCell::default();
assert!(cell.snapshot().is_none());
cell.publish(Value::Number(Number::Int(5)));
assert_eq!(cell.snapshot().as_deref(), Some(&Value::Number(Number::Int(5))));
cell.publish(Value::Number(Number::Int(7)));
assert_eq!(cell.snapshot().as_deref(), Some(&Value::Number(Number::Int(7))));
}
#[test]
fn reject_matrix() {
let rec = record_with_int("a", 5);
let t = |n: i64| Value::Number(Number::Int(n));
let cases = [
(SortDirection::Asc, 6, true, false),
(SortDirection::Asc, 6, false, false),
(SortDirection::Asc, 5, true, true),
(SortDirection::Asc, 5, false, false),
(SortDirection::Asc, 4, true, true),
(SortDirection::Asc, 4, false, true),
(SortDirection::Desc, 4, true, false),
(SortDirection::Desc, 4, false, false),
(SortDirection::Desc, 5, true, true),
(SortDirection::Desc, 5, false, false),
(SortDirection::Desc, 6, true, true),
(SortDirection::Desc, 6, false, true),
];
for (direction, threshold, single_key, expect) in cases {
let p = probe(direction, single_key, &["a"]);
assert_eq!(
p.rejects(&t(threshold), &rec),
expect,
"direction={direction:?} threshold={threshold} single_key={single_key}",
);
}
}
#[test]
fn missing_field_reads_as_none() {
let rec = record_with_int("other", 1);
let threshold = Value::Number(Number::Int(5));
assert!(probe(SortDirection::Desc, true, &["a"]).rejects(&threshold, &rec));
assert!(!probe(SortDirection::Asc, true, &["a"]).rejects(&threshold, &rec));
assert!(probe(SortDirection::Desc, true, &["a"]).rejects(&Value::None, &rec));
assert!(!probe(SortDirection::Desc, false, &["a"]).rejects(&Value::None, &rec));
}
#[test]
fn bail_passes_through() {
let threshold = Value::Number(Number::Int(5));
let p = probe(SortDirection::Desc, true, &["a"]);
assert!(!p.rejects(&threshold, b"\xff\xff\xff\xff"));
let rec = record_with_int("a", 5);
let nested = probe(SortDirection::Desc, true, &["a", "b"]);
assert!(!nested.rejects(&threshold, &rec));
}
#[test]
fn datetime_leaf_compares() {
let older = Datetime(Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap());
let newer = Datetime(Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap());
let rec = wire_record(Object::from(BTreeMap::from([(
Strand::from("created_at"),
Value::Datetime(older),
)])));
let p = probe(SortDirection::Desc, true, &["created_at"]);
assert!(p.rejects(&Value::Datetime(newer), &rec));
assert!(p.rejects(&Value::Datetime(older), &rec));
let rec_newer = wire_record(Object::from(BTreeMap::from([(
Strand::from("created_at"),
Value::Datetime(newer),
)])));
assert!(!p.rejects(&Value::Datetime(older), &rec_newer));
}
#[test]
fn nested_field_path_descends() {
let inner =
Object::from(BTreeMap::from([(Strand::from("score"), Value::Number(Number::Int(3)))]));
let rec = wire_record(Object::from(BTreeMap::from([(
Strand::from("stats"),
Value::Object(inner),
)])));
let p = probe(SortDirection::Desc, true, &["stats", "score"]);
assert!(p.rejects(&Value::Number(Number::Int(9)), &rec));
assert!(!p.rejects(&Value::Number(Number::Int(1)), &rec));
}
#[test]
fn wire_segments_accept_field_chains_only() {
use crate::exec::field_path::{FieldPath, FieldPathPart};
let fields =
FieldPath(vec![FieldPathPart::Field("a".into()), FieldPathPart::Field("b".into())]);
let segs = field_path_wire_segments(&fields).expect("plain field chain");
assert_eq!(segs.len(), 2);
assert_eq!(segs[0].as_str(), "a");
assert_eq!(segs[1].as_str(), "b");
for bad in [
FieldPath(vec![]),
FieldPath(vec![FieldPathPart::Field("a".into()), FieldPathPart::Index(0)]),
FieldPath(vec![FieldPathPart::First]),
FieldPath(vec![FieldPathPart::Last]),
FieldPath(vec![FieldPathPart::Lookup("->edge".into())]),
FieldPath(vec![FieldPathPart::Field("id".into())]),
FieldPath(vec![FieldPathPart::Field("id".into()), FieldPathPart::Field("x".into())]),
] {
assert!(field_path_wire_segments(&bad).is_none(), "expected None for {bad:?}");
}
}
#[test]
fn explain_text_covers_every_variant() {
let p = Arc::new(probe(SortDirection::Desc, true, &["a"]));
assert_eq!(TopKPushdownStatus::NotApplicable.explain_text(), None);
assert_eq!(TopKPushdownStatus::Active(Arc::clone(&p)).explain_text(), Some("yes"));
assert_eq!(
TopKPushdownStatus::Deferred(p).explain_text(),
Some("deferred (runtime field state)")
);
let reasons = [
TopKPushdownReason::UnsupportedOrder,
TopKPushdownReason::LimitTooLarge,
TopKPushdownReason::Tempfiles,
TopKPushdownReason::ComputedFields,
TopKPushdownReason::FieldPermissions,
];
for reason in reasons {
match reason {
TopKPushdownReason::UnsupportedOrder
| TopKPushdownReason::LimitTooLarge
| TopKPushdownReason::Tempfiles
| TopKPushdownReason::ComputedFields
| TopKPushdownReason::FieldPermissions => {}
}
assert!(TopKPushdownStatus::Ineligible(reason).explain_text().is_some());
}
}
}