diff options
Diffstat (limited to 'storage')
15 files changed, 110 insertions, 68 deletions
diff --git a/storage/src/tests/bucketdb/bucketmanagertest.cpp b/storage/src/tests/bucketdb/bucketmanagertest.cpp index dc33bfd04e2..ea3a782d432 100644 --- a/storage/src/tests/bucketdb/bucketmanagertest.cpp +++ b/storage/src/tests/bucketdb/bucketmanagertest.cpp @@ -171,7 +171,7 @@ void BucketManagerTest::setupTestEnvironment(bool fakePersistenceLayer, } // Generate a doc to use for testing.. const DocumentType &type(*_node->getTypeRepo()->getDocumentType("text/html")); - _document = std::make_shared<document::Document>(type, document::DocumentId("id:ns:text/html::ntnu")); + _document = std::make_shared<document::Document>(*_node->getTypeRepo(), type, document::DocumentId("id:ns:text/html::ntnu")); } void BucketManagerTest::addBucketsToDB(uint32_t count) diff --git a/storage/src/tests/distributor/bucketdatabasetest.cpp b/storage/src/tests/distributor/bucketdatabasetest.cpp index 661fd7fee72..fcc64e0cccf 100644 --- a/storage/src/tests/distributor/bucketdatabasetest.cpp +++ b/storage/src/tests/distributor/bucketdatabasetest.cpp @@ -87,7 +87,7 @@ struct ListAllProcessor : public BucketDatabase::EntryProcessor { std::string dump_db(const BucketDatabase& db) { ListAllProcessor proc; - db.forEach(proc, document::BucketId()); + db.for_each_upper_bound(proc, document::BucketId()); return proc.ost.str(); } @@ -122,41 +122,70 @@ TEST_P(BucketDatabaseTest, iterating) { { ListAllProcessor proc; - db().forEach(proc, document::BucketId()); - - EXPECT_EQ( - std::string( - "BucketId(0x4000000000000010) : " - "node(idx=1,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n" - "BucketId(0x400000000000002a) : " - "node(idx=3,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n" - "BucketId(0x400000000000000b) : " - "node(idx=2,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n"), - proc.ost.str()); + db().for_each_upper_bound(proc, document::BucketId()); + + EXPECT_EQ("BucketId(0x4000000000000010) : " + "node(idx=1,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n" + "BucketId(0x400000000000002a) : " + "node(idx=3,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n" + "BucketId(0x400000000000000b) : " + "node(idx=2,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n", + proc.ost.str()); } { ListAllProcessor proc; - db().forEach(proc, document::BucketId(16, 0x2a)); + db().for_each_lower_bound(proc, document::BucketId()); // lbound (in practice) equal to ubound when starting at zero + + EXPECT_EQ("BucketId(0x4000000000000010) : " + "node(idx=1,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n" + "BucketId(0x400000000000002a) : " + "node(idx=3,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n" + "BucketId(0x400000000000000b) : " + "node(idx=2,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n", + proc.ost.str()); + } + + { + ListAllProcessor proc; + db().for_each_upper_bound(proc, document::BucketId(16, 0x2a)); + + EXPECT_EQ("BucketId(0x400000000000000b) : " + "node(idx=2,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n", + proc.ost.str()); + } + + { + ListAllProcessor proc; + db().for_each_lower_bound(proc, document::BucketId(16, 0x2a)); + // Includes 0x2a + EXPECT_EQ("BucketId(0x400000000000002a) : " + "node(idx=3,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n" + "BucketId(0x400000000000000b) : " + "node(idx=2,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n", + proc.ost.str()); + } - EXPECT_EQ( - std::string( - "BucketId(0x400000000000000b) : " - "node(idx=2,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n"), - proc.ost.str()); + { + StoppingProcessor proc; + db().for_each_upper_bound(proc, document::BucketId()); + + EXPECT_EQ("BucketId(0x4000000000000010) : " + "node(idx=1,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n" + "BucketId(0x400000000000002a) : " + "node(idx=3,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n", + proc.ost.str()); } { StoppingProcessor proc; - db().forEach(proc, document::BucketId()); - - EXPECT_EQ( - std::string( - "BucketId(0x4000000000000010) : " - "node(idx=1,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n" - "BucketId(0x400000000000002a) : " - "node(idx=3,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n"), - proc.ost.str()); + db().for_each_lower_bound(proc, document::BucketId()); + + EXPECT_EQ("BucketId(0x4000000000000010) : " + "node(idx=1,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n" + "BucketId(0x400000000000002a) : " + "node(idx=3,crc=0x0,docs=0/0,bytes=1/1,trusted=false,active=false,ready=false)\n", + proc.ost.str()); } } @@ -761,7 +790,7 @@ TEST_P(BucketDatabaseTest, DISABLED_benchmark_const_iteration) { auto elapsed = vespalib::BenchmarkTimer::benchmark([&] { DummyProcessor proc; - db().forEach(proc, document::BucketId()); + db().for_each_upper_bound(proc, document::BucketId()); }, 5); fprintf(stderr, "Full DB iteration of %s takes %g seconds\n", db().toString(false).c_str(), elapsed); diff --git a/storage/src/tests/distributor/getoperationtest.cpp b/storage/src/tests/distributor/getoperationtest.cpp index 8d188f6c005..36a1495579f 100644 --- a/storage/src/tests/distributor/getoperationtest.cpp +++ b/storage/src/tests/distributor/getoperationtest.cpp @@ -85,7 +85,7 @@ struct GetOperationTest : Test, DistributorStripeTestUtil { if (!authorVal.empty()) { const document::DocumentType* type(_repo->getDocumentType("text/html")); - doc = std::make_unique<document::Document>(*type, docId); + doc = std::make_unique<document::Document>(*_repo, *type, docId); doc->setValue(doc->getField("author"), document::StringFieldValue(authorVal)); diff --git a/storage/src/tests/distributor/putoperationtest.cpp b/storage/src/tests/distributor/putoperationtest.cpp index 735666e5c89..2a3f06b1e8c 100644 --- a/storage/src/tests/distributor/putoperationtest.cpp +++ b/storage/src/tests/distributor/putoperationtest.cpp @@ -83,8 +83,12 @@ public: return *_testDocMan.getTypeRepo().getDocumentType("testdoctype1"); } + const document::DocumentTypeRepo& type_repo() const { + return _testDocMan.getTypeRepo(); + } + Document::SP createDummyDocument(const char* ns, const char* id) const { - return std::make_shared<Document>(doc_type(), DocumentId(vespalib::make_string("id:%s:testdoctype1::%s", ns, id))); + return std::make_shared<Document>(type_repo(), doc_type(), DocumentId(vespalib::make_string("id:%s:testdoctype1::%s", ns, id))); } static std::shared_ptr<api::PutCommand> createPut(Document::SP doc) { @@ -98,7 +102,7 @@ PutOperationTest::~PutOperationTest() = default; document::BucketId PutOperationTest::createAndSendSampleDocument(vespalib::duration timeout) { - auto doc = std::make_shared<Document>(doc_type(), DocumentId("id:test:testdoctype1::")); + auto doc = std::make_shared<Document>(type_repo(), doc_type(), DocumentId("id:test:testdoctype1::")); document::BucketId id = operation_context().make_split_bit_constrained_bucket_id(doc->getId()); addIdealNodes(id); @@ -453,7 +457,7 @@ TEST_F(PutOperationTest, no_storage_nodes) { TEST_F(PutOperationTest, update_correct_bucket_on_remapped_put) { setup_stripe(2, 2, "storage:2 distributor:1"); - auto doc = std::make_shared<Document>(doc_type(), DocumentId("id:test:testdoctype1:n=13:uri")); + auto doc = std::make_shared<Document>(type_repo(), doc_type(), DocumentId("id:test:testdoctype1:n=13:uri")); addNodesToBucketDB(document::BucketId(16,13), "0=0,1=0"); sendPut(createPut(doc)); diff --git a/storage/src/tests/distributor/top_level_bucket_db_updater_test.cpp b/storage/src/tests/distributor/top_level_bucket_db_updater_test.cpp index 7b4f688b253..d5d33a178fe 100644 --- a/storage/src/tests/distributor/top_level_bucket_db_updater_test.cpp +++ b/storage/src/tests/distributor/top_level_bucket_db_updater_test.cpp @@ -1697,7 +1697,7 @@ TopLevelBucketDBUpdaterTest::merge_bucket_lists( BucketDumper dumper_tmp(true); for (auto* s : distributor_stripes()) { auto& db = s->getBucketSpaceRepo().get(document::FixedBucketSpaces::default_space()).getBucketDatabase(); - db.forEach(dumper_tmp); + db.for_each_upper_bound(dumper_tmp); } { @@ -1717,7 +1717,7 @@ TopLevelBucketDBUpdaterTest::merge_bucket_lists( BucketDumper dumper(include_bucket_info); for (auto* s : distributor_stripes()) { auto& db = s->getBucketSpaceRepo().get(document::FixedBucketSpaces::default_space()).getBucketDatabase(); - db.forEach(dumper); + db.for_each_upper_bound(dumper); db.clear(); } return dumper.ost.str(); diff --git a/storage/src/tests/distributor/twophaseupdateoperationtest.cpp b/storage/src/tests/distributor/twophaseupdateoperationtest.cpp index 579fd156962..da32225cde3 100644 --- a/storage/src/tests/distributor/twophaseupdateoperationtest.cpp +++ b/storage/src/tests/distributor/twophaseupdateoperationtest.cpp @@ -237,7 +237,7 @@ TwoPhaseUpdateOperationTest::replyToGet( std::shared_ptr<api::StorageReply> reply; if (haveDocument) { - auto doc(std::make_shared<Document>(*_doc_type, DocumentId("id:ns:" + _doc_type->getName() + "::1"))); + auto doc(std::make_shared<Document>(*_repo, *_doc_type, DocumentId("id:ns:" + _doc_type->getName() + "::1"))); doc->setValue("headerval", IntFieldValue(oldTimestamp)); reply = std::make_shared<api::GetReply>(get, doc, oldTimestamp); diff --git a/storage/src/tests/persistence/filestorage/filestormanagertest.cpp b/storage/src/tests/persistence/filestorage/filestormanagertest.cpp index 7f3fe06fc29..4227f3dbe13 100644 --- a/storage/src/tests/persistence/filestorage/filestormanagertest.cpp +++ b/storage/src/tests/persistence/filestorage/filestormanagertest.cpp @@ -485,7 +485,7 @@ TEST_F(FileStorManagerTest, flush) { // Creating a document to test with document::DocumentId docId("id:ns:testdoctype1::crawler:http://www.ntnu.no/"); - auto doc = std::make_shared<Document>(*_testdoctype1, docId); + auto doc = std::make_shared<Document>(*_node->getTypeRepo(), *_testdoctype1, docId); document::BucketId bid(4000); static const uint32_t msgCount = 10; @@ -1032,7 +1032,7 @@ FileStorTestBase::putDoc(DummyStorageLink& top, document::BucketId bucket(16, factory.getBucketId(docId).getRawId()); //std::cerr << "doc bucket is " << bucket << " vs source " << source << "\n"; _node->getPersistenceProvider().createBucket(makeSpiBucket(target)); - Document::SP doc(new Document(*_testdoctype1, docId)); + auto doc = std::make_shared<Document>(*_node->getTypeRepo(), *_testdoctype1, docId); auto cmd = std::make_shared<api::PutCommand>(makeDocumentBucket(target), doc, docNum+1); cmd->setAddress(_Storage3); cmd->setPriority(120); @@ -1073,7 +1073,7 @@ TEST_F(FileStorManagerTest, split_empty_target_with_remapped_ops) { document::DocumentId docId( vespalib::make_string("id:ns:testdoctype1:n=%d:1234", 0x100001)); - auto doc = std::make_shared<Document>(*_testdoctype1, docId); + auto doc = std::make_shared<Document>(*_node->getTypeRepo(), *_testdoctype1, docId); auto putCmd = std::make_shared<api::PutCommand>(makeDocumentBucket(source), doc, 1001); putCmd->setAddress(_Storage3); putCmd->setPriority(120); @@ -1399,7 +1399,7 @@ TEST_F(FileStorManagerTest, delete_bucket) { auto& top = c.top; // Creating a document to test with document::DocumentId docId("id:crawler:testdoctype1:n=4000:http://www.ntnu.no/"); - auto doc = std::make_shared<Document>(*_testdoctype1, docId); + auto doc = std::make_shared<Document>(*_node->getTypeRepo(), *_testdoctype1, docId); document::BucketId bid(16, 4000); createBucket(bid); @@ -1440,7 +1440,7 @@ TEST_F(FileStorManagerTest, delete_bucket_rejects_outdated_bucket_info) { auto& top = c.top; // Creating a document to test with document::DocumentId docId("id:crawler:testdoctype1:n=4000:http://www.ntnu.no/"); - Document::SP doc(new Document(*_testdoctype1, docId)); + auto doc = std::make_shared<Document>(*_node->getTypeRepo(), *_testdoctype1, docId); document::BucketId bid(16, 4000); createBucket(bid); @@ -1487,7 +1487,7 @@ TEST_F(FileStorManagerTest, delete_bucket_with_invalid_bucket_info){ auto& top = c.top; // Creating a document to test with document::DocumentId docId("id:crawler:testdoctype1:n=4000:http://www.ntnu.no/"); - auto doc = std::make_shared<Document>(*_testdoctype1, docId); + auto doc = std::make_shared<Document>(*_node->getTypeRepo(), *_testdoctype1, docId); document::BucketId bid(16, 4000); createBucket(bid); diff --git a/storage/src/tests/storageserver/documentapiconvertertest.cpp b/storage/src/tests/storageserver/documentapiconvertertest.cpp index c375443b265..42944c81f13 100644 --- a/storage/src/tests/storageserver/documentapiconvertertest.cpp +++ b/storage/src/tests/storageserver/documentapiconvertertest.cpp @@ -106,7 +106,7 @@ struct DocumentApiConverterTest : Test { }; TEST_F(DocumentApiConverterTest, put) { - auto doc = std::make_shared<Document>(_html_type, defaultDocId); + auto doc = std::make_shared<Document>(*_repo, _html_type, defaultDocId); documentapi::PutDocumentMessage putmsg(doc); putmsg.setTimestamp(1234); @@ -126,7 +126,7 @@ TEST_F(DocumentApiConverterTest, put) { } TEST_F(DocumentApiConverterTest, forwarded_put) { - auto doc = std::make_shared<Document>(_html_type, DocumentId("id:ns:" + _html_type.getName() + "::test")); + auto doc = std::make_shared<Document>(*_repo, _html_type, DocumentId("id:ns:" + _html_type.getName() + "::test")); auto putmsg = std::make_unique<documentapi::PutDocumentMessage>(doc); auto* putmsg_raw = putmsg.get(); diff --git a/storage/src/tests/storageserver/rpc/storage_api_rpc_service_test.cpp b/storage/src/tests/storageserver/rpc/storage_api_rpc_service_test.cpp index bfc22b9f1ea..26c5b8df5a5 100644 --- a/storage/src/tests/storageserver/rpc/storage_api_rpc_service_test.cpp +++ b/storage/src/tests/storageserver/rpc/storage_api_rpc_service_test.cpp @@ -161,7 +161,7 @@ public: std::shared_ptr<api::PutCommand> create_dummy_put_command() const { auto doc_type = _doc_type_repo->getDocumentType("testdoctype1"); - auto doc = std::make_shared<document::Document>(*doc_type, document::DocumentId("id:foo:testdoctype1::bar")); + auto doc = std::make_shared<document::Document>(*_doc_type_repo, *doc_type, document::DocumentId("id:foo:testdoctype1::bar")); doc->setFieldValue(doc->getField("hstringval"), std::make_unique<document::StringFieldValue>("hello world")); return std::make_shared<api::PutCommand>(makeDocumentBucket(document::BucketId(0)), std::move(doc), 100); } diff --git a/storage/src/vespa/storage/bucketdb/btree_bucket_database.cpp b/storage/src/vespa/storage/bucketdb/btree_bucket_database.cpp index 23421e724a2..baec5494b36 100644 --- a/storage/src/vespa/storage/bucketdb/btree_bucket_database.cpp +++ b/storage/src/vespa/storage/bucketdb/btree_bucket_database.cpp @@ -151,9 +151,16 @@ BTreeBucketDatabase::process_update(const document::BucketId& bucket, EntryUpdat } // TODO need snapshot read with guarding -// FIXME semantics of for-each in judy and bit tree DBs differ, former expects lbound, latter ubound..! -// FIXME but bit-tree code says "lowerBound" in impl and "after" in declaration??? -void BTreeBucketDatabase::forEach(EntryProcessor& proc, const BucketId& after) const { +void BTreeBucketDatabase::for_each_lower_bound(EntryProcessor& proc, const BucketId& at_or_after) const { + for (auto iter = _impl->lower_bound(at_or_after.toKey()); iter.valid(); ++iter) { + if (!proc.process(_impl->const_value_ref_from_valid_iterator(iter))) { + break; + } + } +} + +// TODO need snapshot read with guarding +void BTreeBucketDatabase::for_each_upper_bound(EntryProcessor& proc, const BucketId& after) const { for (auto iter = _impl->upper_bound(after.toKey()); iter.valid(); ++iter) { if (!proc.process(_impl->const_value_ref_from_valid_iterator(iter))) { break; diff --git a/storage/src/vespa/storage/bucketdb/btree_bucket_database.h b/storage/src/vespa/storage/bucketdb/btree_bucket_database.h index c20dad13618..3cf77b5444b 100644 --- a/storage/src/vespa/storage/bucketdb/btree_bucket_database.h +++ b/storage/src/vespa/storage/bucketdb/btree_bucket_database.h @@ -44,7 +44,8 @@ public: std::vector<Entry>& entries) const override; void update(const Entry& newEntry) override; void process_update(const document::BucketId& bucket, EntryUpdateProcessor &processor, bool create_if_nonexisting) override; - void forEach(EntryProcessor&, const document::BucketId& after) const override; + void for_each_lower_bound(EntryProcessor&, const document::BucketId& at_or_after) const override; + void for_each_upper_bound(EntryProcessor&, const document::BucketId& after) const override; Entry upperBound(const document::BucketId& value) const override; uint64_t size() const override; void clear() override; diff --git a/storage/src/vespa/storage/bucketdb/bucketdatabase.h b/storage/src/vespa/storage/bucketdb/bucketdatabase.h index d3d9c34c7fc..4e0b727036a 100644 --- a/storage/src/vespa/storage/bucketdb/bucketdatabase.h +++ b/storage/src/vespa/storage/bucketdb/bucketdatabase.h @@ -99,9 +99,15 @@ public: virtual void process_update(const document::BucketId& bucket, EntryUpdateProcessor &processor, bool create_if_nonexisting) = 0; - virtual void forEach( - EntryProcessor&, - const document::BucketId& after = document::BucketId()) const = 0; + virtual void for_each_lower_bound(EntryProcessor&, const document::BucketId& at_or_after) const = 0; + void for_each_lower_bound(EntryProcessor& proc) const { + for_each_lower_bound(proc, document::BucketId()); + } + + virtual void for_each_upper_bound(EntryProcessor&, const document::BucketId& after) const = 0; + void for_each_upper_bound(EntryProcessor& proc) const { + for_each_upper_bound(proc, document::BucketId()); + } using TrailingInserter = bucketdb::TrailingInserter<Entry>; using Merger = bucketdb::Merger<Entry>; diff --git a/storage/src/vespa/storage/distributor/idealstatemanager.cpp b/storage/src/vespa/storage/distributor/idealstatemanager.cpp index cf255b5ec18..2c33bc490fe 100644 --- a/storage/src/vespa/storage/distributor/idealstatemanager.cpp +++ b/storage/src/vespa/storage/distributor/idealstatemanager.cpp @@ -265,7 +265,7 @@ IdealStateManager::getBucketStatus( void IdealStateManager::dump_bucket_space_db_status(document::BucketSpace bucket_space, std::ostream& out) const { StatusBucketVisitor proc(*this, bucket_space, out); auto& distributorBucketSpace = _op_ctx.bucket_space_repo().get(bucket_space); - distributorBucketSpace.getBucketDatabase().forEach(proc); + distributorBucketSpace.getBucketDatabase().for_each_upper_bound(proc); } void IdealStateManager::getBucketStatus(std::ostream& out) const { diff --git a/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp b/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp index bdf4fa2ba72..515b72520ec 100644 --- a/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp +++ b/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp @@ -672,8 +672,7 @@ std::shared_ptr<document::Document> TwoPhaseUpdateOperation::createBlankDocument() const { const document::DocumentUpdate& up(*_updateCmd->getUpdate()); - auto doc = std::make_shared<document::Document>(up.getType(), up.getId()); - doc->setRepo(*up.getRepoPtr()); + auto doc = std::make_shared<document::Document>(*up.getRepoPtr(), up.getType(), up.getId()); return doc; } diff --git a/storage/src/vespa/storage/distributor/operations/external/visitoroperation.cpp b/storage/src/vespa/storage/distributor/operations/external/visitoroperation.cpp index 9e9196dbee7..ca47ab7478c 100644 --- a/storage/src/vespa/storage/distributor/operations/external/visitoroperation.cpp +++ b/storage/src/vespa/storage/distributor/operations/external/visitoroperation.cpp @@ -388,23 +388,19 @@ VisitorOperation::pickBucketsToVisit(const std::vector<BucketDatabase::Entry>& b std::vector<document::BucketId> bucketVisitOrder; - for (uint32_t i = 0; i < buckets.size(); ++i) { - bucketVisitOrder.push_back(buckets[i].getBucketId()); + for (const auto& bucket : buckets) { + bucketVisitOrder.push_back(bucket.getBucketId()); } VisitorOrder bucketLessThan; std::sort(bucketVisitOrder.begin(), bucketVisitOrder.end(), bucketLessThan); - std::vector<document::BucketId>::const_iterator iter(bucketVisitOrder.begin()); - std::vector<document::BucketId>::const_iterator end(bucketVisitOrder.end()); + auto iter = bucketVisitOrder.begin(); + auto end = bucketVisitOrder.end(); for (; iter != end; ++iter) { - if (bucketLessThan(*iter, _lastBucket) || - *iter == _lastBucket) - { - LOG(spam, - "Skipping bucket %s because it is lower than or equal to progress bucket %s", - iter->toString().c_str(), - _lastBucket.toString().c_str()); + if (bucketLessThan(*iter, _lastBucket) || *iter == _lastBucket) { + LOG(spam, "Skipping bucket %s because it is lower than or equal to progress bucket %s", + iter->toString().c_str(), _lastBucket.toString().c_str()); continue; } LOG(spam, "Iterating: Found in db: %s", iter->toString().c_str()); @@ -460,11 +456,11 @@ getBucketIdAndLast(BucketDatabase& database, { if (!super.contains(last)) { NextEntryFinder proc(super); - database.forEach(proc, super); + database.for_each_upper_bound(proc, super); return proc._next; } else { NextEntryFinder proc(last); - database.forEach(proc, last); + database.for_each_upper_bound(proc, last); return proc._next; } } |