1 /* 2 * Copyright 2016 Couchbase, Inc 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #pragma once 18 19 #include <limits> 20 #include <memory> 21 #include <string> 22 #include <type_traits> 23 24 #include <memcached/dockey.h> 25 26 #include <gsl/gsl> 27 28 #include "ep_types.h" 29 30 class SerialisedDocKey; 31 32 /** 33 * StoredDocKey is a container for key data 34 * 35 * Internally an n byte key is stored in a n + 1 std::string. 36 * a) We zero terminate so that data() is safe for printing as a c-string. 37 * b) We store the DocNamespace in byte 0 (duplicated in parent class DocKey). 38 * This is because StoredDocKey typically ends up being written to disk and 39 * the DocNamespace forms part of the on-disk key. Pre-allocating space 40 * for the DocNamespace means storage components don't have to create a 41 * new buffer into which we layout DocNamespace and key data. 42 */ 43 class StoredDocKey : public DocKeyInterface<StoredDocKey> { 44 public: 45 /** 46 * Construct empty - required for some std containers 47 */ StoredDocKey()48 StoredDocKey() : keydata() { 49 } 50 51 /** 52 * Create a StoredDocKey from key to key+nkey in docNamespace. 53 * @param key pointer to key data 54 * @param nkey byte length of key (which will be copied in) 55 * @docNamespace the namespace the key applies to 56 */ StoredDocKey(const uint8_t* key, size_t nkey, DocNamespace docNamespace)57 StoredDocKey(const uint8_t* key, size_t nkey, DocNamespace docNamespace) { 58 keydata.resize(nkey + namespaceBytes); 59 keydata.replace( 60 namespaceBytes, nkey, reinterpret_cast<const char*>(key), nkey); 61 keydata[0] = static_cast<std::underlying_type<DocNamespace>::type>( 62 docNamespace); 63 } 64 65 /** 66 * Create a StoredDocKey from a DocKey 67 * @param key DocKey that is to be copied-in 68 */ StoredDocKey(const DocKey& key)69 StoredDocKey(const DocKey& key) 70 : StoredDocKey(key.data(), key.size(), key.getDocNamespace()) { 71 } 72 73 /** 74 * Create a StoredDocKey from a std::string. Post 75 * construction size will return std::string::size(); 76 * @param key std::string to be copied-in 77 * @param docNamespace the namespace that the key applies to 78 */ StoredDocKey(const std::string& key, DocNamespace docNamespace)79 StoredDocKey(const std::string& key, DocNamespace docNamespace) 80 : StoredDocKey(reinterpret_cast<const uint8_t*>(key.c_str()), 81 key.size(), 82 docNamespace) { 83 } 84 85 /** 86 * Create a StoredDocKey from a buffer that was previously 87 * obtained from getNamespacedData() 88 * Post construction size() will return nkey - 1 89 * @param key pointer to data to be copied in 90 * @param nkey the number of bytes to be copied 91 */ StoredDocKey(const uint8_t* key, size_t nkey)92 StoredDocKey(const uint8_t* key, size_t nkey) 93 : keydata(reinterpret_cast<const char*>(key), nkey) { 94 } 95 data() const96 const uint8_t* data() const { 97 return reinterpret_cast<const uint8_t*>( 98 &keydata.data()[namespaceBytes]); 99 } 100 size() const101 size_t size() const { 102 return keydata.size() - namespaceBytes; 103 } 104 getDocNamespace() const105 DocNamespace getDocNamespace() const { 106 return DocNamespace(keydata[0]); 107 } 108 c_str() const109 const char* c_str() const { 110 return &keydata.c_str()[namespaceBytes]; 111 } 112 compare(const StoredDocKey& rhs) const113 int compare(const StoredDocKey& rhs) const { 114 return keydata.compare(rhs.keydata); 115 } 116 117 /** 118 * Return the key with the DocNamespace as a prefix. 119 * Thus the "beer::bud" in the Collections namespace becomes "\1beer::bud" 120 */ getDocNameSpacedData() const121 const uint8_t* getDocNameSpacedData() const { 122 return reinterpret_cast<const uint8_t*>(keydata.data()); 123 } 124 125 /** 126 * @return true if StoredDocKey was constructed empty from StoredDocKey() 127 */ empty() const128 bool empty() const { 129 return keydata.empty(); 130 } 131 132 /** 133 * Return the size of the buffer returned by getDocNameSpacedData() 134 */ getDocNameSpacedSize() const135 size_t getDocNameSpacedSize() const { 136 return keydata.size(); 137 } 138 operator ==(const StoredDocKey& rhs) const139 bool operator==(const StoredDocKey& rhs) const { 140 return keydata == rhs.keydata; 141 } 142 operator !=(const StoredDocKey& rhs) const143 bool operator!=(const StoredDocKey& rhs) const { 144 return !(*this == rhs); 145 } 146 operator <(const StoredDocKey& rhs) const147 bool operator<(const StoredDocKey& rhs) const { 148 return keydata < rhs.keydata; 149 } 150 151 protected: 152 static const size_t namespaceBytes = sizeof(DocNamespace); 153 std::string keydata; 154 }; 155 156 std::ostream& operator<<(std::ostream& os, const StoredDocKey& key); 157 158 static_assert(sizeof(DocNamespace) == sizeof(uint8_t), 159 "StoredDocKey: DocNamespace is too large"); 160 161 /** 162 * A hash function for StoredDocKey so they can be used in std::map and friends. 163 */ 164 namespace std { 165 template <> 166 struct hash<StoredDocKey> { operator ()std::hash167 std::size_t operator()(const StoredDocKey& key) const { 168 return key.hash(); 169 } 170 }; 171 } 172 173 class MutationLogEntryV2; 174 class StoredValue; 175 176 /** 177 * SerialisedDocKey maintains the key data in an allocation that is not owned by 178 * the class. The class is essentially immutable, providing a "view" onto the 179 * larger block. 180 * 181 * For example where a StoredDocKey needs to exist as part of a bigger block of 182 * data, SerialisedDocKey is the class to use. 183 * 184 * A limited number of classes are friends and only those classes can construct 185 * a SerialisedDocKey. 186 */ 187 class SerialisedDocKey : public DocKeyInterface<SerialisedDocKey> { 188 public: 189 /** 190 * The copy constructor is deleted due to the bytes living outside of the 191 * object. 192 */ 193 SerialisedDocKey(const SerialisedDocKey& obj) = delete; 194 data() const195 const uint8_t* data() const { 196 return bytes; 197 } 198 size() const199 size_t size() const { 200 return length; 201 } 202 getDocNamespace() const203 DocNamespace getDocNamespace() const { 204 return docNamespace; 205 } 206 operator ==(const DocKey rhs) const207 bool operator==(const DocKey rhs) const { 208 return size() == rhs.size() && 209 getDocNamespace() == rhs.getDocNamespace() && 210 std::memcmp(data(), rhs.data(), size()) == 0; 211 } 212 213 /** 214 * Return how many bytes are (or need to be) allocated to this object 215 */ getObjectSize() const216 size_t getObjectSize() const { 217 return getObjectSize(length); 218 } 219 220 /** 221 * Return how many bytes are needed to store the DocKey 222 * @param key a DocKey that needs to be stored in a SerialisedDocKey 223 */ getObjectSize(const DocKey key)224 static size_t getObjectSize(const DocKey key) { 225 return getObjectSize(key.size()); 226 } 227 228 /** 229 * Create a SerialisedDocKey and return a unique_ptr to the object. 230 * Note that the allocation is bigger than sizeof(SerialisedDocKey) 231 * @param key a DocKey to be stored as a SerialisedDocKey 232 */ 233 struct SerialisedDocKeyDelete { operator ()SerialisedDocKey::SerialisedDocKeyDelete234 void operator()(SerialisedDocKey* p) { 235 p->~SerialisedDocKey(); 236 delete[] reinterpret_cast<uint8_t*>(p); 237 } 238 }; 239 make( const DocKey key)240 static std::unique_ptr<SerialisedDocKey, SerialisedDocKeyDelete> make( 241 const DocKey key) { 242 std::unique_ptr<SerialisedDocKey, SerialisedDocKeyDelete> rval( 243 reinterpret_cast<SerialisedDocKey*>( 244 new uint8_t[getObjectSize(key)])); 245 new (rval.get()) SerialisedDocKey(key); 246 return rval; 247 } 248 249 protected: 250 /** 251 * These following classes are "white-listed". They know how to allocate 252 * and construct this object so are allowed access to the constructor. 253 */ 254 friend class MutationLogEntryV2; 255 friend class StoredValue; 256 SerialisedDocKey()257 SerialisedDocKey() : length(0), docNamespace(), bytes() { 258 } 259 260 /** 261 * Create a SerialisedDocKey from a DocKey. Protected constructor as this 262 * must be used by friends who know how to pre-allocate the object storage. 263 * throws length_error if DocKey::len exceeds 255 264 * @param key a DocKey to be stored 265 */ SerialisedDocKey(const DocKey key)266 SerialisedDocKey(const DocKey key) 267 : length(0), docNamespace(key.getDocNamespace()) { 268 if (key.size() > std::numeric_limits<uint8_t>::max()) { 269 throw std::length_error( 270 "SerialisedDocKey(const DocKey key) " + 271 std::to_string(key.size()) + 272 " exceeds std::numeric_limits<uint8_t>::max()"); 273 } 274 length = gsl::narrow_cast<uint8_t>(key.size()); 275 // Copy the data into bytes, which should be allocated into a larger 276 // buffer. 277 std::copy(key.data(), key.data() + key.size(), bytes); 278 } 279 280 /** 281 * Returns the size in bytes of this object - fixed size plus the variable 282 * length for the bytes making up the key. 283 */ getObjectSize(size_t len)284 static size_t getObjectSize(size_t len) { 285 return sizeof(SerialisedDocKey) + len - sizeof(SerialisedDocKey().bytes); 286 } 287 288 uint8_t length; 289 DocNamespace docNamespace; 290 uint8_t bytes[1]; 291 }; 292 293 std::ostream& operator<<(std::ostream& os, const SerialisedDocKey& key); 294 295 static_assert(std::is_standard_layout<SerialisedDocKey>::value, 296 "SeralisedDocKey: must satisfy is_standard_layout"); 297