use super::*;
use itertools::Itertools;
pub struct UnionConstraint<C> {
constraints: Vec<C>,
}
impl<'a, C> UnionConstraint<C>
where
C: Constraint<'a> + 'a,
{
pub fn new(constraints: Vec<C>) -> Self {
assert!(
!constraints.is_empty(),
"UnionConstraint requires at least one variant; \
use a different constraint type for the empty case"
);
if let Some((i, (a, b))) = constraints
.iter()
.map(|c| c.variables())
.tuple_windows()
.enumerate()
.find(|(_, (a, b))| a != b)
{
panic!(
"all union (or!) variants must mention the same query \
variables: variant {} declares {:?} but variant {} \
declares {:?}",
i,
a,
i + 1,
b
);
}
UnionConstraint { constraints }
}
}
impl<'a, C> Constraint<'a> for UnionConstraint<C>
where
C: Constraint<'a> + 'a,
{
fn variables(&self) -> VariableSet {
self.constraints[0].variables()
}
fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize> {
self.constraints
.iter()
.filter_map(|c| c.estimate(variable, binding))
.reduce(|acc, e| acc + e)
}
fn propose(
&self,
variable: VariableId,
frontier: &Frontier<'_>,
proposals: &mut ProposalBuffer,
) {
let rows = frontier.len();
let base = proposals.len();
let mut satisfied = vec![false; rows];
for c in self.constraints.iter() {
let mut any = false;
let mut all = true;
for (row, slot) in satisfied.iter_mut().enumerate() {
*slot = c.satisfied(&frontier.row(row));
any |= *slot;
all &= *slot;
}
if !any {
continue;
}
let variant_base = proposals.len();
c.propose(variable, frontier, proposals);
if !all {
proposals.retain_region(variant_base, |row, _| satisfied[row as usize]);
}
}
let mut fresh: Vec<(u32, RawInline)> = proposals.tagged(base).collect();
fresh.sort_unstable();
fresh.dedup();
proposals.rewrite_region(base, fresh);
}
fn confirm(&self, variable: VariableId, frontier: &Frontier<'_>, cands: &mut Candidates<'_>) {
let rows = frontier.len();
let mut any_live = vec![0u32; cands.live_word_len()];
let mut satisfied = vec![false; rows];
for c in self.constraints.iter() {
let mut any = false;
let mut all = true;
for (row, slot) in satisfied.iter_mut().enumerate() {
*slot = c.satisfied(&frontier.row(row));
any |= *slot;
all &= *slot;
}
if !any {
continue;
}
let mut scratch = cands.live_words();
if !all {
let mut votes = cands.scratch(&mut scratch);
for i in 0..votes.len() {
if !satisfied[votes.parent(i) as usize] {
votes.kill(i);
}
}
}
c.confirm(variable, frontier, &mut cands.scratch(&mut scratch));
or_words(&mut any_live, &scratch);
}
cands.set_live_words(&any_live);
}
fn satisfied(&self, binding: &Binding) -> bool {
self.constraints.iter().any(|c| c.satisfied(binding))
}
fn influence(&self, variable: VariableId) -> VariableSet {
self.constraints
.iter()
.fold(VariableSet::new_empty(), |acc, c| {
acc.union(c.influence(variable))
})
}
}
#[macro_export]
macro_rules! or {
($($c:expr),+ $(,)?) => (
::std::sync::Arc::new(
$crate::query::unionconstraint::UnionConstraint::new(vec![
$(Box::new($c)
as Box<dyn $crate::query::Constraint + Send + Sync>),+
])
)
)
}
pub use or;
#[cfg(test)]
mod tests {
use super::*;
use crate::query::constantconstraint::ConstantConstraint;
#[derive(Clone, Copy)]
struct RepeatedValueSource {
value: RawInline,
}
impl<'a> Constraint<'a> for RepeatedValueSource {
fn variables(&self) -> VariableSet {
VariableSet::new_singleton(0)
}
fn estimate(&self, variable: VariableId, _binding: &Binding) -> Option<usize> {
(variable == 0).then_some(2)
}
fn propose(
&self,
variable: VariableId,
frontier: &Frontier<'_>,
proposals: &mut ProposalBuffer,
) {
assert_eq!(variable, 0);
for parent in 0..frontier.len() {
proposals.open(parent as u32);
proposals.extend([self.value, self.value]);
}
}
fn confirm(
&self,
_variable: VariableId,
_frontier: &Frontier<'_>,
_candidates: &mut Candidates<'_>,
) {
}
}
#[test]
#[should_panic(expected = "UnionConstraint requires at least one variant")]
fn empty_union_panics_at_construction() {
let _: UnionConstraint<ConstantConstraint> = UnionConstraint::new(vec![]);
}
#[test]
fn union_deduplicates_on_parent_and_value() {
let mut value = [0; 32];
value[31] = 7;
let source = RepeatedValueSource { value };
let union = UnionConstraint::new(vec![source, source]);
let root = Frontier::default();
let selected = [0, 0];
let frontier = root.with_select(&selected);
let mut proposals = ProposalBuffer::new();
union.propose(0, &frontier, &mut proposals);
assert_eq!(
proposals.tagged(0).collect::<Vec<_>>(),
vec![(0, value), (1, value)],
"equal values collapse within a parent but remain distinct across parents",
);
}
}