From bb985870f0cc8dab220d8b6b992ee48d2fc546a0 Mon Sep 17 00:00:00 2001 From: Martchus Date: Fri, 15 Apr 2022 19:18:35 +0200 Subject: [PATCH] Avoid overflow when running out of IDs * Throw an exception instead * Add function that allows re-using lower IDs instead * Move functions to query IDs to read-only operations --- lmdb-typed.cc | 11 --------- lmdb-typed.hh | 67 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 58 insertions(+), 20 deletions(-) diff --git a/lmdb-typed.cc b/lmdb-typed.cc index fea6674..c2d8ff6 100644 --- a/lmdb-typed.cc +++ b/lmdb-typed.cc @@ -2,15 +2,4 @@ namespace LMDBSafe { -IDType MDBGetMaxID(MDBRWTransaction &txn, MDBDbi &dbi) -{ - auto cursor = txn->getRWCursor(dbi); - MDBOutVal maxidval, maxcontent; - auto maxid = IDType(0); - if (!cursor.get(maxidval, maxcontent, MDB_LAST)) { - maxid = maxidval.get(); - } - return maxid; -} - } // namespace LMDBSafe diff --git a/lmdb-typed.hh b/lmdb-typed.hh index b096b6e..a4a5bfe 100644 --- a/lmdb-typed.hh +++ b/lmdb-typed.hh @@ -13,14 +13,6 @@ namespace LMDBSafe { */ using IDType = std::uint32_t; -/*! - * \brief Returns the highest ID used in a database. Returns 0 for an empty DB. - * \remarks - * This makes us start everything at ID "1", which might make it possible to - * treat id 0 as special. - */ -LMDB_SAFE_EXPORT IDType MDBGetMaxID(MDBRWTransaction &txn, MDBDbi &dbi); - /*! * \brief Converts \a t to an std::string. * @@ -298,6 +290,63 @@ public: return stat.ms_entries; } + /*! + * \brief Returns the highest ID or 0 if the database is empty. + */ + IDType maxID() + { + auto cursor = (*d_parent.d_txn)->getCursor(d_parent.d_parent->d_main); + MDBOutVal idval, maxcontent; + auto id = IDType(0); + if (!cursor.get(idval, maxcontent, MDB_LAST)) { + id = idval.get(); + } + return id; + } + + /*! + * \brief Returns the next highest ID in the database. + * \remarks Never returns 0 so it can be used as special "no such ID" value. + * \throws Throws LMDBError when running out of IDs. + */ + IDType nextID() + { + const auto id = maxID(); + if (id < std::numeric_limits::max()) { + return id + 1; + } + throw LMDBError("Running out of IDs"); + } + + /*! + * \brief Returns an ID not used in the database so far. + * \remarks + * - Lower IDs are reused but an extensive search for "gabs" is avoided. + * - Never returns 0 so it can be used as special "no such ID" value. + * \throws Throws LMDBError when running out of IDs. + */ + IDType newID() + { + auto cursor = (*d_parent.d_txn)->getCursor(d_parent.d_parent->d_main); + MDBOutVal idval, maxcontent; + auto id = IDType(1); + if (!cursor.get(idval, maxcontent, MDB_FIRST)) { + id = idval.get(); + } + if (id > 1) { + return id - 1; + } + if (!cursor.get(idval, maxcontent, MDB_LAST)) { + id = idval.get(); + } else { + id = 0; + } + if (id < std::numeric_limits::max()) { + return id + 1; + } + throw LMDBError("Running out of IDs"); + } + //! Get item with id, from main table directly bool get(IDType id, T &t) { @@ -798,7 +847,7 @@ public: { unsigned int flags = 0; if (!id) { - id = MDBGetMaxID(*d_txn, d_parent->d_main) + 1; + id = this->nextID(); flags = MDB_APPEND; } (*d_txn)->put(d_parent->d_main, id, serToString(t), flags);