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