use reifydb_core::interface::catalog::series::Series;
use reifydb_store_column::predicate::{ColRef, Predicate};
use reifydb_value::value::{Value, value_type::ValueType};
pub(crate) fn series_scan_predicate(
series: &Series,
key_range_start: Option<u64>,
key_range_end: Option<u64>,
variant_tag: Option<u8>,
) -> Option<Predicate> {
let key = ColRef::from(series.key.column());
let mut clauses = Vec::new();
if let Some(value) = key_range_start.and_then(|raw| key_value(series, raw)) {
clauses.push(Predicate::GtEq(key.clone(), value));
}
if let Some(value) = key_range_end.and_then(|raw| key_value(series, raw)) {
clauses.push(Predicate::Lt(key, value));
}
if let Some(tag) = variant_tag {
clauses.push(Predicate::Eq(ColRef::from("tag"), Value::Uint1(tag)));
}
match clauses.len() {
0 => None,
1 => clauses.pop(),
_ => Some(Predicate::And(clauses)),
}
}
fn key_value(series: &Series, raw: u64) -> Option<Value> {
let expected = series.key_column_type().unwrap_or(ValueType::Uint8);
let value = series.key_from_u64(raw);
(value.get_type() == expected).then_some(value)
}
#[cfg(test)]
mod tests {
use reifydb_core::{
common::TimeSource,
interface::catalog::{
column::{Column, ColumnIndex},
id::{ColumnId, NamespaceId, SeriesId},
series::SeriesKey,
},
};
use reifydb_value::value::{constraint::TypeConstraint, sumtype::SumTypeId};
use super::*;
fn series_with(key: SeriesKey, columns: Vec<Column>, tag: Option<SumTypeId>) -> Series {
Series {
id: SeriesId(1),
namespace: NamespaceId(1),
name: "s".into(),
columns,
tag,
key,
primary_key: None,
partition_by: vec![],
time: TimeSource::Processing,
encoding: None,
}
}
fn column(name: &str, ty: ValueType) -> Column {
Column {
id: ColumnId(1),
name: name.into(),
constraint: TypeConstraint::unconstrained(ty),
properties: vec![],
index: ColumnIndex(0),
auto_increment: false,
dictionary_id: None,
}
}
fn integer_key() -> SeriesKey {
SeriesKey::Integer {
column: "k".into(),
}
}
#[test]
fn no_filter_fields_produce_no_predicate() {
let series = series_with(integer_key(), vec![], None);
assert!(series_scan_predicate(&series, None, None, None).is_none());
}
#[test]
fn a_lone_start_bound_is_not_wrapped_in_and() {
let series = series_with(integer_key(), vec![], None);
let predicate = series_scan_predicate(&series, Some(10), None, None).unwrap();
match predicate {
Predicate::GtEq(column, value) => {
assert_eq!(column, ColRef::from("k"));
assert_eq!(value, Value::Uint8(10));
}
other => panic!("expected a bare GtEq, got {other:?}"),
}
}
#[test]
fn both_bounds_combine_into_one_and() {
let series = series_with(integer_key(), vec![], None);
let predicate = series_scan_predicate(&series, Some(10), Some(20), None).unwrap();
match predicate {
Predicate::And(clauses) => {
assert_eq!(clauses.len(), 2);
assert!(matches!(clauses[0], Predicate::GtEq(..)));
assert!(matches!(clauses[1], Predicate::Lt(..)));
}
other => panic!("expected an And of two clauses, got {other:?}"),
}
}
#[test]
fn the_end_bound_is_exclusive() {
let series = series_with(integer_key(), vec![], None);
let predicate = series_scan_predicate(&series, None, Some(20), None).unwrap();
match predicate {
Predicate::Lt(_, value) => assert_eq!(value, Value::Uint8(20)),
other => panic!("expected Lt, got {other:?}"),
}
}
#[test]
fn the_tag_clause_names_the_block_column() {
let series = series_with(integer_key(), vec![], Some(SumTypeId(7)));
let predicate = series_scan_predicate(&series, None, None, Some(3)).unwrap();
match predicate {
Predicate::Eq(column, value) => {
assert_eq!(column, ColRef::from("tag"));
assert_eq!(value, Value::Uint1(3));
}
other => panic!("expected Eq on tag, got {other:?}"),
}
}
#[test]
fn the_key_clause_carries_the_declared_key_type() {
let series = series_with(integer_key(), vec![column("k", ValueType::Int4)], None);
let predicate = series_scan_predicate(&series, Some(10), None, None).unwrap();
match predicate {
Predicate::GtEq(_, value) => assert_eq!(value, Value::Int4(10)),
other => panic!("expected GtEq carrying an Int4, got {other:?}"),
}
}
#[test]
fn an_undeclared_key_column_still_filters_as_uint8() {
let series = series_with(integer_key(), vec![column("other", ValueType::Utf8)], None);
let predicate = series_scan_predicate(&series, Some(10), None, None).unwrap();
match predicate {
Predicate::GtEq(_, value) => assert_eq!(value, Value::Uint8(10)),
other => panic!("expected GtEq carrying a Uint8, got {other:?}"),
}
}
#[test]
fn an_unconvertible_key_type_drops_the_key_clause_but_keeps_the_tag() {
let series = series_with(integer_key(), vec![column("k", ValueType::Utf8)], Some(SumTypeId(7)));
let predicate = series_scan_predicate(&series, Some(10), Some(20), Some(3)).unwrap();
match predicate {
Predicate::Eq(column, value) => {
assert_eq!(column, ColRef::from("tag"));
assert_eq!(value, Value::Uint1(3));
}
other => panic!("expected only the tag clause to survive, got {other:?}"),
}
}
#[test]
fn an_unconvertible_key_type_with_no_tag_produces_no_predicate() {
let series = series_with(integer_key(), vec![column("k", ValueType::Utf8)], None);
assert!(series_scan_predicate(&series, Some(10), Some(20), None).is_none());
}
}