dalli 3.1.5 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of dalli might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +14 -1
- data/lib/dalli/client.rb +8 -4
- data/lib/dalli/protocol/base.rb +7 -0
- data/lib/dalli/protocol/binary/request_formatter.rb +1 -1
- data/lib/dalli/protocol/binary.rb +0 -7
- data/lib/dalli/protocol/connection_manager.rb +10 -0
- data/lib/dalli/protocol/meta/key_regularizer.rb +31 -0
- data/lib/dalli/protocol/meta/request_formatter.rb +108 -0
- data/lib/dalli/protocol/meta/response_processor.rb +211 -0
- data/lib/dalli/protocol/meta.rb +177 -0
- data/lib/dalli/protocol/response_buffer.rb +1 -0
- data/lib/dalli/version.rb +1 -1
- data/lib/dalli.rb +1 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 56babb2362639cca3fd24225eea2a76a0d9a075a3b113b7f8ea09814a79bf59a
|
4
|
+
data.tar.gz: 309f6be3fce52b38608c4b22e5dce439e68944796fbe1f2240b2ed40760e8700
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16847c7b39ff624deeb5fecd324d3f0bddafdcd6096c5f65cfa7a79e8b2369930b7078e8fc019bd49015c3415b43f33ea07cf85380b28d3c46199339bc9aeb2a
|
7
|
+
data.tar.gz: 890f87a779947ec269fb9889334eaa7b4424a654995b9b12b723dee7452f82de066b538465800c20bf9ed9bb2c5a81fbca3ae5ff382340f241f0525ad158df0c
|
data/History.md
CHANGED
@@ -4,10 +4,23 @@ Dalli Changelog
|
|
4
4
|
Unreleased
|
5
5
|
==========
|
6
6
|
|
7
|
+
3.2.0
|
8
|
+
==========
|
9
|
+
|
10
|
+
- BREAKING CHANGE: Remove protocol_implementation client option (petergoldstein)
|
11
|
+
- Add protocol option with meta implementation (petergoldstein)
|
12
|
+
|
13
|
+
3.1.6
|
14
|
+
==========
|
15
|
+
|
16
|
+
- Fix bug with cas/cas! with "Not found" value (petergoldstein)
|
17
|
+
- Add Ruby 3.1 to CI (petergoldstein)
|
18
|
+
- Replace reject(&:nil?) with compact (petergoldstein)
|
19
|
+
|
7
20
|
3.1.5
|
8
21
|
==========
|
9
22
|
|
10
|
-
- Fix bug with get_cas key with "Not found" value
|
23
|
+
- Fix bug with get_cas key with "Not found" value (petergoldstein)
|
11
24
|
- Replace should return nil, not raise error, on miss (petergoldstein)
|
12
25
|
|
13
26
|
3.1.4
|
data/lib/dalli/client.rb
CHANGED
@@ -43,8 +43,8 @@ module Dalli
|
|
43
43
|
# #fetch operations.
|
44
44
|
# - :digest_class - defaults to Digest::MD5, allows you to pass in an object that responds to the hexdigest method,
|
45
45
|
# useful for injecting a FIPS compliant hash object.
|
46
|
-
# - :
|
47
|
-
#
|
46
|
+
# - :protocol - one of either :binary or :meta, defaulting to :binary. This sets the protocol that Dalli uses
|
47
|
+
# to communicate with memcached.
|
48
48
|
#
|
49
49
|
def initialize(servers = nil, options = {})
|
50
50
|
@servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
|
@@ -375,7 +375,6 @@ module Dalli
|
|
375
375
|
|
376
376
|
def cas_core(key, always_set, ttl = nil, req_options = nil)
|
377
377
|
(value, cas) = perform(:cas, key)
|
378
|
-
value = nil if !value || value == 'Not found'
|
379
378
|
return if value.nil? && !always_set
|
380
379
|
|
381
380
|
newvalue = yield(value)
|
@@ -403,7 +402,12 @@ module Dalli
|
|
403
402
|
end
|
404
403
|
|
405
404
|
def protocol_implementation
|
406
|
-
@protocol_implementation ||= @options
|
405
|
+
@protocol_implementation ||= case @options[:protocol]&.to_s
|
406
|
+
when 'meta'
|
407
|
+
Dalli::Protocol::Meta
|
408
|
+
else
|
409
|
+
Dalli::Protocol::Binary
|
410
|
+
end
|
407
411
|
end
|
408
412
|
|
409
413
|
##
|
data/lib/dalli/protocol/base.rb
CHANGED
@@ -147,6 +147,13 @@ module Dalli
|
|
147
147
|
|
148
148
|
private
|
149
149
|
|
150
|
+
ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
|
151
|
+
def verify_allowed_quiet!(opkey)
|
152
|
+
return if ALLOWED_QUIET_OPS.include?(opkey)
|
153
|
+
|
154
|
+
raise Dalli::NotPermittedMultiOpError, "The operation #{opkey} is not allowed in a quiet block."
|
155
|
+
end
|
156
|
+
|
150
157
|
##
|
151
158
|
# Checks to see if we can execute the specified operation. Checks
|
152
159
|
# whether the connection is in use, and whether the command is allowed
|
@@ -94,7 +94,7 @@ module Dalli
|
|
94
94
|
key_len = key.nil? ? 0 : key.bytesize
|
95
95
|
value_len = value.nil? ? 0 : value.bytesize
|
96
96
|
header = [REQUEST, OPCODES[opkey], key_len, extra_len, 0, 0, extra_len + key_len + value_len, opaque, cas]
|
97
|
-
body = [bitflags, ttl, key, value].
|
97
|
+
body = [bitflags, ttl, key, value].compact
|
98
98
|
(header + body).pack(FORMAT[opkey])
|
99
99
|
end
|
100
100
|
# rubocop:enable Metrics/ParameterLists
|
@@ -18,13 +18,6 @@ module Dalli
|
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
|
-
ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
|
22
|
-
def verify_allowed_quiet!(opkey)
|
23
|
-
return if ALLOWED_QUIET_OPS.include?(opkey)
|
24
|
-
|
25
|
-
raise Dalli::NotPermittedMultiOpError, "The operation #{opkey} is not allowed in a quiet block."
|
26
|
-
end
|
27
|
-
|
28
21
|
# Retrieval Commands
|
29
22
|
def get(key, options = nil)
|
30
23
|
req = RequestFormatter.standard_request(opkey: :get, key: key)
|
@@ -133,6 +133,16 @@ module Dalli
|
|
133
133
|
@request_in_progress = false
|
134
134
|
end
|
135
135
|
|
136
|
+
def read_line
|
137
|
+
start_request!
|
138
|
+
data = @sock.gets("\r\n")
|
139
|
+
error_on_request!('EOF in read_line') if data.nil?
|
140
|
+
finish_request!
|
141
|
+
data
|
142
|
+
rescue SystemCallError, Timeout::Error, EOFError => e
|
143
|
+
error_on_request!(e)
|
144
|
+
end
|
145
|
+
|
136
146
|
def read(count)
|
137
147
|
start_request!
|
138
148
|
data = @sock.readfull(count)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module Dalli
|
6
|
+
module Protocol
|
7
|
+
class Meta
|
8
|
+
##
|
9
|
+
# The meta protocol requires that keys be ASCII only, so Unicode keys are
|
10
|
+
# not supported. In addition, the use of whitespace in the key is not
|
11
|
+
# allowed.
|
12
|
+
# memcached supports the use of base64 hashes for keys containing
|
13
|
+
# whitespace or non-ASCII characters, provided the 'b' flag is included in the request.
|
14
|
+
class KeyRegularizer
|
15
|
+
WHITESPACE = /\s/.freeze
|
16
|
+
|
17
|
+
def self.encode(key)
|
18
|
+
return [key, false] if key.ascii_only? && !WHITESPACE.match(key)
|
19
|
+
|
20
|
+
[Base64.strict_encode64(key), true]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.decode(encoded_key, base64_encoded)
|
24
|
+
return encoded_key unless base64_encoded
|
25
|
+
|
26
|
+
Base64.strict_decode64(encoded_key).force_encoding('UTF-8')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
module Dalli
|
4
|
+
module Protocol
|
5
|
+
class Meta
|
6
|
+
##
|
7
|
+
# Class that encapsulates logic for formatting meta protocol requests
|
8
|
+
# to memcached.
|
9
|
+
##
|
10
|
+
class RequestFormatter
|
11
|
+
# Since these are string construction methods, we're going to disable these
|
12
|
+
# Rubocop directives. We really can't make this construction much simpler,
|
13
|
+
# and introducing an intermediate object seems like overkill.
|
14
|
+
#
|
15
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
16
|
+
# rubocop:disable Metrics/MethodLength
|
17
|
+
# rubocop:disable Metrics/ParameterLists
|
18
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
19
|
+
def self.meta_get(key:, value: true, return_cas: false, ttl: nil, base64: false, quiet: false)
|
20
|
+
cmd = "mg #{key}"
|
21
|
+
cmd << ' v f' if value
|
22
|
+
cmd << ' c' if return_cas
|
23
|
+
cmd << ' b' if base64
|
24
|
+
cmd << " T#{ttl}" if ttl
|
25
|
+
cmd << ' k q s' if quiet # Return the key in the response if quiet
|
26
|
+
cmd + TERMINATOR
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.meta_set(key:, value:, bitflags: nil, cas: nil, ttl: nil, mode: :set, base64: false, quiet: false)
|
30
|
+
cmd = "ms #{key} #{value.bytesize}"
|
31
|
+
cmd << ' c' unless %i[append prepend].include?(mode)
|
32
|
+
cmd << ' b' if base64
|
33
|
+
cmd << " F#{bitflags}" if bitflags
|
34
|
+
cmd << " C#{cas}" if cas && !cas.zero?
|
35
|
+
cmd << " T#{ttl}" if ttl
|
36
|
+
cmd << " M#{mode_to_token(mode)}"
|
37
|
+
cmd << ' q' if quiet
|
38
|
+
cmd << TERMINATOR
|
39
|
+
cmd << value
|
40
|
+
cmd + TERMINATOR
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.meta_delete(key:, cas: nil, ttl: nil, base64: false, quiet: false)
|
44
|
+
cmd = "md #{key}"
|
45
|
+
cmd << ' b' if base64
|
46
|
+
cmd << " C#{cas}" if cas && !cas.zero?
|
47
|
+
cmd << " T#{ttl}" if ttl
|
48
|
+
cmd << ' q' if quiet
|
49
|
+
cmd + TERMINATOR
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.meta_arithmetic(key:, delta:, initial:, incr: true, cas: nil, ttl: nil, base64: false, quiet: false)
|
53
|
+
cmd = "ma #{key} v"
|
54
|
+
cmd << ' b' if base64
|
55
|
+
cmd << " D#{delta}" if delta
|
56
|
+
cmd << " J#{initial}" if initial
|
57
|
+
cmd << " C#{cas}" if cas && !cas.zero?
|
58
|
+
cmd << " N#{ttl}" if ttl
|
59
|
+
cmd << ' q' if quiet
|
60
|
+
cmd << " M#{incr ? 'I' : 'D'}"
|
61
|
+
cmd + TERMINATOR
|
62
|
+
end
|
63
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
64
|
+
# rubocop:enable Metrics/MethodLength
|
65
|
+
# rubocop:enable Metrics/ParameterLists
|
66
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
67
|
+
|
68
|
+
def self.meta_noop
|
69
|
+
"mn#{TERMINATOR}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.version
|
73
|
+
"version#{TERMINATOR}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.flush(delay: nil, quiet: false)
|
77
|
+
cmd = +'flush_all'
|
78
|
+
cmd << " #{delay}" if delay
|
79
|
+
cmd << ' noreply' if quiet
|
80
|
+
cmd + TERMINATOR
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.stats(arg = nil)
|
84
|
+
cmd = +'stats'
|
85
|
+
cmd << " #{arg}" if arg
|
86
|
+
cmd + TERMINATOR
|
87
|
+
end
|
88
|
+
|
89
|
+
# rubocop:disable Metrics/MethodLength
|
90
|
+
def self.mode_to_token(mode)
|
91
|
+
case mode
|
92
|
+
when :add
|
93
|
+
'E'
|
94
|
+
when :replace
|
95
|
+
'R'
|
96
|
+
when :append
|
97
|
+
'A'
|
98
|
+
when :prepend
|
99
|
+
'P'
|
100
|
+
else
|
101
|
+
'S'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
# rubocop:enable Metrics/MethodLength
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dalli
|
4
|
+
module Protocol
|
5
|
+
class Meta
|
6
|
+
##
|
7
|
+
# Class that encapsulates logic for processing meta protocol responses
|
8
|
+
# from memcached. Includes logic for pulling data from an IO source
|
9
|
+
# and parsing into local values. Handles errors on unexpected values.
|
10
|
+
##
|
11
|
+
class ResponseProcessor
|
12
|
+
EN = 'EN'
|
13
|
+
END_TOKEN = 'END'
|
14
|
+
EX = 'EX'
|
15
|
+
HD = 'HD'
|
16
|
+
MN = 'MN'
|
17
|
+
NF = 'NF'
|
18
|
+
NS = 'NS'
|
19
|
+
OK = 'OK'
|
20
|
+
RESET = 'RESET'
|
21
|
+
STAT = 'STAT'
|
22
|
+
VA = 'VA'
|
23
|
+
VERSION = 'VERSION'
|
24
|
+
|
25
|
+
def initialize(io_source, value_marshaller)
|
26
|
+
@io_source = io_source
|
27
|
+
@value_marshaller = value_marshaller
|
28
|
+
end
|
29
|
+
|
30
|
+
def meta_get_with_value(cache_nils: false)
|
31
|
+
tokens = error_on_unexpected!([VA, EN, HD])
|
32
|
+
return cache_nils ? ::Dalli::NOT_FOUND : nil if tokens.first == EN
|
33
|
+
return true unless tokens.first == VA
|
34
|
+
|
35
|
+
@value_marshaller.retrieve(read_line, bitflags_from_tokens(tokens))
|
36
|
+
end
|
37
|
+
|
38
|
+
def meta_get_with_value_and_cas
|
39
|
+
tokens = error_on_unexpected!([VA, EN, HD])
|
40
|
+
return [nil, 0] if tokens.first == EN
|
41
|
+
|
42
|
+
cas = cas_from_tokens(tokens)
|
43
|
+
return [nil, cas] unless tokens.first == VA
|
44
|
+
|
45
|
+
[@value_marshaller.retrieve(read_line, bitflags_from_tokens(tokens)), cas]
|
46
|
+
end
|
47
|
+
|
48
|
+
def meta_get_without_value
|
49
|
+
tokens = error_on_unexpected!([EN, HD])
|
50
|
+
tokens.first == EN ? nil : true
|
51
|
+
end
|
52
|
+
|
53
|
+
def meta_set_with_cas
|
54
|
+
tokens = error_on_unexpected!([HD, NS, NF, EX])
|
55
|
+
return false unless tokens.first == HD
|
56
|
+
|
57
|
+
cas_from_tokens(tokens)
|
58
|
+
end
|
59
|
+
|
60
|
+
def meta_set_append_prepend
|
61
|
+
tokens = error_on_unexpected!([HD, NS, NF, EX])
|
62
|
+
return false unless tokens.first == HD
|
63
|
+
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
def meta_delete
|
68
|
+
tokens = error_on_unexpected!([HD, NF, EX])
|
69
|
+
tokens.first == HD
|
70
|
+
end
|
71
|
+
|
72
|
+
def decr_incr
|
73
|
+
tokens = error_on_unexpected!([VA, NF, NS, EX])
|
74
|
+
return false if [NS, EX].include?(tokens.first)
|
75
|
+
return nil if tokens.first == NF
|
76
|
+
|
77
|
+
read_line.to_i
|
78
|
+
end
|
79
|
+
|
80
|
+
def stats
|
81
|
+
tokens = error_on_unexpected!([END_TOKEN, STAT])
|
82
|
+
values = {}
|
83
|
+
while tokens.first != END_TOKEN
|
84
|
+
values[tokens[1]] = tokens[2]
|
85
|
+
tokens = next_line_to_tokens
|
86
|
+
end
|
87
|
+
values
|
88
|
+
end
|
89
|
+
|
90
|
+
def flush
|
91
|
+
error_on_unexpected!([OK])
|
92
|
+
|
93
|
+
true
|
94
|
+
end
|
95
|
+
|
96
|
+
def reset
|
97
|
+
error_on_unexpected!([RESET])
|
98
|
+
|
99
|
+
true
|
100
|
+
end
|
101
|
+
|
102
|
+
def version
|
103
|
+
tokens = error_on_unexpected!([VERSION])
|
104
|
+
tokens.last
|
105
|
+
end
|
106
|
+
|
107
|
+
def consume_all_responses_until_mn
|
108
|
+
tokens = next_line_to_tokens
|
109
|
+
|
110
|
+
tokens = next_line_to_tokens while tokens.first != MN
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
def tokens_from_header_buffer(buf)
|
115
|
+
header = header_from_buffer(buf)
|
116
|
+
tokens = header.split
|
117
|
+
header_len = header.bytesize + TERMINATOR.length
|
118
|
+
body_len = body_len_from_tokens(tokens)
|
119
|
+
[tokens, header_len, body_len]
|
120
|
+
end
|
121
|
+
|
122
|
+
def full_response_from_buffer(tokens, body, resp_size)
|
123
|
+
value = @value_marshaller.retrieve(body, bitflags_from_tokens(tokens))
|
124
|
+
[resp_size, tokens.first == VA, cas_from_tokens(tokens), key_from_tokens(tokens), value]
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# This method returns an array of values used in a pipelined
|
129
|
+
# getk process. The first value is the number of bytes by
|
130
|
+
# which to advance the pointer in the buffer. If the
|
131
|
+
# complete response is found in the buffer, this will
|
132
|
+
# be the response size. Otherwise it is zero.
|
133
|
+
#
|
134
|
+
# The remaining three values in the array are the ResponseHeader,
|
135
|
+
# key, and value.
|
136
|
+
##
|
137
|
+
def getk_response_from_buffer(buf)
|
138
|
+
# There's no header in the buffer, so don't advance
|
139
|
+
return [0, nil, nil, nil, nil] unless contains_header?(buf)
|
140
|
+
|
141
|
+
tokens, header_len, body_len = tokens_from_header_buffer(buf)
|
142
|
+
|
143
|
+
# We have a complete response that has no body.
|
144
|
+
# This is either the response to the terminating
|
145
|
+
# noop or, if the status is not MN, an intermediate
|
146
|
+
# error response that needs to be discarded.
|
147
|
+
return [header_len, true, nil, nil, nil] if body_len.zero?
|
148
|
+
|
149
|
+
resp_size = header_len + body_len + TERMINATOR.length
|
150
|
+
# The header is in the buffer, but the body is not. As we don't have
|
151
|
+
# a complete response, don't advance the buffer
|
152
|
+
return [0, nil, nil, nil, nil] unless buf.bytesize >= resp_size
|
153
|
+
|
154
|
+
# The full response is in our buffer, so parse it and return
|
155
|
+
# the values
|
156
|
+
body = buf.slice(header_len, body_len)
|
157
|
+
full_response_from_buffer(tokens, body, resp_size)
|
158
|
+
end
|
159
|
+
|
160
|
+
def contains_header?(buf)
|
161
|
+
buf.include?(TERMINATOR)
|
162
|
+
end
|
163
|
+
|
164
|
+
def header_from_buffer(buf)
|
165
|
+
buf.split(TERMINATOR, 2).first
|
166
|
+
end
|
167
|
+
|
168
|
+
def error_on_unexpected!(expected_codes)
|
169
|
+
tokens = next_line_to_tokens
|
170
|
+
raise Dalli::DalliError, "Response error: #{tokens.first}" unless expected_codes.include?(tokens.first)
|
171
|
+
|
172
|
+
tokens
|
173
|
+
end
|
174
|
+
|
175
|
+
def bitflags_from_tokens(tokens)
|
176
|
+
value_from_tokens(tokens, 'f')&.to_i
|
177
|
+
end
|
178
|
+
|
179
|
+
def cas_from_tokens(tokens)
|
180
|
+
value_from_tokens(tokens, 'c')&.to_i
|
181
|
+
end
|
182
|
+
|
183
|
+
def key_from_tokens(tokens)
|
184
|
+
encoded_key = value_from_tokens(tokens, 'k')
|
185
|
+
base64_encoded = tokens.any?('b')
|
186
|
+
KeyRegularizer.decode(encoded_key, base64_encoded)
|
187
|
+
end
|
188
|
+
|
189
|
+
def body_len_from_tokens(tokens)
|
190
|
+
value_from_tokens(tokens, 's')&.to_i
|
191
|
+
end
|
192
|
+
|
193
|
+
def value_from_tokens(tokens, flag)
|
194
|
+
bitflags_token = tokens.find { |t| t.start_with?(flag) }
|
195
|
+
return 0 unless bitflags_token
|
196
|
+
|
197
|
+
bitflags_token[1..-1]
|
198
|
+
end
|
199
|
+
|
200
|
+
def read_line
|
201
|
+
@io_source.read_line&.chomp!(TERMINATOR)
|
202
|
+
end
|
203
|
+
|
204
|
+
def next_line_to_tokens
|
205
|
+
line = read_line
|
206
|
+
line&.split || []
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'socket'
|
5
|
+
require 'timeout'
|
6
|
+
|
7
|
+
module Dalli
|
8
|
+
module Protocol
|
9
|
+
##
|
10
|
+
# Access point for a single Memcached server, accessed via Memcached's meta
|
11
|
+
# protocol. Contains logic for managing connection state to the server (retries, etc),
|
12
|
+
# formatting requests to the server, and unpacking responses.
|
13
|
+
##
|
14
|
+
class Meta < Base
|
15
|
+
TERMINATOR = "\r\n"
|
16
|
+
|
17
|
+
def response_processor
|
18
|
+
@response_processor ||= ResponseProcessor.new(@connection_manager, @value_marshaller)
|
19
|
+
end
|
20
|
+
|
21
|
+
# NOTE: Additional public methods should be overridden in Dalli::Threadsafe
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Retrieval Commands
|
26
|
+
def get(key, options = nil)
|
27
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
28
|
+
req = RequestFormatter.meta_get(key: encoded_key, base64: base64)
|
29
|
+
write(req)
|
30
|
+
response_processor.meta_get_with_value(cache_nils: cache_nils?(options))
|
31
|
+
end
|
32
|
+
|
33
|
+
def quiet_get_request(key)
|
34
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
35
|
+
RequestFormatter.meta_get(key: encoded_key, return_cas: true, base64: base64, quiet: true)
|
36
|
+
end
|
37
|
+
|
38
|
+
def gat(key, ttl, options = nil)
|
39
|
+
ttl = TtlSanitizer.sanitize(ttl)
|
40
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
41
|
+
req = RequestFormatter.meta_get(key: encoded_key, ttl: ttl, base64: base64)
|
42
|
+
write(req)
|
43
|
+
response_processor.meta_get_with_value(cache_nils: cache_nils?(options))
|
44
|
+
end
|
45
|
+
|
46
|
+
def touch(key, ttl)
|
47
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
48
|
+
req = RequestFormatter.meta_get(key: encoded_key, ttl: ttl, value: false, base64: base64)
|
49
|
+
write(req)
|
50
|
+
response_processor.meta_get_without_value
|
51
|
+
end
|
52
|
+
|
53
|
+
# TODO: This is confusing, as there's a cas command in memcached
|
54
|
+
# and this isn't it. Maybe rename? Maybe eliminate?
|
55
|
+
def cas(key)
|
56
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
57
|
+
req = RequestFormatter.meta_get(key: encoded_key, value: true, return_cas: true, base64: base64)
|
58
|
+
write(req)
|
59
|
+
response_processor.meta_get_with_value_and_cas
|
60
|
+
end
|
61
|
+
|
62
|
+
# Storage Commands
|
63
|
+
def set(key, value, ttl, cas, options)
|
64
|
+
write_storage_req(:set, key, value, ttl, cas, options)
|
65
|
+
response_processor.meta_set_with_cas unless quiet?
|
66
|
+
end
|
67
|
+
|
68
|
+
def add(key, value, ttl, options)
|
69
|
+
write_storage_req(:add, key, value, ttl, nil, options)
|
70
|
+
response_processor.meta_set_with_cas unless quiet?
|
71
|
+
end
|
72
|
+
|
73
|
+
def replace(key, value, ttl, cas, options)
|
74
|
+
write_storage_req(:replace, key, value, ttl, cas, options)
|
75
|
+
response_processor.meta_set_with_cas unless quiet?
|
76
|
+
end
|
77
|
+
|
78
|
+
# rubocop:disable Metrics/ParameterLists
|
79
|
+
def write_storage_req(mode, key, raw_value, ttl = nil, cas = nil, options = {})
|
80
|
+
(value, bitflags) = @value_marshaller.store(key, raw_value, options)
|
81
|
+
ttl = TtlSanitizer.sanitize(ttl) if ttl
|
82
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
83
|
+
req = RequestFormatter.meta_set(key: encoded_key, value: value,
|
84
|
+
bitflags: bitflags, cas: cas,
|
85
|
+
ttl: ttl, mode: mode, quiet: quiet?, base64: base64)
|
86
|
+
write(req)
|
87
|
+
end
|
88
|
+
# rubocop:enable Metrics/ParameterLists
|
89
|
+
|
90
|
+
def append(key, value)
|
91
|
+
write_append_prepend_req(:append, key, value)
|
92
|
+
response_processor.meta_set_append_prepend unless quiet?
|
93
|
+
end
|
94
|
+
|
95
|
+
def prepend(key, value)
|
96
|
+
write_append_prepend_req(:prepend, key, value)
|
97
|
+
response_processor.meta_set_append_prepend unless quiet?
|
98
|
+
end
|
99
|
+
|
100
|
+
# rubocop:disable Metrics/ParameterLists
|
101
|
+
def write_append_prepend_req(mode, key, value, ttl = nil, cas = nil, _options = {})
|
102
|
+
ttl = TtlSanitizer.sanitize(ttl) if ttl
|
103
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
104
|
+
req = RequestFormatter.meta_set(key: encoded_key, value: value, base64: base64,
|
105
|
+
cas: cas, ttl: ttl, mode: mode, quiet: quiet?)
|
106
|
+
write(req)
|
107
|
+
end
|
108
|
+
# rubocop:enable Metrics/ParameterLists
|
109
|
+
|
110
|
+
# Delete Commands
|
111
|
+
def delete(key, cas)
|
112
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
113
|
+
req = RequestFormatter.meta_delete(key: encoded_key, cas: cas,
|
114
|
+
base64: base64, quiet: quiet?)
|
115
|
+
write(req)
|
116
|
+
response_processor.meta_delete unless quiet?
|
117
|
+
end
|
118
|
+
|
119
|
+
# Arithmetic Commands
|
120
|
+
def decr(key, count, ttl, initial)
|
121
|
+
decr_incr false, key, count, ttl, initial
|
122
|
+
end
|
123
|
+
|
124
|
+
def incr(key, count, ttl, initial)
|
125
|
+
decr_incr true, key, count, ttl, initial
|
126
|
+
end
|
127
|
+
|
128
|
+
def decr_incr(incr, key, delta, ttl, initial)
|
129
|
+
ttl = initial ? TtlSanitizer.sanitize(ttl) : nil # Only set a TTL if we want to set a value on miss
|
130
|
+
encoded_key, base64 = KeyRegularizer.encode(key)
|
131
|
+
write(RequestFormatter.meta_arithmetic(key: encoded_key, delta: delta, initial: initial, incr: incr, ttl: ttl,
|
132
|
+
quiet: quiet?, base64: base64))
|
133
|
+
response_processor.decr_incr unless quiet?
|
134
|
+
end
|
135
|
+
|
136
|
+
# Other Commands
|
137
|
+
def flush(delay = 0)
|
138
|
+
write(RequestFormatter.flush(delay: delay))
|
139
|
+
response_processor.flush unless quiet?
|
140
|
+
end
|
141
|
+
|
142
|
+
# Noop is a keepalive operation but also used to demarcate the end of a set of pipelined commands.
|
143
|
+
# We need to read all the responses at once.
|
144
|
+
def noop
|
145
|
+
write_noop
|
146
|
+
response_processor.consume_all_responses_until_mn
|
147
|
+
end
|
148
|
+
|
149
|
+
def stats(info = nil)
|
150
|
+
write(RequestFormatter.stats(info))
|
151
|
+
response_processor.stats
|
152
|
+
end
|
153
|
+
|
154
|
+
def reset_stats
|
155
|
+
write(RequestFormatter.stats('reset'))
|
156
|
+
response_processor.reset
|
157
|
+
end
|
158
|
+
|
159
|
+
def version
|
160
|
+
write(RequestFormatter.version)
|
161
|
+
response_processor.version
|
162
|
+
end
|
163
|
+
|
164
|
+
def write_noop
|
165
|
+
write(RequestFormatter.meta_noop)
|
166
|
+
end
|
167
|
+
|
168
|
+
def authenticate_connection
|
169
|
+
raise Dalli::DalliError, 'Authentication not supported for the meta protocol.'
|
170
|
+
end
|
171
|
+
|
172
|
+
require_relative 'meta/key_regularizer'
|
173
|
+
require_relative 'meta/request_formatter'
|
174
|
+
require_relative 'meta/response_processor'
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
data/lib/dalli/version.rb
CHANGED
data/lib/dalli.rb
CHANGED
@@ -65,6 +65,7 @@ require_relative 'dalli/protocol'
|
|
65
65
|
require_relative 'dalli/protocol/base'
|
66
66
|
require_relative 'dalli/protocol/binary'
|
67
67
|
require_relative 'dalli/protocol/connection_manager'
|
68
|
+
require_relative 'dalli/protocol/meta'
|
68
69
|
require_relative 'dalli/protocol/response_buffer'
|
69
70
|
require_relative 'dalli/protocol/server_config_parser'
|
70
71
|
require_relative 'dalli/protocol/ttl_sanitizer'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dalli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter M. Goldstein
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2022-01-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: connection_pool
|
@@ -128,6 +128,10 @@ files:
|
|
128
128
|
- lib/dalli/protocol/binary/response_processor.rb
|
129
129
|
- lib/dalli/protocol/binary/sasl_authentication.rb
|
130
130
|
- lib/dalli/protocol/connection_manager.rb
|
131
|
+
- lib/dalli/protocol/meta.rb
|
132
|
+
- lib/dalli/protocol/meta/key_regularizer.rb
|
133
|
+
- lib/dalli/protocol/meta/request_formatter.rb
|
134
|
+
- lib/dalli/protocol/meta/response_processor.rb
|
131
135
|
- lib/dalli/protocol/response_buffer.rb
|
132
136
|
- lib/dalli/protocol/server_config_parser.rb
|
133
137
|
- lib/dalli/protocol/ttl_sanitizer.rb
|
@@ -160,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
160
164
|
- !ruby/object:Gem::Version
|
161
165
|
version: '0'
|
162
166
|
requirements: []
|
163
|
-
rubygems_version: 3.
|
167
|
+
rubygems_version: 3.3.4
|
164
168
|
signing_key:
|
165
169
|
specification_version: 4
|
166
170
|
summary: High performance memcached client for Ruby
|