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        if (key.getDocNamespace() == DocNamespace::System) {
46            throw std::invalid_argument(
47                    "DocKey::make incorrect use of SystemKey");
48        }
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        } else {
55            // No collection found, not an error - ok for DefaultNamespace.
56            return DocKey(key, 0, 0);
57        }
58    }
59
60    /**
61     * Factory method to create a Collections::DocKey from a DocKey
62     */
63    static Collections::DocKey make(const ::DocKey& key);
64
65    /**
66     * @return how many bytes of the key are a collection
67     */
68    size_t getCollectionLen() const {
69        return collectionLen;
70    }
71
72    /**
73     * @return how the length of the separator used in creating this
74     */
75    size_t getSeparatorLen() const {
76        return separatorLen;
77    }
78
79    /**
80     * @return const_char_buffer representing the collection part of the DocKey
81     */
82    cb::const_char_buffer getCollection() const {
83        return {reinterpret_cast<const char*>(data()), getCollectionLen()};
84    }
85
86    /**
87     * @return const_char_buffer representing the 'key' part of the DocKey
88     */
89    cb::const_char_buffer getKey() const {
90        return {reinterpret_cast<const char*>(data() + getCollectionLen() +
91                                              getSeparatorLen()),
92                size() - getCollectionLen() - getSeparatorLen()};
93    }
94
95private:
96    DocKey(const ::DocKey& key, uint8_t _collectionLen, uint8_t _separatorLen)
97        : ::DocKey(key),
98          collectionLen(_collectionLen),
99          separatorLen(_separatorLen) {
100        if (_separatorLen > std::numeric_limits<uint8_t>::max()) {
101            throw std::invalid_argument(
102                    "Collections::DocKey invalid separatorLen:" +
103                    std::to_string(_separatorLen));
104        }
105    }
106
107    /**
108     * Perform a search on the DocKey looking for the separator.
109     *
110     * @returns pointer to the start of the separator or nullptr if none found.
111     */
112    static const cb::const_byte_buffer::iterator findCollection(
113            const ::DocKey& key, const std::string& separator) {
114        if (key.size() == 0 || separator.size() == 0 ||
115            separator.size() > key.size()) {
116            return nullptr;
117        }
118
119        // SystemEvent Doc keys all start with $ (SystemEventFactory::make does)
120        // this. The collections separator could legally be $, so we need to
121        // ensure we skip character 0 to correctly split the key.
122        const int start = key.getDocNamespace() == DocNamespace::System ? 1 : 0;
123
124        auto rv = std::search(key.data() + start,
125                              key.data() + key.size(),
126                              separator.begin(),
127                              separator.end());
128        if (rv != (key.data() + key.size())) {
129            return rv;
130        }
131        return nullptr;
132    }
133
134    uint8_t collectionLen;
135    uint8_t separatorLen;
136};
137} // end namespace Collections
138
139namespace std {
140template <>
141struct hash<Collections::DocKey> {
142    std::size_t operator()(const Collections::DocKey& key) const {
143        return key.hash();
144    }
145};
146}