ldb: Add tests for when we should expect a full scan
authorAndrew Bartlett <abartlet@samba.org>
Wed, 23 May 2018 05:15:38 +0000 (17:15 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Wed, 30 May 2018 02:23:28 +0000 (04:23 +0200)
BUG: https://bugzilla.samba.org/show_bug.cgi?id=13448

Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Garming Sam <garming@catalyst.net.nz>
lib/ldb/ldb_tdb/ldb_search.c
lib/ldb/ldb_tdb/ldb_tdb.c
lib/ldb/ldb_tdb/ldb_tdb.h
lib/ldb/tests/python/api.py

index 35583a1..cfc3714 100644 (file)
@@ -806,7 +806,7 @@ int ltdb_search(struct ltdb_context *ctx)
                 * callback error */
                if ( ! ctx->request_terminated && ret != LDB_SUCCESS) {
                        /* Not indexed, so we need to do a full scan */
-                       if (ltdb->warn_unindexed) {
+                       if (ltdb->warn_unindexed || ltdb->disable_full_db_scan) {
                                /* useful for debugging when slow performance
                                 * is caused by unindexed searches */
                                char *expression = ldb_filter_from_tree(ctx, ctx->tree);
@@ -819,6 +819,7 @@ int ltdb_search(struct ltdb_context *ctx)
 
                                talloc_free(expression);
                        }
+
                        if (match_count != 0) {
                                /* the indexing code gave an error
                                 * after having returned at least one
@@ -831,6 +832,14 @@ int ltdb_search(struct ltdb_context *ctx)
                                ltdb->kv_ops->unlock_read(module);
                                return LDB_ERR_OPERATIONS_ERROR;
                        }
+
+                       if (ltdb->disable_full_db_scan) {
+                               ldb_set_errstring(ldb,
+                                                 "ldb FULL SEARCH disabled");
+                               ltdb->kv_ops->unlock_read(module);
+                               return LDB_ERR_INAPPROPRIATE_MATCHING;
+                       }
+
                        ret = ltdb_search_full(ctx);
                        if (ret != LDB_SUCCESS) {
                                ldb_set_errstring(ldb, "Indexed and full searches both failed!\n");
index f9bc35c..8581604 100644 (file)
@@ -2279,6 +2279,22 @@ int init_store(struct ltdb_private *ltdb,
                }
        }
 
+       /*
+        * Override full DB scans
+        *
+        * A full DB scan is expensive on a large database.  This
+        * option is for testing to show that the full DB scan is not
+        * triggered.
+        */
+       {
+               const char *len_str =
+                       ldb_options_find(ldb, options,
+                                        "disable_full_db_scan_for_self_test");
+               if (len_str != NULL) {
+                       ltdb->disable_full_db_scan = true;
+               }
+       }
+
        return LDB_SUCCESS;
 }
 
index 46af8a1..2896c63 100644 (file)
@@ -78,6 +78,12 @@ struct ltdb_private {
         */
        unsigned max_key_length;
 
+       /*
+        * To allow testing that ensures the DB does not fall back
+        * to a full scan
+        */
+       bool disable_full_db_scan;
+
        /*
         * The PID that opened this database so we don't work in a
         * fork()ed child.
index cbc4fcd..9d01535 100755 (executable)
@@ -695,9 +695,12 @@ class SearchTests(LdbBaseTest):
         super(SearchTests, self).setUp()
         self.testdir = tempdir()
         self.filename = os.path.join(self.testdir, "search_test.ldb")
+        options = ["modules:rdn_name"]
+        if hasattr(self, 'IDXCHECK'):
+            options.append("disable_full_db_scan_for_self_test:1")
         self.l = ldb.Ldb(self.url(),
                          flags=self.flags(),
-                         options=["modules:rdn_name"])
+                         options=options)
         try:
             self.l.add(self.index)
         except AttributeError:
@@ -982,6 +985,47 @@ class SearchTests(LdbBaseTest):
                               expression="(|(x=y)(y=b))")
         self.assertEqual(len(res11), 20)
 
+    def test_one_unindexable(self):
+        """Testing a search"""
+
+        try:
+            res11 = self.l.search(base="DC=samba,DC=org",
+                                  scope=ldb.SCOPE_ONELEVEL,
+                                  expression="(y=b*)")
+            if hasattr(self, 'IDX') and \
+               not hasattr(self, 'IDXONE') and \
+               hasattr(self, 'IDXCHECK'):
+                self.fail("Should have failed as un-indexed search")
+
+            self.assertEqual(len(res11), 9)
+
+        except ldb.LdbError as err:
+            enum = err.args[0]
+            estr = err.args[1]
+            self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+            self.assertIn(estr, "ldb FULL SEARCH disabled")
+
+    def test_one_unindexable_presence(self):
+        """Testing a search"""
+
+        try:
+            res11 = self.l.search(base="DC=samba,DC=org",
+                                  scope=ldb.SCOPE_ONELEVEL,
+                                  expression="(y=*)")
+            if hasattr(self, 'IDX') and \
+               not hasattr(self, 'IDXONE') and \
+               hasattr(self, 'IDXCHECK'):
+                self.fail("Should have failed as un-indexed search")
+
+            self.assertEqual(len(res11), 24)
+
+        except ldb.LdbError as err:
+            enum = err.args[0]
+            estr = err.args[1]
+            self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+            self.assertIn(estr, "ldb FULL SEARCH disabled")
+
+
     def test_subtree_and_or(self):
         """Testing a search"""
 
@@ -1062,6 +1106,45 @@ class SearchTests(LdbBaseTest):
                               expression="(@IDXONE=DC=SAMBA,DC=ORG)")
         self.assertEqual(len(res11), 0)
 
+    def test_subtree_unindexable(self):
+        """Testing a search"""
+
+        try:
+            res11 = self.l.search(base="DC=samba,DC=org",
+                                  scope=ldb.SCOPE_SUBTREE,
+                                  expression="(y=b*)")
+            if hasattr(self, 'IDX') and \
+               hasattr(self, 'IDXCHECK'):
+                self.fail("Should have failed as un-indexed search")
+
+            self.assertEqual(len(res11), 9)
+
+        except ldb.LdbError as err:
+            enum = err.args[0]
+            estr = err.args[1]
+            self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+            self.assertIn(estr, "ldb FULL SEARCH disabled")
+
+    def test_subtree_unindexable_presence(self):
+        """Testing a search"""
+
+        try:
+            res11 = self.l.search(base="DC=samba,DC=org",
+                                  scope=ldb.SCOPE_SUBTREE,
+                                  expression="(y=*)")
+            if hasattr(self, 'IDX') and \
+               hasattr(self, 'IDXCHECK'):
+                self.fail("Should have failed as un-indexed search")
+
+            self.assertEqual(len(res11), 24)
+
+        except ldb.LdbError as err:
+            enum = err.args[0]
+            estr = err.args[1]
+            self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+            self.assertIn(estr, "ldb FULL SEARCH disabled")
+
+
     def test_dn_filter_one(self):
         """Testing that a dn= filter succeeds
         (or fails with disallowDNFilter
@@ -1131,6 +1214,13 @@ class IndexedSearchTests(SearchTests):
                     "@IDXATTR": [b"x", b"y", b"ou"]})
         self.IDX = True
 
+class IndexedCheckSearchTests(IndexedSearchTests):
+    """Test searches using the index, to ensure the index doesn't
+       break things (full scan disabled)"""
+    def setUp(self):
+        self.IDXCHECK = True
+        super(IndexedCheckSearchTests, self).setUp()
+
 class IndexedSearchDnFilterTests(SearchTests):
     """Test searches using the index, to ensure the index doesn't
        break things"""
@@ -1153,6 +1243,14 @@ class IndexedAndOneLevelSearchTests(SearchTests):
                     "@IDXATTR": [b"x", b"y", b"ou"],
                     "@IDXONE": [b"1"]})
         self.IDX = True
+        self.IDXONE = True
+
+class IndexedCheckedAndOneLevelSearchTests(IndexedAndOneLevelSearchTests):
+    """Test searches using the index including @IDXONE, to ensure
+       the index doesn't break things (full scan disabled)"""
+    def setUp(self):
+        self.IDXCHECK = True
+        super(IndexedCheckedAndOneLevelSearchTests, self).setUp()
 
 class IndexedAndOneLevelDNFilterSearchTests(SearchTests):
     """Test searches using the index including @IDXONE, to ensure