couchbase 3.5.0-x86_64-darwin-20
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +202 -0
- data/README.md +154 -0
- data/ext/extconf.rb +0 -0
- data/lib/active_support/cache/couchbase_store.rb +339 -0
- data/lib/couchbase/3.0/libcouchbase.bundle +0 -0
- data/lib/couchbase/3.1/libcouchbase.bundle +0 -0
- data/lib/couchbase/3.2/libcouchbase.bundle +0 -0
- data/lib/couchbase/3.3/libcouchbase.bundle +0 -0
- data/lib/couchbase/analytics_options.rb +107 -0
- data/lib/couchbase/authenticator.rb +64 -0
- data/lib/couchbase/binary_collection.rb +128 -0
- data/lib/couchbase/binary_collection_options.rb +24 -0
- data/lib/couchbase/bucket.rb +144 -0
- data/lib/couchbase/cluster.rb +460 -0
- data/lib/couchbase/cluster_registry.rb +49 -0
- data/lib/couchbase/collection.rb +705 -0
- data/lib/couchbase/collection_options.rb +399 -0
- data/lib/couchbase/config_profiles.rb +55 -0
- data/lib/couchbase/configuration.rb +56 -0
- data/lib/couchbase/datastructures/couchbase_list.rb +160 -0
- data/lib/couchbase/datastructures/couchbase_map.rb +194 -0
- data/lib/couchbase/datastructures/couchbase_queue.rb +134 -0
- data/lib/couchbase/datastructures/couchbase_set.rb +128 -0
- data/lib/couchbase/datastructures.rb +24 -0
- data/lib/couchbase/diagnostics.rb +181 -0
- data/lib/couchbase/errors.rb +376 -0
- data/lib/couchbase/json_transcoder.rb +39 -0
- data/lib/couchbase/key_value_scan.rb +117 -0
- data/lib/couchbase/libcouchbase.rb +6 -0
- data/lib/couchbase/logger.rb +85 -0
- data/lib/couchbase/management/analytics_index_manager.rb +1127 -0
- data/lib/couchbase/management/bucket_manager.rb +443 -0
- data/lib/couchbase/management/collection_manager.rb +470 -0
- data/lib/couchbase/management/collection_query_index_manager.rb +222 -0
- data/lib/couchbase/management/query_index_manager.rb +617 -0
- data/lib/couchbase/management/scope_search_index_manager.rb +198 -0
- data/lib/couchbase/management/search_index_manager.rb +424 -0
- data/lib/couchbase/management/user_manager.rb +468 -0
- data/lib/couchbase/management/view_index_manager.rb +237 -0
- data/lib/couchbase/management.rb +29 -0
- data/lib/couchbase/mutation_state.rb +63 -0
- data/lib/couchbase/options.rb +2837 -0
- data/lib/couchbase/protostellar/binary_collection.rb +55 -0
- data/lib/couchbase/protostellar/bucket.rb +51 -0
- data/lib/couchbase/protostellar/client.rb +99 -0
- data/lib/couchbase/protostellar/cluster.rb +163 -0
- data/lib/couchbase/protostellar/collection.rb +152 -0
- data/lib/couchbase/protostellar/connect_options.rb +63 -0
- data/lib/couchbase/protostellar/error_handling.rb +203 -0
- data/lib/couchbase/protostellar/generated/admin/bucket/v1/bucket_pb.rb +61 -0
- data/lib/couchbase/protostellar/generated/admin/bucket/v1/bucket_services_pb.rb +35 -0
- data/lib/couchbase/protostellar/generated/admin/collection/v1/collection_pb.rb +57 -0
- data/lib/couchbase/protostellar/generated/admin/collection/v1/collection_services_pb.rb +36 -0
- data/lib/couchbase/protostellar/generated/admin/query/v1/query_pb.rb +61 -0
- data/lib/couchbase/protostellar/generated/admin/query/v1/query_services_pb.rb +37 -0
- data/lib/couchbase/protostellar/generated/admin/search/v1/search_pb.rb +72 -0
- data/lib/couchbase/protostellar/generated/admin/search/v1/search_services_pb.rb +44 -0
- data/lib/couchbase/protostellar/generated/analytics/v1/analytics_pb.rb +52 -0
- data/lib/couchbase/protostellar/generated/analytics/v1/analytics_services_pb.rb +30 -0
- data/lib/couchbase/protostellar/generated/internal/hooks/v1/hooks_pb.rb +70 -0
- data/lib/couchbase/protostellar/generated/internal/hooks/v1/hooks_services_pb.rb +36 -0
- data/lib/couchbase/protostellar/generated/kv/v1/kv_pb.rb +97 -0
- data/lib/couchbase/protostellar/generated/kv/v1/kv_services_pb.rb +46 -0
- data/lib/couchbase/protostellar/generated/query/v1/query_pb.rb +57 -0
- data/lib/couchbase/protostellar/generated/query/v1/query_services_pb.rb +30 -0
- data/lib/couchbase/protostellar/generated/routing/v1/routing_pb.rb +52 -0
- data/lib/couchbase/protostellar/generated/routing/v1/routing_services_pb.rb +30 -0
- data/lib/couchbase/protostellar/generated/search/v1/search_pb.rb +99 -0
- data/lib/couchbase/protostellar/generated/search/v1/search_services_pb.rb +30 -0
- data/lib/couchbase/protostellar/generated/transactions/v1/transactions_pb.rb +57 -0
- data/lib/couchbase/protostellar/generated/transactions/v1/transactions_services_pb.rb +36 -0
- data/lib/couchbase/protostellar/generated/view/v1/view_pb.rb +51 -0
- data/lib/couchbase/protostellar/generated/view/v1/view_services_pb.rb +30 -0
- data/lib/couchbase/protostellar/generated.rb +9 -0
- data/lib/couchbase/protostellar/management/bucket_manager.rb +67 -0
- data/lib/couchbase/protostellar/management/collection_manager.rb +94 -0
- data/lib/couchbase/protostellar/management/collection_query_index_manager.rb +124 -0
- data/lib/couchbase/protostellar/management/query_index_manager.rb +112 -0
- data/lib/couchbase/protostellar/management.rb +24 -0
- data/lib/couchbase/protostellar/request.rb +78 -0
- data/lib/couchbase/protostellar/request_behaviour.rb +42 -0
- data/lib/couchbase/protostellar/request_generator/admin/bucket.rb +124 -0
- data/lib/couchbase/protostellar/request_generator/admin/collection.rb +94 -0
- data/lib/couchbase/protostellar/request_generator/admin/query.rb +130 -0
- data/lib/couchbase/protostellar/request_generator/admin.rb +24 -0
- data/lib/couchbase/protostellar/request_generator/kv.rb +474 -0
- data/lib/couchbase/protostellar/request_generator/query.rb +133 -0
- data/lib/couchbase/protostellar/request_generator/search.rb +387 -0
- data/lib/couchbase/protostellar/request_generator.rb +26 -0
- data/lib/couchbase/protostellar/response_converter/admin/bucket.rb +55 -0
- data/lib/couchbase/protostellar/response_converter/admin/collection.rb +42 -0
- data/lib/couchbase/protostellar/response_converter/admin/query.rb +59 -0
- data/lib/couchbase/protostellar/response_converter/admin.rb +24 -0
- data/lib/couchbase/protostellar/response_converter/kv.rb +151 -0
- data/lib/couchbase/protostellar/response_converter/query.rb +84 -0
- data/lib/couchbase/protostellar/response_converter/search.rb +136 -0
- data/lib/couchbase/protostellar/response_converter.rb +26 -0
- data/lib/couchbase/protostellar/retry/action.rb +38 -0
- data/lib/couchbase/protostellar/retry/orchestrator.rb +60 -0
- data/lib/couchbase/protostellar/retry/reason.rb +67 -0
- data/lib/couchbase/protostellar/retry/strategies/best_effort.rb +49 -0
- data/lib/couchbase/protostellar/retry/strategies.rb +26 -0
- data/lib/couchbase/protostellar/retry.rb +28 -0
- data/lib/couchbase/protostellar/scope.rb +57 -0
- data/lib/couchbase/protostellar/timeout_defaults.rb +30 -0
- data/lib/couchbase/protostellar/timeouts.rb +83 -0
- data/lib/couchbase/protostellar.rb +29 -0
- data/lib/couchbase/query_options.rb +120 -0
- data/lib/couchbase/railtie.rb +45 -0
- data/lib/couchbase/raw_binary_transcoder.rb +37 -0
- data/lib/couchbase/raw_json_transcoder.rb +38 -0
- data/lib/couchbase/raw_string_transcoder.rb +40 -0
- data/lib/couchbase/scope.rb +256 -0
- data/lib/couchbase/search_options.rb +1622 -0
- data/lib/couchbase/subdoc.rb +290 -0
- data/lib/couchbase/transcoder_flags.rb +62 -0
- data/lib/couchbase/utils/generic_logger_adapter.rb +38 -0
- data/lib/couchbase/utils/stdlib_logger_adapter.rb +65 -0
- data/lib/couchbase/utils/time.rb +69 -0
- data/lib/couchbase/utils.rb +21 -0
- data/lib/couchbase/version.rb +23 -0
- data/lib/couchbase/view_options.rb +65 -0
- data/lib/couchbase.rb +28 -0
- data/lib/rails/generators/couchbase/config/config_generator.rb +27 -0
- metadata +191 -0
@@ -0,0 +1,339 @@
|
|
1
|
+
# Copyright 2020-2021 Couchbase, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "couchbase"
|
16
|
+
require "digest/sha2"
|
17
|
+
require "active_support/cache"
|
18
|
+
|
19
|
+
module ActiveSupport
|
20
|
+
module Cache
|
21
|
+
# A cache store implementation which stores data in Couchbase: https://couchbase.com
|
22
|
+
#
|
23
|
+
# * Local cache. Hot in-memory primary cache within block/middleware scope.
|
24
|
+
# * +read_multi+ and +write_multi+ support.
|
25
|
+
# * +delete_matched+ support using N1QL queries.
|
26
|
+
# * +clear+ for erasing whole collection (optionally can flush the bucket).
|
27
|
+
#
|
28
|
+
# To use this store, add the select it in application config
|
29
|
+
#
|
30
|
+
# config.cache_store = :couchbase_store, {
|
31
|
+
# connection_string: "couchbase://localhost",
|
32
|
+
# username: "app_cache_user",
|
33
|
+
# password: "s3cret",
|
34
|
+
# bucket: "app_cache"
|
35
|
+
# }
|
36
|
+
#
|
37
|
+
# @see https://guides.rubyonrails.org/caching_with_rails.html#cache-stores
|
38
|
+
class CouchbaseStore < Store
|
39
|
+
MAX_KEY_BYTESIZE = 250
|
40
|
+
DEFAULT_ERROR_HANDLER = lambda do |method:, returning:, exception:, logger: CouchbaseStore.logger|
|
41
|
+
logger&.error { "CouchbaseStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
|
42
|
+
end
|
43
|
+
|
44
|
+
# Advertise cache versioning support.
|
45
|
+
def self.supports_cache_versioning?
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
prepend Strategy::LocalCache
|
50
|
+
|
51
|
+
def initialize(options = nil)
|
52
|
+
super
|
53
|
+
@error_handler = @options.delete(:error_handler) { DEFAULT_ERROR_HANDLER }
|
54
|
+
@couchbase_options = {}
|
55
|
+
@couchbase_options[:connection_string] =
|
56
|
+
@options.delete(:connection_string) do
|
57
|
+
raise ArgumentError, "Missing connection string for Couchbase cache store. Use :connection_string in the store options"
|
58
|
+
end
|
59
|
+
@couchbase_options[:username] =
|
60
|
+
@options.delete(:username) do
|
61
|
+
raise ArgumentError, "Missing username for Couchbase cache store. Use :username in the store options"
|
62
|
+
end
|
63
|
+
@couchbase_options[:password] =
|
64
|
+
@options.delete(:password) do
|
65
|
+
raise ArgumentError, "Missing password for Couchbase cache store. Use :password in the store options"
|
66
|
+
end
|
67
|
+
@couchbase_options[:bucket] =
|
68
|
+
@options.delete(:bucket) { raise ArgumentError, "Missing bucket for Couchbase cache store. Use :bucket in the store options" }
|
69
|
+
@couchbase_options[:scope] = @options.delete(:scope) if @options.key?(:scope)
|
70
|
+
@couchbase_options[:collection] = @options.delete(:collection) if @options.key?(:collection)
|
71
|
+
@last_mutation_token = nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def collection
|
75
|
+
@collection ||= build_collection
|
76
|
+
end
|
77
|
+
|
78
|
+
def cluster
|
79
|
+
@cluster ||= build_cluster
|
80
|
+
end
|
81
|
+
|
82
|
+
def inspect
|
83
|
+
"#<#{self.class} options=#{options.inspect} collection=#{@collection.inspect}>"
|
84
|
+
end
|
85
|
+
|
86
|
+
# Deletes all entries with keys matching the regular expression.
|
87
|
+
#
|
88
|
+
# The +matcher+ must be valid pattern for N1QL +REGEXP_MATCHES+ function. More info at
|
89
|
+
# https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/patternmatchingfun.html#section_regex_matches
|
90
|
+
#
|
91
|
+
# Because the operation performed on query engine, and it might take time to propagate changes
|
92
|
+
# from key/value engine to the indexer. Therefore the keys, that were created a moment ago
|
93
|
+
# might not be deleted.
|
94
|
+
#
|
95
|
+
# Also this method assumes, that primary index created on the target bucket
|
96
|
+
def delete_matched(matcher, _options = nil)
|
97
|
+
pattern =
|
98
|
+
case matcher
|
99
|
+
when Regexp
|
100
|
+
matcher.inspect[1..-2]
|
101
|
+
when String
|
102
|
+
matcher.tr("?", ".").gsub("*", ".*")
|
103
|
+
else
|
104
|
+
raise NotImplementedError, "Unable to convert #{matcher.inspect} to Regexp pattern"
|
105
|
+
end
|
106
|
+
operation_options = ::Couchbase::Options::Query(named_parameters: {"pattern" => pattern}, metrics: true)
|
107
|
+
operation_options.consistent_with(::Couchbase::MutationState.new(@last_mutation_token)) if @last_mutation_token
|
108
|
+
begin
|
109
|
+
result = cluster.query("DELETE FROM #{scope_qualifier} cache WHERE REGEXP_MATCHES(META(cache).id, $pattern)", operation_options)
|
110
|
+
result.meta_data.metrics.mutation_count
|
111
|
+
rescue ::Couchbase::Error::ParsingFailure, ::Couchbase::Error::ServiceNotAvailable
|
112
|
+
raise NotImplementedError, "The server does not support delete_matched operation"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Increments an integer value in the cache.
|
117
|
+
#
|
118
|
+
# Note that it uses binary collection interface, therefore will fail if the value is not
|
119
|
+
# represented as ASCII-encoded number using +:raw+.
|
120
|
+
def increment(name, amount = 1, expires_in: nil, initial: nil, **options)
|
121
|
+
instrument :increment, name, amount: amount do
|
122
|
+
failsafe :increment do
|
123
|
+
key = normalize_key(name, options)
|
124
|
+
res = collection.binary.increment(
|
125
|
+
key, ::Couchbase::Options::Increment(delta: amount, expiry: expires_in, initial: initial)
|
126
|
+
)
|
127
|
+
@last_mutation_token = res.mutation_token
|
128
|
+
res.content
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Decrements an integer value in the cache.
|
134
|
+
#
|
135
|
+
# Note that it uses binary collection interface, therefore will fail if the value is not
|
136
|
+
# represented as ASCII-encoded number using +:raw+.
|
137
|
+
#
|
138
|
+
# Note that the counter represented by non-negative number on the server, and it will not
|
139
|
+
# cycle to maximum integer when decrementing zero.
|
140
|
+
def decrement(name, amount = 1, expires_in: nil, initial: nil, **_options)
|
141
|
+
instrument :decrement, name, amount: amount do
|
142
|
+
failsafe :decrement do
|
143
|
+
key = normalize_key(name, options)
|
144
|
+
res = collection.binary.decrement(
|
145
|
+
key, ::Couchbase::Options::Decrement(delta: amount, expiry: expires_in, initial: initial)
|
146
|
+
)
|
147
|
+
@last_mutation_token = res.mutation_token
|
148
|
+
res.content
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Clears the entire cache. Be careful with this method since it could
|
154
|
+
# affect other processes if shared cache is being used.
|
155
|
+
#
|
156
|
+
# When +use_flush+ option set to +true+ it will flush the bucket. Otherwise, it uses N1QL query
|
157
|
+
# and relies on default index.
|
158
|
+
def clear(use_flush: false, **_options)
|
159
|
+
failsafe(:clear) do
|
160
|
+
if use_flush
|
161
|
+
cluster.buckets.flush_bucket(@couchbase_options[:bucket_name])
|
162
|
+
else
|
163
|
+
operation_options = ::Couchbase::Options::Query.new
|
164
|
+
operation_options.consistent_with(::Couchbase::MutationState.new(@last_mutation_token)) if @last_mutation_token
|
165
|
+
cluster.query("DELETE FROM #{scope_qualifier}", operation_options)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def deserialize_entry(payload, raw: false, **)
|
173
|
+
if payload && raw
|
174
|
+
Entry.new(payload, compress: false)
|
175
|
+
else
|
176
|
+
super(payload)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def serialize_entry(entry, raw: false, **options)
|
181
|
+
if raw
|
182
|
+
entry.value.to_s
|
183
|
+
else
|
184
|
+
super(entry, **options)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def serialize_entries(entries, **options)
|
189
|
+
entries.transform_values do |entry|
|
190
|
+
serialize_entry(entry, **options)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Reads an entry from the cache
|
195
|
+
def read_entry(key, **options)
|
196
|
+
deserialize_entry(read_serialized_entry(key, **options), **options)
|
197
|
+
end
|
198
|
+
|
199
|
+
def read_serialized_entry(key, **)
|
200
|
+
failsafe(:read_entry, returning: nil) do
|
201
|
+
res = collection.get(key, ::Couchbase::Options::Get(transcoder: nil))
|
202
|
+
res.content
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Reads multiple entries from the cache implementation. Subclasses MAY
|
207
|
+
# implement this method.
|
208
|
+
def read_multi_entries(names, **options)
|
209
|
+
return {} if names.empty?
|
210
|
+
|
211
|
+
keys = names.map { |name| normalize_key(name, options) }
|
212
|
+
return_value = {}
|
213
|
+
failsafe(:read_multi_entries, returning: return_value) do
|
214
|
+
results = collection.get_multi(keys, ::Couchbase::Options::GetMulti(transcoder: nil))
|
215
|
+
results.each_with_index do |result, index|
|
216
|
+
next unless result.success?
|
217
|
+
|
218
|
+
entry = deserialize_entry(result.content, raw: options[:raw])
|
219
|
+
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(names[index], options))
|
220
|
+
return_value[names[index]] = entry.value
|
221
|
+
end
|
222
|
+
end
|
223
|
+
return_value
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Writes an entry to the cache
|
228
|
+
def write_entry(key, entry, raw: false, **options)
|
229
|
+
write_serialized_entry(key, serialize_entry(entry, raw: raw, **options), raw: raw, **options)
|
230
|
+
end
|
231
|
+
|
232
|
+
def write_serialized_entry(key, payload, expires_in: nil, race_condition_ttl: nil, raw: false, **)
|
233
|
+
if race_condition_ttl && expires_in && expires_in.positive? && !raw
|
234
|
+
# Add few minutes to expiry in the future to support race condition TTLs on read
|
235
|
+
expires_in += 5.minutes
|
236
|
+
end
|
237
|
+
failsafe(:write_entry, returning: false) do
|
238
|
+
res = collection.upsert(key, payload, ::Couchbase::Options::Upsert(transcoder: nil, expiry: expires_in))
|
239
|
+
@last_mutation_token = res.mutation_token
|
240
|
+
true
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def write_multi_entries(entries, **options)
|
245
|
+
return 0 if entries.empty?
|
246
|
+
|
247
|
+
return super if local_cache
|
248
|
+
|
249
|
+
expires_in = options[:expires_in]
|
250
|
+
if options[:race_condition_ttl] && expires_in && expires_in.positive?
|
251
|
+
# Add few minutes to expiry in the future to support race condition TTLs on read
|
252
|
+
expires_in += 5.minutes
|
253
|
+
end
|
254
|
+
operation_options = ::Couchbase::Options::UpsertMulti(transcoder: nil, expiry: expires_in)
|
255
|
+
failsafe(:write_multi_entries, returning: nil) do
|
256
|
+
successful = collection.upsert_multi(serialize_entries(entries, **options), operation_options).select(&:success?)
|
257
|
+
return 0 if successful.empty?
|
258
|
+
|
259
|
+
@last_mutation_token = successful.max_by { |r| r.mutation_token.sequence_number }
|
260
|
+
successful.count
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Deletes an entry from the cache implementation. Subclasses must
|
265
|
+
# implement this method.
|
266
|
+
def delete_entry(key, **_options)
|
267
|
+
failsafe(:delete_entry, returning: false) do
|
268
|
+
res = collection.remove(key)
|
269
|
+
@last_mutation_token = res.mutation_token
|
270
|
+
true
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Deletes multiple entries in the cache. Returns the number of entries deleted.
|
275
|
+
def delete_multi_entries(entries, **_options)
|
276
|
+
return if entries.empty?
|
277
|
+
|
278
|
+
failsafe(:delete_multi_entries, returning: nil) do
|
279
|
+
successful = collection.remove_multi(entries).select(&:success?)
|
280
|
+
return 0 if successful.empty?
|
281
|
+
|
282
|
+
@last_mutation_token = successful.max_by { |r| r.mutation_token.sequence_number }
|
283
|
+
successful.count
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def failsafe(method, returning: nil)
|
288
|
+
yield
|
289
|
+
rescue ::Couchbase::Error::CouchbaseError => e
|
290
|
+
ActiveSupport.error_reporter&.report(e, handled: true, severity: :warning)
|
291
|
+
@error_handler&.call(method: method, exception: e, returning: returning)
|
292
|
+
returning
|
293
|
+
end
|
294
|
+
|
295
|
+
# Truncate keys that exceed 250 characters
|
296
|
+
def normalize_key(key, options)
|
297
|
+
truncate_key super
|
298
|
+
end
|
299
|
+
|
300
|
+
def truncate_key(key)
|
301
|
+
if key && key.bytesize > MAX_KEY_BYTESIZE
|
302
|
+
suffix = ":sha2:#{::Digest::SHA2.hexdigest(key)}"
|
303
|
+
truncate_at = MAX_KEY_BYTESIZE - suffix.bytesize
|
304
|
+
"#{key.mb_chars.limit(truncate_at)}#{suffix}"
|
305
|
+
else
|
306
|
+
key
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
# Connects to the Couchbase cluster
|
311
|
+
def build_cluster
|
312
|
+
::Couchbase::Cluster.connect(
|
313
|
+
@couchbase_options[:connection_string],
|
314
|
+
::Couchbase::Options::Cluster(authenticator: ::Couchbase::PasswordAuthenticator.new(
|
315
|
+
@couchbase_options[:username], @couchbase_options[:password]
|
316
|
+
))
|
317
|
+
)
|
318
|
+
end
|
319
|
+
|
320
|
+
# Connects to the Couchbase cluster, opens specified bucket and returns collection object.
|
321
|
+
def build_collection
|
322
|
+
bucket = cluster.bucket(@couchbase_options[:bucket])
|
323
|
+
if @couchbase_options[:scope] && @couchbase_options[:collection]
|
324
|
+
bucket.scope(@couchbase_options[:scope]).collection(@couchbase_options[:collection])
|
325
|
+
else
|
326
|
+
bucket.default_collection
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def scope_qualifier
|
331
|
+
if @couchbase_options[:scope] && @couchbase_options[:collection]
|
332
|
+
"`#{@couchbase_options[:bucket]}`.#{@couchbase_options[:scope]}.#{@couchbase_options[:collection]}"
|
333
|
+
else
|
334
|
+
"`#{@couchbase_options[:bucket]}`"
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# Copyright 2020-2021 Couchbase, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "couchbase/json_transcoder"
|
16
|
+
|
17
|
+
module Couchbase
|
18
|
+
class Cluster
|
19
|
+
class AnalyticsWarning
|
20
|
+
# @return [Integer]
|
21
|
+
attr_accessor :code
|
22
|
+
|
23
|
+
# @return [String]
|
24
|
+
attr_accessor :message
|
25
|
+
|
26
|
+
# @param [Integer] code
|
27
|
+
# @param [String] message
|
28
|
+
def initialize(code, message)
|
29
|
+
@code = code
|
30
|
+
@message = message
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class AnalyticsMetrics
|
35
|
+
# @return [Integer] duration in milliseconds
|
36
|
+
attr_accessor :elapsed_time
|
37
|
+
|
38
|
+
# @return [Integer] duration in milliseconds
|
39
|
+
attr_accessor :execution_time
|
40
|
+
|
41
|
+
# @return [Integer]
|
42
|
+
attr_accessor :result_count
|
43
|
+
|
44
|
+
# @return [Integer]
|
45
|
+
attr_accessor :result_size
|
46
|
+
|
47
|
+
# @return [Integer]
|
48
|
+
attr_accessor :error_count
|
49
|
+
|
50
|
+
# @return [Integer]
|
51
|
+
attr_accessor :processed_objects
|
52
|
+
|
53
|
+
# @return [Integer]
|
54
|
+
attr_accessor :warning_count
|
55
|
+
|
56
|
+
# @yieldparam [AnalyticsMetrics] self
|
57
|
+
def initialize
|
58
|
+
yield self if block_given?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class AnalyticsMetaData
|
63
|
+
# @return [String]
|
64
|
+
attr_accessor :request_id
|
65
|
+
|
66
|
+
# @return [String]
|
67
|
+
attr_accessor :client_context_id
|
68
|
+
|
69
|
+
# @return [:running, :success, :errors, :completed, :stopped, :timeout, :closed, :fatal, :aborted, :unknown]
|
70
|
+
attr_accessor :status
|
71
|
+
|
72
|
+
# @return [Hash] returns the signature as returned by the query engine which is then decoded as JSON object
|
73
|
+
attr_accessor :signature
|
74
|
+
|
75
|
+
# @return [Array<AnalyticsWarning>]
|
76
|
+
attr_accessor :warnings
|
77
|
+
|
78
|
+
# @return [AnalyticsMetrics]
|
79
|
+
attr_accessor :metrics
|
80
|
+
|
81
|
+
# @yieldparam [AnalyticsMetaData] self
|
82
|
+
def initialize
|
83
|
+
yield self if block_given?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class AnalyticsResult
|
88
|
+
# @return [AnalyticsMetaData]
|
89
|
+
attr_accessor :meta_data
|
90
|
+
|
91
|
+
attr_accessor :transcoder
|
92
|
+
|
93
|
+
# Returns all rows converted using a transcoder
|
94
|
+
#
|
95
|
+
# @return [Array]
|
96
|
+
def rows(transcoder = self.transcoder)
|
97
|
+
@rows.lazy.map { |row| transcoder.decode(row, 0) }
|
98
|
+
end
|
99
|
+
|
100
|
+
# @yieldparam [AnalyticsResult] self
|
101
|
+
def initialize
|
102
|
+
@transcoder = JsonTranscoder.new
|
103
|
+
yield self if block_given?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Copyright 2020-2021 Couchbase, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module Couchbase
|
16
|
+
# Authenticator for username/password credentials
|
17
|
+
class PasswordAuthenticator
|
18
|
+
DEFAULT_SASL_MECHANISMS = [:scram_sha512, :scram_sha256, :scram_sha1].freeze
|
19
|
+
|
20
|
+
attr_accessor :username
|
21
|
+
attr_accessor :password
|
22
|
+
attr_accessor :allowed_sasl_mechanisms
|
23
|
+
|
24
|
+
# Creates a new password authenticator with the default settings.
|
25
|
+
#
|
26
|
+
# @param [String] password the username to use for all authentication requests
|
27
|
+
# @param [String] username the password
|
28
|
+
def initialize(username, password)
|
29
|
+
@username = username
|
30
|
+
@password = password
|
31
|
+
end
|
32
|
+
|
33
|
+
# Creates a LDAP compatible password authenticator which is INSECURE if not used with TLS.
|
34
|
+
#
|
35
|
+
# Please note that this is INSECURE and will leak user credentials on the wire to eavesdroppers. This should
|
36
|
+
# only be enabled in trusted environments.
|
37
|
+
#
|
38
|
+
# @param [String] username the username to use for all authentication.
|
39
|
+
# @param [String] password the password to use alongside the username.
|
40
|
+
# @return [PasswordAuthenticator]
|
41
|
+
def self.ldap_compatible(username, password)
|
42
|
+
new(username, password).tap do |auth|
|
43
|
+
auth.allowed_sasl_mechanisms = [:plain]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Authenticator for TLS client certificate
|
49
|
+
#
|
50
|
+
# @see https://docs.couchbase.com/server/current/manage/manage-security/configure-client-certificates.html
|
51
|
+
class CertificateAuthenticator
|
52
|
+
attr_accessor :certificate_path
|
53
|
+
attr_accessor :key_path
|
54
|
+
|
55
|
+
# Creates a new authenticator with certificate and key paths
|
56
|
+
#
|
57
|
+
# @param [String] certificate_path path to certificate
|
58
|
+
# @param [String] key_path path to private key
|
59
|
+
def initialize(certificate_path, key_path)
|
60
|
+
@certificate_path = certificate_path
|
61
|
+
@key_path = key_path
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# Copyright 2020-2021 Couchbase, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "couchbase/options"
|
16
|
+
require "couchbase/binary_collection_options"
|
17
|
+
|
18
|
+
module Couchbase
|
19
|
+
# Allows to perform certain operations on non-JSON documents.
|
20
|
+
class BinaryCollection
|
21
|
+
alias inspect to_s
|
22
|
+
|
23
|
+
# @param [Couchbase::Collection] collection parent collection
|
24
|
+
def initialize(collection)
|
25
|
+
@collection = collection
|
26
|
+
@backend = collection.instance_variable_get(:@backend)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Appends binary content to the document
|
30
|
+
#
|
31
|
+
# @param [String] id the document id which is used to uniquely identify it
|
32
|
+
# @param [String] content the binary content to append to the document
|
33
|
+
# @param [Options::Append] options custom options to customize the request
|
34
|
+
#
|
35
|
+
# @example Append "bar" to the content of the existing document
|
36
|
+
# collection.upsert("mydoc", "foo")
|
37
|
+
# collection.binary.append("mydoc", "bar", Options::Append(timeout: 3_000))
|
38
|
+
# collection.get("mydoc", Options::Get(transcoder: nil)).content #=> "foobar"
|
39
|
+
#
|
40
|
+
# @return [Collection::MutationResult]
|
41
|
+
def append(id, content, options = Options::Append::DEFAULT)
|
42
|
+
resp = @backend.document_append(@collection.bucket_name, @collection.scope_name, @collection.name,
|
43
|
+
id, content, options.to_backend)
|
44
|
+
Collection::MutationResult.new do |res|
|
45
|
+
res.cas = resp[:cas]
|
46
|
+
res.mutation_token = @collection.send(:extract_mutation_token, resp)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Prepends binary content to the document
|
51
|
+
#
|
52
|
+
# @param [String] id the document id which is used to uniquely identify it
|
53
|
+
# @param [String] content the binary content to prepend to the document
|
54
|
+
# @param [Options::Prepend] options custom options to customize the request
|
55
|
+
#
|
56
|
+
# @example Prepend "bar" to the content of the existing document
|
57
|
+
# collection.upsert("mydoc", "foo")
|
58
|
+
# collection.binary.prepend("mydoc", "bar", Options::Prepend(timeout: 3_000))
|
59
|
+
# collection.get("mydoc", Options::Get(transcoder: nil)).content #=> "barfoo"
|
60
|
+
#
|
61
|
+
# @return [Collection::MutationResult]
|
62
|
+
def prepend(id, content, options = Options::Prepend::DEFAULT)
|
63
|
+
resp = @backend.document_prepend(@collection.bucket_name, @collection.scope_name, @collection.name,
|
64
|
+
id, content, options.to_backend)
|
65
|
+
Collection::MutationResult.new do |res|
|
66
|
+
res.cas = resp[:cas]
|
67
|
+
res.mutation_token = @collection.send(:extract_mutation_token, resp)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Increments the counter document by one of the number defined in the options
|
72
|
+
#
|
73
|
+
# @param [String] id the document id which is used to uniquely identify it
|
74
|
+
# @param [Options::Increment] options custom options to customize the request
|
75
|
+
#
|
76
|
+
# @example Increment value by 10, and initialize to 0 if it does not exist
|
77
|
+
# res = collection.binary.increment("raw_counter", Options::Increment(delta: 10, initial: 0))
|
78
|
+
# res.content #=> 0
|
79
|
+
# res = collection.binary.increment("raw_counter", Options::Increment(delta: 10, initial: 0))
|
80
|
+
# res.content #=> 10
|
81
|
+
#
|
82
|
+
# @return [CounterResult]
|
83
|
+
def increment(id, options = Options::Increment::DEFAULT)
|
84
|
+
resp = @backend.document_increment(@collection.bucket_name, @collection.scope_name, @collection.name, id,
|
85
|
+
options.to_backend)
|
86
|
+
CounterResult.new do |res|
|
87
|
+
res.cas = resp[:cas]
|
88
|
+
res.content = resp[:content]
|
89
|
+
res.mutation_token = @collection.send(:extract_mutation_token, resp)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Decrements the counter document by one of the number defined in the options
|
94
|
+
#
|
95
|
+
# @param [String] id the document id which is used to uniquely identify it
|
96
|
+
# @param [Options::Decrement] options custom options to customize the request
|
97
|
+
#
|
98
|
+
# @example Decrement value by 2, and initialize to 100 if it does not exist
|
99
|
+
# res = collection.binary.decrement("raw_counter", Options::Decrement(delta: 2, initial: 100))
|
100
|
+
# res.value #=> 100
|
101
|
+
# res = collection.binary.decrement("raw_counter", Options::Decrement(delta: 2, initial: 100))
|
102
|
+
# res.value #=> 98
|
103
|
+
#
|
104
|
+
# @return [CounterResult]
|
105
|
+
def decrement(id, options = Options::Decrement::DEFAULT)
|
106
|
+
resp = @backend.document_decrement(@collection.bucket_name, @collection.scope_name, @collection.name, id,
|
107
|
+
options.to_backend)
|
108
|
+
CounterResult.new do |res|
|
109
|
+
res.cas = resp[:cas]
|
110
|
+
res.content = resp[:content]
|
111
|
+
res.mutation_token = @collection.send(:extract_mutation_token, resp)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# @api private
|
116
|
+
# TODO: deprecate in 3.1
|
117
|
+
AppendOptions = ::Couchbase::Options::Append
|
118
|
+
# @api private
|
119
|
+
# TODO: deprecate in 3.1
|
120
|
+
PrependOptions = ::Couchbase::Options::Prepend
|
121
|
+
# @api private
|
122
|
+
# TODO: deprecate in 3.1
|
123
|
+
IncrementOptions = ::Couchbase::Options::Increment
|
124
|
+
# @api private
|
125
|
+
# TODO: deprecate in 3.1
|
126
|
+
DecrementOptions = ::Couchbase::Options::Decrement
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Copyright 2020-2021 Couchbase, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "couchbase/collection_options"
|
16
|
+
|
17
|
+
module Couchbase
|
18
|
+
class BinaryCollection
|
19
|
+
class CounterResult < ::Couchbase::Collection::MutationResult
|
20
|
+
# @return [Integer] current value of the counter
|
21
|
+
attr_accessor :content
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|