diff options
| -rw-r--r-- | src/mongo/db/database_name.h | 131 | ||||
| -rw-r--r-- | src/mongo/db/namespace_string.h | 112 | ||||
| -rw-r--r-- | src/mongo/db/namespace_string_bm.cpp | 22 | ||||
| -rw-r--r-- | src/mongo/db/namespace_string_test.cpp | 94 | ||||
| -rw-r--r-- | src/mongo/util/namespace_string_util_test.cpp | 23 |
5 files changed, 300 insertions, 82 deletions
diff --git a/src/mongo/db/database_name.h b/src/mongo/db/database_name.h index a16c9a0092d..f728c691362 100644 --- a/src/mongo/db/database_name.h +++ b/src/mongo/db/database_name.h @@ -79,7 +79,7 @@ public: return *_dbName; } - private: + protected: StringData _db; mutable std::once_flag _once; mutable const DatabaseName* _dbName = nullptr; @@ -102,7 +102,7 @@ public: return _get().toString(); } - private: + protected: const DatabaseName& _get() const { return _sharedState->get(); } @@ -123,6 +123,26 @@ public: DatabaseName() = default; /** + * Construct a new DatabaseName from a reference. This reference could be a NamespaceString so + * only use the discriminator, tenant id and database name from its data. + */ + DatabaseName(const DatabaseName& dbName) + : _data(dbName.view().substr(0, dbName.sizeWithTenant() + kDataOffset)) {} + + DatabaseName(DatabaseName&& dbName) = default; + + DatabaseName& operator=(DatabaseName&& dbName) = default; + + /** + * Copy assignment operator. dbName could be a NamespaceString so only use the discriminator, + * tenant id and database name from its data. + */ + DatabaseName& operator=(const DatabaseName& dbName) { + _data = dbName.view().substr(0, dbName.sizeWithTenant() + kDataOffset).toString(); + return *this; + } + + /** * This function constructs a DatabaseName without checking for presence of TenantId. * * MUST only be used for tests. @@ -133,16 +153,18 @@ public: } boost::optional<TenantId> tenantId() const { - if (!_hasTenantId()) { + if (!hasTenantId()) { return boost::none; } return TenantId{OID::from(&_data[kDataOffset])}; } + /** + * Returns the size of the name of this Database. + */ size_t size() const { - auto offset = _hasTenantId() ? kDataOffset + OID::kOIDSize : kDataOffset; - return _data.size() - offset; + return static_cast<uint8_t>(_data.front()) & kDatabaseNameOffsetEndMask; } bool isEmpty() const { @@ -253,31 +275,31 @@ public: return toString(); } + /** + * Returns true if the db names of `this` and `other` are equal, ignoring case, *and* they both + * refer to the same tenant ID (or none). + * The tenant comparison *is* case-sensitive. + */ bool equalCaseInsensitive(const DatabaseName& other) const { - return StringData{_data.data() + kDataOffset, _data.size() - kDataOffset} - .equalCaseInsensitive( - StringData{other._data.data() + kDataOffset, other._data.size() - kDataOffset}); + return tenantIdView() == other.tenantIdView() && db().equalCaseInsensitive(other.db()); } int compare(const DatabaseName& other) const { - if (_hasTenantId() && !other._hasTenantId()) { + if (hasTenantId() && !other.hasTenantId()) { return 1; } - if (other._hasTenantId() && !_hasTenantId()) { + if (other.hasTenantId() && !hasTenantId()) { return -1; } - return StringData{_data.data() + kDataOffset, _data.size() - kDataOffset}.compare( - StringData{other._data.data() + kDataOffset, other._data.size() - kDataOffset}); + return StringData{_data.data() + kDataOffset, sizeWithTenant()}.compare( + StringData{other._data.data() + kDataOffset, other.sizeWithTenant()}); } friend bool operator==(const DatabaseName& lhs, const DatabaseName& rhs) { - return lhs._data == rhs._data; - } - - friend bool operator!=(const DatabaseName& lhs, const DatabaseName& rhs) { - return lhs._data != rhs._data; + return lhs.view().substr(kDataOffset, lhs.sizeWithTenant()) == + rhs.view().substr(kDataOffset, rhs.sizeWithTenant()); } friend bool operator<(const DatabaseName& lhs, const DatabaseName& rhs) { @@ -298,14 +320,17 @@ public: template <typename H> friend H AbslHashValue(H h, const DatabaseName& obj) { - return H::combine(std::move(h), obj._data); + // _data might contain a collection : only hash the discriminator, tenant and database. + return H::combine( + std::move(h), + std::string_view{obj.view().substr(0, obj.sizeWithTenant() + kDataOffset)}); } friend auto logAttrs(const DatabaseName& obj) { return "db"_attr = obj; } -private: +protected: friend class NamespaceString; friend class NamespaceStringOrUUID; friend class DatabaseNameUtil; @@ -315,6 +340,34 @@ private: friend class AuthName; /** + * Returns the size of the optional tenant id. + */ + size_t tenantIdSize() const { + return hasTenantId() ? OID::kOIDSize : 0; + } + + /** + * Returns the size of database name plus the size of the tenant. + */ + size_t sizeWithTenant() const { + return size() + tenantIdSize(); + } + + /** + * Returns the offset from the start of _data to the start of the database name. + */ + size_t dbNameOffsetStart() const { + return kDataOffset + tenantIdSize(); + } + + /** + * Returns a view of the internal string. + */ + StringData view() const { + return _data; + } + + /** * Constructs a DatabaseName from the given tenantId and database name. * "dbString" is expected only consist of a db name. It is the caller's responsibility to ensure * the dbString is a valid db name. @@ -350,8 +403,15 @@ private: } StringData db() const { - auto offset = _hasTenantId() ? kDataOffset + OID::kOIDSize : kDataOffset; - return StringData{_data.data() + offset, _data.size() - offset}; + return view().substr(dbNameOffsetStart(), size()); + } + + StringData tenantIdView() const { + if (!hasTenantId()) { + return {}; + } + + return view().substr(kDataOffset, OID::kOIDSize); } std::string toString() const { @@ -359,7 +419,7 @@ private: } std::string toStringWithTenantId() const { - if (_hasTenantId()) { + if (hasTenantId()) { auto tenantId = TenantId{OID::from(&_data[kDataOffset])}; return str::stream() << tenantId.toString() << "_" << db(); } @@ -371,7 +431,7 @@ private: static constexpr uint8_t kTenantIdMask = 0x80; static constexpr uint8_t kDatabaseNameOffsetEndMask = 0x7F; - inline bool _hasTenantId() const { + bool hasTenantId() const { return static_cast<uint8_t>(_data.front()) & kTenantIdMask; } @@ -379,7 +439,30 @@ private: struct TrustedInitTag {}; DatabaseName(std::string data, TrustedInitTag) : _data(std::move(data)) {} - // Same in-memory layout as NamespaceString, see documentation in its header + /** + * We pack all possible namespaces data into a string consisting of these concatenated parts: + * + * Length Name Description + * --------------------------------------------------------------------------------------------- + * 1 discriminator Consists of bit fields: + * [0..6] : dbSize : length of the database part (cannot exceed + * 64 characters). 7 bits. + * [7] : hasTenant : MSB. 1 if there is a tenant. + * + * 12 tenant ID OID that uniquely identify the tenant for this namespace. + * Optional, only if hasTenant == 1. + * + * dbSize database Database name, size equal to the dbSize component of the + * discriminator. + * + * 1 dot Dot character between the database and the collection. + * Optional, only if `this` is a NamespaceString and there is + * space left in the string. + * + * ... collection Collection name. + * Optional, only if `this` is a NamespaceString and there is space + * left in the string. + */ std::string _data{'\0'}; }; diff --git a/src/mongo/db/namespace_string.h b/src/mongo/db/namespace_string.h index 0c5d929cb1d..3872e221b5c 100644 --- a/src/mongo/db/namespace_string.h +++ b/src/mongo/db/namespace_string.h @@ -62,7 +62,7 @@ namespace mongo { -class NamespaceString { +class NamespaceString : private DatabaseName { public: /** * The NamespaceString reserved constants are actually this `ConstantProxy` @@ -210,7 +210,34 @@ public: /** * Constructs a NamespaceString for the given database. */ - explicit NamespaceString(DatabaseName dbName) : _data(std::move(dbName._data)) {} + explicit NamespaceString(DatabaseName dbName) + : DatabaseName(std::move(dbName._data), DatabaseName::TrustedInitTag{}) { + // Given that NamespaceString is a DatabaseName, it is possible the `dbName` parameter is + // created from another `NamespaceString` and contains a collection. In this case we want to + // resize the underlying `data` to discard the previous collection. + const size_t expectedSize = DatabaseName::sizeWithTenant(); + _data.resize(expectedSize + kDataOffset); + } + + /** + * Construct a NamespaceString from a const reference. This constructor is required to avoid + * invoking DatabaseName(const DatabaseName&..) which would discard the collection from the + * underlying data. + */ + NamespaceString(NamespaceString&& ns) = default; + + NamespaceString(const NamespaceString& ns) : DatabaseName(ns._data, TrustedInitTag{}) {} + + NamespaceString& operator=(NamespaceString&& ns) = default; + + /** + * Copy assignment operator. This cannot be defaulted as we must avoid calling DatabaseName copy + * assignment operator which would discard the collection from _data. + */ + NamespaceString& operator=(const NamespaceString& ns) { + _data = ns._data; + return *this; + } /** * Constructs a NamespaceString in the global config db, "config.<collName>". @@ -358,7 +385,7 @@ public: boost::optional<TenantId> tenantId() const { - if (!_hasTenantId()) { + if (!hasTenantId()) { return boost::none; } @@ -376,19 +403,15 @@ public: * This function must only be used in unit tests. */ StringData db_forTest() const { - auto offset = _hasTenantId() ? kDataOffset + OID::kOIDSize : kDataOffset; - return StringData{_data.data() + offset, _dbNameOffsetEnd()}; + return db_deprecated(); } - DatabaseName dbName() const { - auto offset = _hasTenantId() ? kDataOffset + OID::kOIDSize : kDataOffset; - return DatabaseName{_data.substr(0, offset + _dbNameOffsetEnd()), - DatabaseName::TrustedInitTag{}}; + const DatabaseName& dbName() const { + return *this; } StringData coll() const { - const auto offset = - kDataOffset + _dbNameOffsetEnd() + 1 + (_hasTenantId() ? OID::kOIDSize : 0); + const auto offset = kDataOffset + dbSize() + 1 + tenantIdSize(); if (offset > _data.size()) { return {}; } @@ -458,13 +481,16 @@ public: return nss.toStringWithTenantId(); } + /** + * Returns the size of the database and collection (including the 'dot'). + */ size_t size() const { - auto offset = _hasTenantId() ? kDataOffset + OID::kOIDSize : kDataOffset; + auto offset = kDataOffset + tenantIdSize(); return _data.size() - offset; } size_t dbSize() const { - return _dbNameOffsetEnd(); + return DatabaseName::size(); } bool isEmpty() const { @@ -541,8 +567,7 @@ public: * foo.a = false */ bool isDbOnly() const { - auto offset = _hasTenantId() ? kDataOffset + OID::kOIDSize : kDataOffset; - return offset + _dbNameOffsetEnd() == _data.size(); + return kDataOffset + DatabaseName::sizeWithTenant() == _data.size(); } /** @@ -761,11 +786,11 @@ public: static bool validCollectionName(StringData coll); int compare(const NamespaceString& other) const { - if (_hasTenantId() && !other._hasTenantId()) { + if (hasTenantId() && !other.hasTenantId()) { return 1; } - if (other._hasTenantId() && !_hasTenantId()) { + if (other.hasTenantId() && !hasTenantId()) { return -1; } @@ -796,10 +821,6 @@ public: return lhs._data == rhs._data; } - friend bool operator!=(const NamespaceString& lhs, const NamespaceString& rhs) { - return lhs._data != rhs._data; - } - friend bool operator<(const NamespaceString& lhs, const NamespaceString& rhs) { return lhs.compare(rhs) < 0; } @@ -838,8 +859,8 @@ private: * Constructs a NamespaceString from the fully qualified namespace named in "ns" and the * tenantId. "ns" is NOT expected to contain the tenantId. */ - explicit NamespaceString(boost::optional<TenantId> tenantId, StringData ns) - : _data(makeData(tenantId, ns)) {} + NamespaceString(boost::optional<TenantId> tenantId, StringData ns) + : DatabaseName(makeData(tenantId, ns), DatabaseName::TrustedInitTag{}) {} /** * Constructs a NamespaceString for the given database and collection names. @@ -853,12 +874,13 @@ private: "namespaces cannot have embedded null characters", collectionName.find('\0') == std::string::npos); - _data.resize(collectionName.empty() ? dbName._data.size() - : dbName._data.size() + 1 + collectionName.size()); - std::memcpy(_data.data(), dbName._data.data(), dbName._data.size()); + const size_t dbAndDiscriminatorSize = dbName.sizeWithTenant() + kDataOffset; + _data.resize(collectionName.empty() ? dbAndDiscriminatorSize + : dbAndDiscriminatorSize + 1 + collectionName.size()); + std::memcpy(_data.data(), dbName._data.data(), dbAndDiscriminatorSize); if (!collectionName.empty()) { - *reinterpret_cast<uint8_t*>(_data.data() + dbName._data.size()) = '.'; - std::memcpy(_data.data() + dbName._data.size() + 1, + *reinterpret_cast<uint8_t*>(_data.data() + dbAndDiscriminatorSize) = '.'; + std::memcpy(_data.data() + dbAndDiscriminatorSize + 1, collectionName.rawData(), collectionName.size()); } @@ -870,14 +892,14 @@ private: * NOT expected to contain a tenantId. */ NamespaceString(boost::optional<TenantId> tenantId, StringData db, StringData collectionName) - : _data(makeData(tenantId, db, collectionName)) {} + : DatabaseName(makeData(tenantId, db, collectionName), DatabaseName::TrustedInitTag{}) {} std::string toString() const { return ns().toString(); } std::string toStringWithTenantId() const { - if (_hasTenantId()) { + if (hasTenantId()) { return str::stream() << TenantId{OID::from(&_data[kDataOffset])} << "_" << ns(); } @@ -889,7 +911,7 @@ private: * test needing access to ns(). */ StringData ns() const { - auto offset = _hasTenantId() ? kDataOffset + OID::kOIDSize : kDataOffset; + auto offset = kDataOffset + tenantIdSize(); return StringData{_data.data() + offset, _data.size() - offset}; } @@ -900,22 +922,13 @@ private: * at the DatabaseNameUtil::serialize method which takes in a DatabaseName object. */ StringData db_deprecated() const { - auto offset = _hasTenantId() ? kDataOffset + OID::kOIDSize : kDataOffset; - return StringData{_data.data() + offset, _dbNameOffsetEnd()}; + return dbName().db(); } static constexpr size_t kDataOffset = sizeof(uint8_t); static constexpr uint8_t kTenantIdMask = 0x80; static constexpr uint8_t kDatabaseNameOffsetEndMask = 0x7F; - inline bool _hasTenantId() const { - return static_cast<uint8_t>(_data.front()) & kTenantIdMask; - } - - inline size_t _dbNameOffsetEnd() const { - return static_cast<uint8_t>(_data.front()) & kDatabaseNameOffsetEndMask; - } - std::string makeData(boost::optional<TenantId> tenantId, StringData db, StringData collectionName) { @@ -969,21 +982,6 @@ private: return makeData(tenantId, ns.substr(0, dotIndex), ns.substr(dotIndex + 1, ns.size())); } - - // In order to reduce the size of a NamespaceString, we pack all possible namespace data - // into a single std::string with the following in-memory layout: - // - // 1 byte 12 byte optional tenant id remaining bytes - // discriminator (see more below) namespace - // |<------------->|<--------------------------->|<-------------------------------------->| - // [---------------|----|----|----|----|----|----|----|----|----|----|----|----|----|----|] - // 0 1 12 ?? - // - // The MSB of the discriminator tells us whether a tenant id is present, and the remaining - // bits store the offset of end of the databaes component of the namespace. Database names - // must be 64 characters or shorter, so we can be confident the length will fit in three bits. - - std::string _data{'\0'}; }; /** @@ -1020,7 +1018,7 @@ public: /** * Returns the database name. */ - DatabaseName dbName() const { + const DatabaseName& dbName() const { if (holds_alternative<NamespaceString>(_nssOrUUID)) { return get<NamespaceString>(_nssOrUUID).dbName(); } diff --git a/src/mongo/db/namespace_string_bm.cpp b/src/mongo/db/namespace_string_bm.cpp index 0a1f1448841..4a6799e92bb 100644 --- a/src/mongo/db/namespace_string_bm.cpp +++ b/src/mongo/db/namespace_string_bm.cpp @@ -97,6 +97,24 @@ void BM_CreateLongNsFromConstexpr(benchmark::State& state) { } } +void BM_NamespaceStringShortDbName(benchmark::State& state) { + const auto dbName = "short"_sd; + NamespaceString ns = makeNS(dbName, kMapCollName); + + for (auto _ : state) { + benchmark::DoNotOptimize(ns.dbName()); + } +} + +void BM_NamespaceStringLongDbName(benchmark::State& state) { + const std::string dbName(60, 'x'); + NamespaceString ns = makeNS(dbName, kMapCollName); + + for (auto _ : state) { + benchmark::DoNotOptimize(ns.dbName()); + } +} + void searchInMap(benchmark::State& state, const NamespaceString& ns) { immutable::map<DatabaseName, std::shared_ptr<int>> dbMap; @@ -149,6 +167,10 @@ BENCHMARK(BM_CreateShortNsFromConstexpr); BENCHMARK(BM_CreateLongNsFromConstexpr); +BENCHMARK(BM_NamespaceStringShortDbName); + +BENCHMARK(BM_NamespaceStringLongDbName); + BENCHMARK(BM_NamespaceStringShortDbNameMapLookupExist); BENCHMARK(BM_NamespaceStringLongDbNameMapLookupExist); diff --git a/src/mongo/db/namespace_string_test.cpp b/src/mongo/db/namespace_string_test.cpp index 65490efe159..60c6634bb24 100644 --- a/src/mongo/db/namespace_string_test.cpp +++ b/src/mongo/db/namespace_string_test.cpp @@ -56,10 +56,18 @@ protected: return NamespaceString(tenantId, ns); } + NamespaceString makeNamespaceString(const DatabaseName& dbName) { + return NamespaceString(dbName); + } + NamespaceString makeNamespaceString(const DatabaseName& dbName, StringData coll) { return NamespaceString(dbName, coll); } + NamespaceString makeNamespaceString(StringData dbName, StringData coll) { + return NamespaceString(boost::none, dbName, coll); + } + NamespaceString makeNamespaceString(boost::optional<TenantId> tenantId, StringData db, StringData coll) { @@ -306,6 +314,11 @@ TEST_F(NamespaceStringTest, CollectionValidNames) { ASSERT(!NamespaceString::validCollectionName("a\0b"_sd)); } +TEST_F(NamespaceStringTest, DbForSharding) { + ASSERT_EQ(makeNamespaceString("foo", "bar").db_forSharding(), "foo"); + ASSERT_EQ(makeNamespaceString("foo", "").db_forSharding(), "foo"); +} + TEST_F(NamespaceStringTest, nsToDatabase1) { ASSERT_EQUALS("foo", nsToDatabaseSubstring("foo.bar")); ASSERT_EQUALS("foo", nsToDatabaseSubstring("foo")); @@ -371,6 +384,8 @@ TEST_F(NamespaceStringTest, EmptyDbWithColl) { NamespaceString nss = makeNamespaceString(boost::none, "", "coll"); ASSERT_EQ(nss.db_forTest(), StringData{}); ASSERT_EQ(nss.coll(), "coll"); + ASSERT_EQ(nss.dbName(), DatabaseName::kEmpty); + ASSERT_EQ(nss.dbName().compare(DatabaseName::kEmpty), 0); } TEST_F(NamespaceStringTest, NSSWithTenantId) { @@ -379,13 +394,17 @@ TEST_F(NamespaceStringTest, NSSWithTenantId) { { std::string tenantNsStr = str::stream() << tenantId.toString() << "_foo.bar"; NamespaceString nss = makeNamespaceString(tenantId, "foo.bar"); + DatabaseName db = DatabaseName::createDatabaseName_forTest(tenantId, "foo"); + ASSERT_EQ(nss.size(), 7); ASSERT_EQ(nss.ns_forTest(), "foo.bar"); ASSERT_EQ(nss.toString_forTest(), "foo.bar"); ASSERT_EQ(nss.toStringWithTenantId_forTest(), tenantNsStr); ASSERT_EQ(nss.db_forTest(), "foo"); ASSERT_EQ(nss.coll(), "bar"); + ASSERT_EQ(nss.dbName(), db); ASSERT_EQ(nss.dbName().toString_forTest(), "foo"); + ASSERT_EQ(nss.dbName().size(), db.size()); ASSERT_EQ(nss.size(), 7); ASSERT(nss.tenantId()); ASSERT(nss.dbName().tenantId()); @@ -607,6 +626,80 @@ TEST_F(NamespaceStringTest, EmptyNamespaceString) { ASSERT_EQ(emptyNss.toStringForErrorMsg(), ""); } +TEST_F(NamespaceStringTest, NsFromDbOnly) { + DatabaseName db = DatabaseName::createDatabaseName_forTest(boost::none, "testdb"); + NamespaceString ns(db); + + ASSERT_EQ(ns.size(), 6); + ASSERT_EQ(ns, makeNamespaceString(boost::none, "testdb", "")); +} + +TEST_F(NamespaceStringTest, ConstRefAssignmentOperator) { + NamespaceString expected{makeNamespaceString("foo", "bar")}; + + NamespaceString actual; + actual = expected; + + ASSERT_EQ(actual, expected); + ASSERT_EQ(actual.coll(), "bar"); +} + +// Verify we can create a new NamespaceString with a DatabaseName created by ns.dbName(). We must +// ensure we discard the collection from `ns` and we don't end up with `db.collection.collection`. +TEST_F(NamespaceStringTest, NamespaceToDatabaseRoundtrip) { + auto dbName = "test"_sd; + auto collName = "foo"_sd; + auto otherCollName = "othername"_sd; + + NamespaceString ns = makeNamespaceString(boost::none, dbName, collName); + NamespaceString nsDbOnly = + makeNamespaceString(DatabaseName::createDatabaseName_forTest(boost::none, dbName)); + NamespaceString nsWithOtherColl = makeNamespaceString(boost::none, dbName, otherCollName); + + auto runChecks = [&](const DatabaseName& db) { + ASSERT_EQ(db.size(), 4); + + auto nsWithEmptyColl = makeNamespaceString(db); + ASSERT_EQ(nsWithEmptyColl, nsDbOnly); + ASSERT_EQ(nsWithEmptyColl.compare(nsDbOnly), 0); + + auto newNs = makeNamespaceString(db, collName); + ASSERT_EQ(newNs.size(), 8); + ASSERT_EQ(ns, newNs); + ASSERT_EQ(ns.compare(newNs), 0); + + auto anotherColl = makeNamespaceString(db, otherCollName); + ASSERT_EQ(anotherColl.size(), 14); + ASSERT_EQ(anotherColl.coll(), otherCollName); + ASSERT_EQ(anotherColl, nsWithOtherColl); + ASSERT_EQ(anotherColl.compare(nsWithOtherColl), 0); + }; + + const DatabaseName& dbRef = ns.dbName(); + runChecks(dbRef); + + const DatabaseName dbVal(ns.dbName()); + runChecks(dbVal); +} + +TEST_F(NamespaceStringTest, HashTest) { + auto ns = NamespaceString::createNamespaceString_forTest(boost::none, "foo", "bar"); + auto ns2 = NamespaceString::createNamespaceString_forTest(boost::none, "foo", "bar"); + + auto db = DatabaseName::createDatabaseName_forTest(boost::none, "foo"); + + using NsHash = absl::Hash<NamespaceString>; + using DbHash = absl::Hash<DatabaseName>; + + ASSERT_EQ(DbHash()(db), DbHash()(ns.dbName())); + ASSERT_EQ(NsHash()(ns), NsHash()(ns2)); + ASSERT_EQ(NsHash()(ns2), + NsHash()(NamespaceString::createNamespaceString_forTest(ns.dbName(), "bar"))); + + ASSERT_EQ(NsHash()(NamespaceString(ns.dbName())), + NsHash()(NamespaceString::createNamespaceString_forTest(boost::none, "foo", ""))); +} + TEST_F(NamespaceStringTest, isDbOnly) { TenantId tenantId(OID::gen()); @@ -636,7 +729,6 @@ TEST_F(NamespaceStringTest, isDbOnly) { DatabaseName::createDatabaseName_forTest(tenantId, "foo"), "bar")}; for (const auto& nss : dbOnlyNamespaces) { - std::cout << "nss=" << nss.toStringForErrorMsg() << std::endl; ASSERT_TRUE(nss.isDbOnly()); } diff --git a/src/mongo/util/namespace_string_util_test.cpp b/src/mongo/util/namespace_string_util_test.cpp index 0e226c86ddd..260699095ed 100644 --- a/src/mongo/util/namespace_string_util_test.cpp +++ b/src/mongo/util/namespace_string_util_test.cpp @@ -36,6 +36,7 @@ #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/oid.h" #include "mongo/db/namespace_string.h" +#include "mongo/db/tenant_id.h" #include "mongo/idl/server_parameter_test_util.h" #include "mongo/unittest/assert.h" #include "mongo/unittest/framework.h" @@ -156,6 +157,28 @@ TEST(NamespaceStringUtilTest, ASSERT_EQ(nss.dbName().toString_forTest(), dbNameStr); } +TEST(NamespaceStringUtilTest, NamespaceStringToDatabaseNameRoundTrip) { + struct Scenario { + bool multitenancy; + boost::optional<TenantId> tenant; + std::string database; + }; + + for (auto& scenario : { + Scenario{false, boost::none, "foo"}, + Scenario{true, boost::none, "config"}, + Scenario{true, TenantId{OID::gen()}, "foo"}, + }) { + RAIIServerParameterControllerForTest mc("multitenancySupport", scenario.multitenancy); + + auto expected = NamespaceString::createNamespaceString_forTest( + scenario.tenant, scenario.database, "bar"); + auto actual = NamespaceStringUtil::deserialize(expected.dbName(), "bar"); + + ASSERT_EQ(actual, expected); + } +} + // Deserialize NamespaceString when multitenancySupport and featureFlagRequireTenantID are disabled. TEST(NamespaceStringUtilTest, DeserializeMultitenancySupportOffFeatureFlagRequireTenantIDOff) { RAIIServerParameterControllerForTest multitenanyController("multitenancySupport", false); |
