use crate::planner::{family_id, pids_of};
use crate::{access, normalize, reconstruct, Expr, FieldPred, Json, QueryError, QuerySnapshot};
use rushdb_lmpg::reader::TryRead;
use serde_json::Map;
pub struct PointFields(Vec<(String, Vec<u64>)>);
impl PointFields {
pub fn resolve<'n>(
snap: &QuerySnapshot,
tenant: rushdb_types::ids::TenantId,
names: impl IntoIterator<Item = &'n str>,
) -> Result<Option<Self>, QueryError> {
let mut fields = Vec::new();
for name in names {
let Some(pids) = scalar_pids(snap, tenant, name)? else {
return Ok(None);
};
fields.push((name.to_owned(), pids));
}
Ok(Some(Self(fields)))
}
pub(super) fn for_predicate(
snap: &QuerySnapshot,
tenant: rushdb_types::ids::TenantId,
predicate: &Expr,
) -> Result<Option<Self>, QueryError> {
let mut names = std::collections::BTreeSet::new();
if !predicate_fields(predicate, &mut names) {
return Ok(None);
}
Self::resolve(snap, tenant, names.iter().map(String::as_str))
}
pub fn row(
&self,
snap: &QuerySnapshot,
tenant: rushdb_types::ids::TenantId,
ord: u64,
) -> Result<Map<String, Json>, QueryError> {
let mut row = Map::new();
for (field, pids) in &self.0 {
for &pid in pids.iter().rev() {
if let Some(occ) = access::occurrence(
snap,
tenant,
rushdb_types::ids::OwnerKind::Record,
ord,
pid,
0,
)? {
row.insert(
field.clone(),
reconstruct::decode_value(occ.value, &occ.payload)?,
);
break;
}
}
}
Ok(row)
}
}
fn predicate_fields(expr: &Expr, out: &mut std::collections::BTreeSet<String>) -> bool {
match expr {
Expr::And(items) | Expr::Or(items) | Expr::Nor(items) => {
items.iter().all(|item| predicate_fields(item, out))
}
Expr::Xor(a, b) => predicate_fields(a, out) && predicate_fields(b, out),
Expr::Not(inner) => predicate_fields(inner, out),
Expr::Related(_) | Expr::Field(FieldPred::Id(_)) => false,
Expr::Field(
FieldPred::Eq(name, _)
| FieldPred::Ne(name, _)
| FieldPred::Gt(name, _)
| FieldPred::Gte(name, _)
| FieldPred::Lt(name, _)
| FieldPred::Lte(name, _)
| FieldPred::Contains(name, _)
| FieldPred::StartsWith(name, _)
| FieldPred::EndsWith(name, _)
| FieldPred::Exists(name, _)
| FieldPred::IsNull(name, _)
| FieldPred::In(name, _)
| FieldPred::Nin(name, _)
| FieldPred::Type(name, _)
| FieldPred::DatetimeComponent { field: name, .. },
) => {
out.insert(name.clone());
true
}
}
}
fn scalar_pids<R: TryRead>(
snap: &R,
tenant: rushdb_types::ids::TenantId,
name: &str,
) -> Result<Option<Vec<u64>>, QueryError> {
let norm = normalize(name);
if norm.is_empty() || rushdb_types::value::is_reserved_name(&norm) {
return Ok(None);
}
let Some(fid) = family_id(snap, tenant, &norm)? else {
return Ok(Some(Vec::new()));
};
let mut pids = pids_of(snap, tenant, fid)?;
for &pid in &pids {
let (arrays, _) = rushdb_lmpg::derived::array_markers(
snap,
tenant,
rushdb_types::ids::OwnerKind::Record,
pid,
)?;
if arrays {
return Ok(None);
}
}
pids.sort_unstable();
Ok(Some(pids))
}