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
20#pragma once
21
22namespace Collections {
23
24/**
25 * Collections::DocKey extends a DocKey so that the length of the collection
26 * can be recorded (the collection is a prefix on the key).
27 * The length of the collection only describes how many bytes are before
28 * the collection separator, whether the collection is valid or not is not
29 * something this class determines.
30 */
31class DocKey : public ::DocKey {
32public:
33
34    uint32_t hash() const {
35        return ::DocKey::hash(collectionLen);
36    }
37
38    /**
39     * Factory method to create a Collections::DocKey from a DocKey
40     */
41    static DocKey make(const ::DocKey& key, const std::string& separator) {
42        const uint8_t* collection = findCollection(key, separator);
43        if (collection) {
44            return DocKey(key, collection - key.data(), separator.size());
45        } else {
46            // No collection found, not an error - ok for DefaultNamespace.
47            return DocKey(key, 0, 0);
48        }
49    }
50
51    /**
52     * @return how many bytes of the key are a collection
53     */
54    size_t getCollectionLen() const {
55        return collectionLen;
56    }
57
58    /**
59     * @return how the length of the separator used in creating this
60     */
61    size_t getSeparatorLen() const {
62        return separatorLen;
63    }
64
65    /**
66     * @return const_char_buffer representing the collection part of the DocKey
67     */
68    cb::const_char_buffer getCollection() const {
69        return {reinterpret_cast<const char*>(data()), getCollectionLen()};
70    }
71
72    /**
73     * @return const_char_buffer representing the 'key' part of the DocKey
74     */
75    cb::const_char_buffer getKey() const {
76        return {reinterpret_cast<const char*>(data() + getCollectionLen() +
77                                              getSeparatorLen()),
78                size() - getCollectionLen() - getSeparatorLen()};
79    }
80
81private:
82    DocKey(const ::DocKey& key, size_t _collectionLen, size_t _separatorLen)
83        : ::DocKey(key),
84          collectionLen(_collectionLen),
85          separatorLen(_separatorLen) {
86        if (_separatorLen > std::numeric_limits<uint8_t>::max()) {
87            throw std::invalid_argument(
88                    "Collections::DocKey invalid separatorLen:" +
89                    std::to_string(_separatorLen));
90        }
91    }
92
93    /**
94     * Perform a search on the DocKey looking for the separator.
95     *
96     * @returns pointer to the start of the separator or nullptr if none found.
97     */
98    static const cb::const_byte_buffer::iterator findCollection(
99            const ::DocKey& key, const std::string& separator) {
100        if (key.size() == 0 || separator.size() == 0 ||
101            separator.size() > key.size()) {
102            return nullptr;
103        }
104
105        // SystemEvent Doc keys all start with $ (SystemEventFactory::make does)
106        // this. The collections separator could legally be $, so we need to
107        // ensure we skip character 0 to correctly split the key.
108        const int start = key.getDocNamespace() == DocNamespace::System ? 1 : 0;
109
110        auto rv = std::search(key.data() + start,
111                              key.data() + key.size(),
112                              separator.begin(),
113                              separator.end());
114        if (rv != (key.data() + key.size())) {
115            return rv;
116        }
117        return nullptr;
118    }
119
120    uint8_t collectionLen;
121    uint8_t separatorLen;
122};
123} // end namespace Collections
124
125namespace std {
126template <>
127struct hash<Collections::DocKey> {
128    std::size_t operator()(const Collections::DocKey& key) const {
129        return key.hash();
130    }
131};
132}