_site/cover/mcd_emulator_text.COVER.html

1 %% Copyright (c) 2022 Peter Morgan <peter.james.morgan@gmail.com>
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
16 -module(mcd_emulator_text).
17
18
19 -export([recv/1]).
20 -include_lib("kernel/include/logger.hrl").
21 -include_lib("stdlib/include/ms_transform.hrl").
22 -include("mcd_emulator.hrl").
23
24
25 recv(#{message := #{command := quit}}) ->
26
:-(
stop;
27
28 recv(#{message := #{command := set,
29 key := Key,
30 data := Data,
31 expiry := Expiry,
32 noreply := Noreply,
33 flags := Flags},
34 data := #{table := Table}} = Arg) ->
35 39 ?LOG_DEBUG(Arg),
36
37 39 case mcd_config:maximum(value_size) of
38 Maximum when byte_size(Data) > Maximum ->
39
:-(
ets:delete(Table, Key),
40
:-(
{continue,
41 {encode, #{command => server_error,
42 reason => "object too large for cache"}}};
43
44 _SmallerThanMaximum ->
45 39 ets:insert(Table,
46 #entry{key = Key,
47 flags = Flags,
48 expiry = Expiry,
49 data = Data}),
50 39 {continue,
51 [{expire,
52 #{key => Key,
53 seconds => Expiry}} |
54 30 [{encode, #{command => stored}} || not(Noreply)]]}
55 end;
56
57 recv(#{message := #{command := gat,
58 keys := Keys,
59 expiry := Expiry},
60 data := #{table := Table}}) ->
61 2 {continue,
62 lists:foldr(
63 fun
64 (Key, A) ->
65 3 case ets:lookup(Table, Key) of
66 [#entry{flags = Flags, data = Data}] ->
67 2 [{encode,
68 #{command => value,
69 key => Key,
70 flags => Flags,
71 data => Data}},
72
73 {expire,
74 #{key => Key,
75 seconds => Expiry}} | A];
76
77 [] ->
78 1 A
79 end
80 end,
81 [{encode, 'end'}],
82 Keys)};
83
84 recv(#{message := #{command := gats,
85 keys := Keys,
86 expiry := Expiry},
87 data := #{table := Table}}) ->
88 1 {continue,
89 lists:foldr(
90 fun
91 (Key, A) ->
92 2 case ets:lookup(Table, Key) of
93 [#entry{flags = Flags, cas = Unique, data = Data}] ->
94 2 [{encode,
95 #{command => value,
96 key => Key,
97 flags => Flags,
98 cas => Unique,
99 data => Data}},
100
101 {expire,
102 #{key => Key,
103 seconds => Expiry}} | A];
104
105 [] ->
106
:-(
A
107 end
108 end,
109 [{encode, 'end'}],
110 Keys)};
111
112 recv(#{message := #{command := touch,
113 key := Key,
114 expiry := Expiry,
115 noreply := Noreply},
116 data := #{table := Table}}) ->
117 1 case ets:update_element(
118 Table,
119 Key,
120 [{#entry.expiry, Expiry},
121 {#entry.touched, erlang:monotonic_time()}]) of
122 true ->
123 1 {continue,
124 [{expire,
125 #{key => Key,
126 seconds => Expiry}} |
127 1 [{encode,
128 1 #{command => touched}} || not(Noreply)]]};
129
130 false ->
131
:-(
{continue,
132
:-(
[{encode, #{command => not_found}} || not(Noreply)]}
133 end;
134
135 recv(#{message := #{command := append,
136 key := Key,
137 data := Data,
138 expiry := Expiry,
139 noreply := Noreply,
140 flags := Flags},
141 data := #{table := Table}} = Arg) ->
142 1 ?LOG_DEBUG(Arg),
143
144 1 case ets:select_replace(
145 Table,
146 ets:fun2ms(
147 fun
148 (#entry{key = Candidate,
149 cas = CAS,
150 data = D0} = Existing)
151 when Candidate =:= Key ->
152 Existing#entry{flags = Flags,
153 expiry = Expiry,
154 cas = CAS + 1,
155 data = [D0, Data]}
156 end)) of
157 1 ->
158 1 {continue,
159 [{expire,
160 #{key => Key,
161 seconds => Expiry}} |
162
:-(
[{encode,
163 1 #{command => stored}} || not(Noreply)]]};
164
165 0 ->
166
:-(
{continue,
167
:-(
[{encode, #{command => not_stored}} || not(Noreply)]}
168 end;
169
170 recv(#{message := #{command := prepend,
171 key := Key,
172 data := Data,
173 expiry := Expiry,
174 noreply := Noreply,
175 flags := Flags},
176 data := #{table := Table}} = Arg) ->
177 1 ?LOG_DEBUG(Arg),
178
179 1 case ets:select_replace(
180 Table,
181 ets:fun2ms(
182 fun
183 (#entry{key = Candidate,
184 cas = CAS,
185 data = D0} = Existing)
186 when Candidate =:= Key ->
187 Existing#entry{flags = Flags,
188 expiry = Expiry,
189 cas = CAS + 1,
190 data = [Data, D0]}
191 end)) of
192 1 ->
193 1 {continue,
194 [{expire,
195 #{key => Key,
196 seconds => Expiry}} |
197
:-(
[{encode,
198 1 #{command => stored}} || not(Noreply)]]};
199
200 0 ->
201
:-(
{continue,
202
:-(
[{encode, #{command => not_stored}} || not(Noreply)]}
203 end;
204
205 recv(#{message := #{command := cas,
206 key := Key,
207 data := Data,
208 expiry := Expiry,
209 unique := Expected,
210 noreply := Noreply,
211 flags := Flags},
212 data := #{table := Table}} = Arg) ->
213 7 ?LOG_DEBUG(Arg),
214 7 case ets:lookup(Table, Key) of
215 [#entry{cas = Expected}] ->
216 3 case ets:select_replace(
217 Table,
218 ets:fun2ms(
219 fun
220 (#entry{key = Candiate,
221 cas = Actual} = Existing)
222 when Candiate =:= Key,
223 Expected == Actual ->
224 Existing#entry{flags = Flags,
225 expiry = Expiry,
226 cas = Actual + 1,
227 data = Data}
228 end)) of
229
230 1 ->
231 3 {continue,
232 [{expire,
233 #{key => Key,
234 seconds => Expiry}} |
235 2 [{encode, #{command => stored}} || not(Noreply)]]};
236
237 0 ->
238
:-(
{continue,
239
:-(
[{encode,
240
:-(
#{command => exists}} || not(Noreply)]}
241 end;
242
243 [#entry{}] ->
244 3 {continue,
245 3 [{encode,
246 3 #{command => exists}} || not(Noreply)]};
247
248 [] ->
249 1 {continue,
250 1 [{encode,
251 1 #{command => not_found}} || not(Noreply)]}
252 end;
253
254 recv(#{message := #{command := add,
255 key := Key,
256 data := Data,
257 expiry := Expiry,
258 noreply := Noreply,
259 flags := Flags},
260 data := #{table := Table}} = Arg) ->
261 8 ?LOG_DEBUG(Arg),
262 8 case ets:insert_new(Table,
263 #entry{key = Key,
264 flags = Flags,
265 expiry = Expiry,
266 data = Data}) of
267 true ->
268 7 {continue,
269 [{expire,
270 #{key => Key,
271 6 seconds => Expiry}} | [{encode, stored} || not(Noreply)]]};
272
273 false ->
274 1 {continue, [{encode, not_stored} || not(Noreply)]}
275 end;
276
277 recv(#{message := #{command := replace,
278 key := Key,
279 data := Data,
280 expiry := Expiry,
281 noreply := Noreply,
282 flags := Flags},
283 data := #{table := Table}} = Arg) ->
284 3 ?LOG_DEBUG(Arg),
285 3 case ets:select_replace(
286 Table,
287 ets:fun2ms(
288 fun
289 (#entry{key = Candiate} = Existing) when Candiate =:= Key ->
290 Existing#entry{flags = Flags, expiry = Expiry, data = Data}
291 end)) of
292 1 ->
293 2 {continue,
294 [{expire,
295 #{key => Key,
296 1 seconds => Expiry}} | [{encode, stored} || not(Noreply)]]};
297
298 0 ->
299 1 {continue, [{encode, not_stored} || not(Noreply)]}
300 end;
301
302 recv(#{message := #{command := get, keys := Keys},
303 data := #{table := Table}}) ->
304 35 {continue,
305 lists:foldr(
306 fun
307 (Key, A) ->
308 35 case ets:lookup(Table, Key) of
309 [#entry{flags = Flags, data = Data}] ->
310 28 [{encode,
311 #{command => value,
312 key => Key,
313 flags => Flags,
314 data => Data}} | A];
315
316 [] ->
317 7 A
318 end
319 end,
320 [{encode, 'end'}],
321 Keys)};
322
323 recv(#{message := #{command := gets, keys := Keys},
324 data := #{table := Table}}) ->
325 9 {continue,
326 lists:foldr(
327 fun
328 (Key, A) ->
329 12 case ets:lookup(Table, Key) of
330 [#entry{flags = Flags,
331 cas = Unique,
332 data = Data}] ->
333 10 [{encode,
334 #{command => value,
335 key => Key,
336 flags => Flags,
337 cas => Unique,
338 data => Data}} | A];
339
340 [] ->
341 2 A
342 end
343 end,
344 [{encode, 'end'}],
345 Keys)};
346
347 recv(#{message := #{command := delete, key := Key, noreply := Noreply},
348 data := #{table := Table}}) ->
349 7 case ets:select_delete(
350 Table,
351 ets:fun2ms(
352 fun
353 (#entry{key = Candidate}) ->
354 Candidate == Key
355 end)) of
356 0 ->
357 2 {continue, [{encode, #{command => not_found}} || not(Noreply)]};
358
359 1 ->
360 5 {continue, [{encode, #{command => deleted}} || not(Noreply)]}
361 end;
362
363 %% Text Commands
364
365 recv(#{data := #{table := Table},
366 message := #{command := Command,
367 key := Key,
368 noreply := Noreply}} = Arg)
369 when Command == incr; Command == decr ->
370 31 try ets:update_counter(
371 Table,
372 Key,
373 [delta(Arg), cas(Arg)]) of
374
375 [Result, _] when is_integer(Result) ->
376 15 {continue,
377 13 [{encode,
378 15 #{command => incrdecr, value => Result}} || not(Noreply)]}
379
380 catch
381 error:badarg ->
382 16 case ets:lookup(Table, Key) of
383 [#entry{data = ExistingText}] when is_binary(ExistingText) ->
384 12 try
385 12 ExistingTextAsInteger = binary_to_integer(ExistingText),
386
387 8 ets:select_replace(
388 Table,
389 ets:fun2ms(
390 fun
391 (#entry{key = FoundKey,
392 data = FoundData} = Entry)
393 when FoundKey =:= Key,
394 FoundData == ExistingText ->
395 Entry#entry{data = ExistingTextAsInteger}
396 end)),
397
398 8 ?FUNCTION_NAME(Arg)
399
400 catch
401 error:badarg ->
402 4 {continue,
403 {encode,
404 2 [#{command => client_error,
405 reason => "cannot increment or decrement "
406 4 "non-numeric value"} || not(Noreply)]}}
407 end;
408
409 [] ->
410 4 {continue,
411 {encode,
412 2 [#{command => not_found} || not(Noreply)]}}
413 end
414 end;
415
416 recv(#{message := #{command := flush_all,
417 expiry := Expiry,
418 noreply := Noreply}}) ->
419 2 {continue, [{flush_all, Expiry} | [{encode, ok} || not(Noreply)]]};
420
421 recv(#{message := #{command := verbosity,
422 level := _,
423 noreply := Noreply}}) ->
424
:-(
{continue, [{encode, ok} || not(Noreply)]};
425
426 recv(#{message := #{command := stats}}) ->
427
:-(
{continue,
428 lists:foldl(
429 fun
430 ({Key, Value}, A) when is_integer(Value) ->
431
:-(
[{encode,
432 #{command => stat,
433 key => atom_to_list(Key),
434 value => integer_to_list(Value)}} | A]
435 end,
436 [{encode, 'end'}],
437 mcd_stat:all())}.
438
439
440 delta(#{message := #{command := incr, value := Delta}}) ->
441 21 {#entry.data, Delta, mcd_util:max(uint64), 0};
442
443 delta(#{message := #{command := decr, value := Delta}}) ->
444 10 {#entry.data, -Delta, mcd_util:min(uint64), 0}.
445
446
447 cas(#{message := #{command := Command}}) when Command == incr;
448 Command == decr ->
449 31 {#entry.cas, 1, mcd_util:max(uint64), 0}.
Line Hits Source