]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Add record search capability to the LMDB backend. 15827/head
authorMiod Vallat <miod.vallat@powerdns.com>
Fri, 11 Jul 2025 16:02:47 +0000 (18:02 +0200)
committerMiod Vallat <miod.vallat@powerdns.com>
Wed, 16 Jul 2025 05:18:07 +0000 (07:18 +0200)
Fixes: #14079
Signed-off-by: Miod Vallat <miod.vallat@powerdns.com>
docs/backends/lmdb.rst
docs/upgrading.rst
modules/lmdbbackend/lmdbbackend.cc
modules/lmdbbackend/lmdbbackend.hh
regression-tests.api/test_Zones.py

index a0dac246563120b7c9b0a8bb2c301ff692da8e02..9c5e8fd67126b8d6797a2c8918a0dc7ac41633b1 100644 (file)
@@ -11,7 +11,7 @@ LMDB backend
 * DNSSEC: Yes
 * Disabled data: Yes
 * Comments: No
-* Search: No
+* Search: since version 5.0.0
 * Views: Yes
 * API: Read-Write
 * Multiple instances: No
index 2b6cfdafe04e9d9979564f02f14741c2fdf11f90..9c458a82cc8aeb547a962ffc2aadf31571ea15cd 100644 (file)
@@ -11,8 +11,8 @@ upgrade notes if your version is older than 3.4.2.
 4.9.0 to 5.0.0/master
 ---------------------
 
-LMDB backend, views and DNS Update support
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LMDB backend, views
+^^^^^^^^^^^^^^^^^^^
 
 Version 5.0.0-alpha1 ships a new version of the LMDB database schema (called version 6), in support of the new views feature.
 There is no downgrade process.
@@ -25,8 +25,17 @@ While many things have been thoroughly tested, some loose ends likely remain.
 Specifically, catalog zones have not been updated for views support at all.
 Most other things are expected to work; if you find something wrong, please :ref:`let us know <getting-support>`.
 
+LMDB backend, DNS Update support
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
 The LMDB backend also now supports :doc:`DNS Update <dnsupdate>` (RFC2136).
 
+LMDB backend, search support
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The record search from the :doc:`HTTP API <http-api/search>` functionality has 
+been implemented in the LMDB backend.
+
 LOC record parsing
 ^^^^^^^^^^^^^^^^^^
 
index 2f78de9f86f8739a7fa18b0389cabe34b55c1c84..bc7ba9da265d0c67f2e581c01dbea5baa22a3aca 100644 (file)
@@ -1426,6 +1426,32 @@ bool LMDBBackend::replaceComments([[maybe_unused]] domainid_t domain_id, [[maybe
   return comments.empty();
 }
 
+bool LMDBBackend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
+{
+  SimpleMatch simpleMatch(pattern, true);
+  std::vector<DomainInfo> domains;
+  getAllDomains(&domains, false, true);
+  for (const auto& info : domains) {
+    if (!list(info.zone, info.id, true)) {
+      return false;
+    }
+    DNSResourceRecord rec;
+    while (get(rec)) {
+      if (maxResults == 0) {
+        continue;
+      }
+      if (simpleMatch.match(rec.qname.toStringNoDot()) || simpleMatch.match(rec.content)) {
+        result.emplace_back(rec);
+        --maxResults;
+      }
+    }
+    if (maxResults == 0) {
+      break;
+    }
+  }
+  return true;
+}
+
 // FIXME: this is not very efficient
 static DNSName keyUnconv(std::string& instr)
 {
index c3997e9dfb9ddb1d10d12444df574c128c20f82b..1071a50731db73fd0a586b6ad06c44d4b8b81d5b 100644 (file)
@@ -88,6 +88,7 @@ public:
   bool feedEnts3(domainid_t domain_id, const DNSName& domain, map<DNSName, bool>& nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow) override;
   bool replaceRRSet(domainid_t domain_id, const DNSName& qname, const QType& qt, const vector<DNSResourceRecord>& rrset) override;
   bool replaceComments(domainid_t domain_id, const DNSName& qname, const QType& qt, const vector<Comment>& comments) override;
+  bool searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result) override;
 
   void viewList(vector<string>& /* result */) override;
   void viewListZones(const string& /* view */, vector<ZoneName>& /* result */) override;
index 8a4871a988b1252fcd95a548008b3372667ec931..d85f1e36637c49bc3beb268287a7276cac26f923 100644 (file)
@@ -2118,7 +2118,6 @@ $NAME$  1D  IN  SOA ns1.example.org. hostmaster.example.org. (
         self.assertEqual(serverset['records'], rrset2['records'])
         self.assertEqual(serverset['comments'], rrset['comments'])
 
-    @unittest.skipIf(is_auth_lmdb(), "No search in LMDB")
     def test_search_rr_exact_zone(self):
         name = unique_zone_name()
         self.create_zone(name=name, serial=22, soa_edit_api='')
@@ -2138,7 +2137,6 @@ $NAME$  1D  IN  SOA ns1.example.org. hostmaster.example.org. (
              u'ttl': 3600, u'type': u'SOA', u'name': name},
         ])
 
-    @unittest.skipIf(is_auth_lmdb(), "No search in LMDB")
     def test_search_rr_exact_zone_filter_type_zone(self):
         name = unique_zone_name()
         data_type = "zone"
@@ -2150,7 +2148,6 @@ $NAME$  1D  IN  SOA ns1.example.org. hostmaster.example.org. (
             {u'object_type': u'zone', u'name': name, u'zone_id': name},
         ])
 
-    @unittest.skipIf(is_auth_lmdb(), "No search in LMDB")
     def test_search_rr_exact_zone_filter_type_record(self):
         name = unique_zone_name()
         data_type = "record"
@@ -2170,7 +2167,6 @@ $NAME$  1D  IN  SOA ns1.example.org. hostmaster.example.org. (
              u'ttl': 3600, u'type': u'SOA', u'name': name},
         ])
 
-    @unittest.skipIf(is_auth_lmdb(), "No search in LMDB")
     def test_search_rr_substring(self):
         name = unique_zone_name()
         search = name[5:-5]
@@ -2181,7 +2177,6 @@ $NAME$  1D  IN  SOA ns1.example.org. hostmaster.example.org. (
         # should return zone, SOA, ns1, ns2
         self.assertEqual(len(r.json()), 4)
 
-    @unittest.skipIf(is_auth_lmdb(), "No search in LMDB")
     def test_search_rr_case_insensitive(self):
         name = unique_zone_name()+'testsuffix.'
         self.create_zone(name=name)
@@ -2191,7 +2186,7 @@ $NAME$  1D  IN  SOA ns1.example.org. hostmaster.example.org. (
         # should return zone, SOA, ns1, ns2
         self.assertEqual(len(r.json()), 4)
 
-    @unittest.skipIf(is_auth_lmdb(), "No search or comments in LMDB")
+    @unittest.skipIf(is_auth_lmdb(), "No comments in LMDB")
     def test_search_rr_comment(self):
         name = unique_zone_name()
         rrsets = [{
@@ -2219,7 +2214,6 @@ $NAME$  1D  IN  SOA ns1.example.org. hostmaster.example.org. (
         self.assertEqual(data[0]['name'], name)
         self.assertEqual(data[0]['content'], rrsets[0]['comments'][0]['content'])
 
-    @unittest.skipIf(is_auth_lmdb(), "No search in LMDB")
     def test_search_after_rectify_with_ent(self):
         name = unique_zone_name()
         search = name.split('.')[0]