kanidmd_lib/plugins/
memberof.rs

1// Member Of
2//
3// Generate reverse relationships for groups to their members.
4//
5// Note referential integrity MUST be run first - this is to avoid the situation
6// demonstrated in test_delete_mo_multi_cycle - that is, when we delete B, we trigger
7// an update to C. C then triggers to A, which re-reades C + D. Because D still has not
8// been update, it's stale reference to B flows to A, causing refint to fail the mod.
9//
10// As a result, we first need to run refint to clean up all dangling references, then memberof
11// fixes the graph of memberships
12
13use std::collections::{BTreeMap, BTreeSet};
14use std::sync::Arc;
15
16use crate::entry::{Entry, EntryCommitted, EntrySealed};
17use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
18use crate::plugins::Plugin;
19use crate::prelude::*;
20use crate::value::PartialValue;
21
22pub struct MemberOf;
23
24fn do_group_memberof(
25    qs: &mut QueryServerWriteTransaction,
26    uuid: Uuid,
27    tgte: &mut EntryInvalidCommitted,
28) -> Result<(), OperationError> {
29    //  search where we are member
30    let groups = qs
31        .internal_search(filter!(f_and!([
32            f_eq(Attribute::Class, EntryClass::Group.into()),
33            f_or!([
34                f_eq(Attribute::Member, PartialValue::Refer(uuid)),
35                f_eq(Attribute::DynMember, PartialValue::Refer(uuid))
36            ])
37        ])))
38        .map_err(|e| {
39            admin_error!("internal search failure -> {:?}", e);
40            e
41        })?;
42
43    // Ensure we are MO capable. We only add this if it's not already present.
44    tgte.add_ava_if_not_exist(Attribute::Class, EntryClass::MemberOf.into());
45    // Clear the dmo + mos, we will recreate them now.
46    // This is how we handle deletes/etc.
47    tgte.purge_ava(Attribute::MemberOf);
48    tgte.purge_ava(Attribute::DirectMemberOf);
49
50    // What are our direct and indirect mos?
51    let dmo = ValueSetRefer::from_iter(groups.iter().map(|g| g.get_uuid()));
52
53    let mut mo = ValueSetRefer::from_iter(
54        groups
55            .iter()
56            .filter_map(|g| {
57                g.get_ava_set(Attribute::MemberOf)
58                    .and_then(|s| s.as_refer_set())
59                    .map(|s| s.iter())
60            })
61            .flatten()
62            .copied(),
63    );
64
65    // Add all the direct mo's and mos.
66    if let Some(dmo) = dmo {
67        // We need to clone this else type checker gets real sad.
68        tgte.set_ava_set(&Attribute::DirectMemberOf, dmo.clone());
69
70        if let Some(mo) = &mut mo {
71            let dmo = dmo as ValueSet;
72            mo.merge(&dmo)?;
73        } else {
74            // Means MO is empty, so we need to duplicate dmo to allow things to
75            // proceed.
76            mo = Some(dmo);
77        };
78    };
79
80    if let Some(mo) = mo {
81        tgte.set_ava_set(&Attribute::MemberOf, mo);
82    }
83
84    trace!(
85        "Updating {:?} to be dir mo {:?}",
86        uuid,
87        tgte.get_ava_set(Attribute::DirectMemberOf)
88    );
89    trace!(
90        "Updating {:?} to be mo {:?}",
91        uuid,
92        tgte.get_ava_set(Attribute::MemberOf)
93    );
94    Ok(())
95}
96
97fn do_leaf_memberof(
98    qs: &mut QueryServerWriteTransaction,
99    all_affected_uuids: BTreeSet<Uuid>,
100) -> Result<(), OperationError> {
101    trace!("---");
102
103    // We just put everything into the filter here, the query code will remove
104    // anything that is a group.
105    let all_affected_filter: Vec<_> = all_affected_uuids
106        .into_iter()
107        .map(|u| f_eq(Attribute::Uuid, PartialValue::Uuid(u)))
108        .collect();
109
110    if all_affected_filter.is_empty() {
111        trace!("all affected filter is empty, return");
112        return Ok(());
113    }
114
115    // These are all the affected entries.
116    let leaf_entries = qs.internal_search_writeable(&filter!(f_and!([
117        f_andnot(f_eq(Attribute::Class, EntryClass::Group.into())),
118        FC::Or(all_affected_filter)
119    ])))?;
120
121    if leaf_entries.is_empty() {
122        trace!("leaf entries empty, return");
123        return Ok(());
124    }
125
126    let mut leaf_entries: BTreeMap<_, _> = leaf_entries
127        .into_iter()
128        .map(|entry_tuple| (entry_tuple.0.get_uuid(), entry_tuple))
129        .collect();
130
131    let mut changes = Vec::with_capacity(leaf_entries.len());
132
133    // Now that we know which *entries* changed, we actually have to load the groups *again*
134    // because the affected entries could still be a DMO/MO of a group that *wasn't* in the
135    // change set, and we still need to reflect that they exist.
136
137    let mut groups_or = Vec::with_capacity(leaf_entries.len() * 2);
138
139    for uuid in leaf_entries.keys().copied() {
140        groups_or.push(f_eq(Attribute::Member, PartialValue::Refer(uuid)));
141        groups_or.push(f_eq(Attribute::DynMember, PartialValue::Refer(uuid)));
142    }
143
144    let all_groups = qs
145        .internal_search(filter!(f_and!([
146            f_eq(Attribute::Class, EntryClass::Group.into()),
147            FC::Or(groups_or)
148        ])))
149        .map_err(|err| {
150            error!(?err, "internal search failure");
151            err
152        })?;
153
154    /*
155     * Previously we went through the remaining items and processed them one at a time, but
156     * that has significant performance limits, since if we update a large dyn group, we then
157     * have to perform N searches for each affected member, which may be repeatedly searching
158     * for the same groups over and over again.
159     *
160     * Instead, at this point we know that in memberof application the entire group tree is
161     * now stable, and all we need to do is reflect those values into our entries. We can do
162     * this in two steps. First we load *all* the groups that relate to our leaf entries that
163     * we need to reflect.
164     *
165     * Then we can go through that in a single pass updating our entries that need to be
166     * updated. Since we know that the leaf entries aren't groups, we don't have a collision
167     * as a result of using internal_search_writeable.
168     */
169
170    // Clear the existing Mo and Dmo on the write stripe.
171    for (_pre, tgte) in leaf_entries.values_mut() {
172        // Ensure we are MO capable. We only add this if it's not already present.
173        tgte.add_ava_if_not_exist(Attribute::Class, EntryClass::MemberOf.into());
174        // Clear the dmo + mos, we will recreate them now.
175        // This is how we handle deletes/etc.
176        tgte.purge_ava(Attribute::MemberOf);
177        tgte.purge_ava(Attribute::DirectMemberOf);
178    }
179
180    // Now, we go through all the groups, and from each one we update the relevant
181    // target entry as needed.
182    for group in all_groups {
183        trace!(group_id = %group.get_display_id());
184        // Our group uuid that we add to direct members.
185        let group_uuid = group.get_uuid();
186
187        let memberof_ref = group.get_ava_refer(Attribute::MemberOf);
188
189        let member_ref = group.get_ava_refer(Attribute::Member);
190        let dynmember_ref = group.get_ava_refer(Attribute::DynMember);
191
192        let dir_members = member_ref
193            .iter()
194            .flat_map(|set| set.iter())
195            .chain(dynmember_ref.iter().flat_map(|set| set.iter()))
196            .copied();
197
198        // These are the entries that are direct members and need to reflect the group
199        // as mo and it's mo for indirect mo.
200        for dir_member in dir_members {
201            if let Some((_pre, tgte)) = leaf_entries.get_mut(&dir_member) {
202                trace!(?dir_member, entry_id = ?tgte.get_display_id());
203                // We were in the group, lets update.
204                if let Some(dmo_set) = tgte.get_ava_refer_mut(Attribute::DirectMemberOf) {
205                    dmo_set.insert(group_uuid);
206                } else {
207                    let dmo = ValueSetRefer::new(group_uuid);
208                    tgte.set_ava_set(&Attribute::DirectMemberOf, dmo);
209                }
210
211                // We're also in member of this group.
212                if let Some(mo_set) = tgte.get_ava_refer_mut(Attribute::MemberOf) {
213                    mo_set.insert(group_uuid);
214                } else {
215                    let mo = ValueSetRefer::new(group_uuid);
216                    tgte.set_ava_set(&Attribute::MemberOf, mo);
217                }
218
219                // If the group has memberOf attributes, we propagate these to
220                // our entry now.
221                if let Some(group_mo) = memberof_ref {
222                    // IMPORTANT this can't be a NONE because we just create MO in
223                    // the step above!
224                    if let Some(mo_set) = tgte.get_ava_refer_mut(Attribute::MemberOf) {
225                        mo_set.extend(group_mo.iter())
226                    }
227                }
228
229                if cfg!(debug_assertions) {
230                    if let Some(dmo) = group.get_ava_refer(Attribute::DirectMemberOf) {
231                        if let Some(mo) = group.get_ava_refer(Attribute::MemberOf) {
232                            debug_assert!(mo.is_superset(dmo))
233                        }
234                    }
235                }
236            }
237            // Done updating that leaf entry.
238            // Remember in the None case it could be that the group has a member which *isn't*
239            // being altered as a leaf in this operation.
240        }
241        // Next group.
242    }
243
244    // Now only write back leaf entries that actually were changed as a result of the memberof
245    // process.
246    leaf_entries
247        .into_iter()
248        .try_for_each(|(auuid, (pre, tgte))| {
249            // Only write if a change occurred.
250            if pre.get_ava_set(Attribute::MemberOf) != tgte.get_ava_set(Attribute::MemberOf)
251                || pre.get_ava_set(Attribute::DirectMemberOf)
252                    != tgte.get_ava_set(Attribute::DirectMemberOf)
253            {
254                trace!("=> processing affected uuid {:?}", auuid);
255
256                if cfg!(debug_assertions) {
257                    if let Some(dmo_set) = tgte.get_ava_refer(Attribute::DirectMemberOf) {
258                        trace!(?dmo_set);
259
260                        if let Some(mo_set) = tgte.get_ava_refer(Attribute::MemberOf) {
261                            trace!(?mo_set);
262                            debug_assert!(mo_set.is_superset(dmo_set));
263                        } else {
264                            unreachable!();
265                        }
266                    } else {
267                        trace!("NONE");
268                    };
269
270                    if let Some(pre_dmo_set) = pre.get_ava_refer(Attribute::DirectMemberOf) {
271                        trace!(?pre_dmo_set);
272
273                        if let Some(pre_mo_set) = pre.get_ava_refer(Attribute::MemberOf) {
274                            trace!(?pre_mo_set);
275                            debug_assert!(pre_mo_set.is_superset(pre_dmo_set));
276                        } else {
277                            unreachable!();
278                        }
279                    } else {
280                        trace!("NONE");
281                    };
282                };
283
284                changes.push((pre, tgte));
285            } else {
286                trace!("=> ignoring unmodified uuid {:?}", auuid);
287            }
288            Ok(())
289        })?;
290
291    // Write the batch out in a single stripe.
292    qs.internal_apply_writable(changes)
293    // Done! 🎉
294}
295
296// This is how you know the good code is here.
297#[allow(clippy::cognitive_complexity)]
298fn apply_memberof(
299    qs: &mut QueryServerWriteTransaction,
300    // TODO: Experiment with HashSet/BTreeSet here instead of vec.
301    // May require https://github.com/rust-lang/rust/issues/62924 to allow popping
302    mut affected_uuids: BTreeSet<Uuid>,
303) -> Result<(), OperationError> {
304    trace!(" => entering apply_memberof");
305
306    // Because of how replication works, we don't send MO over a replication boundary.
307    // As a result, we always need to trigger for any changed uuid, so we keep the
308    // initial affected set for the leaf resolution.
309    //
310    // As we proceed, we'll also add the affected members of our groups that are
311    // changing.
312    let mut all_affected_uuids: BTreeSet<_> = affected_uuids.iter().copied().collect();
313
314    // While there are still affected uuids.
315    while !affected_uuids.is_empty() {
316        trace!(?affected_uuids);
317
318        // Ignore recycled/tombstones
319        let filt = filter!(f_and!([
320            f_eq(Attribute::Class, EntryClass::Group.into()),
321            FC::Or(
322                affected_uuids
323                    .iter()
324                    .copied()
325                    .map(|u| f_eq(Attribute::Uuid, PartialValue::Uuid(u)))
326                    .collect()
327            )
328        ]));
329
330        // Clear the set for the next iteration
331        affected_uuids.clear();
332
333        let work_set = qs.internal_search_writeable(&filt)?;
334        let mut changes = Vec::with_capacity(work_set.len());
335
336        for (pre, mut tgte) in work_set.into_iter() {
337            let guuid = pre.get_uuid();
338
339            trace!(
340                "=> processing group update -> {:?} {}",
341                guuid,
342                tgte.get_display_id()
343            );
344
345            do_group_memberof(qs, guuid, &mut tgte)?;
346
347            // Did we change? Note we don't check if the class changed, only if mo changed.
348            if pre.get_ava_set(Attribute::MemberOf) != tgte.get_ava_set(Attribute::MemberOf)
349                || pre.get_ava_set(Attribute::DirectMemberOf)
350                    != tgte.get_ava_set(Attribute::DirectMemberOf)
351            {
352                // Yes we changed - we now must process all our members, as they need to
353                // inherit changes. Some of these members COULD be non groups, but we
354                // handle them in the subsequent steps.
355                trace!(
356                    "{:?} {} changed, flagging members as groups to change. ",
357                    guuid,
358                    tgte.get_display_id()
359                );
360
361                // Since our groups memberof (and related, direct member of) has changed, we
362                // need to propagate these values forward into our members. At this point we
363                // mark all our members as being part of the affected set.
364                let pre_member = pre.get_ava_refer(Attribute::Member);
365                let post_member = tgte.get_ava_refer(Attribute::Member);
366
367                match (pre_member, post_member) {
368                    (Some(pre_m), Some(post_m)) => {
369                        affected_uuids.extend(pre_m);
370                        affected_uuids.extend(post_m);
371                    }
372                    (Some(members), None) | (None, Some(members)) => {
373                        // Doesn't matter what order, just that they are affected
374                        affected_uuids.extend(members);
375                    }
376                    (None, None) => {}
377                };
378
379                let pre_dynmember = pre.get_ava_refer(Attribute::DynMember);
380                let post_dynmember = tgte.get_ava_refer(Attribute::DynMember);
381
382                match (pre_dynmember, post_dynmember) {
383                    (Some(pre_m), Some(post_m)) => {
384                        affected_uuids.extend(pre_m);
385                        affected_uuids.extend(post_m);
386                    }
387                    (Some(members), None) | (None, Some(members)) => {
388                        // Doesn't matter what order, just that they are affected
389                        affected_uuids.extend(members);
390                    }
391                    (None, None) => {}
392                };
393
394                // push the entries to pre/cand
395                changes.push((pre, tgte));
396            } else {
397                // If the group is stable, then we *only* need to update memberof
398                // on members that may have been added or removed. This exists to
399                // optimise when we add a member to a group, but without changing the
400                // group's mo/dmo to save re-writing mo to all the other members.
401                //
402                // If the group's memberof has been through the unstable state,
403                // all our members are already fully loaded into the affected sets.
404                //
405                // NOTE: This filtering of what members were actually impacted is
406                // performed in the call to post_modify_inner.
407
408                trace!("{:?} {} stable", guuid, tgte.get_display_id());
409            }
410        }
411
412        // Write this stripe if populated.
413        if !changes.is_empty() {
414            trace!("wrote stripe {}", changes.len());
415            qs.internal_apply_writable(changes).map_err(|err| {
416                error!(?err, "Failed to commit memberof group set");
417                err
418            })?;
419        }
420
421        // Reflect the full set of affected uuids into our all affected set.
422        all_affected_uuids.extend(affected_uuids.iter());
423
424        // Next loop!
425        trace!("-------------------------------------");
426    }
427
428    // ALL GROUP MOS + DMOS ARE NOW STABLE. We can update oul leaf entries as required.
429    do_leaf_memberof(qs, all_affected_uuids)
430}
431
432impl Plugin for MemberOf {
433    fn id() -> &'static str {
434        Attribute::MemberOf.as_ref()
435    }
436
437    #[instrument(level = "debug", name = "memberof_post_create", skip_all)]
438    fn post_create(
439        qs: &mut QueryServerWriteTransaction,
440        cand: &[Entry<EntrySealed, EntryCommitted>],
441        ce: &CreateEvent,
442    ) -> Result<(), OperationError> {
443        Self::post_create_inner(qs, cand, &ce.ident)
444    }
445
446    #[instrument(level = "debug", name = "memberof_post_repl_refresh", skip_all)]
447    fn post_repl_refresh(
448        qs: &mut QueryServerWriteTransaction,
449        cand: &[Entry<EntrySealed, EntryCommitted>],
450    ) -> Result<(), OperationError> {
451        let ident = Identity::from_internal();
452        Self::post_create_inner(qs, cand, &ident)
453    }
454
455    #[instrument(level = "debug", name = "memberof_post_repl_incremental", skip_all)]
456    fn post_repl_incremental(
457        qs: &mut QueryServerWriteTransaction,
458        pre_cand: &[Arc<EntrySealedCommitted>],
459        cand: &[EntrySealedCommitted],
460        conflict_uuids: &BTreeSet<Uuid>,
461    ) -> Result<(), OperationError> {
462        // If a uuid was in a conflict state, it will be present in the cand/pre_cand set,
463        // but it *may not* trigger dyn groups as the conflict before and after may satisfy
464        // the filter as it exists.
465        //
466        // In these cases we need to force dynmembers to be reloaded if any conflict occurs
467        // to ensure that all our memberships are accurate.
468        let force_dyngroup_cand_update = !conflict_uuids.is_empty();
469
470        // IMPORTANT - we need this for now so that dyngroup doesn't error on us, since
471        // repl is internal and dyngroup has a safety check to prevent external triggers.
472        let ident_internal = Identity::from_internal();
473        Self::post_modify_inner(
474            qs,
475            pre_cand,
476            cand,
477            &ident_internal,
478            force_dyngroup_cand_update,
479        )
480    }
481
482    #[instrument(level = "debug", name = "memberof_post_modify", skip_all)]
483    fn post_modify(
484        qs: &mut QueryServerWriteTransaction,
485        pre_cand: &[Arc<Entry<EntrySealed, EntryCommitted>>],
486        cand: &[Entry<EntrySealed, EntryCommitted>],
487        me: &ModifyEvent,
488    ) -> Result<(), OperationError> {
489        Self::post_modify_inner(qs, pre_cand, cand, &me.ident, false)
490    }
491
492    #[instrument(level = "debug", name = "memberof_post_batch_modify", skip_all)]
493    fn post_batch_modify(
494        qs: &mut QueryServerWriteTransaction,
495        pre_cand: &[Arc<Entry<EntrySealed, EntryCommitted>>],
496        cand: &[Entry<EntrySealed, EntryCommitted>],
497        me: &BatchModifyEvent,
498    ) -> Result<(), OperationError> {
499        Self::post_modify_inner(qs, pre_cand, cand, &me.ident, false)
500    }
501
502    #[instrument(level = "debug", name = "memberof_pre_delete", skip_all)]
503    fn pre_delete(
504        _qs: &mut QueryServerWriteTransaction,
505        cand: &mut Vec<EntryInvalidCommitted>,
506        _de: &DeleteEvent,
507    ) -> Result<(), OperationError> {
508        // Ensure that when an entry is deleted, that we remove its memberof values,
509        // and convert direct memberof to recycled direct memberof.
510
511        for entry in cand.iter_mut() {
512            if let Some(direct_mo_vs) = entry.pop_ava(Attribute::DirectMemberOf) {
513                entry.set_ava_set(&Attribute::RecycledDirectMemberOf, direct_mo_vs);
514            } else {
515                // Ensure it's empty
516                entry.purge_ava(Attribute::RecycledDirectMemberOf);
517            }
518            entry.purge_ava(Attribute::MemberOf);
519        }
520
521        Ok(())
522    }
523
524    #[instrument(level = "debug", name = "memberof_post_delete", skip_all)]
525    fn post_delete(
526        qs: &mut QueryServerWriteTransaction,
527        cand: &[Entry<EntrySealed, EntryCommitted>],
528        _de: &DeleteEvent,
529    ) -> Result<(), OperationError> {
530        // Similar condition to create - we only trigger updates on groups's members,
531        // so that they can find they are no longer a mo of what was deleted.
532        let affected_uuids = cand
533            .iter()
534            .filter_map(|e| {
535                // Is it a group?
536                if e.attribute_equality(Attribute::Class, &EntryClass::Group.into()) {
537                    e.get_ava_as_refuuid(Attribute::Member)
538                } else {
539                    None
540                }
541            })
542            .flatten()
543            .chain(
544                // Or a dyn group?
545                cand.iter()
546                    .filter_map(|post| {
547                        if post.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into()) {
548                            post.get_ava_as_refuuid(Attribute::DynMember)
549                        } else {
550                            None
551                        }
552                    })
553                    .flatten(),
554            )
555            .collect();
556
557        apply_memberof(qs, affected_uuids)
558    }
559
560    #[instrument(level = "debug", name = "memberof::verify", skip_all)]
561    fn verify(qs: &mut QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
562        let mut r = Vec::with_capacity(0);
563
564        let filt_in = filter!(f_pres(Attribute::Class));
565
566        let all_cand = match qs
567            .internal_search(filt_in)
568            .map_err(|_| Err(ConsistencyError::QueryServerSearchFailure))
569        {
570            Ok(all_cand) => all_cand,
571            Err(e) => return vec![e],
572        };
573
574        // First we have to build a direct membership map. This saves us
575        // needing to run queries since we already have every entry on hand
576        // from the all_cand search.
577        let mut direct_membership_map: BTreeMap<Uuid, BTreeSet<Uuid>> = Default::default();
578
579        let pv_class: PartialValue = EntryClass::Group.into();
580
581        for entry in all_cand.iter() {
582            if !entry.attribute_equality(Attribute::Class, &pv_class) {
583                // Not a group, move on.
584                continue;
585            }
586
587            let group_uuid = entry.get_uuid();
588
589            let member_iter = entry
590                .get_ava_refer(Attribute::Member)
591                .into_iter()
592                .flat_map(|set| set.iter())
593                .chain(
594                    entry
595                        .get_ava_refer(Attribute::DynMember)
596                        .into_iter()
597                        .flat_map(|set| set.iter()),
598                );
599
600            for member_uuid in member_iter {
601                let member_groups = direct_membership_map.entry(*member_uuid).or_default();
602                member_groups.insert(group_uuid);
603            }
604        }
605
606        // for each entry in the DB (live).
607        for e in all_cand {
608            let uuid = e.get_uuid();
609
610            let d_groups_set: Option<&BTreeSet<Uuid>> = direct_membership_map.get(&uuid);
611
612            trace!(
613                "DMO search groups {:?} -> {:?}",
614                e.get_display_id(),
615                d_groups_set
616            );
617
618            // Remember, we only need to check direct memberships, because when memberof
619            // it applies it clones dmo -> mo, so validation of all dmo sets implies mo is
620            // valid (and a subset) of dmo.
621
622            match (e.get_ava_set(Attribute::DirectMemberOf), d_groups_set) {
623                (Some(edmos), Some(b)) => {
624                    // Can they both be reference sets?
625                    match edmos.as_refer_set() {
626                        Some(a) => {
627                            let diff: Vec<_> = a.symmetric_difference(b).collect();
628                            if !diff.is_empty() {
629                                error!(
630                                    "MemberOfInvalid: Entry {}, DMO has inconsistencies",
631                                    e.get_display_id(),
632                                );
633                                trace!(entry_direct_member_of = ?a);
634                                trace!(expected_direct_groups = ?b);
635                                trace!(?diff);
636
637                                r.push(Err(ConsistencyError::MemberOfInvalid(e.get_id())));
638                            }
639                        }
640                        _ => {
641                            error!("MemberOfInvalid: Entry {}, DMO has incorrect syntax - should be reference uuid set", e.get_display_id());
642                            r.push(Err(ConsistencyError::MemberOfInvalid(e.get_id())));
643                        }
644                    }
645                }
646                (None, None) => {
647                    // Ok
648                }
649                (entry_direct_member_of, expected_direct_groups) => {
650                    error!(
651                        "MemberOfInvalid directmemberof set and DMO search set differ in presence: {}",
652                        e.get_display_id()
653                    );
654                    // trace!(?e);
655                    trace!(?entry_direct_member_of);
656                    trace!(?expected_direct_groups);
657                    r.push(Err(ConsistencyError::MemberOfInvalid(e.get_id())));
658                }
659            }
660        }
661
662        r
663    }
664}
665
666impl MemberOf {
667    fn post_create_inner(
668        qs: &mut QueryServerWriteTransaction,
669        cand: &[Entry<EntrySealed, EntryCommitted>],
670        ident: &Identity,
671    ) -> Result<(), OperationError> {
672        let dyngroup_change = super::dyngroup::DynGroup::post_create(qs, cand, ident)?;
673
674        let affected_uuids = cand
675            .iter()
676            .map(|e| e.get_uuid())
677            .chain(dyngroup_change)
678            // In a create, we have to always examine our members as being affected.
679            .chain(
680                cand.iter()
681                    .filter_map(|e| {
682                        // Is it a group?
683                        if e.attribute_equality(Attribute::Class, &EntryClass::Group.into()) {
684                            e.get_ava_as_refuuid(Attribute::Member)
685                        } else {
686                            None
687                        }
688                    })
689                    .flatten(),
690            )
691            .collect();
692
693        apply_memberof(qs, affected_uuids)
694    }
695
696    fn post_modify_inner(
697        qs: &mut QueryServerWriteTransaction,
698        pre_cand: &[Arc<EntrySealedCommitted>],
699        cand: &[EntrySealedCommitted],
700        ident: &Identity,
701        force_dyngroup_cand_update: bool,
702    ) -> Result<(), OperationError> {
703        let dyngroup_change = super::dyngroup::DynGroup::post_modify(
704            qs,
705            pre_cand,
706            cand,
707            ident,
708            force_dyngroup_cand_update,
709        )?;
710
711        let mut affected_uuids: BTreeSet<_> = cand
712            .iter()
713            .map(|post| post.get_uuid())
714            .chain(dyngroup_change)
715            .collect();
716
717        for (pre, post) in pre_cand.iter().zip(cand.iter()).filter(|(pre, post)| {
718            post.attribute_equality(Attribute::Class, &EntryClass::Group.into())
719                || pre.attribute_equality(Attribute::Class, &EntryClass::Group.into())
720        }) {
721            let pre_member = pre.get_ava_refer(Attribute::Member);
722            let post_member = post.get_ava_refer(Attribute::Member);
723
724            match (pre_member, post_member) {
725                (Some(pre_m), Some(post_m)) => {
726                    // Show only the *changed* uuids for leaf resolution.
727                    affected_uuids.extend(pre_m.symmetric_difference(post_m));
728                }
729                (Some(members), None) | (None, Some(members)) => {
730                    // Doesn't matter what order, just that they are affected
731                    affected_uuids.extend(members);
732                }
733                (None, None) => {}
734            };
735
736            let pre_dynmember = pre.get_ava_refer(Attribute::DynMember);
737            let post_dynmember = post.get_ava_refer(Attribute::DynMember);
738
739            match (pre_dynmember, post_dynmember) {
740                (Some(pre_m), Some(post_m)) => {
741                    // Show only the *changed* uuids.
742                    affected_uuids.extend(pre_m.symmetric_difference(post_m));
743                }
744                (Some(members), None) | (None, Some(members)) => {
745                    // Doesn't matter what order, just that they are affected
746                    affected_uuids.extend(members);
747                }
748                (None, None) => {}
749            };
750        }
751
752        apply_memberof(qs, affected_uuids)
753    }
754}
755
756#[cfg(test)]
757mod tests {
758    use crate::prelude::*;
759
760    const UUID_A: &str = "aaaaaaaa-f82e-4484-a407-181aa03bda5c";
761    const UUID_B: &str = "bbbbbbbb-2438-4384-9891-48f4c8172e9b";
762    const UUID_C: &str = "cccccccc-9b01-423f-9ba6-51aa4bbd5dd2";
763    const UUID_D: &str = "dddddddd-2ab3-48e3-938d-1b4754cd2984";
764
765    lazy_static! {
766        static ref EA: EntryInitNew = entry_init!(
767            (Attribute::Class, EntryClass::Group.to_value()),
768            (Attribute::Class, EntryClass::MemberOf.to_value()),
769            (Attribute::Name, Value::new_iname("testgroup_a")),
770            (Attribute::Uuid, Value::Uuid(uuid::uuid!(UUID_A)))
771        );
772        static ref EB: EntryInitNew = entry_init!(
773            (Attribute::Class, EntryClass::Group.to_value()),
774            (Attribute::Class, EntryClass::MemberOf.to_value()),
775            (Attribute::Name, Value::new_iname("testgroup_b")),
776            (Attribute::Uuid, Value::Uuid(uuid::uuid!(UUID_B)))
777        );
778        static ref EC: EntryInitNew = entry_init!(
779            (Attribute::Class, EntryClass::Group.to_value()),
780            (Attribute::Class, EntryClass::MemberOf.to_value()),
781            (Attribute::Name, Value::new_iname("testgroup_c")),
782            (Attribute::Uuid, Value::Uuid(uuid::uuid!(UUID_C)))
783        );
784        static ref ED: EntryInitNew = entry_init!(
785            (Attribute::Class, EntryClass::Group.to_value()),
786            (Attribute::Class, EntryClass::MemberOf.to_value()),
787            (Attribute::Name, Value::new_iname("testgroup_d")),
788            (Attribute::Uuid, Value::Uuid(uuid::uuid!(UUID_D)))
789        );
790    }
791
792    macro_rules! assert_memberof_int {
793        (
794            $qs:expr,
795            $ea:expr,
796            $eb:expr,
797            $mo:expr,
798            $cand:expr
799        ) => {{
800            let filt = filter!(f_and!([
801                f_eq(Attribute::Uuid, PartialValue::new_uuid_s($ea).unwrap()),
802                f_eq($mo, PartialValue::new_refer_s($eb).unwrap())
803            ]));
804            let cands = $qs.internal_search(filt).expect("Internal search failure");
805            debug!("assert_mo_cands {:?}", cands);
806            assert_eq!(cands.len(), $cand);
807        }};
808    }
809
810    macro_rules! assert_memberof {
811        (
812            $qs:expr,
813            $ea:expr,
814            $eb:expr
815        ) => {{
816            assert_memberof_int!($qs, $ea, $eb, Attribute::MemberOf, 1);
817        }};
818    }
819
820    macro_rules! assert_dirmemberof {
821        (
822            $qs:expr,
823            $ea:expr,
824            $eb:expr
825        ) => {{
826            assert_memberof_int!($qs, $ea, $eb, Attribute::DirectMemberOf, 1);
827        }};
828    }
829
830    macro_rules! assert_not_memberof {
831        (
832            $qs:expr,
833            $ea:expr,
834            $eb:expr
835        ) => {{
836            assert_memberof_int!($qs, $ea, $eb, Attribute::MemberOf, 0);
837        }};
838    }
839
840    macro_rules! assert_not_dirmemberof {
841        (
842            $qs:expr,
843            $ea:expr,
844            $eb:expr
845        ) => {{
846            assert_memberof_int!($qs, $ea, $eb, Attribute::DirectMemberOf, 0);
847        }};
848    }
849
850    #[test]
851    fn test_create_mo_single() {
852        // A -> B
853        let mut ea = EA.clone();
854        let eb = EB.clone();
855
856        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
857
858        let preload = Vec::with_capacity(0);
859        let create = vec![ea, eb];
860        run_create_test!(
861            Ok(()),
862            preload,
863            create,
864            None,
865            |qs: &mut QueryServerWriteTransaction| {
866                //                      V-- this uuid is
867                //                                  V-- memberof this UUID
868                assert_memberof!(qs, UUID_B, UUID_A);
869                assert_not_memberof!(qs, UUID_A, UUID_B);
870
871                assert_dirmemberof!(qs, UUID_B, UUID_A);
872                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
873            }
874        );
875    }
876
877    #[test]
878    fn test_create_mo_nested() {
879        // A -> B -> C
880        let mut ea = EA.clone();
881
882        let mut eb = EB.clone();
883
884        let ec = EC.clone();
885
886        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
887        eb.add_ava(Attribute::Member, Value::new_refer_s(UUID_C).unwrap());
888
889        let preload = Vec::with_capacity(0);
890        let create = vec![ea, eb, ec];
891        run_create_test!(
892            Ok(()),
893            preload,
894            create,
895            None,
896            |qs: &mut QueryServerWriteTransaction| {
897                //                      V-- this uuid is
898                //                                  V-- memberof this UUID
899                assert_not_memberof!(qs, UUID_A, UUID_A);
900                assert_not_memberof!(qs, UUID_A, UUID_B);
901                assert_not_memberof!(qs, UUID_A, UUID_C);
902
903                assert_memberof!(qs, UUID_B, UUID_A);
904                assert_not_memberof!(qs, UUID_B, UUID_B);
905                assert_not_memberof!(qs, UUID_B, UUID_C);
906
907                // This is due to nestig, C should be MO both!
908                assert_memberof!(qs, UUID_C, UUID_A);
909                assert_memberof!(qs, UUID_C, UUID_B);
910                assert_not_memberof!(qs, UUID_C, UUID_C);
911
912                assert_not_dirmemberof!(qs, UUID_A, UUID_A);
913                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
914                assert_not_dirmemberof!(qs, UUID_A, UUID_C);
915
916                assert_dirmemberof!(qs, UUID_B, UUID_A);
917                assert_not_dirmemberof!(qs, UUID_B, UUID_B);
918                assert_not_dirmemberof!(qs, UUID_B, UUID_C);
919
920                assert_not_dirmemberof!(qs, UUID_C, UUID_A);
921                assert_dirmemberof!(qs, UUID_C, UUID_B);
922                assert_not_dirmemberof!(qs, UUID_C, UUID_C);
923            }
924        );
925    }
926
927    #[test]
928    fn test_create_mo_cycle() {
929        // A -> B -> C -
930        // ^-----------/
931        let mut ea = EA.clone();
932
933        let mut eb = EB.clone();
934
935        let mut ec = EC.clone();
936
937        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
938        eb.add_ava(Attribute::Member, Value::new_refer_s(UUID_C).unwrap());
939        ec.add_ava(Attribute::Member, Value::new_refer_s(UUID_A).unwrap());
940
941        let preload = Vec::with_capacity(0);
942        let create = vec![ea, eb, ec];
943        run_create_test!(
944            Ok(()),
945            preload,
946            create,
947            None,
948            |qs: &mut QueryServerWriteTransaction| {
949                //                      V-- this uuid is
950                //                                  V-- memberof this UUID
951                assert_memberof!(qs, UUID_A, UUID_A);
952                assert_memberof!(qs, UUID_A, UUID_B);
953                assert_memberof!(qs, UUID_A, UUID_C);
954
955                assert_memberof!(qs, UUID_B, UUID_A);
956                assert_memberof!(qs, UUID_B, UUID_B);
957                assert_memberof!(qs, UUID_B, UUID_C);
958
959                assert_memberof!(qs, UUID_C, UUID_A);
960                assert_memberof!(qs, UUID_C, UUID_B);
961                assert_memberof!(qs, UUID_C, UUID_C);
962
963                assert_not_dirmemberof!(qs, UUID_A, UUID_A);
964                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
965                assert_dirmemberof!(qs, UUID_A, UUID_C);
966
967                assert_dirmemberof!(qs, UUID_B, UUID_A);
968                assert_not_dirmemberof!(qs, UUID_B, UUID_B);
969                assert_not_dirmemberof!(qs, UUID_B, UUID_C);
970
971                assert_not_dirmemberof!(qs, UUID_C, UUID_A);
972                assert_dirmemberof!(qs, UUID_C, UUID_B);
973                assert_not_dirmemberof!(qs, UUID_C, UUID_C);
974            }
975        );
976    }
977
978    #[test]
979    fn test_create_mo_multi_cycle() {
980        // A -> B -> C --> D -
981        // ^-----------/    /
982        // |---------------/
983        let mut ea = EA.clone();
984
985        let mut eb = EB.clone();
986
987        let mut ec = EC.clone();
988
989        let mut ed = ED.clone();
990
991        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
992        eb.add_ava(Attribute::Member, Value::new_refer_s(UUID_C).unwrap());
993
994        ec.add_ava(Attribute::Member, Value::new_refer_s(UUID_A).unwrap());
995        ec.add_ava(Attribute::Member, Value::new_refer_s(UUID_D).unwrap());
996
997        ed.add_ava(Attribute::Member, Value::new_refer_s(UUID_A).unwrap());
998
999        let preload = Vec::with_capacity(0);
1000        let create = vec![ea, eb, ec, ed];
1001        run_create_test!(
1002            Ok(()),
1003            preload,
1004            create,
1005            None,
1006            |qs: &mut QueryServerWriteTransaction| {
1007                //                      V-- this uuid is
1008                //                                  V-- memberof this UUID
1009                assert_memberof!(qs, UUID_A, UUID_A);
1010                assert_memberof!(qs, UUID_A, UUID_B);
1011                assert_memberof!(qs, UUID_A, UUID_C);
1012                assert_memberof!(qs, UUID_A, UUID_D);
1013
1014                assert_memberof!(qs, UUID_B, UUID_A);
1015                assert_memberof!(qs, UUID_B, UUID_B);
1016                assert_memberof!(qs, UUID_B, UUID_C);
1017                assert_memberof!(qs, UUID_B, UUID_D);
1018
1019                assert_memberof!(qs, UUID_C, UUID_A);
1020                assert_memberof!(qs, UUID_C, UUID_B);
1021                assert_memberof!(qs, UUID_C, UUID_C);
1022                assert_memberof!(qs, UUID_C, UUID_D);
1023
1024                assert_memberof!(qs, UUID_D, UUID_A);
1025                assert_memberof!(qs, UUID_D, UUID_B);
1026                assert_memberof!(qs, UUID_D, UUID_C);
1027                assert_memberof!(qs, UUID_D, UUID_D);
1028
1029                assert_not_dirmemberof!(qs, UUID_A, UUID_A);
1030                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
1031                assert_dirmemberof!(qs, UUID_A, UUID_C);
1032                assert_dirmemberof!(qs, UUID_A, UUID_D);
1033
1034                assert_dirmemberof!(qs, UUID_B, UUID_A);
1035                assert_not_dirmemberof!(qs, UUID_B, UUID_B);
1036                assert_not_dirmemberof!(qs, UUID_B, UUID_C);
1037                assert_not_dirmemberof!(qs, UUID_B, UUID_D);
1038
1039                assert_not_dirmemberof!(qs, UUID_C, UUID_A);
1040                assert_dirmemberof!(qs, UUID_C, UUID_B);
1041                assert_not_dirmemberof!(qs, UUID_C, UUID_C);
1042                assert_not_dirmemberof!(qs, UUID_C, UUID_D);
1043
1044                assert_not_dirmemberof!(qs, UUID_D, UUID_A);
1045                assert_not_dirmemberof!(qs, UUID_D, UUID_B);
1046                assert_dirmemberof!(qs, UUID_D, UUID_C);
1047                assert_not_dirmemberof!(qs, UUID_D, UUID_D);
1048            }
1049        );
1050    }
1051
1052    #[test]
1053    fn test_modify_mo_add_simple() {
1054        // A    B
1055        // Add member
1056        // A -> B
1057        let ea = EA.clone();
1058        let eb = EB.clone();
1059
1060        let preload = vec![ea, eb];
1061        run_modify_test!(
1062            Ok(()),
1063            preload,
1064            filter!(f_eq(
1065                Attribute::Uuid,
1066                PartialValue::new_uuid_s(UUID_A).unwrap()
1067            )),
1068            ModifyList::new_list(vec![Modify::Present(
1069                Attribute::Member,
1070                Value::new_refer_s(UUID_B).unwrap()
1071            )]),
1072            None,
1073            |_| {},
1074            |qs: &mut QueryServerWriteTransaction| {
1075                //                      V-- this uuid is
1076                //                                  V-- memberof this UUID
1077                assert_memberof!(qs, UUID_B, UUID_A);
1078                assert_not_memberof!(qs, UUID_A, UUID_B);
1079
1080                assert_dirmemberof!(qs, UUID_B, UUID_A);
1081                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
1082            }
1083        );
1084    }
1085
1086    #[test]
1087    fn test_modify_mo_add_nested_1() {
1088        // A    B -> C
1089        // Add member A -> B
1090        // A -> B -> C
1091        let ea = EA.clone();
1092        let mut eb = EB.clone();
1093        let ec = EC.clone();
1094
1095        eb.add_ava(Attribute::Member, Value::new_refer_s(UUID_C).unwrap());
1096
1097        let preload = vec![ea, eb, ec];
1098        run_modify_test!(
1099            Ok(()),
1100            preload,
1101            filter!(f_eq(
1102                Attribute::Uuid,
1103                PartialValue::new_uuid_s(UUID_A).unwrap()
1104            )),
1105            ModifyList::new_list(vec![Modify::Present(
1106                Attribute::Member,
1107                Value::new_refer_s(UUID_B).unwrap()
1108            )]),
1109            None,
1110            |_| {},
1111            |qs: &mut QueryServerWriteTransaction| {
1112                //                      V-- this uuid is
1113                //                                  V-- memberof this UUID
1114                assert_not_memberof!(qs, UUID_A, UUID_A);
1115                assert_not_memberof!(qs, UUID_A, UUID_B);
1116                assert_not_memberof!(qs, UUID_A, UUID_C);
1117
1118                assert_memberof!(qs, UUID_B, UUID_A);
1119                assert_not_memberof!(qs, UUID_B, UUID_B);
1120                assert_not_memberof!(qs, UUID_B, UUID_C);
1121
1122                assert_memberof!(qs, UUID_C, UUID_A);
1123                assert_memberof!(qs, UUID_C, UUID_B);
1124                assert_not_memberof!(qs, UUID_C, UUID_C);
1125
1126                assert_not_dirmemberof!(qs, UUID_A, UUID_A);
1127                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
1128                assert_not_dirmemberof!(qs, UUID_A, UUID_C);
1129
1130                assert_dirmemberof!(qs, UUID_B, UUID_A);
1131                assert_not_dirmemberof!(qs, UUID_B, UUID_B);
1132                assert_not_dirmemberof!(qs, UUID_B, UUID_C);
1133
1134                assert_not_dirmemberof!(qs, UUID_C, UUID_A);
1135                assert_dirmemberof!(qs, UUID_C, UUID_B);
1136                assert_not_dirmemberof!(qs, UUID_C, UUID_C);
1137            }
1138        );
1139    }
1140
1141    #[test]
1142    fn test_modify_mo_add_nested_2() {
1143        // A -> B    C
1144        // Add member B -> C
1145        // A -> B -> C
1146        let mut ea = EA.clone();
1147        let eb = EB.clone();
1148        let ec = EC.clone();
1149
1150        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
1151
1152        let preload = vec![ea, eb, ec];
1153        run_modify_test!(
1154            Ok(()),
1155            preload,
1156            filter!(f_eq(
1157                Attribute::Uuid,
1158                PartialValue::new_uuid_s(UUID_B).unwrap()
1159            )),
1160            ModifyList::new_list(vec![Modify::Present(
1161                Attribute::Member,
1162                Value::new_refer_s(UUID_C).unwrap()
1163            )]),
1164            None,
1165            |_| {},
1166            |qs: &mut QueryServerWriteTransaction| {
1167                //                      V-- this uuid is
1168                //                                  V-- memberof this UUID
1169                assert_not_memberof!(qs, UUID_A, UUID_A);
1170                assert_not_memberof!(qs, UUID_A, UUID_B);
1171                assert_not_memberof!(qs, UUID_A, UUID_C);
1172
1173                assert_memberof!(qs, UUID_B, UUID_A);
1174                assert_not_memberof!(qs, UUID_B, UUID_B);
1175                assert_not_memberof!(qs, UUID_B, UUID_C);
1176
1177                assert_memberof!(qs, UUID_C, UUID_A);
1178                assert_memberof!(qs, UUID_C, UUID_B);
1179                assert_not_memberof!(qs, UUID_C, UUID_C);
1180
1181                assert_not_dirmemberof!(qs, UUID_A, UUID_A);
1182                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
1183                assert_not_dirmemberof!(qs, UUID_A, UUID_C);
1184
1185                assert_dirmemberof!(qs, UUID_B, UUID_A);
1186                assert_not_dirmemberof!(qs, UUID_B, UUID_B);
1187                assert_not_dirmemberof!(qs, UUID_B, UUID_C);
1188
1189                assert_not_dirmemberof!(qs, UUID_C, UUID_A);
1190                assert_dirmemberof!(qs, UUID_C, UUID_B);
1191                assert_not_dirmemberof!(qs, UUID_C, UUID_C);
1192            }
1193        );
1194    }
1195
1196    #[test]
1197    fn test_modify_mo_add_cycle() {
1198        // A -> B -> C
1199        //
1200        // Add member C -> A
1201        // A -> B -> C -
1202        // ^-----------/
1203        let mut ea = EA.clone();
1204        let mut eb = EB.clone();
1205        let ec = EC.clone();
1206
1207        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
1208        eb.add_ava(Attribute::Member, Value::new_refer_s(UUID_C).unwrap());
1209
1210        let preload = vec![ea, eb, ec];
1211        run_modify_test!(
1212            Ok(()),
1213            preload,
1214            filter!(f_eq(
1215                Attribute::Uuid,
1216                PartialValue::new_uuid_s(UUID_C).unwrap()
1217            )),
1218            ModifyList::new_list(vec![Modify::Present(
1219                Attribute::Member,
1220                Value::new_refer_s(UUID_A).unwrap()
1221            )]),
1222            None,
1223            |_| {},
1224            |qs: &mut QueryServerWriteTransaction| {
1225                //                      V-- this uuid is
1226                //                                  V-- memberof this UUID
1227                assert_memberof!(qs, UUID_A, UUID_A);
1228                assert_memberof!(qs, UUID_A, UUID_B);
1229                assert_memberof!(qs, UUID_A, UUID_C);
1230
1231                assert_memberof!(qs, UUID_B, UUID_A);
1232                assert_memberof!(qs, UUID_B, UUID_B);
1233                assert_memberof!(qs, UUID_B, UUID_C);
1234
1235                assert_memberof!(qs, UUID_C, UUID_A);
1236                assert_memberof!(qs, UUID_C, UUID_B);
1237                assert_memberof!(qs, UUID_C, UUID_C);
1238
1239                assert_not_dirmemberof!(qs, UUID_A, UUID_A);
1240                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
1241                assert_dirmemberof!(qs, UUID_A, UUID_C);
1242
1243                assert_dirmemberof!(qs, UUID_B, UUID_A);
1244                assert_not_dirmemberof!(qs, UUID_B, UUID_B);
1245                assert_not_dirmemberof!(qs, UUID_B, UUID_C);
1246
1247                assert_not_dirmemberof!(qs, UUID_C, UUID_A);
1248                assert_dirmemberof!(qs, UUID_C, UUID_B);
1249                assert_not_dirmemberof!(qs, UUID_C, UUID_C);
1250            }
1251        );
1252    }
1253
1254    #[test]
1255    fn test_modify_mo_add_multi_cycle() {
1256        // A -> B -> C --> D
1257        //
1258        // Add member C -> A
1259        // Add member C -> D
1260        // Add member D -> A
1261        //
1262        // A -> B -> C --> D -
1263        // ^-----------/    /
1264        // |---------------/
1265        let mut ea = EA.clone();
1266        let mut eb = EB.clone();
1267        let mut ec = EC.clone();
1268        let ed = ED.clone();
1269
1270        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
1271        eb.add_ava(Attribute::Member, Value::new_refer_s(UUID_C).unwrap());
1272        ec.add_ava(Attribute::Member, Value::new_refer_s(UUID_D).unwrap());
1273
1274        let preload = vec![ea, eb, ec, ed];
1275        run_modify_test!(
1276            Ok(()),
1277            preload,
1278            filter!(f_or!([
1279                f_eq(Attribute::Uuid, PartialValue::new_uuid_s(UUID_C).unwrap()),
1280                f_eq(Attribute::Uuid, PartialValue::new_uuid_s(UUID_D).unwrap()),
1281            ])),
1282            ModifyList::new_list(vec![Modify::Present(
1283                Attribute::Member,
1284                Value::new_refer_s(UUID_A).unwrap()
1285            )]),
1286            None,
1287            |_| {},
1288            |qs: &mut QueryServerWriteTransaction| {
1289                //                      V-- this uuid is
1290                //                                  V-- memberof this UUID
1291                assert_memberof!(qs, UUID_A, UUID_A);
1292                assert_memberof!(qs, UUID_A, UUID_B);
1293                assert_memberof!(qs, UUID_A, UUID_C);
1294                assert_memberof!(qs, UUID_A, UUID_D);
1295
1296                assert_memberof!(qs, UUID_B, UUID_A);
1297                assert_memberof!(qs, UUID_B, UUID_B);
1298                assert_memberof!(qs, UUID_B, UUID_C);
1299                assert_memberof!(qs, UUID_B, UUID_D);
1300
1301                assert_memberof!(qs, UUID_C, UUID_A);
1302                assert_memberof!(qs, UUID_C, UUID_B);
1303                assert_memberof!(qs, UUID_C, UUID_C);
1304                assert_memberof!(qs, UUID_C, UUID_D);
1305
1306                assert_memberof!(qs, UUID_D, UUID_A);
1307                assert_memberof!(qs, UUID_D, UUID_B);
1308                assert_memberof!(qs, UUID_D, UUID_C);
1309                assert_memberof!(qs, UUID_D, UUID_D);
1310
1311                assert_not_dirmemberof!(qs, UUID_A, UUID_A);
1312                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
1313                assert_dirmemberof!(qs, UUID_A, UUID_C);
1314                assert_dirmemberof!(qs, UUID_A, UUID_D);
1315
1316                assert_dirmemberof!(qs, UUID_B, UUID_A);
1317                assert_not_dirmemberof!(qs, UUID_B, UUID_B);
1318                assert_not_dirmemberof!(qs, UUID_B, UUID_C);
1319                assert_not_dirmemberof!(qs, UUID_B, UUID_D);
1320
1321                assert_not_dirmemberof!(qs, UUID_C, UUID_A);
1322                assert_dirmemberof!(qs, UUID_C, UUID_B);
1323                assert_not_dirmemberof!(qs, UUID_C, UUID_C);
1324                assert_not_dirmemberof!(qs, UUID_C, UUID_D);
1325
1326                assert_not_dirmemberof!(qs, UUID_D, UUID_A);
1327                assert_not_dirmemberof!(qs, UUID_D, UUID_B);
1328                assert_dirmemberof!(qs, UUID_D, UUID_C);
1329                assert_not_dirmemberof!(qs, UUID_D, UUID_D);
1330            }
1331        );
1332    }
1333
1334    #[test]
1335    fn test_modify_mo_del_simple() {
1336        // A -> B
1337        // remove member A -> B
1338        // A    B
1339        let mut ea = EA.clone();
1340        let mut eb = EB.clone();
1341
1342        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
1343        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1344
1345        let preload = vec![ea, eb];
1346        run_modify_test!(
1347            Ok(()),
1348            preload,
1349            filter!(f_eq(
1350                Attribute::Uuid,
1351                PartialValue::new_uuid_s(UUID_A).unwrap()
1352            )),
1353            ModifyList::new_list(vec![Modify::Removed(
1354                Attribute::Member,
1355                PartialValue::new_refer_s(UUID_B).unwrap()
1356            )]),
1357            None,
1358            |_| {},
1359            |qs: &mut QueryServerWriteTransaction| {
1360                //                      V-- this uuid is
1361                //                                  V-- memberof this UUID
1362                assert_not_memberof!(qs, UUID_B, UUID_A);
1363                assert_not_memberof!(qs, UUID_A, UUID_B);
1364
1365                assert_not_dirmemberof!(qs, UUID_B, UUID_A);
1366                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
1367            }
1368        );
1369    }
1370
1371    #[test]
1372    fn test_modify_mo_del_nested_1() {
1373        // A -> B -> C
1374        // Remove A -> B
1375        // A    B -> C
1376        let mut ea = EA.clone();
1377        let mut eb = EB.clone();
1378        let mut ec = EC.clone();
1379
1380        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
1381        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1382        eb.add_ava(Attribute::Member, Value::new_refer_s(UUID_C).unwrap());
1383        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1384
1385        let preload = vec![ea, eb, ec];
1386        run_modify_test!(
1387            Ok(()),
1388            preload,
1389            filter!(f_eq(
1390                Attribute::Uuid,
1391                PartialValue::new_uuid_s(UUID_A).unwrap()
1392            )),
1393            ModifyList::new_list(vec![Modify::Removed(
1394                Attribute::Member,
1395                PartialValue::new_refer_s(UUID_B).unwrap()
1396            )]),
1397            None,
1398            |_| {},
1399            |qs: &mut QueryServerWriteTransaction| {
1400                //                      V-- this uuid is
1401                //                                  V-- memberof this UUID
1402                assert_not_memberof!(qs, UUID_A, UUID_A);
1403                assert_not_memberof!(qs, UUID_A, UUID_B);
1404                assert_not_memberof!(qs, UUID_A, UUID_C);
1405
1406                assert_not_memberof!(qs, UUID_B, UUID_A);
1407                assert_not_memberof!(qs, UUID_B, UUID_B);
1408                assert_not_memberof!(qs, UUID_B, UUID_C);
1409
1410                assert_not_memberof!(qs, UUID_C, UUID_A);
1411                assert_memberof!(qs, UUID_C, UUID_B);
1412                assert_not_memberof!(qs, UUID_C, UUID_C);
1413
1414                assert_not_dirmemberof!(qs, UUID_A, UUID_A);
1415                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
1416                assert_not_dirmemberof!(qs, UUID_A, UUID_C);
1417
1418                assert_not_dirmemberof!(qs, UUID_B, UUID_A);
1419                assert_not_dirmemberof!(qs, UUID_B, UUID_B);
1420                assert_not_dirmemberof!(qs, UUID_B, UUID_C);
1421
1422                assert_not_dirmemberof!(qs, UUID_C, UUID_A);
1423                assert_dirmemberof!(qs, UUID_C, UUID_B);
1424                assert_not_dirmemberof!(qs, UUID_C, UUID_C);
1425            }
1426        );
1427    }
1428
1429    #[test]
1430    fn test_modify_mo_del_nested_2() {
1431        // A -> B -> C
1432        // Remove B -> C
1433        // A -> B    C
1434        let mut ea = EA.clone();
1435        let mut eb = EB.clone();
1436        let mut ec = EC.clone();
1437
1438        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
1439        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1440        eb.add_ava(Attribute::Member, Value::new_refer_s(UUID_C).unwrap());
1441        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1442        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1443
1444        let preload = vec![ea, eb, ec];
1445        run_modify_test!(
1446            Ok(()),
1447            preload,
1448            filter!(f_eq(
1449                Attribute::Uuid,
1450                PartialValue::new_uuid_s(UUID_B).unwrap()
1451            )),
1452            ModifyList::new_list(vec![Modify::Removed(
1453                Attribute::Member,
1454                PartialValue::new_refer_s(UUID_C).unwrap()
1455            )]),
1456            None,
1457            |_| {},
1458            |qs: &mut QueryServerWriteTransaction| {
1459                //                      V-- this uuid is
1460                //                                  V-- memberof this UUID
1461                assert_not_memberof!(qs, UUID_A, UUID_A);
1462                assert_not_memberof!(qs, UUID_A, UUID_B);
1463                assert_not_memberof!(qs, UUID_A, UUID_C);
1464
1465                assert_memberof!(qs, UUID_B, UUID_A);
1466                assert_not_memberof!(qs, UUID_B, UUID_B);
1467                assert_not_memberof!(qs, UUID_B, UUID_C);
1468
1469                assert_not_memberof!(qs, UUID_C, UUID_A);
1470                assert_not_memberof!(qs, UUID_C, UUID_B);
1471                assert_not_memberof!(qs, UUID_C, UUID_C);
1472
1473                assert_not_dirmemberof!(qs, UUID_A, UUID_A);
1474                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
1475                assert_not_dirmemberof!(qs, UUID_A, UUID_C);
1476
1477                assert_dirmemberof!(qs, UUID_B, UUID_A);
1478                assert_not_dirmemberof!(qs, UUID_B, UUID_B);
1479                assert_not_dirmemberof!(qs, UUID_B, UUID_C);
1480
1481                assert_not_dirmemberof!(qs, UUID_C, UUID_A);
1482                assert_not_dirmemberof!(qs, UUID_C, UUID_B);
1483                assert_not_dirmemberof!(qs, UUID_C, UUID_C);
1484            }
1485        );
1486    }
1487
1488    #[test]
1489    fn test_modify_mo_del_cycle() {
1490        // A -> B -> C -
1491        // ^-----------/
1492        // Remove C -> A
1493        // A -> B -> C
1494        let mut ea = EA.clone();
1495        let mut eb = EB.clone();
1496        let mut ec = EC.clone();
1497
1498        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
1499        ea.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_C).unwrap());
1500        ea.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1501        ea.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1502
1503        eb.add_ava(Attribute::Member, Value::new_refer_s(UUID_C).unwrap());
1504        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_C).unwrap());
1505        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1506        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1507
1508        ec.add_ava(Attribute::Member, Value::new_refer_s(UUID_A).unwrap());
1509        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_C).unwrap());
1510        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1511        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1512
1513        let preload = vec![ea, eb, ec];
1514        run_modify_test!(
1515            Ok(()),
1516            preload,
1517            filter!(f_eq(
1518                Attribute::Uuid,
1519                PartialValue::new_uuid_s(UUID_C).unwrap()
1520            )),
1521            ModifyList::new_list(vec![Modify::Removed(
1522                Attribute::Member,
1523                PartialValue::new_refer_s(UUID_A).unwrap()
1524            )]),
1525            None,
1526            |_| {},
1527            |qs: &mut QueryServerWriteTransaction| {
1528                //                      V-- this uuid is
1529                //                                  V-- memberof this UUID
1530                assert_not_memberof!(qs, UUID_A, UUID_A);
1531                assert_not_memberof!(qs, UUID_A, UUID_B);
1532                assert_not_memberof!(qs, UUID_A, UUID_C);
1533
1534                assert_memberof!(qs, UUID_B, UUID_A);
1535                assert_not_memberof!(qs, UUID_B, UUID_B);
1536                assert_not_memberof!(qs, UUID_B, UUID_C);
1537
1538                assert_memberof!(qs, UUID_C, UUID_A);
1539                assert_memberof!(qs, UUID_C, UUID_B);
1540                assert_not_memberof!(qs, UUID_C, UUID_C);
1541
1542                assert_not_dirmemberof!(qs, UUID_A, UUID_A);
1543                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
1544                assert_not_dirmemberof!(qs, UUID_A, UUID_C);
1545
1546                assert_dirmemberof!(qs, UUID_B, UUID_A);
1547                assert_not_dirmemberof!(qs, UUID_B, UUID_B);
1548                assert_not_dirmemberof!(qs, UUID_B, UUID_C);
1549
1550                assert_not_dirmemberof!(qs, UUID_C, UUID_A);
1551                assert_dirmemberof!(qs, UUID_C, UUID_B);
1552                assert_not_dirmemberof!(qs, UUID_C, UUID_C);
1553            }
1554        );
1555    }
1556
1557    #[test]
1558    fn test_modify_mo_del_multi_cycle() {
1559        // A -> B -> C --> D -
1560        // ^-----------/    /
1561        // |---------------/
1562        //
1563        // Remove C -> D
1564        // Remove C -> A
1565        //
1566        // A -> B -> C    D -
1567        // ^                /
1568        // |---------------/
1569        let mut ea = EA.clone();
1570        let mut eb = EB.clone();
1571        let mut ec = EC.clone();
1572        let mut ed = ED.clone();
1573
1574        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
1575        ea.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_D).unwrap());
1576        ea.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_C).unwrap());
1577        ea.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1578        ea.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1579
1580        eb.add_ava(Attribute::Member, Value::new_refer_s(UUID_C).unwrap());
1581        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_D).unwrap());
1582        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_C).unwrap());
1583        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1584        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1585
1586        ec.add_ava(Attribute::Member, Value::new_refer_s(UUID_A).unwrap());
1587        ec.add_ava(Attribute::Member, Value::new_refer_s(UUID_D).unwrap());
1588        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_D).unwrap());
1589        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_C).unwrap());
1590        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1591        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1592
1593        ed.add_ava(Attribute::Member, Value::new_refer_s(UUID_A).unwrap());
1594        ed.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_D).unwrap());
1595        ed.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_C).unwrap());
1596        ed.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1597        ed.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1598
1599        let preload = vec![ea, eb, ec, ed];
1600        run_modify_test!(
1601            Ok(()),
1602            preload,
1603            filter!(f_eq(
1604                Attribute::Uuid,
1605                PartialValue::new_uuid_s(UUID_C).unwrap()
1606            )),
1607            ModifyList::new_list(vec![
1608                Modify::Removed(
1609                    Attribute::Member,
1610                    PartialValue::new_refer_s(UUID_A).unwrap()
1611                ),
1612                Modify::Removed(
1613                    Attribute::Member,
1614                    PartialValue::new_refer_s(UUID_D).unwrap()
1615                ),
1616            ]),
1617            None,
1618            |_| {},
1619            |qs: &mut QueryServerWriteTransaction| {
1620                //                      V-- this uuid is
1621                //                                  V-- memberof this UUID
1622                assert_not_memberof!(qs, UUID_A, UUID_A);
1623                assert_not_memberof!(qs, UUID_A, UUID_B);
1624                assert_not_memberof!(qs, UUID_A, UUID_C);
1625                assert_memberof!(qs, UUID_A, UUID_D);
1626
1627                assert_memberof!(qs, UUID_B, UUID_A);
1628                assert_not_memberof!(qs, UUID_B, UUID_B);
1629                assert_not_memberof!(qs, UUID_B, UUID_C);
1630                assert_memberof!(qs, UUID_B, UUID_D);
1631
1632                assert_memberof!(qs, UUID_C, UUID_A);
1633                assert_memberof!(qs, UUID_C, UUID_B);
1634                assert_not_memberof!(qs, UUID_C, UUID_C);
1635                assert_memberof!(qs, UUID_C, UUID_D);
1636
1637                assert_not_memberof!(qs, UUID_D, UUID_A);
1638                assert_not_memberof!(qs, UUID_D, UUID_B);
1639                assert_not_memberof!(qs, UUID_D, UUID_C);
1640                assert_not_memberof!(qs, UUID_D, UUID_D);
1641
1642                assert_not_dirmemberof!(qs, UUID_A, UUID_A);
1643                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
1644                assert_not_dirmemberof!(qs, UUID_A, UUID_C);
1645                assert_dirmemberof!(qs, UUID_A, UUID_D);
1646
1647                assert_dirmemberof!(qs, UUID_B, UUID_A);
1648                assert_not_dirmemberof!(qs, UUID_B, UUID_B);
1649                assert_not_dirmemberof!(qs, UUID_B, UUID_C);
1650                assert_not_dirmemberof!(qs, UUID_B, UUID_D);
1651
1652                assert_not_dirmemberof!(qs, UUID_C, UUID_A);
1653                assert_dirmemberof!(qs, UUID_C, UUID_B);
1654                assert_not_dirmemberof!(qs, UUID_C, UUID_C);
1655                assert_not_dirmemberof!(qs, UUID_C, UUID_D);
1656
1657                assert_not_dirmemberof!(qs, UUID_D, UUID_A);
1658                assert_not_dirmemberof!(qs, UUID_D, UUID_B);
1659                assert_not_dirmemberof!(qs, UUID_D, UUID_C);
1660                assert_not_dirmemberof!(qs, UUID_D, UUID_D);
1661            }
1662        );
1663    }
1664
1665    #[test]
1666    fn test_delete_mo_simple() {
1667        // X -> B
1668        let mut ea = EA.clone();
1669        let mut eb = EB.clone();
1670
1671        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
1672        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1673
1674        let preload = vec![ea, eb];
1675        run_delete_test!(
1676            Ok(()),
1677            preload,
1678            filter!(f_eq(
1679                Attribute::Uuid,
1680                PartialValue::new_uuid_s(UUID_A).unwrap()
1681            )),
1682            None,
1683            |qs: &mut QueryServerWriteTransaction| {
1684                //                      V-- this uuid is
1685                //                                  V-- memberof this UUID
1686                assert_not_memberof!(qs, UUID_B, UUID_A);
1687                assert_not_memberof!(qs, UUID_A, UUID_B);
1688
1689                assert_not_dirmemberof!(qs, UUID_B, UUID_A);
1690                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
1691            }
1692        );
1693    }
1694
1695    #[test]
1696    fn test_delete_mo_nested_head() {
1697        // X -> B -> C
1698        let mut ea = EA.clone();
1699        let mut eb = EB.clone();
1700        let mut ec = EC.clone();
1701
1702        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
1703        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1704
1705        eb.add_ava(Attribute::Member, Value::new_refer_s(UUID_C).unwrap());
1706        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1707        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1708
1709        let preload = vec![ea, eb, ec];
1710        run_delete_test!(
1711            Ok(()),
1712            preload,
1713            filter!(f_eq(
1714                Attribute::Uuid,
1715                PartialValue::new_uuid_s(UUID_A).unwrap()
1716            )),
1717            None,
1718            |qs: &mut QueryServerWriteTransaction| {
1719                //                      V-- this uuid is
1720                //                                  V-- memberof this UUID
1721                assert_not_memberof!(qs, UUID_B, UUID_A);
1722                assert_not_memberof!(qs, UUID_B, UUID_B);
1723                assert_not_memberof!(qs, UUID_B, UUID_C);
1724
1725                assert_not_memberof!(qs, UUID_C, UUID_A);
1726                assert_memberof!(qs, UUID_C, UUID_B);
1727                assert_not_memberof!(qs, UUID_C, UUID_C);
1728
1729                assert_not_dirmemberof!(qs, UUID_B, UUID_A);
1730                assert_not_dirmemberof!(qs, UUID_B, UUID_B);
1731                assert_not_dirmemberof!(qs, UUID_B, UUID_C);
1732
1733                assert_not_dirmemberof!(qs, UUID_C, UUID_A);
1734                assert_dirmemberof!(qs, UUID_C, UUID_B);
1735                assert_not_dirmemberof!(qs, UUID_C, UUID_C);
1736            }
1737        );
1738    }
1739
1740    #[test]
1741    fn test_delete_mo_nested_branch() {
1742        // A -> X -> C
1743        let mut ea = EA.clone();
1744        let mut eb = EB.clone();
1745        let mut ec = EC.clone();
1746
1747        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
1748        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1749
1750        eb.add_ava(Attribute::Member, Value::new_refer_s(UUID_C).unwrap());
1751        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1752        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1753
1754        let preload = vec![ea, eb, ec];
1755        run_delete_test!(
1756            Ok(()),
1757            preload,
1758            filter!(f_eq(
1759                Attribute::Uuid,
1760                PartialValue::new_uuid_s(UUID_B).unwrap()
1761            )),
1762            None,
1763            |qs: &mut QueryServerWriteTransaction| {
1764                //                      V-- this uuid is
1765                //                                  V-- memberof this UUID
1766                assert_not_memberof!(qs, UUID_A, UUID_A);
1767                assert_not_memberof!(qs, UUID_A, UUID_B);
1768                assert_not_memberof!(qs, UUID_A, UUID_C);
1769
1770                assert_not_memberof!(qs, UUID_C, UUID_A);
1771                assert_not_memberof!(qs, UUID_C, UUID_B);
1772                assert_not_memberof!(qs, UUID_C, UUID_C);
1773
1774                assert_not_dirmemberof!(qs, UUID_A, UUID_A);
1775                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
1776                assert_not_dirmemberof!(qs, UUID_A, UUID_C);
1777
1778                assert_not_dirmemberof!(qs, UUID_C, UUID_A);
1779                assert_not_dirmemberof!(qs, UUID_C, UUID_B);
1780                assert_not_dirmemberof!(qs, UUID_C, UUID_C);
1781            }
1782        );
1783    }
1784
1785    #[test]
1786    fn test_delete_mo_cycle() {
1787        // X -> B -> C -
1788        // ^-----------/
1789        let mut ea = EA.clone();
1790        let mut eb = EB.clone();
1791        let mut ec = EC.clone();
1792
1793        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
1794        ea.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1795        ea.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1796        ea.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_C).unwrap());
1797
1798        eb.add_ava(Attribute::Member, Value::new_refer_s(UUID_C).unwrap());
1799        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1800        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1801        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_C).unwrap());
1802
1803        ec.add_ava(Attribute::Member, Value::new_refer_s(UUID_A).unwrap());
1804        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1805        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1806        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_C).unwrap());
1807
1808        let preload = vec![ea, eb, ec];
1809        run_delete_test!(
1810            Ok(()),
1811            preload,
1812            filter!(f_eq(
1813                Attribute::Uuid,
1814                PartialValue::new_uuid_s(UUID_A).unwrap()
1815            )),
1816            None,
1817            |qs: &mut QueryServerWriteTransaction| {
1818                //                      V-- this uuid is
1819                //                                  V-- memberof this UUID
1820                assert_not_memberof!(qs, UUID_B, UUID_A);
1821                assert_not_memberof!(qs, UUID_B, UUID_B);
1822                assert_not_memberof!(qs, UUID_B, UUID_C);
1823
1824                assert_not_memberof!(qs, UUID_C, UUID_A);
1825                assert_memberof!(qs, UUID_C, UUID_B);
1826                assert_not_memberof!(qs, UUID_C, UUID_C);
1827
1828                assert_not_dirmemberof!(qs, UUID_B, UUID_A);
1829                assert_not_dirmemberof!(qs, UUID_B, UUID_B);
1830                assert_not_dirmemberof!(qs, UUID_B, UUID_C);
1831
1832                assert_not_dirmemberof!(qs, UUID_C, UUID_A);
1833                assert_dirmemberof!(qs, UUID_C, UUID_B);
1834                assert_not_dirmemberof!(qs, UUID_C, UUID_C);
1835            }
1836        );
1837    }
1838
1839    #[test]
1840    fn test_delete_mo_multi_cycle() {
1841        // A -> X -> C --> D -
1842        // ^-----------/    /
1843        // |---------------/
1844        let mut ea = EA.clone();
1845        let mut eb = EB.clone();
1846        let mut ec = EC.clone();
1847        let mut ed = ED.clone();
1848
1849        ea.add_ava(Attribute::Member, Value::new_refer_s(UUID_B).unwrap());
1850        ea.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1851        ea.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1852        ea.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_C).unwrap());
1853        ea.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_D).unwrap());
1854
1855        eb.add_ava(Attribute::Member, Value::new_refer_s(UUID_C).unwrap());
1856        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1857        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1858        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_C).unwrap());
1859        eb.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_D).unwrap());
1860
1861        ec.add_ava(Attribute::Member, Value::new_refer_s(UUID_A).unwrap());
1862        ec.add_ava(Attribute::Member, Value::new_refer_s(UUID_D).unwrap());
1863        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1864        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1865        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_C).unwrap());
1866        ec.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_D).unwrap());
1867
1868        ed.add_ava(Attribute::Member, Value::new_refer_s(UUID_A).unwrap());
1869        ed.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_A).unwrap());
1870        ed.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_B).unwrap());
1871        ed.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_C).unwrap());
1872        ed.add_ava(Attribute::MemberOf, Value::new_refer_s(UUID_D).unwrap());
1873
1874        let preload = vec![ea, eb, ec, ed];
1875        run_delete_test!(
1876            Ok(()),
1877            preload,
1878            filter!(f_eq(
1879                Attribute::Uuid,
1880                PartialValue::new_uuid_s(UUID_B).unwrap()
1881            )),
1882            None,
1883            |qs: &mut QueryServerWriteTransaction| {
1884                //                      V-- this uuid is
1885                //                                  V-- memberof this UUID
1886                assert_not_memberof!(qs, UUID_A, UUID_B);
1887                assert_not_memberof!(qs, UUID_A, UUID_A);
1888                assert_memberof!(qs, UUID_A, UUID_C);
1889                assert_memberof!(qs, UUID_A, UUID_D);
1890
1891                assert_not_memberof!(qs, UUID_C, UUID_A);
1892                assert_not_memberof!(qs, UUID_C, UUID_B);
1893                assert_not_memberof!(qs, UUID_C, UUID_C);
1894                assert_not_memberof!(qs, UUID_C, UUID_D);
1895
1896                assert_not_memberof!(qs, UUID_D, UUID_A);
1897                assert_not_memberof!(qs, UUID_D, UUID_B);
1898                assert_memberof!(qs, UUID_D, UUID_C);
1899                assert_not_memberof!(qs, UUID_D, UUID_D);
1900
1901                assert_not_dirmemberof!(qs, UUID_A, UUID_A);
1902                assert_not_dirmemberof!(qs, UUID_A, UUID_B);
1903                assert_dirmemberof!(qs, UUID_A, UUID_C);
1904                assert_dirmemberof!(qs, UUID_A, UUID_D);
1905
1906                assert_not_dirmemberof!(qs, UUID_C, UUID_A);
1907                assert_not_dirmemberof!(qs, UUID_C, UUID_B);
1908                assert_not_dirmemberof!(qs, UUID_C, UUID_C);
1909                assert_not_dirmemberof!(qs, UUID_C, UUID_D);
1910
1911                assert_not_dirmemberof!(qs, UUID_D, UUID_A);
1912                assert_not_dirmemberof!(qs, UUID_C, UUID_B);
1913                assert_dirmemberof!(qs, UUID_D, UUID_C);
1914                assert_not_dirmemberof!(qs, UUID_D, UUID_D);
1915            }
1916        );
1917    }
1918}