1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2017 Couchbase, Inc
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 
18 #include <memcached/dockey.h>
19 #include <gsl/gsl>
20 
21 #include "collections/collections_types.h"
22 
23 #pragma once
24 
25 namespace Collections {
26 
27 /**
28  * Collections::DocKey extends a DocKey so that the length of the collection
29  * can be recorded (the collection is a prefix on the key).
30  * The length of the collection only describes how many bytes are before
31  * the collection separator, whether the collection is valid or not is not
32  * something this class determines.
33  */
34 class DocKey : public ::DocKey {
35 public:
36 
hash() const37     uint32_t hash() const {
38         return ::DocKey::hash(collectionLen);
39     }
40 
41     /**
42      * Factory method to create a Collections::DocKey from a DocKey
43      */
make(const ::DocKey& key, const std::string& separator)44     static DocKey make(const ::DocKey& key, const std::string& separator) {
45         switch (key.getDocNamespace()) {
46         case DocNamespace::DefaultCollection:
47             return DocKey(key, 0, 0);
48         case DocNamespace::Collections: {
49             const uint8_t* collection = findCollection(key, separator);
50             if (collection) {
51                 return DocKey(key,
52                               gsl::narrow<uint8_t>(collection - key.data()),
53                               gsl::narrow<uint8_t>(separator.size()));
54             }
55             // No collection found in this key, so return empty (len 0)
56             return DocKey(key, 0, 0);
57         }
58         case DocNamespace::System:
59             throw std::invalid_argument(
60                     "DocKey::make incorrect use of SystemKey");
61         }
62         throw std::invalid_argument("DocKey::make invalid key.namespace: " +
63                                     std::to_string(int(key.getDocNamespace())));
64     }
65 
66     /**
67      * Factory method to create a Collections::DocKey from a DocKey
68      */
69     static Collections::DocKey make(const ::DocKey& key);
70 
71     /**
72      * @return how many bytes of the key are a collection
73      */
getCollectionLen() const74     size_t getCollectionLen() const {
75         return collectionLen;
76     }
77 
78     /**
79      * @return how the length of the separator used in creating this
80      */
getSeparatorLen() const81     size_t getSeparatorLen() const {
82         return separatorLen;
83     }
84 
85     /**
86      * @return const_char_buffer representing the collection part of the DocKey
87      */
getCollection() const88     cb::const_char_buffer getCollection() const {
89         return {reinterpret_cast<const char*>(data()), getCollectionLen()};
90     }
91 
92     /**
93      * @return const_char_buffer representing the 'key' part of the DocKey
94      */
getKey() const95     cb::const_char_buffer getKey() const {
96         return {reinterpret_cast<const char*>(data() + getCollectionLen() +
97                                               getSeparatorLen()),
98                 size() - getCollectionLen() - getSeparatorLen()};
99     }
100 
101 private:
DocKey(const ::DocKey& key, uint8_t _collectionLen, uint8_t _separatorLen)102     DocKey(const ::DocKey& key, uint8_t _collectionLen, uint8_t _separatorLen)
103         : ::DocKey(key),
104           collectionLen(_collectionLen),
105           separatorLen(_separatorLen) {
106         if (_separatorLen > std::numeric_limits<uint8_t>::max()) {
107             throw std::invalid_argument(
108                     "Collections::DocKey invalid separatorLen:" +
109                     std::to_string(_separatorLen));
110         }
111     }
112 
113     /**
114      * Perform a search on the DocKey looking for the separator.
115      *
116      * @returns pointer to the start of the separator or nullptr if none found.
117      */
findCollection( const ::DocKey& key, const std::string& separator)118     static const cb::const_byte_buffer::iterator findCollection(
119             const ::DocKey& key, const std::string& separator) {
120         if (key.size() == 0 || separator.size() == 0 ||
121             separator.size() > key.size()) {
122             return nullptr;
123         }
124 
125         // SystemEvent Doc keys all start with $ (SystemEventFactory::make does)
126         // this. The collections separator could legally be $, so we need to
127         // ensure we skip character 0 to correctly split the key.
128         const int start = key.getDocNamespace() == DocNamespace::System ? 1 : 0;
129 
130         auto rv = std::search(key.data() + start,
131                               key.data() + key.size(),
132                               separator.begin(),
133                               separator.end());
134         if (rv != (key.data() + key.size())) {
135             return rv;
136         }
137         return nullptr;
138     }
139 
140     uint8_t collectionLen;
141     uint8_t separatorLen;
142 };
143 } // end namespace Collections
144 
145 namespace std {
146 template <>
147 struct hash<Collections::DocKey> {
operator ()std::hash148     std::size_t operator()(const Collections::DocKey& key) const {
149         return key.hash();
150     }
151 };
152 }
153