Browse Source

[2091b] encode node label data in more memory-efficient way.

i.e., instead of holding them as Name objects, hold them as serialized
opaque data generated by the corresponding LabelSequence, following the
node structure.  the size of the storage for the labels data is encoded
as part of node "flags".

insert/find now internally uses LabelSequence, and compare() can now return
NONE.  It requires some adjustments to other part of the code and tests, but
they should be generally straightforward.

in tests, note especially that we now have a separeate "." level.  that's
the main reason for the big change to dumpTree().
JINMEI Tatuya 12 years ago
parent
commit
a03aed77a9
3 changed files with 210 additions and 196 deletions
  1. 5 9
      src/lib/datasrc/memory_datasrc.cc
  2. 107 112
      src/lib/datasrc/rbtree.h
  3. 98 75
      src/lib/datasrc/tests/rbtree_unittest.cc

+ 5 - 9
src/lib/datasrc/memory_datasrc.cc

@@ -479,15 +479,11 @@ ZoneData::findNode(const Name& name, RBTreeNodeChain<Domain>& node_path,
         if (node->getFlag(domain_flag::WILD) && // maybe a wildcard, check only
             (options & ZoneFinder::NO_WILDCARD) == 0) { // if not disabled.
             if (node_path.getLastComparisonResult().getRelation() ==
-                NameComparisonResult::COMMONANCESTOR &&
-                node_path.getLastComparisonResult().getCommonLabels() > 1) {
-                // Wildcard canceled.  Treat it as NXDOMAIN.
-                // Note: Because the way the tree stores relative names, we
-                // will have exactly one common label (the ".") in case we have
-                // nothing common under the node we got, and we will get
-                // more common labels otherwise (yes, this relies on the
-                // internal RBTree structure, which leaks out through this
-                // little bit).
+                NameComparisonResult::COMMONANCESTOR) {
+                // This means, e.g., we have *.wild.example and
+                // bar.foo.wild.example and are looking for
+                // baz.foo.wild.example. The common ancestor, foo.wild.example,
+                // should cancel wildcard.  Treat it as NXDOMAIN.
                 LOG_DEBUG(logger, DBG_TRACE_DATA,
                           DATASRC_MEM_WILDCARD_CANCEL).arg(name);
                 return (ResultType(ZoneFinder::NXDOMAIN, NULL,

+ 107 - 112
src/lib/datasrc/rbtree.h

@@ -26,10 +26,12 @@
 #include <exceptions/exceptions.h>
 #include <util/memory_segment.h>
 #include <dns/name.h>
+#include <dns/labelsequence.h>
 
 #include <boost/utility.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/interprocess/offset_ptr.hpp>
+#include <boost/static_assert.hpp>
 
 #include <ostream>
 #include <algorithm>
@@ -38,27 +40,6 @@
 namespace isc {
 namespace datasrc {
 
-namespace helper {
-
-/// \brief Helper function to remove the base domain from super domain.
-///
-/// The precondition of this function is the super_name contains the
-/// sub_name so
-/// \code Name a("a.b.c");
-/// Name b("b.c");
-/// Name c = a - b;
-/// \endcode
-/// c will contain "a".
-///
-/// \note Functions in this namespace is not intended to be used outside of
-///     RBTree implementation.
-inline isc::dns::Name
-operator-(const isc::dns::Name& super_name, const isc::dns::Name& sub_name) {
-    return (super_name.split(0, super_name.getLabelCount() -
-                             sub_name.getLabelCount()));
-}
-}
-
 /// Forward declare RBTree class here is convinent for following friend
 /// class declare inside RBNode and RBTreeNodeChain
 template <typename T>
@@ -109,11 +90,8 @@ private:
     /// "null" node.
     RBNode();
 
-    /// \brief Constructor from the node name.
-    ///
-    /// \param name The *relative* domain name (if this will live inside
-    ///     a.b.c and is called d.e.a.b.c, then you pass d.e).
-    RBNode(const isc::dns::Name& name);
+    /// \brief Constructor from normal nodes.
+    RBNode(size_t labels_capacity);
 
     /// \brief Destructor
     ~RBNode();
@@ -130,10 +108,13 @@ private:
     /// \param mem_sgmt A \c MemorySegment from which memory for the new
     /// \c RBNode is allocated.
     static RBNode<T>* create(util::MemorySegment& mem_sgmt,
-                             const dns::Name& name)
+                             const dns::LabelSequence& labels)
     {
-        void* p = mem_sgmt.allocate(sizeof(RBNode<T>));
-        return (new(p) RBNode<T>(name));
+        const size_t labels_len = labels.getSerializedLength();
+        void* p = mem_sgmt.allocate(sizeof(RBNode<T>) + labels_len);
+        RBNode<T>* node = new(p) RBNode<T>(labels_len);
+        labels.serialize(node->getLabelsData(), labels_len);
+        return (node);
     }
 
     /// \brief Destruct and deallocate \c RBNode
@@ -146,8 +127,24 @@ private:
     /// that was originally created by the \c create() method (the behavior
     /// is undefined if this condition isn't met).
     static void destroy(util::MemorySegment& mem_sgmt, RBNode<T>* rbnode) {
+        const size_t labels_capacity = rbnode->labels_capacity_;
         rbnode->~RBNode<T>();
-        mem_sgmt.deallocate(rbnode, sizeof(RBNode<T>));
+        mem_sgmt.deallocate(rbnode, sizeof(RBNode<T>) + labels_capacity);
+    }
+
+    /// TBD
+    void resetLabels(const dns::LabelSequence& labels) {
+        labels.serialize(getLabelsData(), labels_capacity_);
+    }
+
+    /// TBD
+    const void* getLabelsData() const {
+        return (this + 1);
+    }
+
+    /// TBD
+    void* getLabelsData() {
+        return (this + 1);
     }
 
 public:
@@ -167,9 +164,9 @@ public:
         FLAG_CALLBACK = 1, ///< Callback enabled. See \ref callback
         FLAG_RED = 2, ///< Node color; 1 if node is red, 0 if node is black.
         FLAG_SUBTREE_ROOT = 4, ///< Set if the node is the root of a subtree
-        FLAG_USER1 = 0x80000000U, ///< Application specific flag
-        FLAG_USER2 = 0x40000000U, ///< Application specific flag
-        FLAG_USER3 = 0x20000000U  ///< Application specific flag
+        FLAG_USER1 = 0x400000U, ///< Application specific flag
+        FLAG_USER2 = 0x200000U, ///< Application specific flag
+        FLAG_USER3 = 0x100000U  ///< Application specific flag
     };
 private:
     // Some flag values are expected to be used for internal purposes
@@ -190,7 +187,15 @@ public:
     ///
     /// To get the absolute name of one node, the node path from the top node
     /// to current node has to be recorded.
-    const isc::dns::Name& getName() const { return (name_); }
+    const isc::dns::Name getName() const {
+        assert(labels_capacity_ != 0);
+        return (dns::Name(dns::LabelSequence(getLabelsData()).toText()));
+    }
+
+    dns::LabelSequence getLabels() const {
+        assert(labels_capacity_ != 0);
+        return (dns::LabelSequence(getLabelsData()));
+    }
 
     /// \brief Return the data stored in this node.
     ///
@@ -397,8 +402,6 @@ private:
     RBNodeColor color_;
     //@}
 
-    /// \brief Relative name of the node.
-    isc::dns::Name     name_;
     /// \brief Data stored here.
     NodeDataPtr       data_;
 
@@ -425,20 +428,21 @@ private:
     /// RBTree::find().
     ///
     /// \todo It might be needed to put it into more general attributes field.
-    uint32_t flags_;
+    uint32_t flags_ : 23;          // largest flag being 0x400000
+    const uint32_t labels_capacity_ : 9; // range is 0..511
+    /// TBD
+    BOOST_STATIC_ASSERT((1 << 9) > dns::LabelSequence::MAX_SERIALIZED_LENGTH);
 };
 
-
 // This is only to support NULL nodes.
 template <typename T>
 RBNode<T>::RBNode() :
     parent_(NULL),
     left_(NULL),
     right_(NULL),
-    // dummy name, the value doesn't matter:
-    name_(isc::dns::Name::ROOT_NAME()),
     down_(NULL),
-    flags_(FLAG_SUBTREE_ROOT)
+    flags_(FLAG_SUBTREE_ROOT),
+    labels_capacity_(0)
 {
     // Some compilers object to use of "this" in initializer lists.
     parent_ = this;
@@ -448,17 +452,16 @@ RBNode<T>::RBNode() :
 }
 
 template <typename T>
-RBNode<T>::RBNode(const isc::dns::Name& name) :
+RBNode<T>::RBNode(size_t labels_capacity) :
     parent_(NULL_NODE()),
     left_(NULL_NODE()),
     right_(NULL_NODE()),
-    name_(name),
     down_(NULL_NODE()),
-    flags_(FLAG_RED | FLAG_SUBTREE_ROOT)
+    flags_(FLAG_RED | FLAG_SUBTREE_ROOT),
+    labels_capacity_(labels_capacity)
 {
 }
 
-
 template <typename T>
 RBNode<T>::~RBNode() {
 }
@@ -1194,7 +1197,7 @@ private:
     /// of old node will be move into new node, and old node became
     /// non-terminal
     void nodeFission(util::MemorySegment& mem_sgmt, RBNode<T>& node,
-                     const isc::dns::Name& sub_name);
+                     const isc::dns::LabelSequence& sub_labels);
     //@}
 
     RBNode<T>* NULLNODE;
@@ -1262,19 +1265,17 @@ RBTree<T>::find(const isc::dns::Name& target_name,
                 bool (*callback)(const RBNode<T>&, CBARG),
                 CBARG callback_arg) const
 {
-    using namespace helper;
-
     if (!node_path.isEmpty()) {
         isc_throw(isc::BadValue, "RBTree::find is given a non empty chain");
     }
 
     RBNode<T>* node = root_.get();
     Result ret = NOTFOUND;
-    isc::dns::Name name = target_name;
+    dns::LabelSequence target_labels(target_name);
 
     while (node != NULLNODE) {
         node_path.last_compared_ = node;
-        node_path.last_comparison_ = name.compare(node->name_);
+        node_path.last_comparison_ = target_labels.compare(node->getLabels());
         const isc::dns::NameComparisonResult::NameRelation relation =
             node_path.last_comparison_.getRelation();
 
@@ -1285,22 +1286,13 @@ RBTree<T>::find(const isc::dns::Name& target_name,
                 ret = EXACTMATCH;
             }
             break;
+        } else if (relation == isc::dns::NameComparisonResult::NONE) {
+            // If the two labels have no hierarchical relationship in terms
+            // of matching, we should continue the binary search.
+            node = (node_path.last_comparison_.getOrder() < 0) ?
+                node->getLeft() : node->getRight();
         } else {
-            const int common_label_count =
-                node_path.last_comparison_.getCommonLabels();
-            // If the common label count is 1, there is no common label between
-            // the two names, except the trailing "dot".  In this case the two
-            // sequences of labels have essentially no hierarchical
-            // relationship in terms of matching, so we should continue the
-            // binary search.  One important exception is when the node
-            // represents the root name ("."), in which case the comparison
-            // result must indeed be considered subdomain matching. (We use
-            // getLength() to check if the name is root, which is an equivalent
-            // but cheaper way).
-            if (common_label_count == 1 && node->name_.getLength() != 1) {
-                node = (node_path.last_comparison_.getOrder() < 0) ?
-                    node->getLeft() : node->getRight();
-            } else if (relation == isc::dns::NameComparisonResult::SUBDOMAIN) {
+            if (relation == isc::dns::NameComparisonResult::SUBDOMAIN) {
                 if (needsReturnEmptyNode_ || !node->isEmpty()) {
                     ret = PARTIALMATCH;
                     *target = node;
@@ -1312,7 +1304,8 @@ RBTree<T>::find(const isc::dns::Name& target_name,
                     }
                 }
                 node_path.push(node);
-                name = name - node->name_;
+                target_labels.stripRight(
+                    node_path.last_comparison_.getCommonLabels());
                 node = node->getDown();
             } else {
                 break;
@@ -1384,6 +1377,7 @@ RBTree<T>::previousNode(RBTreeNodeChain<T>& node_path) const {
     // all the cases and decide where to go from there.
     switch (node_path.last_comparison_.getRelation()) {
         case dns::NameComparisonResult::COMMONANCESTOR:
+        case dns::NameComparisonResult::NONE:
             // We compared with a leaf in the tree and wanted to go to one of
             // the children. But the child was not there. It now depends on the
             // direction in which we wanted to go.
@@ -1448,9 +1442,6 @@ RBTree<T>::previousNode(RBTreeNodeChain<T>& node_path) const {
             // already, which located the exact node. The rest of the function
             // goes one domain left and returns it for us.
             break;
-        default:
-            // This must not happen as Name::compare() never returns NONE.
-            isc_throw(isc::Unexpected, "Name::compare() returned unexpected result");
     }
 
     // So, the node_path now contains the path to a node we want previous for.
@@ -1509,16 +1500,15 @@ typename RBTree<T>::Result
 RBTree<T>::insert(util::MemorySegment& mem_sgmt,
                   const isc::dns::Name& target_name, RBNode<T>** new_node)
 {
-    using namespace helper;
     RBNode<T>* parent = NULLNODE;
     RBNode<T>* current = root_.get();
     RBNode<T>* up_node = NULLNODE;
-    isc::dns::Name name = target_name;
+    isc::dns::LabelSequence target_labels(target_name);
 
     int order = -1;
     while (current != NULLNODE) {
         const isc::dns::NameComparisonResult compare_result =
-            name.compare(current->name_);
+            target_labels.compare(current->getLabels());
         const isc::dns::NameComparisonResult::NameRelation relation =
             compare_result.getRelation();
         if (relation == isc::dns::NameComparisonResult::EQUAL) {
@@ -1526,32 +1516,26 @@ RBTree<T>::insert(util::MemorySegment& mem_sgmt,
                 *new_node = current;
             }
             return (ALREADYEXISTS);
+        } else if (relation == isc::dns::NameComparisonResult::NONE) {
+            parent = current;
+            order = compare_result.getOrder();
+            current = order < 0 ? current->getLeft() : current->getRight();
+        } else if (relation == isc::dns::NameComparisonResult::SUBDOMAIN) {
+            // insert sub domain to sub tree
+            parent = NULLNODE;
+            up_node = current;
+            target_labels.stripRight(compare_result.getCommonLabels());
+            current = current->getDown();
         } else {
-            const int common_label_count = compare_result.getCommonLabels();
-            // Note: see find() for the check of getLength().
-            if (common_label_count == 1 && current->name_.getLength() != 1) {
-                parent = current;
-                order = compare_result.getOrder();
-                current = order < 0 ? current->getLeft() : current->getRight();
-            } else {
-                // insert sub domain to sub tree
-                if (relation == isc::dns::NameComparisonResult::SUBDOMAIN) {
-                    parent = NULLNODE;
-                    up_node = current;
-                    name = name - current->name_;
-                    current = current->getDown();
-                } else {
-                    // The number of labels in common is fewer
-                    // than the number of labels at the current
-                    // node, so the current node must be adjusted
-                    // to have just the common suffix, and a down
-                    // pointer made to a new tree.
-                    const isc::dns::Name common_ancestor = name.split(
-                        name.getLabelCount() - common_label_count,
-                        common_label_count);
-                    nodeFission(mem_sgmt, *current, common_ancestor);
-                }
-            }
+            // The number of labels in common is fewer
+            // than the number of labels at the current
+            // node, so the current node must be adjusted
+            // to have just the common suffix, and a down
+            // pointer made to a new tree.
+            dns::LabelSequence common_ancestor = target_labels;
+            common_ancestor.stripLeft(target_labels.getLabelCount() -
+                                      compare_result.getCommonLabels());
+            nodeFission(mem_sgmt, *current, common_ancestor);
         }
     }
 
@@ -1559,7 +1543,7 @@ RBTree<T>::insert(util::MemorySegment& mem_sgmt,
         &(up_node->down_) : &root_;
     // Once a new node is created, no exception will be thrown until the end
     // of the function, so we can simply create and hold a new node pointer.
-    RBNode<T>* node = RBNode<T>::create(mem_sgmt, name);
+    RBNode<T>* node = RBNode<T>::create(mem_sgmt, target_labels);
     node->parent_ = parent;
     if (parent == NULLNODE) {
         *current_root = node;
@@ -1596,19 +1580,30 @@ RBTree<T>::deleteAllNodes(util::MemorySegment& mem_sgmt) {
 template <typename T>
 void
 RBTree<T>::nodeFission(util::MemorySegment& mem_sgmt, RBNode<T>& node,
-                       const isc::dns::Name& base_name)
+                       const isc::dns::LabelSequence& base_labels)
 {
-    using namespace helper;
-    const isc::dns::Name sub_name = node.name_ - base_name;
-    // Note: the following code is not entirely exception safe (name copy
-    // can result in exception), but will soon be so within Trac #2091.
-    RBNode<T>* down_node = RBNode<T>::create(mem_sgmt, sub_name);
-    node.name_ = base_name;
-    // the rest of this function should be exception free so that it keeps
-    // consistent behavior (i.e., a weak form of strong exception guarantee)
-    // even if code after the call to this function throws an exception.
+    dns::LabelSequence sub_labels = node.getLabels();
+    sub_labels.stripRight(base_labels.getLabelCount());
+
+    // Once a new node is created, no exception will be thrown until
+    // the end of the function, and it will keep consistent behavior
+    // (i.e., a weak form of strong exception guarantee) even if code
+    // after the call to this function throws an exception.
+    RBNode<T>* down_node = RBNode<T>::create(mem_sgmt, sub_labels);
+    node.resetLabels(base_labels);
+
     std::swap(node.data_, down_node->data_);
-    std::swap(node.flags_, down_node->flags_);
+
+    // Swap flags bitfields; yes, this is ugly.  The right solution is to
+    // implement the above note, then we won't have to swap the flags in the
+    // first place.
+    struct {
+        uint32_t flags_ : 23;
+        uint32_t unused_ : 9;
+    } tmp;
+    tmp.flags_ = node.flags_;
+    node.flags_ = down_node->flags_;
+    down_node->flags_ = tmp.flags_;
 
     down_node->down_ = node.getDown();
     node.down_ = down_node;
@@ -1767,7 +1762,7 @@ RBTree<T>::dumpTreeHelper(std::ostream& os, const RBNode<T>* node,
     }
 
     indent(os, depth);
-    os << node->name_.toText() << " ("
+    os << node->getName().toText() << " ("
               << ((node->getColor() == RBNode<T>::BLACK) ? "black" : "red") << ")";
     if (node->isEmpty()) {
         os << " [invisible]";
@@ -1780,10 +1775,10 @@ RBTree<T>::dumpTreeHelper(std::ostream& os, const RBNode<T>* node,
     const RBNode<T>* down = node->getDown();
     if (down != NULLNODE) {
         indent(os, depth + 1);
-        os << "begin down from " << node->name_.toText() << "\n";
+        os << "begin down from " << node->getName().toText() << "\n";
         dumpTreeHelper(os, down, depth + 1);
         indent(os, depth + 1);
-        os << "end down from " << node->name_.toText() << "\n";
+        os << "end down from " << node->getName().toText() << "\n";
     }
     dumpTreeHelper(os, node->getLeft(), depth + 1);
     dumpTreeHelper(os, node->getRight(), depth + 1);

+ 98 - 75
src/lib/datasrc/tests/rbtree_unittest.cc

@@ -40,6 +40,8 @@ const size_t Name::MAX_LABELS;
 
 /* The initial structure of rbtree
  *
+*              .
+ *             |
  *             b
  *           /   \
  *          a    d.e.f
@@ -105,7 +107,7 @@ protected:
 };
 
 TEST_F(RBTreeTest, nodeCount) {
-    EXPECT_EQ(14, rbtree.getNodeCount());
+    EXPECT_EQ(15, rbtree.getNodeCount());
 
     // Delete all nodes, then the count should be set to 0.  This also tests
     // the behavior of deleteAllNodes().
@@ -123,60 +125,61 @@ TEST_F(RBTreeTest, insertNames) {
                                                         Name("d.e.f"),
                                                         &rbtnode));
     EXPECT_EQ(Name("d.e.f"), rbtnode->getName());
-    EXPECT_EQ(14, rbtree.getNodeCount());
+    EXPECT_EQ(15, rbtree.getNodeCount());
 
-    //insert not exist node
-    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(mem_sgmt_, Name("."),
+    // insert not exist node
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(mem_sgmt_, Name("0"),
                                                   &rbtnode));
-    EXPECT_EQ(Name("."), rbtnode->getName());
-    EXPECT_EQ(15, rbtree.getNodeCount());
+    EXPECT_EQ(Name("0"), rbtnode->getName());
+    EXPECT_EQ(16, rbtree.getNodeCount());
 
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(mem_sgmt_,
                                                   Name("example.com"),
                                                   &rbtnode));
-    EXPECT_EQ(16, rbtree.getNodeCount());
+    EXPECT_EQ(17, rbtree.getNodeCount());
     rbtnode->setData(RBNode<int>::NodeDataPtr(new int(12)));
 
-    // return ALREADYEXISTS, since node "example.com" already has been explicitly inserted
+    // return ALREADYEXISTS, since node "example.com" already has
+    // been explicitly inserted
     EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree.insert(mem_sgmt_,
                                                         Name("example.com"),
                                                         &rbtnode));
-    EXPECT_EQ(16, rbtree.getNodeCount());
+    EXPECT_EQ(17, rbtree.getNodeCount());
 
     // split the node "d.e.f"
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(mem_sgmt_, Name("k.e.f"),
                                                   &rbtnode));
     EXPECT_EQ(Name("k"), rbtnode->getName());
-    EXPECT_EQ(18, rbtree.getNodeCount());
+    EXPECT_EQ(19, rbtree.getNodeCount());
 
     // split the node "g.h"
     EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree.insert(mem_sgmt_, Name("h"),
                                                         &rbtnode));
     EXPECT_EQ(Name("h"), rbtnode->getName());
-    EXPECT_EQ(19, rbtree.getNodeCount());
+    EXPECT_EQ(20, rbtree.getNodeCount());
 
     // add child domain
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(mem_sgmt_,
                                                   Name("m.p.w.y.d.e.f"),
                                                   &rbtnode));
     EXPECT_EQ(Name("m"), rbtnode->getName());
-    EXPECT_EQ(20, rbtree.getNodeCount());
+    EXPECT_EQ(21, rbtree.getNodeCount());
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(mem_sgmt_,
                                                   Name("n.p.w.y.d.e.f"),
                                                   &rbtnode));
     EXPECT_EQ(Name("n"), rbtnode->getName());
-    EXPECT_EQ(21, rbtree.getNodeCount());
+    EXPECT_EQ(22, rbtree.getNodeCount());
 
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(mem_sgmt_, Name("l.a"),
                                                   &rbtnode));
     EXPECT_EQ(Name("l"), rbtnode->getName());
-    EXPECT_EQ(22, rbtree.getNodeCount());
+    EXPECT_EQ(23, rbtree.getNodeCount());
 
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(mem_sgmt_, Name("r.d.e.f"),
                                                   &rbtnode));
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(mem_sgmt_, Name("s.d.e.f"),
                                                   &rbtnode));
-    EXPECT_EQ(24, rbtree.getNodeCount());
+    EXPECT_EQ(25, rbtree.getNodeCount());
 
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(mem_sgmt_,
                                                   Name("h.w.y.d.e.f"),
@@ -232,7 +235,8 @@ TEST_F(RBTreeTest, findName) {
               rbtree_expose_empty_node.find(Name("m.d.e.f"), &crbtnode));
 
     // find rbtnode
-    EXPECT_EQ(RBTree<int>::EXACTMATCH, rbtree.find(Name("q.w.y.d.e.f"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::EXACTMATCH, rbtree.find(Name("q.w.y.d.e.f"),
+                                                   &rbtnode));
     EXPECT_EQ(Name("q"), rbtnode->getName());
 }
 
@@ -388,8 +392,10 @@ TEST_F(RBTreeTest, getAbsoluteNameError) {
 
 /*
  *the domain order should be:
- * a, b, c, d.e.f, x.d.e.f, w.y.d.e.f, o.w.y.d.e.f, p.w.y.d.e.f, q.w.y.d.e.f,
- * z.d.e.f, j.z.d.e.f, g.h, i.g.h, k.g.h
+ * ., a, b, c, d.e.f, x.d.e.f, w.y.d.e.f, o.w.y.d.e.f, p.w.y.d.e.f,
+ * q.w.y.d.e.f, z.d.e.f, j.z.d.e.f, g.h, i.g.h, k.g.h
+ *             . (no data, can't be found)
+ *             |
  *             b
  *           /   \
  *          a    d.e.f
@@ -467,6 +473,11 @@ previousWalk(RBTree<int>& rbtree, const RBNode<int>* node,
     }
 
     // We should have reached the start of the tree.
+    ASSERT_NE(static_cast<void*>(NULL), node);
+    EXPECT_EQ(".", node->getLabels().toText());
+
+    // With one more call it results in NULL
+    node = rbtree.previousNode(node_path);
     EXPECT_EQ(static_cast<void*>(NULL), node);
 
     // Calling previousNode() yet again should still return NULL without
@@ -508,18 +519,23 @@ TEST_F(RBTreeTest, previousNode) {
         EXPECT_EQ(RBTree<int>::EXACTMATCH,
                   rbtree.find(Name(names[0]), &node, node_path));
         EXPECT_NE(static_cast<void*>(NULL), node);
-        EXPECT_EQ(static_cast<void*>(NULL), rbtree.previousNode(node_path));
+        node = rbtree.previousNode(node_path);
+        ASSERT_NE(static_cast<void*>(NULL), node);
+        EXPECT_EQ(".", node->getLabels().toText());
         node = NULL;
         node_path.clear();
     }
 
     {
         SCOPED_TRACE("Start before the first");
-        // If we start before the lowest (0 < a), we should not get a node nor
+        // If we start before the lowest (. < 0. < a.), we should not get a
+        // node.  Its previous node should be the root.
         EXPECT_EQ(RBTree<int>::NOTFOUND,
                   rbtree.find<void*>(Name("0"), &node, node_path, NULL, NULL));
         EXPECT_EQ(static_cast<void*>(NULL), node);
-        EXPECT_EQ(static_cast<void*>(NULL), rbtree.previousNode(node_path));
+        node = rbtree.previousNode(node_path);
+        ASSERT_NE(static_cast<void*>(NULL), node);
+        EXPECT_EQ(".", node->getLabels().toText());
         node = NULL;
         node_path.clear();
     }
@@ -667,8 +683,8 @@ TEST_F(RBTreeTest, getLastComparedNode) {
     EXPECT_EQ(RBTree<int>::EXACTMATCH,
               tree.find(Name("x.d.e.f"), &expected_node, chain));
     EXPECT_EQ(expected_node, chain.getLastComparedNode());
-    // 2 = # labels of "x."
-    comparisonChecks(chain, 0, 2, NameComparisonResult::EQUAL);
+    // 1 = # labels of "x" (note: excluding ".")
+    comparisonChecks(chain, 0, 1, NameComparisonResult::EQUAL);
     chain.clear();
 
     // Partial match, search stopped at the matching node, which should be
@@ -678,8 +694,8 @@ TEST_F(RBTreeTest, getLastComparedNode) {
     EXPECT_EQ(RBTree<int>::PARTIALMATCH,
               tree.find(Name("x.k.g.h"), &crbtnode, chain));
     EXPECT_EQ(expected_node, chain.getLastComparedNode());
-    // k.g.h < x.k.g.h, 2 = # labels of "k."
-    comparisonChecks(chain, 1, 2, NameComparisonResult::SUBDOMAIN);
+    // k.g.h < x.k.g.h, 1 = # labels of "k"
+    comparisonChecks(chain, 1, 1, NameComparisonResult::SUBDOMAIN);
     chain.clear();
 
     // Partial match, search stopped in the subtree below the matching node
@@ -689,8 +705,8 @@ TEST_F(RBTreeTest, getLastComparedNode) {
     EXPECT_EQ(RBTree<int>::PARTIALMATCH,
               tree.find(Name("a.d.e.f"), &crbtnode, chain));
     EXPECT_EQ(expected_node, chain.getLastComparedNode());
-    // a < x, 1 = # labels of "." (trailing dot)
-    comparisonChecks(chain, -1, 1, NameComparisonResult::COMMONANCESTOR);
+    // a < x, no common labels
+    comparisonChecks(chain, -1, 0, NameComparisonResult::NONE);
     chain.clear();
 
     // Partial match, search stopped in the subtree below the matching node
@@ -700,8 +716,8 @@ TEST_F(RBTreeTest, getLastComparedNode) {
     EXPECT_EQ(RBTree<int>::PARTIALMATCH,
               tree.find(Name("zz.d.e.f"), &crbtnode, chain));
     EXPECT_EQ(expected_node, chain.getLastComparedNode());
-    // zz > z, 1 = # labels of "." (trailing dot)
-    comparisonChecks(chain, 1, 1, NameComparisonResult::COMMONANCESTOR);
+    // zz > z, no common label
+    comparisonChecks(chain, 1, 0, NameComparisonResult::NONE);
     chain.clear();
 
     // Partial match, search stopped at a node for a super domain of the
@@ -711,8 +727,8 @@ TEST_F(RBTreeTest, getLastComparedNode) {
     EXPECT_EQ(RBTree<int>::PARTIALMATCH,
               tree.find(Name("y.d.e.f"), &crbtnode, chain));
     EXPECT_EQ(expected_node, chain.getLastComparedNode());
-    // y < w.y, 2 = # labels of "y."
-    comparisonChecks(chain, -1, 2, NameComparisonResult::SUPERDOMAIN);
+    // y < w.y, 1 = # labels of "y"
+    comparisonChecks(chain, -1, 1, NameComparisonResult::SUPERDOMAIN);
     chain.clear();
 
     // Partial match, search stopped at a node that share a common ancestor
@@ -721,26 +737,28 @@ TEST_F(RBTreeTest, getLastComparedNode) {
     EXPECT_EQ(RBTree<int>::PARTIALMATCH,
               tree.find(Name("z.y.d.e.f"), &crbtnode, chain));
     EXPECT_EQ(expected_node, chain.getLastComparedNode());
-    // z.y > w.y, 2 = # labels of "y."
-    comparisonChecks(chain, 1, 2, NameComparisonResult::COMMONANCESTOR);
+    // z.y > w.y, 1 = # labels of "y"
+    comparisonChecks(chain, 1, 1, NameComparisonResult::COMMONANCESTOR);
     chain.clear();
 
-    // Search stops in the highest level after following a left branch.
+    // Search stops in the highest level (under ".") after following a left
+    // branch. (find() still returns PARTIALMATCH due to the top level ".")
     EXPECT_EQ(RBTree<int>::EXACTMATCH, tree.find(Name("c"), &expected_node));
-    EXPECT_EQ(RBTree<int>::NOTFOUND,
+    EXPECT_EQ(RBTree<int>::PARTIALMATCH,
               tree.find(Name("bb"), &crbtnode, chain));
     EXPECT_EQ(expected_node, chain.getLastComparedNode());
-    // bb < c, 1 = # labels of "." (trailing dot)
-    comparisonChecks(chain, -1, 1, NameComparisonResult::COMMONANCESTOR);
+    // bb < c, no common label
+    //comparisonChecks(chain, -1, 1, NameComparisonResult::COMMONANCESTOR);
+    comparisonChecks(chain, -1, 0, NameComparisonResult::NONE);
     chain.clear();
 
-    // Search stops in the highest level after following a right branch.
-    // (the expected node is the same as the previous case)
-    EXPECT_EQ(RBTree<int>::NOTFOUND,
+    // Search stops in the highest level (under ".") after following a right
+    // branch. (the expected node is the same as the previous case)
+    EXPECT_EQ(RBTree<int>::PARTIALMATCH,
               tree.find(Name("d"), &crbtnode, chain));
     EXPECT_EQ(expected_node, chain.getLastComparedNode());
-    // d > c, 1 = # labels of "." (trailing dot)
-    comparisonChecks(chain, 1, 1, NameComparisonResult::COMMONANCESTOR);
+    // d > c, no common label
+    comparisonChecks(chain, 1, 0, NameComparisonResult::NONE);
     chain.clear();
 }
 
@@ -748,48 +766,53 @@ TEST_F(RBTreeTest, dumpTree) {
     std::ostringstream str;
     std::ostringstream str2;
     rbtree.dumpTree(str);
-    str2 << "tree has 14 node(s)\n"
-            "b. (black) [subtreeroot]\n"
-            "     a. (black)\n"
-            "          NULL\n"
-            "          NULL\n"
-            "     d.e.f. (black) [invisible]\n"
-            "          begin down from d.e.f.\n"
-            "          w.y. (black) [invisible] [subtreeroot]\n"
-            "               begin down from w.y.\n"
-            "               p. (black) [subtreeroot]\n"
-            "                    o. (red)\n"
+    str2 << "tree has 15 node(s)\n"
+            ". (black) [invisible] [subtreeroot]\n"
+            "     begin down from .\n"
+            "     b. (black) [subtreeroot]\n"
+            "          a. (black)\n"
+            "               NULL\n"
+            "               NULL\n"
+            "          d.e.f. (black) [invisible]\n"
+            "               begin down from d.e.f.\n"
+            "               w.y. (black) [invisible] [subtreeroot]\n"
+            "                    begin down from w.y.\n"
+            "                    p. (black) [subtreeroot]\n"
+            "                         o. (red)\n"
+            "                              NULL\n"
+            "                              NULL\n"
+            "                         q. (red)\n"
+            "                              NULL\n"
+            "                              NULL\n"
+            "                    end down from w.y.\n"
+            "                    x. (red)\n"
             "                         NULL\n"
             "                         NULL\n"
-            "                    q. (red)\n"
+            "                    z. (red)\n"
+            "                         begin down from z.\n"
+            "                         j. (black) [subtreeroot]\n"
+            "                              NULL\n"
+            "                              NULL\n"
+            "                         end down from z.\n"
             "                         NULL\n"
             "                         NULL\n"
-            "               end down from w.y.\n"
-            "               x. (red)\n"
+            "               end down from d.e.f.\n"
+            "               c. (red)\n"
             "                    NULL\n"
             "                    NULL\n"
-            "               z. (red)\n"
-            "                    begin down from z.\n"
-            "                    j. (black) [subtreeroot]\n"
-            "                         NULL\n"
+            "               g.h. (red)\n"
+            "                    begin down from g.h.\n"
+            "                    i. (black) [subtreeroot]\n"
             "                         NULL\n"
-            "                    end down from z.\n"
+            "                         k. (red)\n"
+            "                              NULL\n"
+            "                              NULL\n"
+            "                    end down from g.h.\n"
             "                    NULL\n"
             "                    NULL\n"
-            "          end down from d.e.f.\n"
-            "          c. (red)\n"
-            "               NULL\n"
-            "               NULL\n"
-            "          g.h. (red)\n"
-            "               begin down from g.h.\n"
-            "               i. (black) [subtreeroot]\n"
-            "                    NULL\n"
-            "                    k. (red)\n"
-            "                         NULL\n"
-            "                         NULL\n"
-            "               end down from g.h.\n"
-            "               NULL\n"
-            "               NULL\n";
+            "     end down from .\n"
+            "     NULL\n"
+            "     NULL\n";
     EXPECT_EQ(str2.str(), str.str());
 }