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
25namespace 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 */
34class DocKey : public ::DocKey {
35public:
36
37    uint32_t hash() const {
38        return ::DocKey::hash(collectionLen);
39    }
40
41    /**
42     * Factory method to create a Collections::DocKey from a DocKey
43     */
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     */
74    size_t getCollectionLen() const {
75        return collectionLen;
76    }
77
78    /**
79     * @return how the length of the separator used in creating this
80     */
81    size_t getSeparatorLen() const {
82        return separatorLen;
83    }
84
85    /**
86     * @return const_char_buffer representing the collection part of the DocKey
87     */
88    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     */
95    cb::const_char_buffer getKey() const {
96        return {reinterpret_cast<const char*>(data() + getCollectionLen() +
97                                              getSeparatorLen()),
98                size() - getCollectionLen() - getSeparatorLen()};
99    }
100
101private:
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     */
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
145namespace std {
146template <>
147struct hash<Collections::DocKey> {
148    std::size_t operator()(const Collections::DocKey& key) const {
149        return key.hash();
150    }
151};
152}
153