/** * Copyright (C) 2012-2015 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #pragma once #include "mongo/db/jsobj.h" namespace mongo { class BSONObj; template class StatusWith; /** * ChunkVersions consist of a major/minor version scoped to a version epoch * * Version configurations (format: major version, epoch): * * 1. (0, 0) - collection is dropped. * 2. (0, n), n > 0 - applicable only to shardVersion; shard has no chunk. * 3. (n, 0), n > 0 - invalid configuration. * 4. (n, m), n > 0, m > 0 - normal sharded collection version. * * TODO: This is a "manual type" but, even so, still needs to comform to what's * expected from types. */ struct ChunkVersion { public: /** * The name for the shard version information field, which shard-aware commands should include * if they want to convey shard version. */ static const char kShardVersionField[]; ChunkVersion() : _combined(0), _epoch(OID()) {} ChunkVersion(int major, int minor, const OID& epoch) : _combined(static_cast(minor) | (static_cast(major) << 32)), _epoch(epoch) {} /** * Interprets the specified BSON content as the format for commands, which is in the form: * { ..., shardVersion: [ , ], ... } */ static StatusWith parseFromBSONForCommands(const BSONObj& obj); /** * Parses the BSON formatted by ChunkVersion::appendWithFieldForCommands. * * Interprets the specified BSON content as the format for commands, which is in the form: * { ..., : [ , ], ... }. */ static StatusWith parseFromBSONWithFieldForCommands(const BSONObj& obj, StringData field); /** * Note: if possible, use ChunkVersion::parseFromBSONForCommands or * ChunkVersion::parseFromBSONWithFieldForCommands instead. Phasing out this function. * * Interprets the specified BSON content as the format for the setShardVersion command, which * is in the form: * { ..., version: [ ], versionEpoch: [ ], ... } */ static StatusWith parseFromBSONForSetShardVersion(const BSONObj& obj); /** * Note: if possible, use ChunkVersion::parseFromBSONForCommands or * ChunkVersion::parseFromBSONWithFieldForCommands instead. Phasing out this function. * * Interprets the specified BSON content as the format for chunk persistence, which is in the * form: * { ..., lastmod: [ ], lastmodEpoch: [ ], ... } */ static StatusWith parseFromBSONForChunk(const BSONObj& obj); /** * Interprets the lastmod (combined major/minor) from a BSONObj without an epoch * { ..., lastmod: [ ], ... } * and then sets the returned ChunkVersion's epoch field to 'epoch'. */ static StatusWith parseFromBSONAndSetEpoch(const BSONObj& obj, const OID& epoch); /** * Indicates a dropped collection. All components are zeroes (OID is zero time, zero * machineId/inc). */ static ChunkVersion DROPPED() { return ChunkVersion(0, 0, OID()); } /** * Indicates that the collection is not sharded. Same as DROPPED. */ static ChunkVersion UNSHARDED() { return ChunkVersion(0, 0, OID()); } static ChunkVersion IGNORED() { ChunkVersion version = ChunkVersion(); version._epoch.init(Date_t(), true); // ignored OID is zero time, max machineId/inc return version; } static ChunkVersion fromDeprecatedLong(unsigned long long num, const OID& epoch) { ChunkVersion version(0, 0, epoch); version._combined = num; return version; } static bool isIgnoredVersion(const ChunkVersion& version) { return version.majorVersion() == 0 && version.minorVersion() == 0 && version.epoch() == IGNORED().epoch(); } void incMajor() { _combined = static_cast(majorVersion() + 1) << 32; } void incMinor() { _combined++; } // Note: this shouldn't be used as a substitute for version except in specific cases - // epochs make versions more complex unsigned long long toLong() const { return _combined; } bool isSet() const { return _combined > 0; } int majorVersion() const { return _combined >> 32; } int minorVersion() const { return _combined & 0xFFFF; } OID epoch() const { return _epoch; } // // Explicit comparison operators - versions with epochs have non-trivial comparisons. // > < operators do not check epoch cases. Generally if using == we need to handle // more complex cases. // bool operator==(const ChunkVersion& otherVersion) const { return equals(otherVersion); } bool operator>(const ChunkVersion& otherVersion) const { return this->_combined > otherVersion._combined; } bool operator>=(const ChunkVersion& otherVersion) const { return this->_combined >= otherVersion._combined; } bool operator<(const ChunkVersion& otherVersion) const { return this->_combined < otherVersion._combined; } bool operator<=(const ChunkVersion& otherVersion) const { return this->_combined <= otherVersion._combined; } // // Equivalence comparison types. // // Can we write to this data and not have a problem? bool isWriteCompatibleWith(const ChunkVersion& otherVersion) const { if (!hasEqualEpoch(otherVersion)) return false; return otherVersion.majorVersion() == majorVersion(); } // Is this the same version? bool equals(const ChunkVersion& otherVersion) const { if (!hasEqualEpoch(otherVersion)) return false; return otherVersion._combined == _combined; } /** * Returns true if the otherVersion is the same as this version and enforces strict epoch * checking (empty epochs are not wildcards). */ bool isStrictlyEqualTo(const ChunkVersion& otherVersion) const { if (otherVersion._epoch != _epoch) return false; return otherVersion._combined == _combined; } /** * Returns true if this version is (strictly) in the same epoch as the other version and * this version is older. Returns false if we're not sure because the epochs are different * or if this version is newer. */ bool isOlderThan(const ChunkVersion& otherVersion) const { if (otherVersion._epoch != _epoch) return false; if (majorVersion() != otherVersion.majorVersion()) return majorVersion() < otherVersion.majorVersion(); return minorVersion() < otherVersion.minorVersion(); } // Is this in the same epoch? bool hasEqualEpoch(const ChunkVersion& otherVersion) const { return hasEqualEpoch(otherVersion._epoch); } bool hasEqualEpoch(const OID& otherEpoch) const { return _epoch == otherEpoch; } // // BSON input/output // // The idea here is to make the BSON input style very flexible right now, so we // can then tighten it up in the next version. We can accept either a BSONObject field // with version and epoch, or version and epoch in different fields (either is optional). // In this case, epoch always is stored in a field name of the version field name + "Epoch" // // // { version : } and { version : [,] } format // static ChunkVersion fromBSON(const BSONElement& el, const std::string& prefix, bool* canParse) { *canParse = true; int type = el.type(); if (type == Array) { return fromBSON(BSONArray(el.Obj()), canParse); } if (type == jstOID) { return ChunkVersion(0, 0, el.OID()); } if (type == bsonTimestamp || type == Date) { return fromDeprecatedLong(el._numberLong(), OID()); } *canParse = false; return ChunkVersion(0, 0, OID()); } // // { version : , versionEpoch : } object format // static ChunkVersion fromBSON(const BSONObj& obj, const std::string& prefix = "") { bool canParse; return fromBSON(obj, prefix, &canParse); } static ChunkVersion fromBSON(const BSONObj& obj, const std::string& prefixIn, bool* canParse) { *canParse = true; std::string prefix = prefixIn; // "version" doesn't have a "cluster constanst" because that field is never // written to the config. if (prefixIn == "" && !obj["version"].eoo()) { prefix = (std::string) "version"; } // TODO: use ChunkType::DEPRECATED_lastmod() // NOTE: type_chunk.h includes this file else if (prefixIn == "" && !obj["lastmod"].eoo()) { prefix = (std::string) "lastmod"; } ChunkVersion version = fromBSON(obj[prefix], prefixIn, canParse); if (obj[prefix + "Epoch"].type() == jstOID) { version._epoch = obj[prefix + "Epoch"].OID(); *canParse = true; } return version; } // // { version : [, ] } format // static ChunkVersion fromBSON(const BSONArray& arr, bool* canParse) { *canParse = false; ChunkVersion version; BSONObjIterator it(arr); if (!it.more()) return version; version = fromBSON(it.next(), "", canParse); if (!(*canParse)) return version; *canParse = true; if (!it.more()) return version; BSONElement next = it.next(); if (next.type() != jstOID) return version; version._epoch = next.OID(); return version; } // // Currently our BSON output is to two different fields, to cleanly work with older // versions that know nothing about epochs. // BSONObj toBSONWithPrefix(const std::string& prefix) const { invariant(!prefix.empty()); BSONObjBuilder b; b.appendTimestamp(prefix, _combined); b.append(prefix + "Epoch", _epoch); return b.obj(); } /** * Note: if possible, use ChunkVersion::appendForCommands or * ChunkVersion::appendWithFieldForCommands instead. Phasing out this function. */ void addToBSON(BSONObjBuilder& b, const std::string& prefix) const { b.appendElements(toBSONWithPrefix(prefix)); } /** * Appends the contents to the specified builder in the format expected by the setShardVersion * command. */ void appendForSetShardVersion(BSONObjBuilder* builder) const; /** * Appends the contents to the specified builder in the format expected by the sharded commands. */ void appendForCommands(BSONObjBuilder* builder) const; /** * Appends the contents as an array to "builder" with the field name "field" in the format * expected by the sharded commands. * * { ..., : [ , ], ... } * * Use ChunkVersion::parseFromBSONWithFieldForCommands to retrieve the ChunkVersion from the * BSON created by this function. */ void appendWithFieldForCommands(BSONObjBuilder* builder, StringData field) const; /** * Appends the contents to the specified builder in the format expected by the chunk * serialization/deserialization code. */ void appendForChunk(BSONObjBuilder* builder) const; std::string toString() const { StringBuilder sb; sb << majorVersion() << "|" << minorVersion() << "||" << _epoch; return sb.str(); } BSONObj toBSON() const; private: uint64_t _combined; OID _epoch; }; inline std::ostream& operator<<(std::ostream& s, const ChunkVersion& v) { s << v.toString(); return s; } } // namespace mongo