#ifndef PERSISTENT_DATA_DATA_STRUCTURES_DAMAGE_VISITOR_H #define PERSISTENT_DATA_DATA_STRUCTURES_DAMAGE_VISITOR_H #include "persistent-data/data-structures/btree.h" #include "persistent-data/data-structures/btree_node_checker.h" #include "persistent-data/run.h" //---------------------------------------------------------------- namespace persistent_data { namespace btree_detail { struct damage { typedef std::shared_ptr ptr; damage(run lost_keys, std::string const &desc) : lost_keys_(lost_keys), desc_(desc) { } run lost_keys_; std::string desc_; }; inline std::ostream &operator <<(std::ostream &out, damage const &d) { out << "btree damage[lost_keys = " << d.lost_keys_ << ", \"" << d.desc_ << "\"]"; return out; } class noop_damage_visitor { public: virtual void visit(btree_path const &path, damage const &d) { } }; // Tracks damage in a single level btree. Use multiple // trackers if you have a multilayer tree. class damage_tracker { public: damage_tracker(); typedef run run64; typedef boost::optional maybe_run64; void bad_node(); maybe_run64 good_internal(block_address begin); // remember 'end' is the one-past-the-end value, so // take the last key in the leaf and add one. maybe_run64 good_leaf(block_address begin, block_address end); maybe_run64 end(); private: bool damaged_; block_address damage_begin_; }; // As we walk a btree we need to know if we've moved into a // different sub tree (by looking at the btree_path). class path_tracker { public: path_tracker(); // returns the old path if the tree has changed. btree_path const *next_path(btree_path const &p); btree_path const ¤t_path() const; private: std::list paths_; }; //---------------------------------------------------------------- // This class implements consistency checking for the btrees. It // also allows the caller to visit all accessible values. // Derive from this if you want some additional checks. It's worth // summarising what is checked: // // Implemented // ----------- // // - block_nr // - nr_entries < max_entries // - max_entries fits in block // - max_entries is divisible by 3 // - nr_entries > minimum (except for root nodes) // // Not implemented // --------------- // // - leaf | internal flags (this can be inferred from siblings) //---------------------------------------------------------------- template class btree_damage_visitor : public btree::visitor { public: typedef btree_detail::node_location node_location; typedef run run64; typedef boost::optional maybe_run64; btree_damage_visitor(ValueVisitor &value_visitor, DamageVisitor &damage_visitor, bool ignore_non_fatal) : avoid_repeated_visits_(true), value_visitor_(value_visitor), damage_visitor_(damage_visitor), ignore_non_fatal_(ignore_non_fatal) { } bool visit_internal(node_location const &loc, btree_detail::node_ref const &n) { update_path(loc.path); return check_internal(loc, n); } bool visit_internal_leaf(node_location const &loc, btree_detail::node_ref const &n) { update_path(loc.path); return check_leaf(loc, n); } bool visit_leaf(node_location const &loc, btree_detail::node_ref const &n) { update_path(loc.path); bool r = check_leaf(loc, n); // If anything goes wrong with the checks, we skip // the value visiting. if (!r) return false; visit_values(loc.path, n); return true; } void visit_complete() { end_walk(); } typedef typename btree::visitor::error_outcome error_outcome; error_outcome error_accessing_node(node_location const &l, block_address b, std::string const &what) { update_path(l.path); report_damage(what); return btree::visitor::EXCEPTION_HANDLED; } private: void visit_values(btree_path const &path, node_ref const &n) { btree_path p2(path); unsigned nr = n.get_nr_entries(); for (unsigned i = 0; i < nr; i++) { p2.push_back(n.key_at(i)); value_visitor_.visit(p2, n.value_at(i)); p2.pop_back(); } } bool check_internal(node_location const &loc, btree_detail::node_ref const &n) { if (loc.is_sub_root()) new_root(loc.level()); if (already_visited(n)) return false; else if (!checker_.check_block_nr(n) || !checker_.check_value_size(n) || !checker_.check_max_entries(n) || !check_nr_entries(loc, n) || !checker_.check_ordered_keys(n) || !checker_.check_parent_key(n, loc.is_sub_root() ? boost::optional() : loc.key)) { report_damage(checker_.get_last_error_string()); return false; } good_internal(n.key_at(0)); return true; } template bool check_leaf(node_location const &loc, btree_detail::node_ref const &n) { if (loc.is_sub_root()) new_root(loc.level()); if (already_visited(n)) return false; else if (!checker_.check_block_nr(n) || !checker_.check_value_size(n) || !checker_.check_max_entries(n) || !check_nr_entries(loc, n) || !checker_.check_ordered_keys(n) || !checker_.check_parent_key(n, loc.is_sub_root() ? boost::optional() : loc.key) || !checker_.check_leaf_key(n, last_leaf_key_[loc.level()])) { report_damage(checker_.get_last_error_string()); return false; } if (n.get_nr_entries() > 0) { last_leaf_key_[loc.level()] = n.key_at(n.get_nr_entries() - 1); good_leaf(n.key_at(0), n.key_at(n.get_nr_entries() - 1) + 1); } return true; } template bool already_visited(node const &n) { block_address b = n.get_location(); if (avoid_repeated_visits_) { if (seen_.count(b) > 0) return true; seen_.insert(b); } return false; } void new_root(unsigned level) { // we're starting a new subtree, so should // reset the last_leaf value. last_leaf_key_[level] = boost::optional(); } //-------------------------------- // damage tracking void report_damage(std::string const &desc) { damage_reasons_.push_back(desc); dt_.bad_node(); } void good_internal(block_address b) { maybe_run64 mr = dt_.good_internal(b); if (mr) issue_damage(path_tracker_.current_path(), *mr); } void good_leaf(block_address b, block_address e) { maybe_run64 mr = dt_.good_leaf(b, e); if (mr) issue_damage(path_tracker_.current_path(), *mr); } void end_walk() { maybe_issue_damage(path_tracker_.current_path()); } void issue_damage(btree_path const &path, run64 const &r) { damage d(r, build_damage_desc()); clear_damage_desc(); damage_visitor_.visit(path, d); } std::string build_damage_desc() const { std::string r; std::list::const_iterator it, end = damage_reasons_.end(); for (it = damage_reasons_.begin(); it != end; ++it) r += *it; return r; } void clear_damage_desc() { damage_reasons_.clear(); } void maybe_issue_damage(btree_path const &path) { maybe_run64 mr = dt_.end(); if (mr) issue_damage(path, *mr); } void update_path(btree_path const &path) { btree_path const *old_path = path_tracker_.next_path(path); if (old_path) // we need to emit any errors that // were accrued against the old // path. maybe_issue_damage(*old_path); } template bool check_nr_entries(node_location const &loc, btree_detail::node_ref const &n) { return ignore_non_fatal_ || checker_.check_nr_entries(n, loc.is_sub_root()); } //-------------------------------- bool avoid_repeated_visits_; ValueVisitor &value_visitor_; DamageVisitor &damage_visitor_; std::set seen_; boost::optional last_leaf_key_[Levels]; btree_node_checker checker_; path_tracker path_tracker_; damage_tracker dt_; std::list damage_reasons_; bool ignore_non_fatal_; }; } template void btree_visit_values(btree const &tree, ValueVisitor &value_visitor, DamageVisitor &damage_visitor, bool ignore_non_fatal = false) { btree_detail::btree_damage_visitor v(value_visitor, damage_visitor, ignore_non_fatal); tree.visit_depth_first(v); } } //---------------------------------------------------------------- #endif