line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Redis::CappedCollection; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 NAME |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
Redis::CappedCollection - Provides fixed size (determined by 'maxmemory' |
6
|
|
|
|
|
|
|
Redis server setting) collections with FIFO data removal. |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=head1 VERSION |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
This documentation refers to C version 1.09 |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=cut |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
#-- Pragmas -------------------------------------------------------------------- |
15
|
|
|
|
|
|
|
|
16
|
98
|
|
|
98
|
|
7845670
|
use 5.010; |
|
98
|
|
|
|
|
230
|
|
17
|
98
|
|
|
98
|
|
318
|
use strict; |
|
98
|
|
|
|
|
99
|
|
|
98
|
|
|
|
|
1456
|
|
18
|
98
|
|
|
98
|
|
289
|
use warnings; |
|
98
|
|
|
|
|
90
|
|
|
98
|
|
|
|
|
1770
|
|
19
|
98
|
|
|
98
|
|
12038
|
use bytes; |
|
98
|
|
|
|
|
252
|
|
|
98
|
|
|
|
|
314
|
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
# ENVIRONMENT ------------------------------------------------------------------ |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
our $VERSION = '1.09'; |
24
|
|
|
|
|
|
|
|
25
|
98
|
|
|
|
|
4513
|
use Exporter qw( |
26
|
|
|
|
|
|
|
import |
27
|
98
|
|
|
98
|
|
3143
|
); |
|
98
|
|
|
|
|
110
|
|
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
our @EXPORT_OK = qw( |
30
|
|
|
|
|
|
|
$DATA_VERSION |
31
|
|
|
|
|
|
|
$DEFAULT_CONNECTION_TIMEOUT |
32
|
|
|
|
|
|
|
$DEFAULT_OPERATION_TIMEOUT |
33
|
|
|
|
|
|
|
$DEFAULT_SERVER |
34
|
|
|
|
|
|
|
$DEFAULT_PORT |
35
|
|
|
|
|
|
|
$NAMESPACE |
36
|
|
|
|
|
|
|
$MIN_MEMORY_RESERVE |
37
|
|
|
|
|
|
|
$MAX_MEMORY_RESERVE |
38
|
|
|
|
|
|
|
$DEFAULT_CLEANUP_ITEMS |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
$E_NO_ERROR |
41
|
|
|
|
|
|
|
$E_MISMATCH_ARG |
42
|
|
|
|
|
|
|
$E_DATA_TOO_LARGE |
43
|
|
|
|
|
|
|
$E_NETWORK |
44
|
|
|
|
|
|
|
$E_MAXMEMORY_LIMIT |
45
|
|
|
|
|
|
|
$E_MAXMEMORY_POLICY |
46
|
|
|
|
|
|
|
$E_COLLECTION_DELETED |
47
|
|
|
|
|
|
|
$E_REDIS |
48
|
|
|
|
|
|
|
$E_DATA_ID_EXISTS |
49
|
|
|
|
|
|
|
$E_OLDER_THAN_ALLOWED |
50
|
|
|
|
|
|
|
$E_NONEXISTENT_DATA_ID |
51
|
|
|
|
|
|
|
$E_INCOMP_DATA_VERSION |
52
|
|
|
|
|
|
|
$E_REDIS_DID_NOT_RETURN_DATA |
53
|
|
|
|
|
|
|
$E_UNKNOWN_ERROR |
54
|
|
|
|
|
|
|
); |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
#-- load the modules ----------------------------------------------------------- |
57
|
|
|
|
|
|
|
|
58
|
98
|
|
|
98
|
|
314
|
use Carp; |
|
98
|
|
|
|
|
98
|
|
|
98
|
|
|
|
|
3572
|
|
59
|
98
|
|
|
98
|
|
30252
|
use Const::Fast; |
|
98
|
|
|
|
|
155722
|
|
|
98
|
|
|
|
|
438
|
|
60
|
98
|
|
|
|
|
4402
|
use Digest::SHA1 qw( |
61
|
|
|
|
|
|
|
sha1_hex |
62
|
98
|
|
|
98
|
|
39285
|
); |
|
98
|
|
|
|
|
40144
|
|
63
|
98
|
|
|
|
|
6055
|
use List::Util qw( |
64
|
|
|
|
|
|
|
min |
65
|
98
|
|
|
98
|
|
448
|
); |
|
98
|
|
|
|
|
98
|
|
66
|
98
|
|
|
98
|
|
1742
|
use Mouse; |
|
98
|
|
|
|
|
39933
|
|
|
98
|
|
|
|
|
539
|
|
67
|
98
|
|
|
98
|
|
26804
|
use Mouse::Util::TypeConstraints; |
|
98
|
|
|
|
|
107
|
|
|
98
|
|
|
|
|
462
|
|
68
|
98
|
|
|
|
|
5791
|
use Params::Util qw( |
69
|
|
|
|
|
|
|
_ARRAY |
70
|
|
|
|
|
|
|
_ARRAY0 |
71
|
|
|
|
|
|
|
_HASH0 |
72
|
|
|
|
|
|
|
_CLASSISA |
73
|
|
|
|
|
|
|
_INSTANCE |
74
|
|
|
|
|
|
|
_NONNEGINT |
75
|
|
|
|
|
|
|
_NUMBER |
76
|
|
|
|
|
|
|
_STRING |
77
|
98
|
|
|
98
|
|
26728
|
); |
|
98
|
|
|
|
|
86835
|
|
78
|
98
|
|
|
98
|
|
50084
|
use Redis '1.976'; |
|
98
|
|
|
|
|
1567551
|
|
|
98
|
|
|
|
|
2518
|
|
79
|
98
|
|
|
|
|
4291
|
use Redis::CappedCollection::Util qw( |
80
|
|
|
|
|
|
|
format_message |
81
|
98
|
|
|
98
|
|
26694
|
); |
|
98
|
|
|
|
|
143
|
|
82
|
98
|
|
|
98
|
|
389
|
use Try::Tiny; |
|
98
|
|
|
|
|
102
|
|
|
98
|
|
|
|
|
781837
|
|
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
class_type 'Redis'; |
85
|
|
|
|
|
|
|
class_type 'Test::RedisServer'; |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
#-- declarations --------------------------------------------------------------- |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=head1 SYNOPSIS |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
use 5.010; |
92
|
|
|
|
|
|
|
use strict; |
93
|
|
|
|
|
|
|
use warnings; |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
#-- Common |
96
|
|
|
|
|
|
|
use Redis::CappedCollection qw( |
97
|
|
|
|
|
|
|
$DEFAULT_SERVER |
98
|
|
|
|
|
|
|
$DEFAULT_PORT |
99
|
|
|
|
|
|
|
); |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
my $server = $DEFAULT_SERVER.':'.$DEFAULT_PORT; |
102
|
|
|
|
|
|
|
my $coll = Redis::CappedCollection->create( redis => { server => $server } ); |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
# Insert new data into collection |
105
|
|
|
|
|
|
|
my $list_id = $coll->insert( 'Some List_id', 'Some Data_id', 'Some data' ); |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
# Change the element of the list with the ID $list_id |
108
|
|
|
|
|
|
|
$updated = $coll->update( $list_id, $data_id, 'New data' ); |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
# Get data from a list with the ID $list_id |
111
|
|
|
|
|
|
|
@data = $coll->receive( $list_id ); |
112
|
|
|
|
|
|
|
# or to obtain the data ordered from the oldest to the newest |
113
|
|
|
|
|
|
|
while ( my ( $list_id, $data ) = $coll->pop_oldest ) { |
114
|
|
|
|
|
|
|
say "List '$list_id' had '$data'"; |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
A brief example of the C usage is provided in |
118
|
|
|
|
|
|
|
L"An Example"> section. |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
The data structures used by C on Redis server |
121
|
|
|
|
|
|
|
are explained in L"CappedCollection data structure"> section. |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=head1 ABSTRACT |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
Redis::CappedCollection module provides fixed sized collections that have |
126
|
|
|
|
|
|
|
a auto-FIFO age-out feature. |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
The collection consists of multiple lists containing data items ordered |
129
|
|
|
|
|
|
|
by time. Each list must have an unique ID within the collection and each |
130
|
|
|
|
|
|
|
data item has unique ID within its list. |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
Automatic data removal (when size limit is reached) may remove the oldest |
133
|
|
|
|
|
|
|
item from any list. |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
Collection size is determined by 'maxmemory' Redis server setting. |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=head1 DESCRIPTION |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
Main features of the package are: |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
=over 3 |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
=item * |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
Support creation of capped collection, status monitoring, |
146
|
|
|
|
|
|
|
updating the data set, obtaining consistent data from the collection, |
147
|
|
|
|
|
|
|
automatic data removal, error reporting. |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
=item * |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
Simple API for inserting and retrieving data and for managing collection. |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
=back |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
Capped collections are fixed-size collections that have an auto-FIFO |
156
|
|
|
|
|
|
|
age-out feature based on the time of the inserted data. When collection |
157
|
|
|
|
|
|
|
size reaches memory limit, the oldest data elements are removed automatically |
158
|
|
|
|
|
|
|
to provide space for the new elements. |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
The lists in capped collection store their data items ordered by item time. |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
To insert a new data item into the capped collection, provide list ID, data ID, |
163
|
|
|
|
|
|
|
data and optional data time (current time is used if not specified). |
164
|
|
|
|
|
|
|
If there is a list with the given ID, the data is inserted into the existing list, |
165
|
|
|
|
|
|
|
otherwise the new list is created automatically. |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
You may update the existing data in the collection, providing list ID, data ID and |
168
|
|
|
|
|
|
|
optional data time. If no time is specified, the updated data will keep |
169
|
|
|
|
|
|
|
its existing time. |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
Once the space is fully utilized, newly added data will replace |
172
|
|
|
|
|
|
|
the oldest data in the collection. |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
Limits are specified when the collection is created. |
175
|
|
|
|
|
|
|
Collection size is determined by 'maxmemory' redis server settings. |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
The package includes the utilities to dump and restore the collection: |
178
|
|
|
|
|
|
|
F, F . |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=head2 EXPORT |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
None by default. |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
Additional constants are available for import, which can be used |
185
|
|
|
|
|
|
|
to define some type of parameters. |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
These are the defaults: |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=head3 $DEFAULT_SERVER |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
Default Redis local server: C<'localhost'>. |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=cut |
194
|
|
|
|
|
|
|
const our $DEFAULT_SERVER => 'localhost'; |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
=head3 $DEFAULT_PORT |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
Default Redis server port: 6379. |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
=cut |
201
|
|
|
|
|
|
|
const our $DEFAULT_PORT => 6379; |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
=head3 $DEFAULT_CONNECTION_TIMEOUT |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
Default socket timeout for connection, number of seconds: 0.1 . |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
=cut |
208
|
|
|
|
|
|
|
const our $DEFAULT_CONNECTION_TIMEOUT => 0.1; |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=head3 $DEFAULT_OPERATION_TIMEOUT |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
Default socket timeout for read and write operations, number of seconds: 1. |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
=cut |
215
|
|
|
|
|
|
|
const our $DEFAULT_OPERATION_TIMEOUT => 1; |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=head3 $NAMESPACE |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
Namespace name used keys on the Redis server: C<'C'>. |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
=cut |
222
|
|
|
|
|
|
|
const our $NAMESPACE => 'C'; |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
=head3 $MIN_MEMORY_RESERVE, $MAX_MEMORY_RESERVE |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
Minimum and maximum memory reserve limits based on 'maxmemory' |
227
|
|
|
|
|
|
|
configuration of the Redis server. |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
Not used when C<'maxmemory'> = 0 (not set in the F). |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
The following values are used by default: |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
$MIN_MEMORY_RESERVE = 0.05; # 5% |
234
|
|
|
|
|
|
|
$MAX_MEMORY_RESERVE = 0.5; # 50% |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
=cut |
237
|
|
|
|
|
|
|
const our $MIN_MEMORY_RESERVE => 0.05; # 5% memory reserve coefficient |
238
|
|
|
|
|
|
|
const our $MAX_MEMORY_RESERVE => 0.5; # 50% memory reserve coefficient |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=head3 $DEFAULT_CLEANUP_ITEMS |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
Number of additional elements to delete from collection during cleanup procedure when collection size |
244
|
|
|
|
|
|
|
exceeds 'maxmemory'. |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
Default 100 elements. 0 means no minimal cleanup required, |
247
|
|
|
|
|
|
|
so memory cleanup will be performed only to free up sufficient amount of memory. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
=cut |
250
|
|
|
|
|
|
|
const our $DEFAULT_CLEANUP_ITEMS => 100; |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
=head3 $DATA_VERSION |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
Current data structure version - 3. |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
=cut |
257
|
|
|
|
|
|
|
const our $DATA_VERSION => 3; # incremented for each incompatible data structure change |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
=over |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
=item Error codes |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
More details about error codes are provided in L section. |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
=back |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
Possible error codes: |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
=cut |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=over 3 |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=item C<$E_NO_ERROR> |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
0 - No error |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
=cut |
278
|
|
|
|
|
|
|
const our $E_NO_ERROR => -1000; |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
=item C<$E_MISMATCH_ARG> |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
1 - Invalid argument. |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
Thrown by methods when there is a missing required argument or argument value is invalid. |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
=cut |
287
|
|
|
|
|
|
|
const our $E_MISMATCH_ARG => -1001; |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
=item C<$E_DATA_TOO_LARGE> |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
2 - Data is too large. |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
=cut |
294
|
|
|
|
|
|
|
const our $E_DATA_TOO_LARGE => -1002; |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
=item C<$E_NETWORK> |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
3 - Error in connection to Redis server. |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
=cut |
301
|
|
|
|
|
|
|
const our $E_NETWORK => -1003; |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=item C<$E_MAXMEMORY_LIMIT> |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
4 - Command not allowed when used memory > 'maxmemory'. |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
This means that the command is not allowed when used memory > C |
308
|
|
|
|
|
|
|
in the F file. |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
=cut |
311
|
|
|
|
|
|
|
const our $E_MAXMEMORY_LIMIT => -1004; |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
=item C<$E_MAXMEMORY_POLICY> |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
5 - Redis server have incompatible C setting. |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
Thrown when Redis server have incompatible C setting in F. |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
=cut |
320
|
|
|
|
|
|
|
const our $E_MAXMEMORY_POLICY => -1005; |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
=item C<$E_COLLECTION_DELETED> |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
6 - Collection elements was removed prior to use. |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
This means that the system part of the collection was removed prior to use. |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
=cut |
329
|
|
|
|
|
|
|
const our $E_COLLECTION_DELETED => -1006; |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
=item C<$E_REDIS> |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
7 - Redis error message. |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
This means that other Redis error message detected. |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
=cut |
338
|
|
|
|
|
|
|
const our $E_REDIS => -1007; |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
=item C<$E_DATA_ID_EXISTS> |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
8 - Attempt to add data with an existing ID |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
This means that you are trying to insert data with an ID that is already in |
345
|
|
|
|
|
|
|
the data list. |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
=cut |
348
|
|
|
|
|
|
|
const our $E_DATA_ID_EXISTS => -1008; |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
=item C<$E_OLDER_THAN_ALLOWED> |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
9 - Attempt to add outdated data |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
This means that you are trying to insert the data with the time older than |
355
|
|
|
|
|
|
|
the time of the oldest element currently stored in collection. |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=cut |
358
|
|
|
|
|
|
|
const our $E_OLDER_THAN_ALLOWED => -1009; |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
=item C<$E_NONEXISTENT_DATA_ID> |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
10 - Attempt to access the elements missing in the collection. |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
This means that you are trying to update data which does not exist. |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
=cut |
367
|
|
|
|
|
|
|
const our $E_NONEXISTENT_DATA_ID => -1010; |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
=item C<$E_INCOMP_DATA_VERSION> |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
11 - Attempt to access the collection with incompatible data structure, created |
372
|
|
|
|
|
|
|
by an older or newer version of this module. |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
=cut |
375
|
|
|
|
|
|
|
const our $E_INCOMP_DATA_VERSION => -1011; |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
=item C<$E_REDIS_DID_NOT_RETURN_DATA> |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
12 - The Redis server did not return data. |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
Check the settings in the file F. |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
=cut |
384
|
|
|
|
|
|
|
const our $E_REDIS_DID_NOT_RETURN_DATA => -1012; |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=item C<$E_UNKNOWN_ERROR> |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
13 - Unknown error. |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
Possibly you should modify the constructor parameters for more intense automatic memory release. |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
=back |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
=cut |
395
|
|
|
|
|
|
|
const our $E_UNKNOWN_ERROR => -1013; |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
our $DEBUG = 0; |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
our %ERROR = ( |
400
|
|
|
|
|
|
|
$E_NO_ERROR => 'No error', |
401
|
|
|
|
|
|
|
$E_MISMATCH_ARG => 'Invalid argument', |
402
|
|
|
|
|
|
|
$E_DATA_TOO_LARGE => 'Data is too large', |
403
|
|
|
|
|
|
|
$E_NETWORK => 'Error in connection to Redis server', |
404
|
|
|
|
|
|
|
$E_MAXMEMORY_LIMIT => "Command not allowed when used memory > 'maxmemory'", |
405
|
|
|
|
|
|
|
$E_MAXMEMORY_POLICY => "Redis server have incompatible 'maxmemory-policy' setting. Use 'noeviction' only.", |
406
|
|
|
|
|
|
|
$E_COLLECTION_DELETED => 'Collection elements was removed prior to use', |
407
|
|
|
|
|
|
|
$E_REDIS => 'Redis error message', |
408
|
|
|
|
|
|
|
$E_DATA_ID_EXISTS => 'Attempt to add data to an existing ID', |
409
|
|
|
|
|
|
|
$E_OLDER_THAN_ALLOWED => 'Attempt to add data over outdated', |
410
|
|
|
|
|
|
|
$E_NONEXISTENT_DATA_ID => 'Non-existent data id', |
411
|
|
|
|
|
|
|
$E_INCOMP_DATA_VERSION => 'Incompatible data version', |
412
|
|
|
|
|
|
|
$E_REDIS_DID_NOT_RETURN_DATA => 'The Redis server did not return data', |
413
|
|
|
|
|
|
|
$E_UNKNOWN_ERROR => 'Unknown error', |
414
|
|
|
|
|
|
|
); |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
const our $REDIS_ERROR_CODE => 'ERR'; |
417
|
|
|
|
|
|
|
const our $REDIS_MEMORY_ERROR_CODE => 'OOM'; |
418
|
|
|
|
|
|
|
const our $REDIS_MEMORY_ERROR_MSG => "$REDIS_MEMORY_ERROR_CODE $ERROR{ $E_MAXMEMORY_LIMIT }."; |
419
|
|
|
|
|
|
|
const our $MAX_DATASIZE => 512*1024*1024; # A String value can be at max 512 Megabytes in length. |
420
|
|
|
|
|
|
|
const my $MAX_REMOVE_RETRIES => 2; # the number of remove retries when memory limit is near |
421
|
|
|
|
|
|
|
const my $USED_MEMORY_POLICY => 'noeviction'; |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
# status field names |
424
|
|
|
|
|
|
|
const my $_LISTS => 'lists'; |
425
|
|
|
|
|
|
|
const my $_ITEMS => 'items'; |
426
|
|
|
|
|
|
|
const my $_OLDER_ALLOWED => 'older_allowed'; |
427
|
|
|
|
|
|
|
const my $_CLEANUP_BYTES => 'cleanup_bytes'; |
428
|
|
|
|
|
|
|
const my $_CLEANUP_ITEMS => 'cleanup_items'; |
429
|
|
|
|
|
|
|
const my $_MEMORY_RESERVE => 'memory_reserve'; |
430
|
|
|
|
|
|
|
const my $_DATA_VERSION => 'data_version'; |
431
|
|
|
|
|
|
|
const my $_LAST_REMOVED_TIME => 'last_removed_time'; |
432
|
|
|
|
|
|
|
const my $_LAST_CLEANUP_BYTES => 'last_cleanup_bytes'; |
433
|
|
|
|
|
|
|
# information fields |
434
|
|
|
|
|
|
|
const my $_LAST_CLEANUP_ITEMS => 'last_cleanup_items'; |
435
|
|
|
|
|
|
|
const my $_LAST_CLEANUP_MAXMEMORY => 'last_cleanup_maxmemory'; |
436
|
|
|
|
|
|
|
const my $_LAST_CLEANUP_USED_MEMORY => 'last_cleanup_used_memory'; |
437
|
|
|
|
|
|
|
const my $_LAST_CLEANUP_BYTES_MUST_BE_DELETED => 'last_bytes_must_be_deleted'; |
438
|
|
|
|
|
|
|
const my $_INSERTS_SINCE_CLEANING => 'inserts_since_cleaning'; |
439
|
|
|
|
|
|
|
const my $_UPDATES_SINCE_CLEANING => 'updates_since_cleaning'; |
440
|
|
|
|
|
|
|
const my $_MAX_LAST_CLEANUP_VALUES => 10; |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
my $_lua_namespace = "local NAMESPACE = '".$NAMESPACE."'"; |
443
|
|
|
|
|
|
|
my $_lua_queue_key = "local QUEUE_KEY = NAMESPACE..':Q:'..coll_name"; |
444
|
|
|
|
|
|
|
my $_lua_status_key = "local STATUS_KEY = NAMESPACE..':S:'..coll_name"; |
445
|
|
|
|
|
|
|
my $_lua_data_keys = "local DATA_KEYS = NAMESPACE..':D:'..coll_name"; |
446
|
|
|
|
|
|
|
my $_lua_time_keys = "local TIME_KEYS = NAMESPACE..':T:'..coll_name"; |
447
|
|
|
|
|
|
|
my $_lua_data_key = "local DATA_KEY = DATA_KEYS..':'..list_id"; |
448
|
|
|
|
|
|
|
my $_lua_time_key = "local TIME_KEY = TIME_KEYS..':'..list_id"; |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
my %lua_script_body; |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
my $_lua_clean_data = <<"END_CLEAN_DATA"; |
453
|
|
|
|
|
|
|
-- remove the control structures of the collection |
454
|
|
|
|
|
|
|
if redis.call( 'EXISTS', QUEUE_KEY ) == 1 then |
455
|
|
|
|
|
|
|
ret = ret + redis.call( 'DEL', QUEUE_KEY ) |
456
|
|
|
|
|
|
|
end |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
-- each element of the list are deleted separately, as total number of items may be too large to send commands 'DEL' |
459
|
|
|
|
|
|
|
$_lua_data_keys |
460
|
|
|
|
|
|
|
$_lua_time_keys |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
local arr = redis.call( 'KEYS', DATA_KEYS..':*' ) |
463
|
|
|
|
|
|
|
if #arr > 0 then |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
-- remove structures store data lists |
466
|
|
|
|
|
|
|
for i = 1, #arr do |
467
|
|
|
|
|
|
|
ret = ret + redis.call( 'DEL', arr[i] ) |
468
|
|
|
|
|
|
|
end |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
-- remove structures store time lists |
471
|
|
|
|
|
|
|
arr = redis.call( 'KEYS', TIME_KEYS..':*' ) |
472
|
|
|
|
|
|
|
for i = 1, #arr do |
473
|
|
|
|
|
|
|
ret = ret + redis.call( 'DEL', arr[i] ) |
474
|
|
|
|
|
|
|
end |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
end |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
return ret |
479
|
|
|
|
|
|
|
END_CLEAN_DATA |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
my $_lua_cleaning = <<"END_CLEANING"; |
482
|
|
|
|
|
|
|
local REDIS_USED_MEMORY = 0 |
483
|
|
|
|
|
|
|
local REDIS_MAXMEMORY = 0 |
484
|
|
|
|
|
|
|
local ROLLBACK = {} |
485
|
|
|
|
|
|
|
local TOTAL_BYTES_DELETED = 0 |
486
|
|
|
|
|
|
|
local LAST_CLEANUP_BYTES_MUST_BE_DELETED = 0 |
487
|
|
|
|
|
|
|
local LAST_CLEANUP_ITEMS = 0 |
488
|
|
|
|
|
|
|
local LAST_OPERATION = '' |
489
|
|
|
|
|
|
|
local INSERTS_SINCE_CLEANING = 0 |
490
|
|
|
|
|
|
|
local UPDATES_SINCE_CLEANING = 0 |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
local _DEBUG, _DEBUG_ID, _FUNC_NAME |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
local table_merge = function ( t1, t2 ) |
495
|
|
|
|
|
|
|
for key, val in pairs( t2 ) do |
496
|
|
|
|
|
|
|
t1[ key ] = val |
497
|
|
|
|
|
|
|
end |
498
|
|
|
|
|
|
|
end |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
local _debug_log = function ( values ) |
501
|
|
|
|
|
|
|
table_merge( values, { |
502
|
|
|
|
|
|
|
_DEBUG_ID = _DEBUG_ID, |
503
|
|
|
|
|
|
|
_FUNC_NAME = _FUNC_NAME, |
504
|
|
|
|
|
|
|
REDIS_USED_MEMORY = REDIS_USED_MEMORY, |
505
|
|
|
|
|
|
|
list_id = list_id, |
506
|
|
|
|
|
|
|
data_id = data_id, |
507
|
|
|
|
|
|
|
data_len = #data, |
508
|
|
|
|
|
|
|
ROLLBACK = ROLLBACK |
509
|
|
|
|
|
|
|
} ) |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
redis.log( redis.LOG_NOTICE, _FUNC_NAME..': '..cjson.encode( values ) ) |
512
|
|
|
|
|
|
|
end |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
local _setup = function ( argv_idx, func_name, extra_data_len ) |
515
|
|
|
|
|
|
|
LAST_OPERATION = func_name |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
REDIS_MAXMEMORY = tonumber( redis.call( 'CONFIG', 'GET', 'maxmemory' )[2] ) |
518
|
|
|
|
|
|
|
local memory_reserve_coefficient = 1 + tonumber( redis.call( 'HGET', STATUS_KEY, '$_MEMORY_RESERVE' ) ) |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
local redis_used_memory = string.match( |
521
|
|
|
|
|
|
|
redis.call( 'INFO', 'memory' ), |
522
|
|
|
|
|
|
|
'used_memory:(%d+)' |
523
|
|
|
|
|
|
|
) |
524
|
|
|
|
|
|
|
REDIS_USED_MEMORY = tonumber( redis_used_memory ) |
525
|
|
|
|
|
|
|
LAST_CLEANUP_BYTES_MUST_BE_DELETED = REDIS_USED_MEMORY + extra_data_len - math.floor( REDIS_MAXMEMORY / memory_reserve_coefficient ) |
526
|
|
|
|
|
|
|
if LAST_CLEANUP_BYTES_MUST_BE_DELETED < 0 or REDIS_MAXMEMORY == 0 then |
527
|
|
|
|
|
|
|
LAST_CLEANUP_BYTES_MUST_BE_DELETED = 0 |
528
|
|
|
|
|
|
|
end |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
INSERTS_SINCE_CLEANING = tonumber( redis.call( 'HGET', STATUS_KEY, '$_INSERTS_SINCE_CLEANING' ) ) |
531
|
|
|
|
|
|
|
if INSERTS_SINCE_CLEANING == nil then |
532
|
|
|
|
|
|
|
INSERTS_SINCE_CLEANING = 0 |
533
|
|
|
|
|
|
|
end |
534
|
|
|
|
|
|
|
UPDATES_SINCE_CLEANING = tonumber( redis.call( 'HGET', STATUS_KEY, '$_UPDATES_SINCE_CLEANING' ) ) |
535
|
|
|
|
|
|
|
if UPDATES_SINCE_CLEANING == nil then |
536
|
|
|
|
|
|
|
UPDATES_SINCE_CLEANING = 0 |
537
|
|
|
|
|
|
|
end |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
_FUNC_NAME = func_name |
540
|
|
|
|
|
|
|
_DEBUG_ID = tonumber( ARGV[ argv_idx ] ) |
541
|
|
|
|
|
|
|
if _DEBUG_ID ~= 0 then |
542
|
|
|
|
|
|
|
_DEBUG = true |
543
|
|
|
|
|
|
|
_debug_log( { |
544
|
|
|
|
|
|
|
_STEP = '_setup', |
545
|
|
|
|
|
|
|
maxmemory = REDIS_MAXMEMORY, |
546
|
|
|
|
|
|
|
redis_used_memory = REDIS_USED_MEMORY, |
547
|
|
|
|
|
|
|
bytes_must_be_deleted = LAST_CLEANUP_BYTES_MUST_BE_DELETED |
548
|
|
|
|
|
|
|
} ) |
549
|
|
|
|
|
|
|
else |
550
|
|
|
|
|
|
|
_DEBUG = false |
551
|
|
|
|
|
|
|
end |
552
|
|
|
|
|
|
|
end |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
local renew_last_cleanup_values = function () |
555
|
|
|
|
|
|
|
if LAST_CLEANUP_ITEMS > 0 then |
556
|
|
|
|
|
|
|
local last_cleanup_bytes_values = cjson.decode( redis.call( 'HGET', STATUS_KEY, '$_LAST_CLEANUP_BYTES' ) ) |
557
|
|
|
|
|
|
|
local last_cleanup_items_values = cjson.decode( redis.call( 'HGET', STATUS_KEY, '$_LAST_CLEANUP_ITEMS' ) ) |
558
|
|
|
|
|
|
|
if #last_cleanup_bytes_values >= 10 then |
559
|
|
|
|
|
|
|
table.remove ( last_cleanup_bytes_values, 1 ) |
560
|
|
|
|
|
|
|
table.remove ( last_cleanup_items_values, 1 ) |
561
|
|
|
|
|
|
|
end |
562
|
|
|
|
|
|
|
table.insert( last_cleanup_bytes_values, TOTAL_BYTES_DELETED ) |
563
|
|
|
|
|
|
|
table.insert( last_cleanup_items_values, LAST_CLEANUP_ITEMS ) |
564
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_BYTES', cjson.encode( last_cleanup_bytes_values ) ) |
565
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_ITEMS', cjson.encode( last_cleanup_items_values ) ) |
566
|
|
|
|
|
|
|
end |
567
|
|
|
|
|
|
|
end |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
local cleaning_error = function ( error_msg ) |
570
|
|
|
|
|
|
|
if _DEBUG then |
571
|
|
|
|
|
|
|
_debug_log( { |
572
|
|
|
|
|
|
|
_STEP = 'cleaning_error', |
573
|
|
|
|
|
|
|
error_msg = error_msg |
574
|
|
|
|
|
|
|
} ) |
575
|
|
|
|
|
|
|
end |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
for _, rollback_command in ipairs( ROLLBACK ) do |
578
|
|
|
|
|
|
|
redis.call( unpack( rollback_command ) ) |
579
|
|
|
|
|
|
|
end |
580
|
|
|
|
|
|
|
-- Level 2 points the error to where the function that called error was called |
581
|
|
|
|
|
|
|
renew_last_cleanup_values() |
582
|
|
|
|
|
|
|
error( error_msg, 2 ) |
583
|
|
|
|
|
|
|
end |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
local table_val_to_str = function ( v ) |
586
|
|
|
|
|
|
|
if "string" == type( v ) then |
587
|
|
|
|
|
|
|
v = string.gsub( v, "\\n", "\\\\n" ) |
588
|
|
|
|
|
|
|
if string.match( string.gsub( v, "[^'\\"]", "" ), '^"+\$' ) then |
589
|
|
|
|
|
|
|
return "'" .. v .. "'" |
590
|
|
|
|
|
|
|
end |
591
|
|
|
|
|
|
|
return '"' .. string.gsub( v, '"', '\\\\"' ) .. '"' |
592
|
|
|
|
|
|
|
else |
593
|
|
|
|
|
|
|
return "table" == type( v ) and table_tostring( v ) or tostring( v ) |
594
|
|
|
|
|
|
|
end |
595
|
|
|
|
|
|
|
end |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
local table_key_to_str = function ( k ) |
598
|
|
|
|
|
|
|
if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*\$" ) then |
599
|
|
|
|
|
|
|
return k |
600
|
|
|
|
|
|
|
else |
601
|
|
|
|
|
|
|
return "[" .. table_val_to_str( k ) .. "]" |
602
|
|
|
|
|
|
|
end |
603
|
|
|
|
|
|
|
end |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
local table_tostring = function ( tbl ) |
606
|
|
|
|
|
|
|
local result, done = {}, {} |
607
|
|
|
|
|
|
|
for k, v in ipairs( tbl ) do |
608
|
|
|
|
|
|
|
table.insert( result, table_val_to_str( v ) ) |
609
|
|
|
|
|
|
|
done[ k ] = true |
610
|
|
|
|
|
|
|
end |
611
|
|
|
|
|
|
|
for k, v in pairs( tbl ) do |
612
|
|
|
|
|
|
|
if not done[ k ] then |
613
|
|
|
|
|
|
|
table.insert( result, table_key_to_str( k ) .. "=" .. table_val_to_str( v ) ) |
614
|
|
|
|
|
|
|
end |
615
|
|
|
|
|
|
|
end |
616
|
|
|
|
|
|
|
return "{" .. table.concat( result, "," ) .. "}" |
617
|
|
|
|
|
|
|
end |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
-- deleting old data to make room for new data |
620
|
|
|
|
|
|
|
local cleaning = function ( list_id, data_id, is_cleaning_needed ) |
621
|
|
|
|
|
|
|
local coll_items = tonumber( redis.call( 'HGET', STATUS_KEY, '$_ITEMS' ) ) |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
if coll_items == 0 then |
624
|
|
|
|
|
|
|
return |
625
|
|
|
|
|
|
|
end |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
local cleanup_bytes = tonumber( redis.call( 'HGET', STATUS_KEY, '$_CLEANUP_BYTES' ) ) |
628
|
|
|
|
|
|
|
local cleanup_items = tonumber( redis.call( 'HGET', STATUS_KEY, '$_CLEANUP_ITEMS' ) ) |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
if not ( is_cleaning_needed or LAST_CLEANUP_BYTES_MUST_BE_DELETED > 0 ) then |
631
|
|
|
|
|
|
|
return |
632
|
|
|
|
|
|
|
end |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
if _DEBUG then |
635
|
|
|
|
|
|
|
_debug_log( { |
636
|
|
|
|
|
|
|
_STEP = 'Before cleanings', |
637
|
|
|
|
|
|
|
coll_items = coll_items, |
638
|
|
|
|
|
|
|
cleanup_items = cleanup_items, |
639
|
|
|
|
|
|
|
cleanup_bytes = cleanup_bytes, |
640
|
|
|
|
|
|
|
} ) |
641
|
|
|
|
|
|
|
end |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
local items_deleted = 0 |
644
|
|
|
|
|
|
|
local bytes_deleted = 0 |
645
|
|
|
|
|
|
|
local lists_deleted = 0 |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
repeat |
648
|
|
|
|
|
|
|
if redis.call( 'EXISTS', QUEUE_KEY ) == 0 then |
649
|
|
|
|
|
|
|
-- Level 2 points the error to where the function that called error was called |
650
|
|
|
|
|
|
|
renew_last_cleanup_values() |
651
|
|
|
|
|
|
|
error( 'Queue key does not exist', 2 ) |
652
|
|
|
|
|
|
|
end |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
-- continue to work with the to_delete (requiring removal) data and for them using the prefix 'to_delete_' |
655
|
|
|
|
|
|
|
local to_delete_list_id, last_removed_time = unpack( redis.call( 'ZRANGE', QUEUE_KEY, 0, 0, 'WITHSCORES' ) ) |
656
|
|
|
|
|
|
|
last_removed_time = tonumber( last_removed_time ) |
657
|
|
|
|
|
|
|
-- key data structures |
658
|
|
|
|
|
|
|
local to_delete_data_key = DATA_KEYS..':'..to_delete_list_id |
659
|
|
|
|
|
|
|
local to_delete_time_key = TIME_KEYS..':'..to_delete_list_id |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
-- looking for the oldest data |
662
|
|
|
|
|
|
|
local to_delete_data_id |
663
|
|
|
|
|
|
|
local to_delete_data |
664
|
|
|
|
|
|
|
local items = redis.call( 'HLEN', to_delete_data_key ) |
665
|
|
|
|
|
|
|
-- #FIXME: to_delete_data -> to_delete_data_len |
666
|
|
|
|
|
|
|
-- HSTRLEN key field |
667
|
|
|
|
|
|
|
-- Available since 3.2.0. |
668
|
|
|
|
|
|
|
if items == 1 then |
669
|
|
|
|
|
|
|
to_delete_data_id, to_delete_data = unpack( redis.call( 'HGETALL', to_delete_data_key ) ) |
670
|
|
|
|
|
|
|
else |
671
|
|
|
|
|
|
|
to_delete_data_id = unpack( redis.call( 'ZRANGE', to_delete_time_key, 0, 0 ) ) |
672
|
|
|
|
|
|
|
to_delete_data = redis.call( 'HGET', to_delete_data_key, to_delete_data_id ) |
673
|
|
|
|
|
|
|
end |
674
|
|
|
|
|
|
|
local to_delete_data_len = #to_delete_data |
675
|
|
|
|
|
|
|
to_delete_data = nil -- free memory |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
if _DEBUG then |
678
|
|
|
|
|
|
|
_debug_log( { |
679
|
|
|
|
|
|
|
_STEP = 'Before real cleaning', |
680
|
|
|
|
|
|
|
items = items, |
681
|
|
|
|
|
|
|
to_delete_list_id = to_delete_list_id, |
682
|
|
|
|
|
|
|
to_delete_data_id = to_delete_data_id, |
683
|
|
|
|
|
|
|
to_delete_data_len = to_delete_data_len |
684
|
|
|
|
|
|
|
} ) |
685
|
|
|
|
|
|
|
end |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
if to_delete_list_id == list_id and to_delete_data_id == data_id then |
688
|
|
|
|
|
|
|
if items_deleted == 0 then |
689
|
|
|
|
|
|
|
-- Its first attempt to clean the conflicting data, for which the primary operation executed. |
690
|
|
|
|
|
|
|
-- In this case, we are roll back operations that have been made before, and immediately return an error, |
691
|
|
|
|
|
|
|
-- shifting the handling of such errors on the client. |
692
|
|
|
|
|
|
|
cleaning_error( "$REDIS_MEMORY_ERROR_MSG" ) |
693
|
|
|
|
|
|
|
end |
694
|
|
|
|
|
|
|
break |
695
|
|
|
|
|
|
|
end |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
if _DEBUG then |
698
|
|
|
|
|
|
|
_debug_log( { |
699
|
|
|
|
|
|
|
_STEP = 'Why it is cleared?', |
700
|
|
|
|
|
|
|
coll_items = coll_items, |
701
|
|
|
|
|
|
|
cleanup_bytes = cleanup_bytes, |
702
|
|
|
|
|
|
|
cleanup_items = cleanup_items, |
703
|
|
|
|
|
|
|
items_deleted = items_deleted, |
704
|
|
|
|
|
|
|
bytes_deleted = bytes_deleted, |
705
|
|
|
|
|
|
|
} ) |
706
|
|
|
|
|
|
|
end |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
-- actually remove the oldest item |
709
|
|
|
|
|
|
|
redis.call( 'HDEL', to_delete_data_key, to_delete_data_id ) |
710
|
|
|
|
|
|
|
items = items - 1 |
711
|
|
|
|
|
|
|
coll_items = coll_items - 1 |
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_REMOVED_TIME', last_removed_time ) |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
if items > 0 then |
716
|
|
|
|
|
|
|
-- If the list has more data |
717
|
|
|
|
|
|
|
redis.call( 'ZREM', to_delete_time_key, to_delete_data_id ) |
718
|
|
|
|
|
|
|
local oldest_item_time = tonumber( redis.call( 'ZRANGE', to_delete_time_key, 0, 0, 'WITHSCORES' )[2] ) |
719
|
|
|
|
|
|
|
redis.call( 'ZADD', QUEUE_KEY, oldest_item_time, to_delete_list_id ) |
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
if items == 1 then |
722
|
|
|
|
|
|
|
redis.call( 'DEL', to_delete_time_key ) |
723
|
|
|
|
|
|
|
end |
724
|
|
|
|
|
|
|
else |
725
|
|
|
|
|
|
|
-- If the list does not have data |
726
|
|
|
|
|
|
|
-- remove the name of the list from the queue collection |
727
|
|
|
|
|
|
|
redis.call( 'ZREM', QUEUE_KEY, to_delete_list_id ) |
728
|
|
|
|
|
|
|
lists_deleted = lists_deleted + 1 |
729
|
|
|
|
|
|
|
end |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
-- amount of data collection decreased |
732
|
|
|
|
|
|
|
items_deleted = items_deleted + 1 |
733
|
|
|
|
|
|
|
bytes_deleted = bytes_deleted + to_delete_data_len |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
if _DEBUG then |
736
|
|
|
|
|
|
|
_debug_log( { |
737
|
|
|
|
|
|
|
_STEP = 'After real cleaning', |
738
|
|
|
|
|
|
|
to_delete_data_key = to_delete_data_key, |
739
|
|
|
|
|
|
|
to_delete_data_id = to_delete_data_id, |
740
|
|
|
|
|
|
|
items = items, |
741
|
|
|
|
|
|
|
items_deleted = items_deleted, |
742
|
|
|
|
|
|
|
bytes_deleted = bytes_deleted, |
743
|
|
|
|
|
|
|
} ) |
744
|
|
|
|
|
|
|
end |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
until |
747
|
|
|
|
|
|
|
coll_items <= 0 |
748
|
|
|
|
|
|
|
or ( |
749
|
|
|
|
|
|
|
items_deleted >= cleanup_items |
750
|
|
|
|
|
|
|
and bytes_deleted >= cleanup_bytes |
751
|
|
|
|
|
|
|
) |
752
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
if items_deleted > 0 then |
754
|
|
|
|
|
|
|
-- reduce the number of items in the collection |
755
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_ITEMS', -items_deleted ) |
756
|
|
|
|
|
|
|
end |
757
|
|
|
|
|
|
|
if lists_deleted > 0 then |
758
|
|
|
|
|
|
|
-- reduce the number of lists stored in a collection |
759
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_LISTS', -lists_deleted ) |
760
|
|
|
|
|
|
|
end |
761
|
|
|
|
|
|
|
|
762
|
|
|
|
|
|
|
if _DEBUG then |
763
|
|
|
|
|
|
|
_debug_log( { |
764
|
|
|
|
|
|
|
_STEP = 'Cleaning finished', |
765
|
|
|
|
|
|
|
items_deleted = items_deleted, |
766
|
|
|
|
|
|
|
bytes_deleted = bytes_deleted, |
767
|
|
|
|
|
|
|
lists_deleted = lists_deleted, |
768
|
|
|
|
|
|
|
cleanup_bytes = cleanup_bytes, |
769
|
|
|
|
|
|
|
cleanup_items = cleanup_items, |
770
|
|
|
|
|
|
|
coll_items = coll_items, |
771
|
|
|
|
|
|
|
} ) |
772
|
|
|
|
|
|
|
end |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
if bytes_deleted > 0 then |
775
|
|
|
|
|
|
|
if TOTAL_BYTES_DELETED == 0 then -- first cleaning |
776
|
|
|
|
|
|
|
INSERTS_SINCE_CLEANING = 0 |
777
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_INSERTS_SINCE_CLEANING', INSERTS_SINCE_CLEANING ) |
778
|
|
|
|
|
|
|
UPDATES_SINCE_CLEANING = 0 |
779
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_UPDATES_SINCE_CLEANING', UPDATES_SINCE_CLEANING ) |
780
|
|
|
|
|
|
|
end |
781
|
|
|
|
|
|
|
|
782
|
|
|
|
|
|
|
TOTAL_BYTES_DELETED = TOTAL_BYTES_DELETED + bytes_deleted |
783
|
|
|
|
|
|
|
LAST_CLEANUP_ITEMS = LAST_CLEANUP_ITEMS + items_deleted |
784
|
|
|
|
|
|
|
|
785
|
|
|
|
|
|
|
-- information values |
786
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_MAXMEMORY', REDIS_MAXMEMORY ) |
787
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_USED_MEMORY', REDIS_USED_MEMORY ) |
788
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_BYTES_MUST_BE_DELETED', LAST_CLEANUP_BYTES_MUST_BE_DELETED ) |
789
|
|
|
|
|
|
|
end |
790
|
|
|
|
|
|
|
end |
791
|
|
|
|
|
|
|
|
792
|
|
|
|
|
|
|
local call_with_error_control = function ( list_id, data_id, ... ) |
793
|
|
|
|
|
|
|
local retries = $MAX_REMOVE_RETRIES |
794
|
|
|
|
|
|
|
local ret |
795
|
|
|
|
|
|
|
local error_msg = '' |
796
|
|
|
|
|
|
|
repeat |
797
|
|
|
|
|
|
|
ret = redis.pcall( ... ) |
798
|
|
|
|
|
|
|
if type( ret ) == 'table' and ret.err ~= nil then |
799
|
|
|
|
|
|
|
error_msg = "$REDIS_MEMORY_ERROR_MSG - " .. ret.err .. " (call = " .. table_tostring( { ... } ) .. ")" |
800
|
|
|
|
|
|
|
if _DEBUG then |
801
|
|
|
|
|
|
|
_debug_log( { |
802
|
|
|
|
|
|
|
_STEP = 'call_with_error_control', |
803
|
|
|
|
|
|
|
error_msg = error_msg, |
804
|
|
|
|
|
|
|
retries = retries |
805
|
|
|
|
|
|
|
} ) |
806
|
|
|
|
|
|
|
end |
807
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
cleaning( list_id, data_id, true ) |
809
|
|
|
|
|
|
|
else |
810
|
|
|
|
|
|
|
break |
811
|
|
|
|
|
|
|
end |
812
|
|
|
|
|
|
|
retries = retries - 1 |
813
|
|
|
|
|
|
|
until retries == 0 |
814
|
|
|
|
|
|
|
|
815
|
|
|
|
|
|
|
if retries == 0 then |
816
|
|
|
|
|
|
|
-- Operation returned an error related to insufficient memory. |
817
|
|
|
|
|
|
|
-- Start cleaning process and then re-try operation. |
818
|
|
|
|
|
|
|
-- Repeat the cycle of operation + memory cleaning a couple of times and return an error / fail, |
819
|
|
|
|
|
|
|
-- if it still did not work. |
820
|
|
|
|
|
|
|
cleaning_error( error_msg ) |
821
|
|
|
|
|
|
|
end |
822
|
|
|
|
|
|
|
|
823
|
|
|
|
|
|
|
return ret |
824
|
|
|
|
|
|
|
end |
825
|
|
|
|
|
|
|
END_CLEANING |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
$lua_script_body{insert} = <<"END_INSERT"; |
828
|
|
|
|
|
|
|
-- adding data to a list of collections |
829
|
|
|
|
|
|
|
|
830
|
|
|
|
|
|
|
local coll_name = ARGV[1] |
831
|
|
|
|
|
|
|
local list_id = ARGV[2] |
832
|
|
|
|
|
|
|
local data_id = ARGV[3] |
833
|
|
|
|
|
|
|
local data = ARGV[4] |
834
|
|
|
|
|
|
|
local data_time = tonumber( ARGV[5] ) |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
-- key data storage structures |
837
|
|
|
|
|
|
|
$_lua_namespace |
838
|
|
|
|
|
|
|
$_lua_queue_key |
839
|
|
|
|
|
|
|
$_lua_status_key |
840
|
|
|
|
|
|
|
$_lua_data_keys |
841
|
|
|
|
|
|
|
$_lua_time_keys |
842
|
|
|
|
|
|
|
$_lua_data_key |
843
|
|
|
|
|
|
|
$_lua_time_key |
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
-- determine whether there is a list of data and a collection |
846
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
847
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, 0, 0, 0 } |
848
|
|
|
|
|
|
|
end |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
-- verification of the existence of old data with new data identifier |
851
|
|
|
|
|
|
|
if redis.call( 'HEXISTS', DATA_KEY, data_id ) == 1 then |
852
|
|
|
|
|
|
|
return { $E_DATA_ID_EXISTS, 0, 0, 0 } |
853
|
|
|
|
|
|
|
end |
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
-- Validating the time of new data, if required |
856
|
|
|
|
|
|
|
local last_removed_time = tonumber( redis.call( 'HGET', STATUS_KEY, '$_LAST_REMOVED_TIME' ) ) |
857
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
if tonumber( redis.call( 'HGET', STATUS_KEY, '$_OLDER_ALLOWED' ) ) == 0 then |
859
|
|
|
|
|
|
|
if redis.call( 'EXISTS', QUEUE_KEY ) == 1 then |
860
|
|
|
|
|
|
|
if data_time < last_removed_time then |
861
|
|
|
|
|
|
|
return { $E_OLDER_THAN_ALLOWED, 0, 0, 0 } |
862
|
|
|
|
|
|
|
end |
863
|
|
|
|
|
|
|
end |
864
|
|
|
|
|
|
|
end |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
-- deleting obsolete data, if it is necessary |
867
|
|
|
|
|
|
|
$_lua_cleaning |
868
|
|
|
|
|
|
|
local data_len = #data |
869
|
|
|
|
|
|
|
_setup( 6, 'insert', data_len ) -- 6 -> is the index of ARGV[6] |
870
|
|
|
|
|
|
|
cleaning( list_id, data_id, false ) |
871
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
-- add data to the list |
873
|
|
|
|
|
|
|
-- Remember that the list and the collection can be automatically deleted after the "crowding out" old data |
874
|
|
|
|
|
|
|
|
875
|
|
|
|
|
|
|
-- the existing data |
876
|
|
|
|
|
|
|
local items = redis.call( 'HLEN', DATA_KEY ) |
877
|
|
|
|
|
|
|
local existing_id, existing_time |
878
|
|
|
|
|
|
|
if items == 1 then |
879
|
|
|
|
|
|
|
existing_id = unpack( redis.call( 'HGETALL', DATA_KEY ) ) |
880
|
|
|
|
|
|
|
existing_time = tonumber( redis.call( 'ZSCORE', QUEUE_KEY, list_id ) ) |
881
|
|
|
|
|
|
|
end |
882
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
-- actually add data to the list |
884
|
|
|
|
|
|
|
call_with_error_control( list_id, data_id, 'HSET', DATA_KEY, data_id, data ) |
885
|
|
|
|
|
|
|
data = nil -- free memory |
886
|
|
|
|
|
|
|
table.insert( ROLLBACK, 1, { 'HDEL', DATA_KEY, data_id } ) |
887
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
if redis.call( 'HLEN', DATA_KEY ) == 1 then -- list recreated after cleaning |
889
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_LISTS', 1 ) |
890
|
|
|
|
|
|
|
table.insert( ROLLBACK, 1, { 'HINCRBY', STATUS_KEY, '$_LISTS', -1 } ) |
891
|
|
|
|
|
|
|
call_with_error_control( list_id, data_id, 'ZADD', QUEUE_KEY, data_time, list_id ) |
892
|
|
|
|
|
|
|
else |
893
|
|
|
|
|
|
|
if items == 1 then |
894
|
|
|
|
|
|
|
call_with_error_control( list_id, data_id, 'ZADD', TIME_KEY, existing_time, existing_id ) |
895
|
|
|
|
|
|
|
table.insert( ROLLBACK, 1, { 'ZREM', TIME_KEY, existing_id } ) |
896
|
|
|
|
|
|
|
end |
897
|
|
|
|
|
|
|
call_with_error_control( list_id, data_id, 'ZADD', TIME_KEY, data_time, data_id ) |
898
|
|
|
|
|
|
|
local oldest_item_time = redis.call( 'ZRANGE', TIME_KEY, 0, 0, 'WITHSCORES' )[2] |
899
|
|
|
|
|
|
|
redis.call( 'ZADD', QUEUE_KEY, oldest_item_time, list_id ) |
900
|
|
|
|
|
|
|
end |
901
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
-- reflect the addition of new data |
903
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_ITEMS', 1 ) |
904
|
|
|
|
|
|
|
if data_time < last_removed_time then |
905
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_REMOVED_TIME', 0 ) |
906
|
|
|
|
|
|
|
end |
907
|
|
|
|
|
|
|
|
908
|
|
|
|
|
|
|
renew_last_cleanup_values() |
909
|
|
|
|
|
|
|
-- redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_BYTES', LAST_CLEANUP_BYTES ) |
910
|
|
|
|
|
|
|
INSERTS_SINCE_CLEANING = INSERTS_SINCE_CLEANING + 1 |
911
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_INSERTS_SINCE_CLEANING', INSERTS_SINCE_CLEANING ) |
912
|
|
|
|
|
|
|
|
913
|
|
|
|
|
|
|
return { $E_NO_ERROR, LAST_CLEANUP_ITEMS, REDIS_USED_MEMORY, TOTAL_BYTES_DELETED } |
914
|
|
|
|
|
|
|
END_INSERT |
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
$lua_script_body{update} = <<"END_UPDATE"; |
917
|
|
|
|
|
|
|
-- update the data in the list of collections |
918
|
|
|
|
|
|
|
|
919
|
|
|
|
|
|
|
local coll_name = ARGV[1] |
920
|
|
|
|
|
|
|
local list_id = ARGV[2] |
921
|
|
|
|
|
|
|
local data_id = ARGV[3] |
922
|
|
|
|
|
|
|
local data = ARGV[4] |
923
|
|
|
|
|
|
|
local new_data_time = tonumber( ARGV[5] ) |
924
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
-- key data storage structures |
926
|
|
|
|
|
|
|
$_lua_namespace |
927
|
|
|
|
|
|
|
$_lua_queue_key |
928
|
|
|
|
|
|
|
$_lua_status_key |
929
|
|
|
|
|
|
|
$_lua_data_keys |
930
|
|
|
|
|
|
|
$_lua_time_keys |
931
|
|
|
|
|
|
|
$_lua_data_key |
932
|
|
|
|
|
|
|
$_lua_time_key |
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
-- determine whether there is a list of data and a collection |
935
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
936
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, 0, 0, 0 } |
937
|
|
|
|
|
|
|
end |
938
|
|
|
|
|
|
|
if redis.call( 'EXISTS', DATA_KEY ) == 0 then |
939
|
|
|
|
|
|
|
return { $E_NONEXISTENT_DATA_ID, 0, 0, 0 } |
940
|
|
|
|
|
|
|
end |
941
|
|
|
|
|
|
|
local extra_data_len |
942
|
|
|
|
|
|
|
local data_len = #data |
943
|
|
|
|
|
|
|
if redis.call( 'HEXISTS', DATA_KEY, data_id ) == 1 then |
944
|
|
|
|
|
|
|
-- #FIXME: existed_data -> existed_data_len |
945
|
|
|
|
|
|
|
-- HSTRLEN key field |
946
|
|
|
|
|
|
|
-- Available since 3.2.0. |
947
|
|
|
|
|
|
|
local existed_data = redis.call( 'HGET', DATA_KEY, data_id ) |
948
|
|
|
|
|
|
|
extra_data_len = data_len - #existed_data |
949
|
|
|
|
|
|
|
existed_data = nil -- free memory |
950
|
|
|
|
|
|
|
else |
951
|
|
|
|
|
|
|
return { $E_NONEXISTENT_DATA_ID, 0, 0, 0 } |
952
|
|
|
|
|
|
|
end |
953
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
local last_removed_time = tonumber( redis.call( 'HGET', STATUS_KEY, '$_LAST_REMOVED_TIME' ) ) |
955
|
|
|
|
|
|
|
if tonumber( redis.call( 'HGET', STATUS_KEY, '$_OLDER_ALLOWED' ) ) == 0 then |
956
|
|
|
|
|
|
|
if new_data_time ~= 0 and new_data_time < last_removed_time then |
957
|
|
|
|
|
|
|
return { $E_OLDER_THAN_ALLOWED, 0, 0, 0 } |
958
|
|
|
|
|
|
|
end |
959
|
|
|
|
|
|
|
end |
960
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
-- deleting obsolete data, if it can be necessary |
962
|
|
|
|
|
|
|
$_lua_cleaning |
963
|
|
|
|
|
|
|
_setup( 6, 'update', extra_data_len ) -- 6 is the index of ARGV[6] |
964
|
|
|
|
|
|
|
cleaning( list_id, data_id, false ) |
965
|
|
|
|
|
|
|
|
966
|
|
|
|
|
|
|
-- data change |
967
|
|
|
|
|
|
|
-- Remember that the list and the collection can be automatically deleted after the "crowding out" old data |
968
|
|
|
|
|
|
|
if redis.call( 'HEXISTS', DATA_KEY, data_id ) == 0 then |
969
|
|
|
|
|
|
|
return { $E_NONEXISTENT_DATA_ID, 0, 0, 0 } |
970
|
|
|
|
|
|
|
end |
971
|
|
|
|
|
|
|
|
972
|
|
|
|
|
|
|
-- data to be changed were not removed |
973
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
-- actually change |
975
|
|
|
|
|
|
|
call_with_error_control( list_id, data_id, 'HSET', DATA_KEY, data_id, data ) |
976
|
|
|
|
|
|
|
data = nil -- free memory |
977
|
|
|
|
|
|
|
|
978
|
|
|
|
|
|
|
if new_data_time ~= 0 then |
979
|
|
|
|
|
|
|
if redis.call( 'HLEN', DATA_KEY ) == 1 then |
980
|
|
|
|
|
|
|
redis.call( 'ZADD', QUEUE_KEY, new_data_time, list_id ) |
981
|
|
|
|
|
|
|
else |
982
|
|
|
|
|
|
|
redis.call( 'ZADD', TIME_KEY, new_data_time, data_id ) |
983
|
|
|
|
|
|
|
local oldest_item_time = tonumber( redis.call( 'ZRANGE', TIME_KEY, 0, 0, 'WITHSCORES' )[2] ) |
984
|
|
|
|
|
|
|
redis.call( 'ZADD', QUEUE_KEY, oldest_item_time, list_id ) |
985
|
|
|
|
|
|
|
end |
986
|
|
|
|
|
|
|
|
987
|
|
|
|
|
|
|
if new_data_time < last_removed_time then |
988
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_REMOVED_TIME', 0 ) |
989
|
|
|
|
|
|
|
end |
990
|
|
|
|
|
|
|
end |
991
|
|
|
|
|
|
|
|
992
|
|
|
|
|
|
|
renew_last_cleanup_values() |
993
|
|
|
|
|
|
|
UPDATES_SINCE_CLEANING = UPDATES_SINCE_CLEANING + 1 |
994
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_UPDATES_SINCE_CLEANING', UPDATES_SINCE_CLEANING ) |
995
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
return { $E_NO_ERROR, LAST_CLEANUP_ITEMS, REDIS_USED_MEMORY, TOTAL_BYTES_DELETED } |
997
|
|
|
|
|
|
|
END_UPDATE |
998
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
$lua_script_body{upsert} = <<"END_UPSERT"; |
1000
|
|
|
|
|
|
|
-- update or insert the data in the list of collections |
1001
|
|
|
|
|
|
|
|
1002
|
|
|
|
|
|
|
local coll_name = ARGV[1] |
1003
|
|
|
|
|
|
|
local list_id = ARGV[2] |
1004
|
|
|
|
|
|
|
local data_id = ARGV[3] |
1005
|
|
|
|
|
|
|
local data_time = tonumber( ARGV[5] ) |
1006
|
|
|
|
|
|
|
local start_time = ARGV[7] |
1007
|
|
|
|
|
|
|
|
1008
|
|
|
|
|
|
|
-- key data storage structures |
1009
|
|
|
|
|
|
|
$_lua_namespace |
1010
|
|
|
|
|
|
|
$_lua_data_keys |
1011
|
|
|
|
|
|
|
$_lua_data_key |
1012
|
|
|
|
|
|
|
|
1013
|
|
|
|
|
|
|
-- verification of the existence of old data with new data identifier |
1014
|
|
|
|
|
|
|
if redis.call( 'HEXISTS', DATA_KEY, data_id ) == 1 then |
1015
|
|
|
|
|
|
|
if data_time == -1 then |
1016
|
|
|
|
|
|
|
ARGV[5] = '0' |
1017
|
|
|
|
|
|
|
end |
1018
|
|
|
|
|
|
|
$lua_script_body{update} |
1019
|
|
|
|
|
|
|
else |
1020
|
|
|
|
|
|
|
if data_time == -1 then |
1021
|
|
|
|
|
|
|
ARGV[5] = start_time |
1022
|
|
|
|
|
|
|
end |
1023
|
|
|
|
|
|
|
$lua_script_body{insert} |
1024
|
|
|
|
|
|
|
end |
1025
|
|
|
|
|
|
|
END_UPSERT |
1026
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
$lua_script_body{receive} = <<"END_RECEIVE"; |
1028
|
|
|
|
|
|
|
-- returns the data from the list |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
local coll_name = ARGV[1] |
1031
|
|
|
|
|
|
|
local list_id = ARGV[2] |
1032
|
|
|
|
|
|
|
local mode = ARGV[3] |
1033
|
|
|
|
|
|
|
local data_id = ARGV[4] |
1034
|
|
|
|
|
|
|
|
1035
|
|
|
|
|
|
|
-- key data storage structures |
1036
|
|
|
|
|
|
|
$_lua_namespace |
1037
|
|
|
|
|
|
|
$_lua_status_key |
1038
|
|
|
|
|
|
|
$_lua_data_keys |
1039
|
|
|
|
|
|
|
$_lua_data_key |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
-- determine whether there is a list of data and a collection |
1042
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
1043
|
|
|
|
|
|
|
-- sort of a mistake |
1044
|
|
|
|
|
|
|
return nil |
1045
|
|
|
|
|
|
|
end |
1046
|
|
|
|
|
|
|
|
1047
|
|
|
|
|
|
|
if mode == 'val' then |
1048
|
|
|
|
|
|
|
-- returns the specified element of the data list |
1049
|
|
|
|
|
|
|
return redis.call( 'HGET', DATA_KEY, data_id ) |
1050
|
|
|
|
|
|
|
elseif mode == 'len' then |
1051
|
|
|
|
|
|
|
-- returns the length of the data list |
1052
|
|
|
|
|
|
|
return redis.call( 'HLEN', DATA_KEY ) |
1053
|
|
|
|
|
|
|
elseif mode == 'vals' then |
1054
|
|
|
|
|
|
|
-- returns all the data from the list |
1055
|
|
|
|
|
|
|
return redis.call( 'HVALS', DATA_KEY ) |
1056
|
|
|
|
|
|
|
elseif mode == 'all' then |
1057
|
|
|
|
|
|
|
-- returns all data IDs and data values of the data list |
1058
|
|
|
|
|
|
|
return redis.call( 'HGETALL', DATA_KEY ) |
1059
|
|
|
|
|
|
|
else |
1060
|
|
|
|
|
|
|
-- sort of a mistake |
1061
|
|
|
|
|
|
|
return nil |
1062
|
|
|
|
|
|
|
end |
1063
|
|
|
|
|
|
|
|
1064
|
|
|
|
|
|
|
END_RECEIVE |
1065
|
|
|
|
|
|
|
|
1066
|
|
|
|
|
|
|
$lua_script_body{pop_oldest} = <<"END_POP_OLDEST"; |
1067
|
|
|
|
|
|
|
-- retrieve the oldest data stored in the collection |
1068
|
|
|
|
|
|
|
|
1069
|
|
|
|
|
|
|
local coll_name = ARGV[1] |
1070
|
|
|
|
|
|
|
|
1071
|
|
|
|
|
|
|
-- key data storage structures |
1072
|
|
|
|
|
|
|
$_lua_namespace |
1073
|
|
|
|
|
|
|
$_lua_queue_key |
1074
|
|
|
|
|
|
|
$_lua_status_key |
1075
|
|
|
|
|
|
|
|
1076
|
|
|
|
|
|
|
-- determine whether there is a list of data and a collection |
1077
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
1078
|
|
|
|
|
|
|
-- sort of a mistake |
1079
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, nil, nil, nil } |
1080
|
|
|
|
|
|
|
end |
1081
|
|
|
|
|
|
|
if redis.call( 'EXISTS', QUEUE_KEY ) == 0 then |
1082
|
|
|
|
|
|
|
return { $E_NO_ERROR, false, nil, nil } |
1083
|
|
|
|
|
|
|
end |
1084
|
|
|
|
|
|
|
|
1085
|
|
|
|
|
|
|
-- initialize the data returned from the script |
1086
|
|
|
|
|
|
|
local list_exist = 0 |
1087
|
|
|
|
|
|
|
local list_id = false |
1088
|
|
|
|
|
|
|
local data = false |
1089
|
|
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
-- identifier of the list with the oldest data |
1091
|
|
|
|
|
|
|
list_id = unpack( redis.call( 'ZRANGE', QUEUE_KEY, 0, 0 ) ) |
1092
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
-- key data storage structures |
1094
|
|
|
|
|
|
|
$_lua_data_keys |
1095
|
|
|
|
|
|
|
$_lua_time_keys |
1096
|
|
|
|
|
|
|
$_lua_data_key |
1097
|
|
|
|
|
|
|
$_lua_time_key |
1098
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
-- determine whether there is a list of data and a collection |
1100
|
|
|
|
|
|
|
if redis.call( 'EXISTS', DATA_KEY ) == 0 then |
1101
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, nil, nil, nil } |
1102
|
|
|
|
|
|
|
end |
1103
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
-- Features the oldest data |
1105
|
|
|
|
|
|
|
local items = redis.call( 'HLEN', DATA_KEY ) |
1106
|
|
|
|
|
|
|
local data_id |
1107
|
|
|
|
|
|
|
if items == 1 then |
1108
|
|
|
|
|
|
|
data_id = unpack( redis.call( 'HGETALL', DATA_KEY ) ) |
1109
|
|
|
|
|
|
|
else |
1110
|
|
|
|
|
|
|
data_id = unpack( redis.call( 'ZRANGE', TIME_KEY, 0, 0 ) ) |
1111
|
|
|
|
|
|
|
end |
1112
|
|
|
|
|
|
|
local last_removed_time = tonumber( redis.call( 'ZRANGE', QUEUE_KEY, 0, 0, 'WITHSCORES' )[2] ) |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
-- get data |
1115
|
|
|
|
|
|
|
|
1116
|
|
|
|
|
|
|
-- actually get data |
1117
|
|
|
|
|
|
|
data = redis.call( 'HGET', DATA_KEY, data_id ) |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
-- delete the data from the list |
1120
|
|
|
|
|
|
|
redis.call( 'HDEL', DATA_KEY, data_id ) |
1121
|
|
|
|
|
|
|
items = items - 1 |
1122
|
|
|
|
|
|
|
|
1123
|
|
|
|
|
|
|
-- obtain information about the data that has become the oldest |
1124
|
|
|
|
|
|
|
local oldest_item_time = tonumber( redis.call( 'ZRANGE', TIME_KEY, 0, 0, 'WITHSCORES' )[2] ) |
1125
|
|
|
|
|
|
|
|
1126
|
|
|
|
|
|
|
if items > 0 then |
1127
|
|
|
|
|
|
|
-- If the list has more data |
1128
|
|
|
|
|
|
|
|
1129
|
|
|
|
|
|
|
-- delete the information about the time of the data |
1130
|
|
|
|
|
|
|
redis.call( 'ZREM', TIME_KEY, data_id ) |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
redis.call( 'ZADD', QUEUE_KEY, oldest_item_time, list_id ) |
1133
|
|
|
|
|
|
|
|
1134
|
|
|
|
|
|
|
if items == 1 then |
1135
|
|
|
|
|
|
|
-- delete the list data structure 'zset' |
1136
|
|
|
|
|
|
|
redis.call( 'DEL', TIME_KEY ) |
1137
|
|
|
|
|
|
|
end |
1138
|
|
|
|
|
|
|
else |
1139
|
|
|
|
|
|
|
-- if the list is no more data |
1140
|
|
|
|
|
|
|
-- delete the list data structure 'zset' |
1141
|
|
|
|
|
|
|
redis.call( 'DEL', TIME_KEY ) |
1142
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
-- reduce the number of lists stored in a collection |
1144
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_LISTS', -1 ) |
1145
|
|
|
|
|
|
|
-- remove the name of the list from the queue collection |
1146
|
|
|
|
|
|
|
redis.call( 'ZREM', QUEUE_KEY, list_id ) |
1147
|
|
|
|
|
|
|
end |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_ITEMS', -1 ) |
1150
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_REMOVED_TIME', last_removed_time ) |
1151
|
|
|
|
|
|
|
|
1152
|
|
|
|
|
|
|
return { $E_NO_ERROR, true, list_id, data } |
1153
|
|
|
|
|
|
|
END_POP_OLDEST |
1154
|
|
|
|
|
|
|
|
1155
|
|
|
|
|
|
|
$lua_script_body{collection_info} = <<"END_COLLECTION_INFO"; |
1156
|
|
|
|
|
|
|
-- to obtain information on the status of the collection |
1157
|
|
|
|
|
|
|
|
1158
|
|
|
|
|
|
|
local coll_name = ARGV[1] |
1159
|
|
|
|
|
|
|
|
1160
|
|
|
|
|
|
|
-- key data storage structures |
1161
|
|
|
|
|
|
|
$_lua_namespace |
1162
|
|
|
|
|
|
|
$_lua_queue_key |
1163
|
|
|
|
|
|
|
$_lua_status_key |
1164
|
|
|
|
|
|
|
|
1165
|
|
|
|
|
|
|
-- determine whether there is a collection |
1166
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
1167
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, false, false, false, false, false, false, false } |
1168
|
|
|
|
|
|
|
end |
1169
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
local oldest_item_time = redis.call( 'ZRANGE', QUEUE_KEY, 0, 0, 'WITHSCORES' )[2] |
1171
|
|
|
|
|
|
|
local lists, items, older_allowed, cleanup_bytes, cleanup_items, memory_reserve, data_version, last_removed_time = unpack( redis.call( 'HMGET', STATUS_KEY, |
1172
|
|
|
|
|
|
|
'$_LISTS', |
1173
|
|
|
|
|
|
|
'$_ITEMS', |
1174
|
|
|
|
|
|
|
'$_OLDER_ALLOWED', |
1175
|
|
|
|
|
|
|
'$_CLEANUP_BYTES', |
1176
|
|
|
|
|
|
|
'$_CLEANUP_ITEMS', |
1177
|
|
|
|
|
|
|
'$_MEMORY_RESERVE', |
1178
|
|
|
|
|
|
|
'$_DATA_VERSION', |
1179
|
|
|
|
|
|
|
'$_LAST_REMOVED_TIME' |
1180
|
|
|
|
|
|
|
) ) |
1181
|
|
|
|
|
|
|
|
1182
|
|
|
|
|
|
|
if type( data_version ) ~= 'string' then data_version = '0' end |
1183
|
|
|
|
|
|
|
|
1184
|
|
|
|
|
|
|
return { |
1185
|
|
|
|
|
|
|
$E_NO_ERROR, |
1186
|
|
|
|
|
|
|
lists, |
1187
|
|
|
|
|
|
|
items, |
1188
|
|
|
|
|
|
|
older_allowed, |
1189
|
|
|
|
|
|
|
cleanup_bytes, |
1190
|
|
|
|
|
|
|
cleanup_items, |
1191
|
|
|
|
|
|
|
memory_reserve, |
1192
|
|
|
|
|
|
|
data_version, |
1193
|
|
|
|
|
|
|
last_removed_time, |
1194
|
|
|
|
|
|
|
oldest_item_time |
1195
|
|
|
|
|
|
|
} |
1196
|
|
|
|
|
|
|
END_COLLECTION_INFO |
1197
|
|
|
|
|
|
|
|
1198
|
|
|
|
|
|
|
$lua_script_body{oldest_time} = <<"END_OLDEST_TIME"; |
1199
|
|
|
|
|
|
|
-- to obtain time corresponding to the oldest data in the collection |
1200
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
local coll_name = ARGV[1] |
1202
|
|
|
|
|
|
|
|
1203
|
|
|
|
|
|
|
-- key data storage structures |
1204
|
|
|
|
|
|
|
$_lua_namespace |
1205
|
|
|
|
|
|
|
$_lua_queue_key |
1206
|
|
|
|
|
|
|
$_lua_status_key |
1207
|
|
|
|
|
|
|
|
1208
|
|
|
|
|
|
|
-- determine whe, falther there is a collection |
1209
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
1210
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, false } |
1211
|
|
|
|
|
|
|
end |
1212
|
|
|
|
|
|
|
|
1213
|
|
|
|
|
|
|
local oldest_item_time = redis.call( 'ZRANGE', QUEUE_KEY, 0, 0, 'WITHSCORES' )[2] |
1214
|
|
|
|
|
|
|
return { $E_NO_ERROR, oldest_item_time } |
1215
|
|
|
|
|
|
|
END_OLDEST_TIME |
1216
|
|
|
|
|
|
|
|
1217
|
|
|
|
|
|
|
$lua_script_body{list_info} = <<"END_LIST_INFO"; |
1218
|
|
|
|
|
|
|
-- to obtain information on the status of the data list |
1219
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
local coll_name = ARGV[1] |
1221
|
|
|
|
|
|
|
local list_id = ARGV[2] |
1222
|
|
|
|
|
|
|
|
1223
|
|
|
|
|
|
|
-- key data storage structures |
1224
|
|
|
|
|
|
|
$_lua_namespace |
1225
|
|
|
|
|
|
|
$_lua_queue_key |
1226
|
|
|
|
|
|
|
$_lua_status_key |
1227
|
|
|
|
|
|
|
$_lua_data_keys |
1228
|
|
|
|
|
|
|
$_lua_data_key |
1229
|
|
|
|
|
|
|
|
1230
|
|
|
|
|
|
|
-- determine whether there is a list of data and a collection |
1231
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
1232
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, false, nil } |
1233
|
|
|
|
|
|
|
end |
1234
|
|
|
|
|
|
|
if redis.call( 'EXISTS', DATA_KEY ) == 0 then |
1235
|
|
|
|
|
|
|
return { $E_NO_ERROR, false, nil } |
1236
|
|
|
|
|
|
|
end |
1237
|
|
|
|
|
|
|
|
1238
|
|
|
|
|
|
|
-- the length of the data list |
1239
|
|
|
|
|
|
|
local items = redis.call( 'HLEN', DATA_KEY ) |
1240
|
|
|
|
|
|
|
|
1241
|
|
|
|
|
|
|
-- the second data |
1242
|
|
|
|
|
|
|
local oldest_item_time = redis.call( 'ZSCORE', QUEUE_KEY, list_id ) |
1243
|
|
|
|
|
|
|
|
1244
|
|
|
|
|
|
|
return { $E_NO_ERROR, items, oldest_item_time } |
1245
|
|
|
|
|
|
|
END_LIST_INFO |
1246
|
|
|
|
|
|
|
|
1247
|
|
|
|
|
|
|
$lua_script_body{drop_collection} = <<"END_DROP_COLLECTION"; |
1248
|
|
|
|
|
|
|
-- to remove the entire collection |
1249
|
|
|
|
|
|
|
|
1250
|
|
|
|
|
|
|
local coll_name = ARGV[1] |
1251
|
|
|
|
|
|
|
|
1252
|
|
|
|
|
|
|
-- key data storage structures |
1253
|
|
|
|
|
|
|
$_lua_namespace |
1254
|
|
|
|
|
|
|
$_lua_queue_key |
1255
|
|
|
|
|
|
|
$_lua_status_key |
1256
|
|
|
|
|
|
|
|
1257
|
|
|
|
|
|
|
-- initialize the data returned from the script |
1258
|
|
|
|
|
|
|
local ret = 0 -- the number of deleted items |
1259
|
|
|
|
|
|
|
|
1260
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 1 then |
1261
|
|
|
|
|
|
|
ret = ret + redis.call( 'DEL', STATUS_KEY ) |
1262
|
|
|
|
|
|
|
end |
1263
|
|
|
|
|
|
|
|
1264
|
|
|
|
|
|
|
$_lua_clean_data |
1265
|
|
|
|
|
|
|
END_DROP_COLLECTION |
1266
|
|
|
|
|
|
|
|
1267
|
|
|
|
|
|
|
$lua_script_body{clear_collection} = <<"END_CLEAR_COLLECTION"; |
1268
|
|
|
|
|
|
|
-- to remove the entire collection data |
1269
|
|
|
|
|
|
|
|
1270
|
|
|
|
|
|
|
local coll_name = ARGV[1] |
1271
|
|
|
|
|
|
|
|
1272
|
|
|
|
|
|
|
-- key data storage structures |
1273
|
|
|
|
|
|
|
$_lua_namespace |
1274
|
|
|
|
|
|
|
$_lua_queue_key |
1275
|
|
|
|
|
|
|
$_lua_status_key |
1276
|
|
|
|
|
|
|
|
1277
|
|
|
|
|
|
|
-- initialize the data returned from the script |
1278
|
|
|
|
|
|
|
local ret = 0 -- the number of deleted items |
1279
|
|
|
|
|
|
|
|
1280
|
|
|
|
|
|
|
redis.call( 'HMSET', STATUS_KEY, |
1281
|
|
|
|
|
|
|
'$_LISTS', 0, |
1282
|
|
|
|
|
|
|
'$_ITEMS', 0, |
1283
|
|
|
|
|
|
|
'$_LAST_REMOVED_TIME', 0 |
1284
|
|
|
|
|
|
|
); |
1285
|
|
|
|
|
|
|
|
1286
|
|
|
|
|
|
|
$_lua_clean_data |
1287
|
|
|
|
|
|
|
END_CLEAR_COLLECTION |
1288
|
|
|
|
|
|
|
|
1289
|
|
|
|
|
|
|
$lua_script_body{drop_list} = <<"END_DROP_LIST"; |
1290
|
|
|
|
|
|
|
-- to remove the data_list |
1291
|
|
|
|
|
|
|
|
1292
|
|
|
|
|
|
|
local coll_name = ARGV[1] |
1293
|
|
|
|
|
|
|
local list_id = ARGV[2] |
1294
|
|
|
|
|
|
|
|
1295
|
|
|
|
|
|
|
-- key data storage structures |
1296
|
|
|
|
|
|
|
$_lua_namespace |
1297
|
|
|
|
|
|
|
$_lua_queue_key |
1298
|
|
|
|
|
|
|
$_lua_status_key |
1299
|
|
|
|
|
|
|
$_lua_data_keys |
1300
|
|
|
|
|
|
|
$_lua_data_key |
1301
|
|
|
|
|
|
|
|
1302
|
|
|
|
|
|
|
-- determine whether there is a list of data and a collection |
1303
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
1304
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, 0 } |
1305
|
|
|
|
|
|
|
end |
1306
|
|
|
|
|
|
|
if redis.call( 'EXISTS', DATA_KEY ) == 0 then |
1307
|
|
|
|
|
|
|
return { $E_NO_ERROR, 0 } |
1308
|
|
|
|
|
|
|
end |
1309
|
|
|
|
|
|
|
|
1310
|
|
|
|
|
|
|
-- initialize the data returned from the script |
1311
|
|
|
|
|
|
|
local ret = 0 -- the number of deleted items |
1312
|
|
|
|
|
|
|
|
1313
|
|
|
|
|
|
|
-- key data storage structures |
1314
|
|
|
|
|
|
|
$_lua_time_keys |
1315
|
|
|
|
|
|
|
$_lua_time_key |
1316
|
|
|
|
|
|
|
|
1317
|
|
|
|
|
|
|
-- determine the size of the data in the list and delete the list structure |
1318
|
|
|
|
|
|
|
local bytes_deleted = 0 |
1319
|
|
|
|
|
|
|
local vals = redis.call( 'HVALS', DATA_KEY ) |
1320
|
|
|
|
|
|
|
local list_items = #vals |
1321
|
|
|
|
|
|
|
for i = 1, list_items do |
1322
|
|
|
|
|
|
|
bytes_deleted = bytes_deleted + #vals[ i ] |
1323
|
|
|
|
|
|
|
end |
1324
|
|
|
|
|
|
|
redis.call( 'DEL', DATA_KEY, TIME_KEY ) |
1325
|
|
|
|
|
|
|
|
1326
|
|
|
|
|
|
|
-- reduce the number of items in the collection |
1327
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_ITEMS', -list_items ) |
1328
|
|
|
|
|
|
|
-- reduce the number of lists stored in a collection |
1329
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_LISTS', -1 ) |
1330
|
|
|
|
|
|
|
-- remove the name of the list from the queue collection |
1331
|
|
|
|
|
|
|
redis.call( 'ZREM', QUEUE_KEY, list_id ) |
1332
|
|
|
|
|
|
|
|
1333
|
|
|
|
|
|
|
return { $E_NO_ERROR, 1 } |
1334
|
|
|
|
|
|
|
END_DROP_LIST |
1335
|
|
|
|
|
|
|
|
1336
|
|
|
|
|
|
|
$lua_script_body{verify_collection} = <<"END_VERIFY_COLLECTION"; |
1337
|
|
|
|
|
|
|
-- creation of the collection and characterization of the collection by accessing existing collection |
1338
|
|
|
|
|
|
|
|
1339
|
|
|
|
|
|
|
local coll_name = ARGV[1]; |
1340
|
|
|
|
|
|
|
local older_allowed = ARGV[2]; |
1341
|
|
|
|
|
|
|
local cleanup_bytes = ARGV[3]; |
1342
|
|
|
|
|
|
|
local cleanup_items = ARGV[4]; |
1343
|
|
|
|
|
|
|
local memory_reserve = ARGV[5]; |
1344
|
|
|
|
|
|
|
|
1345
|
|
|
|
|
|
|
local data_version = '$DATA_VERSION' |
1346
|
|
|
|
|
|
|
|
1347
|
|
|
|
|
|
|
-- key data storage structures |
1348
|
|
|
|
|
|
|
$_lua_namespace |
1349
|
|
|
|
|
|
|
$_lua_status_key |
1350
|
|
|
|
|
|
|
|
1351
|
|
|
|
|
|
|
-- determine whether there is a collection |
1352
|
|
|
|
|
|
|
local status_exist = redis.call( 'EXISTS', STATUS_KEY ); |
1353
|
|
|
|
|
|
|
|
1354
|
|
|
|
|
|
|
if status_exist == 1 then |
1355
|
|
|
|
|
|
|
-- if there is a collection |
1356
|
|
|
|
|
|
|
older_allowed, cleanup_bytes, cleanup_items, memory_reserve, data_version = unpack( redis.call( 'HMGET', STATUS_KEY, |
1357
|
|
|
|
|
|
|
'$_OLDER_ALLOWED', |
1358
|
|
|
|
|
|
|
'$_CLEANUP_BYTES', |
1359
|
|
|
|
|
|
|
'$_CLEANUP_ITEMS', |
1360
|
|
|
|
|
|
|
'$_MEMORY_RESERVE', |
1361
|
|
|
|
|
|
|
'$_DATA_VERSION' |
1362
|
|
|
|
|
|
|
) ); |
1363
|
|
|
|
|
|
|
|
1364
|
|
|
|
|
|
|
if type( data_version ) ~= 'string' then data_version = '0' end |
1365
|
|
|
|
|
|
|
else |
1366
|
|
|
|
|
|
|
-- if you want to create a new collection |
1367
|
|
|
|
|
|
|
redis.call( 'HMSET', STATUS_KEY, |
1368
|
|
|
|
|
|
|
'$_LISTS', 0, |
1369
|
|
|
|
|
|
|
'$_ITEMS', 0, |
1370
|
|
|
|
|
|
|
'$_OLDER_ALLOWED', older_allowed, |
1371
|
|
|
|
|
|
|
'$_CLEANUP_BYTES', cleanup_bytes, |
1372
|
|
|
|
|
|
|
'$_CLEANUP_ITEMS', cleanup_items, |
1373
|
|
|
|
|
|
|
'$_MEMORY_RESERVE', memory_reserve, |
1374
|
|
|
|
|
|
|
'$_DATA_VERSION', data_version, |
1375
|
|
|
|
|
|
|
'$_LAST_REMOVED_TIME', 0, |
1376
|
|
|
|
|
|
|
'$_LAST_CLEANUP_BYTES', '[0]', |
1377
|
|
|
|
|
|
|
'$_LAST_CLEANUP_ITEMS', '[0]' |
1378
|
|
|
|
|
|
|
); |
1379
|
|
|
|
|
|
|
end |
1380
|
|
|
|
|
|
|
|
1381
|
|
|
|
|
|
|
return { |
1382
|
|
|
|
|
|
|
status_exist, |
1383
|
|
|
|
|
|
|
older_allowed, |
1384
|
|
|
|
|
|
|
cleanup_bytes, |
1385
|
|
|
|
|
|
|
cleanup_items, |
1386
|
|
|
|
|
|
|
memory_reserve, |
1387
|
|
|
|
|
|
|
data_version |
1388
|
|
|
|
|
|
|
}; |
1389
|
|
|
|
|
|
|
END_VERIFY_COLLECTION |
1390
|
|
|
|
|
|
|
|
1391
|
|
|
|
|
|
|
$lua_script_body{_long_term_operation} = <<"END_LONG_TERM_OPERATION"; |
1392
|
|
|
|
|
|
|
local coll_name = ARGV[1]; |
1393
|
|
|
|
|
|
|
local return_as_insert = tonumber( ARGV[2] ); |
1394
|
|
|
|
|
|
|
|
1395
|
|
|
|
|
|
|
local STATUS_KEY = 'C:S:'..coll_name; |
1396
|
|
|
|
|
|
|
local DATA_VERSION_KEY = '$_DATA_VERSION'; |
1397
|
|
|
|
|
|
|
|
1398
|
|
|
|
|
|
|
local LIST = 'Test_list'; |
1399
|
|
|
|
|
|
|
local DATA = 'Data'; |
1400
|
|
|
|
|
|
|
local MAX_WORKING_CYCLES = 5000000; |
1401
|
|
|
|
|
|
|
|
1402
|
|
|
|
|
|
|
redis.call( 'DEL', LIST ); |
1403
|
|
|
|
|
|
|
|
1404
|
|
|
|
|
|
|
local ret; |
1405
|
|
|
|
|
|
|
local i = 1; |
1406
|
|
|
|
|
|
|
while i < MAX_WORKING_CYCLES do |
1407
|
|
|
|
|
|
|
-- simple active actions |
1408
|
|
|
|
|
|
|
local data_version = redis.call( 'HGET', STATUS_KEY, DATA_VERSION_KEY ); |
1409
|
|
|
|
|
|
|
ret = redis.call( 'HSET', LIST, i, DATA ); |
1410
|
|
|
|
|
|
|
|
1411
|
|
|
|
|
|
|
i = i + 1; |
1412
|
|
|
|
|
|
|
end |
1413
|
|
|
|
|
|
|
|
1414
|
|
|
|
|
|
|
if return_as_insert == 1 then |
1415
|
|
|
|
|
|
|
return { $E_NO_ERROR, 0, 0, 0 }; |
1416
|
|
|
|
|
|
|
else |
1417
|
|
|
|
|
|
|
return { $E_NO_ERROR, ret, '_long_term_operation' }; |
1418
|
|
|
|
|
|
|
end |
1419
|
|
|
|
|
|
|
END_LONG_TERM_OPERATION |
1420
|
|
|
|
|
|
|
|
1421
|
|
|
|
|
|
|
subtype __PACKAGE__.'::NonNegInt', |
1422
|
|
|
|
|
|
|
as 'Int', |
1423
|
|
|
|
|
|
|
where { $_ >= 0 }, |
1424
|
|
|
|
|
|
|
message { format_message( '%s is not a non-negative integer!', $_ ) } |
1425
|
|
|
|
|
|
|
; |
1426
|
|
|
|
|
|
|
|
1427
|
|
|
|
|
|
|
subtype __PACKAGE__.'::NonNegNum', |
1428
|
|
|
|
|
|
|
as 'Num', |
1429
|
|
|
|
|
|
|
where { $_ >= 0 }, |
1430
|
|
|
|
|
|
|
message { format_message( '%s is not a non-negative number!', $_ ) } |
1431
|
|
|
|
|
|
|
; |
1432
|
|
|
|
|
|
|
|
1433
|
|
|
|
|
|
|
subtype __PACKAGE__.'::NonEmptNameStr', |
1434
|
|
|
|
|
|
|
as 'Str', |
1435
|
|
|
|
|
|
|
where { $_ ne '' && $_ !~ /:/ }, |
1436
|
|
|
|
|
|
|
message { format_message( '%s is not a non-empty string!', $_ ) } |
1437
|
|
|
|
|
|
|
; |
1438
|
|
|
|
|
|
|
|
1439
|
|
|
|
|
|
|
subtype __PACKAGE__.'::DataStr', |
1440
|
|
|
|
|
|
|
as 'Str', |
1441
|
|
|
|
|
|
|
where { bytes::length( $_ ) <= $MAX_DATASIZE }, |
1442
|
|
|
|
|
|
|
message { format_message( "'%s' is not a valid data string!", $_ ) } |
1443
|
|
|
|
|
|
|
; |
1444
|
|
|
|
|
|
|
|
1445
|
|
|
|
|
|
|
#-- constructor ---------------------------------------------------------------- |
1446
|
|
|
|
|
|
|
|
1447
|
|
|
|
|
|
|
=head2 CONSTRUCTOR |
1448
|
|
|
|
|
|
|
|
1449
|
|
|
|
|
|
|
=head3 create |
1450
|
|
|
|
|
|
|
|
1451
|
|
|
|
|
|
|
create( redis => $server, name => $name, ... ) |
1452
|
|
|
|
|
|
|
|
1453
|
|
|
|
|
|
|
Create a new collection on the Redis server and return an C |
1454
|
|
|
|
|
|
|
object to access it. Must be called as a class method only. |
1455
|
|
|
|
|
|
|
|
1456
|
|
|
|
|
|
|
The C creates and returns a C object that is configured |
1457
|
|
|
|
|
|
|
to work with the default settings if the corresponding arguments were not given. |
1458
|
|
|
|
|
|
|
|
1459
|
|
|
|
|
|
|
C argument can be either an existing object of L class |
1460
|
|
|
|
|
|
|
(which is then used for all communication with Redis server) or a hash reference used to create a |
1461
|
|
|
|
|
|
|
new internal Redis object. See documentation of L module for details. |
1462
|
|
|
|
|
|
|
|
1463
|
|
|
|
|
|
|
C takes arguments in key-value pairs. |
1464
|
|
|
|
|
|
|
|
1465
|
|
|
|
|
|
|
This example illustrates a C call with all the valid arguments: |
1466
|
|
|
|
|
|
|
|
1467
|
|
|
|
|
|
|
my $coll = Redis::CappedCollection->create( |
1468
|
|
|
|
|
|
|
redis => { server => "$server:$port" }, # Redis object |
1469
|
|
|
|
|
|
|
# or hash reference to parameters to create a new Redis object. |
1470
|
|
|
|
|
|
|
name => 'Some name', # Redis::CappedCollection collection name. |
1471
|
|
|
|
|
|
|
cleanup_bytes => 50_000, # The minimum size, in bytes, |
1472
|
|
|
|
|
|
|
# of the data to be released when performing memory cleanup. |
1473
|
|
|
|
|
|
|
cleanup_items => 1_000, # The minimum number of the collection |
1474
|
|
|
|
|
|
|
# elements to be realesed when performing memory cleanup. |
1475
|
|
|
|
|
|
|
max_datasize => 1_000_000, # Maximum size, in bytes, of the data. |
1476
|
|
|
|
|
|
|
# Default 512MB. |
1477
|
|
|
|
|
|
|
older_allowed => 0, # Allow adding an element to collection that's older |
1478
|
|
|
|
|
|
|
# than the last element removed from collection. |
1479
|
|
|
|
|
|
|
# Default 0. |
1480
|
|
|
|
|
|
|
check_maxmemory => 1, # Controls if collection should try to find out maximum |
1481
|
|
|
|
|
|
|
# available memory from Redis. |
1482
|
|
|
|
|
|
|
# In some cases Redis implementation forbids such request, |
1483
|
|
|
|
|
|
|
# but setting 'check_maxmemory' to false can be used |
1484
|
|
|
|
|
|
|
# as a workaround. |
1485
|
|
|
|
|
|
|
memory_reserve => 0.05, # Reserve coefficient of 'maxmemory'. |
1486
|
|
|
|
|
|
|
# Not used when 'maxmemory' == 0 (it is not set in the redis.conf). |
1487
|
|
|
|
|
|
|
# When you add or modify the data trying to ensure |
1488
|
|
|
|
|
|
|
# reserve of free memory for metadata and bookkeeping. |
1489
|
|
|
|
|
|
|
reconnect_on_error => 0, # Controls ability to force re-connection with Redis on error. |
1490
|
|
|
|
|
|
|
connection_timeout => $DEFAULT_CONNECTION_TIMEOUT, # Socket timeout for connection, |
1491
|
|
|
|
|
|
|
# number of seconds (can be fractional). |
1492
|
|
|
|
|
|
|
# NOTE: Changes external socket configuration. |
1493
|
|
|
|
|
|
|
operation_timeout => $DEFAULT_OPERATION_TIMEOUT, # Socket timeout for read and write operations, |
1494
|
|
|
|
|
|
|
# number of seconds (can be fractional). |
1495
|
|
|
|
|
|
|
# NOTE: Changes external socket configuration. |
1496
|
|
|
|
|
|
|
); |
1497
|
|
|
|
|
|
|
|
1498
|
|
|
|
|
|
|
The C and C arguments are required. |
1499
|
|
|
|
|
|
|
Do not use the symbol C<':'> in C. |
1500
|
|
|
|
|
|
|
|
1501
|
|
|
|
|
|
|
The following examples illustrate other uses of the C method: |
1502
|
|
|
|
|
|
|
|
1503
|
|
|
|
|
|
|
my $redis = Redis->new( server => "$server:$port" ); |
1504
|
|
|
|
|
|
|
my $coll = Redis::CappedCollection->create( redis => $redis, name => 'Next collection' ); |
1505
|
|
|
|
|
|
|
my $next_coll = Redis::CappedCollection->create( redis => $coll, name => 'Some name' ); |
1506
|
|
|
|
|
|
|
|
1507
|
|
|
|
|
|
|
An error exception is thrown (C) if an argument is not valid or the collection with |
1508
|
|
|
|
|
|
|
same name already exists. |
1509
|
|
|
|
|
|
|
|
1510
|
|
|
|
|
|
|
=cut |
1511
|
|
|
|
|
|
|
sub create { |
1512
|
0
|
0
|
|
0
|
1
|
|
my $class = _CLASSISA( shift, __PACKAGE__ ) or _croak( 'Must be called as a class method only' ); |
1513
|
0
|
|
|
|
|
|
return $class->new( @_, _create_from_naked_new => 0 ); |
1514
|
|
|
|
|
|
|
} |
1515
|
|
|
|
|
|
|
|
1516
|
|
|
|
|
|
|
sub BUILD { |
1517
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
1518
|
|
|
|
|
|
|
|
1519
|
0
|
|
|
|
|
|
my $redis = $self->redis; |
1520
|
0
|
0
|
|
|
|
|
if ( _INSTANCE( $redis, 'Redis' ) ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1521
|
|
|
|
|
|
|
# have to look into the Redis object ... |
1522
|
0
|
|
|
|
|
|
$self->_server( $redis->{server} ); |
1523
|
0
|
|
|
|
|
|
$self->_redis( $redis ); |
1524
|
|
|
|
|
|
|
} elsif ( _INSTANCE( $redis, 'Test::RedisServer' ) ) { |
1525
|
|
|
|
|
|
|
# to test only |
1526
|
|
|
|
|
|
|
# have to look into the Test::RedisServer object ... |
1527
|
0
|
|
|
|
|
|
my $conf = $redis->conf; |
1528
|
0
|
0
|
|
|
|
|
$conf->{server} = '127.0.0.1:'.$conf->{port} unless exists $conf->{server}; |
1529
|
0
|
|
|
|
|
|
$self->_server( $conf->{server} ); |
1530
|
0
|
|
|
|
|
|
$self->_redis( Redis->new( server => $conf->{server} ) ); |
1531
|
|
|
|
|
|
|
} elsif ( _INSTANCE( $redis, __PACKAGE__ ) ) { |
1532
|
0
|
|
|
|
|
|
$self->_server( $redis->_server ); |
1533
|
0
|
|
|
|
|
|
$self->_redis( $self->_redis ); |
1534
|
|
|
|
|
|
|
} else { # $redis is hash ref |
1535
|
0
|
|
0
|
|
|
|
$self->_server( $redis->{server} // "$DEFAULT_SERVER:$DEFAULT_PORT" ); |
1536
|
|
|
|
|
|
|
|
1537
|
|
|
|
|
|
|
# defaults for the case when the Redis object we create |
1538
|
0
|
0
|
|
|
|
|
$redis->{reconnect} = 0 unless exists $redis->{reconnect}; |
1539
|
0
|
0
|
|
|
|
|
$redis->{every} = 1000 unless exists $redis->{every}; # 1 ms |
1540
|
0
|
0
|
|
|
|
|
$redis->{conservative_reconnect} = 0 unless exists $redis->{conservative_reconnect}; |
1541
|
0
|
0
|
|
|
|
|
$redis->{cnx_timeout} = $DEFAULT_CONNECTION_TIMEOUT unless exists $redis->{cnx_timeout}; |
1542
|
0
|
0
|
|
|
|
|
$redis->{read_timeout} = $DEFAULT_OPERATION_TIMEOUT unless exists $redis->{read_timeout}; |
1543
|
0
|
0
|
|
|
|
|
$redis->{write_timeout} = $DEFAULT_OPERATION_TIMEOUT unless exists $redis->{write_timeout}; |
1544
|
|
|
|
|
|
|
|
1545
|
0
|
|
|
|
|
|
$self->_redis( $self->_redis_constructor( $redis ) ); |
1546
|
0
|
|
|
|
|
|
$self->_use_external_connection( 0 ); |
1547
|
|
|
|
|
|
|
} |
1548
|
|
|
|
|
|
|
|
1549
|
0
|
|
|
|
|
|
$self->_connection_timeout_trigger( $self->connection_timeout ); |
1550
|
0
|
|
|
|
|
|
$self->_operation_timeout_trigger( $self->operation_timeout ); |
1551
|
|
|
|
|
|
|
|
1552
|
0
|
0
|
|
|
|
|
if ( $self->_create_from_naked_new ) { |
1553
|
0
|
|
|
|
|
|
warn 'Redis::CappedCollection->new() is deprecated and will be removed in future. Please use either create() or open() instead.'; |
1554
|
|
|
|
|
|
|
} else { |
1555
|
0
|
0
|
0
|
|
|
|
_croak( format_message( "Collection '%s' already exists", $self->name ) ) |
1556
|
|
|
|
|
|
|
if !$self->_create_from_open && $self->collection_exists( name => $self->name ); |
1557
|
|
|
|
|
|
|
} |
1558
|
|
|
|
|
|
|
|
1559
|
0
|
|
|
|
|
|
my $maxmemory; |
1560
|
0
|
0
|
|
|
|
|
if ( $self->_check_maxmemory ) { |
1561
|
0
|
|
|
|
|
|
( undef, $maxmemory ) = $self->_call_redis( 'CONFIG', 'GET', 'maxmemory' ); |
1562
|
0
|
0
|
|
|
|
|
defined( _NONNEGINT( $maxmemory ) ) |
1563
|
|
|
|
|
|
|
or $self->_throw( $E_NETWORK ); |
1564
|
|
|
|
|
|
|
} else { |
1565
|
|
|
|
|
|
|
# 0 means all system memory |
1566
|
0
|
|
|
|
|
|
$maxmemory = 0; |
1567
|
|
|
|
|
|
|
} |
1568
|
|
|
|
|
|
|
|
1569
|
0
|
|
|
|
|
|
my ( $major, $minor ) = $self->_redis->info->{redis_version} =~ /^(\d+)\.(\d+)/; |
1570
|
0
|
0
|
0
|
|
|
|
if ( $major < 2 || ( $major == 2 && $minor < 8 ) ) { |
|
|
|
0
|
|
|
|
|
1571
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_REDIS ); |
1572
|
0
|
|
|
|
|
|
_croak( "Need a Redis server version 2.8 or higher" ); |
1573
|
|
|
|
|
|
|
} |
1574
|
|
|
|
|
|
|
|
1575
|
0
|
0
|
|
|
|
|
$self->_throw( $E_MAXMEMORY_POLICY ) |
1576
|
|
|
|
|
|
|
unless $self->_maxmemory_policy_ok; |
1577
|
|
|
|
|
|
|
|
1578
|
0
|
|
|
|
|
|
$self->_maxmemory( $maxmemory ); |
1579
|
0
|
0
|
|
|
|
|
$self->max_datasize( min $self->_maxmemory, $self->max_datasize ) |
1580
|
|
|
|
|
|
|
if $self->_maxmemory; |
1581
|
|
|
|
|
|
|
|
1582
|
0
|
|
|
|
|
|
$self->_queue_key( $NAMESPACE.':Q:'.$self->name ); |
1583
|
0
|
|
|
|
|
|
$self->_status_key( _make_status_key( $self->name ) ); |
1584
|
0
|
|
|
|
|
|
$self->_data_keys( _make_data_key( $self->name ) ); |
1585
|
0
|
|
|
|
|
|
$self->_time_keys( $NAMESPACE.':T:'.$self->name ); |
1586
|
|
|
|
|
|
|
|
1587
|
0
|
0
|
|
|
|
|
$self->_verify_collection unless $self->_create_from_open; |
1588
|
|
|
|
|
|
|
} |
1589
|
|
|
|
|
|
|
|
1590
|
|
|
|
|
|
|
#-- public attributes ---------------------------------------------------------- |
1591
|
|
|
|
|
|
|
|
1592
|
|
|
|
|
|
|
=head3 open |
1593
|
|
|
|
|
|
|
|
1594
|
|
|
|
|
|
|
open( redis => $server, name => $name, ... ) |
1595
|
|
|
|
|
|
|
|
1596
|
|
|
|
|
|
|
Example: |
1597
|
|
|
|
|
|
|
|
1598
|
|
|
|
|
|
|
my $redis = Redis->new( server => "$server:$port" ); |
1599
|
|
|
|
|
|
|
my $coll = Redis::CappedCollection::open( redis => $redis, name => 'Some name' ); |
1600
|
|
|
|
|
|
|
|
1601
|
|
|
|
|
|
|
Create a C object to work with an existing collection |
1602
|
|
|
|
|
|
|
(created by L). It must be called as a class method only. |
1603
|
|
|
|
|
|
|
|
1604
|
|
|
|
|
|
|
C takes optional arguments. These arguments are in key-value pairs. |
1605
|
|
|
|
|
|
|
Arguments description is the same as for L method. |
1606
|
|
|
|
|
|
|
|
1607
|
|
|
|
|
|
|
=over 3 |
1608
|
|
|
|
|
|
|
|
1609
|
|
|
|
|
|
|
=item I |
1610
|
|
|
|
|
|
|
|
1611
|
|
|
|
|
|
|
=item I |
1612
|
|
|
|
|
|
|
|
1613
|
|
|
|
|
|
|
=item I |
1614
|
|
|
|
|
|
|
|
1615
|
|
|
|
|
|
|
=item I |
1616
|
|
|
|
|
|
|
|
1617
|
|
|
|
|
|
|
=item I |
1618
|
|
|
|
|
|
|
|
1619
|
|
|
|
|
|
|
=item I |
1620
|
|
|
|
|
|
|
|
1621
|
|
|
|
|
|
|
=item I |
1622
|
|
|
|
|
|
|
|
1623
|
|
|
|
|
|
|
=back |
1624
|
|
|
|
|
|
|
|
1625
|
|
|
|
|
|
|
The C and C arguments are mandatory. |
1626
|
|
|
|
|
|
|
|
1627
|
|
|
|
|
|
|
The C creates and returns a C object that is configured |
1628
|
|
|
|
|
|
|
to work with the default settings if the corresponding arguments are not given. |
1629
|
|
|
|
|
|
|
|
1630
|
|
|
|
|
|
|
If C argument is not a L object, a new connection to Redis is established using |
1631
|
|
|
|
|
|
|
passed hash reference to create a new L object. |
1632
|
|
|
|
|
|
|
|
1633
|
|
|
|
|
|
|
An error exception is thrown (C) if an argument is not valid. |
1634
|
|
|
|
|
|
|
|
1635
|
|
|
|
|
|
|
=cut |
1636
|
|
|
|
|
|
|
my @_asked_parameters = qw( |
1637
|
|
|
|
|
|
|
redis |
1638
|
|
|
|
|
|
|
name |
1639
|
|
|
|
|
|
|
max_datasize |
1640
|
|
|
|
|
|
|
check_maxmemory |
1641
|
|
|
|
|
|
|
reconnect_on_error |
1642
|
|
|
|
|
|
|
connection_timeout |
1643
|
|
|
|
|
|
|
operation_timeout |
1644
|
|
|
|
|
|
|
); |
1645
|
|
|
|
|
|
|
my @_status_parameters = qw( |
1646
|
|
|
|
|
|
|
older_allowed |
1647
|
|
|
|
|
|
|
cleanup_bytes |
1648
|
|
|
|
|
|
|
cleanup_items |
1649
|
|
|
|
|
|
|
memory_reserve |
1650
|
|
|
|
|
|
|
); |
1651
|
|
|
|
|
|
|
|
1652
|
|
|
|
|
|
|
sub open { |
1653
|
0
|
0
|
|
0
|
1
|
|
my $class = _CLASSISA( shift, __PACKAGE__ ) or _croak( 'Must be called as a class method only' ); |
1654
|
|
|
|
|
|
|
|
1655
|
0
|
|
|
|
|
|
my %params = @_; |
1656
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%params, \@_asked_parameters ); |
1657
|
|
|
|
|
|
|
|
1658
|
0
|
0
|
|
|
|
|
_croak( "'redis' argument is required" ) unless exists $params{redis}; |
1659
|
0
|
0
|
|
|
|
|
_croak( "'name' argument is required" ) unless exists $params{name}; |
1660
|
|
|
|
|
|
|
|
1661
|
0
|
|
|
|
|
|
my $use_external_connection = ref( $params{redis} ) ne 'HASH'; |
1662
|
0
|
|
|
|
|
|
my $redis = $params{redis} = _get_redis( $params{redis} ); |
1663
|
0
|
|
|
|
|
|
my $name = $params{name}; |
1664
|
0
|
0
|
|
|
|
|
if ( collection_exists( redis => $redis, name => $name ) ) { |
1665
|
0
|
|
|
|
|
|
my $info = collection_info( redis => $redis, name => $name ); |
1666
|
0
|
0
|
|
|
|
|
$info->{data_version} == $DATA_VERSION or _croak( $ERROR{ $E_INCOMP_DATA_VERSION } ); |
1667
|
0
|
|
|
|
|
|
$params{ $_ } = $info->{ $_ } foreach @_status_parameters; |
1668
|
0
|
|
|
|
|
|
return $class->new( %params, |
1669
|
|
|
|
|
|
|
_create_from_naked_new => 0, |
1670
|
|
|
|
|
|
|
_create_from_open => 1, |
1671
|
|
|
|
|
|
|
_use_external_connection => $use_external_connection, |
1672
|
|
|
|
|
|
|
); |
1673
|
|
|
|
|
|
|
} else { |
1674
|
0
|
|
|
|
|
|
_croak( format_message( "Collection '%s' does not exist", $name ) ); |
1675
|
|
|
|
|
|
|
}; |
1676
|
|
|
|
|
|
|
} |
1677
|
|
|
|
|
|
|
|
1678
|
|
|
|
|
|
|
=head2 METHODS |
1679
|
|
|
|
|
|
|
|
1680
|
|
|
|
|
|
|
An exception is thrown (C) if any method argument is not valid or |
1681
|
|
|
|
|
|
|
if a required argument is missing. |
1682
|
|
|
|
|
|
|
|
1683
|
|
|
|
|
|
|
ATTENTION: In the L module the synchronous commands throw an |
1684
|
|
|
|
|
|
|
exception on receipt of an error reply, or return a non-error reply directly. |
1685
|
|
|
|
|
|
|
|
1686
|
|
|
|
|
|
|
=cut |
1687
|
|
|
|
|
|
|
|
1688
|
|
|
|
|
|
|
=head3 name |
1689
|
|
|
|
|
|
|
|
1690
|
|
|
|
|
|
|
Get collection C attribute (collection ID). |
1691
|
|
|
|
|
|
|
The method returns the current value of the attribute. |
1692
|
|
|
|
|
|
|
The C attribute value is used in the L. |
1693
|
|
|
|
|
|
|
|
1694
|
|
|
|
|
|
|
=cut |
1695
|
|
|
|
|
|
|
has name => ( |
1696
|
|
|
|
|
|
|
is => 'ro', |
1697
|
|
|
|
|
|
|
clearer => '_clear_name', |
1698
|
|
|
|
|
|
|
isa => __PACKAGE__.'::NonEmptNameStr', |
1699
|
|
|
|
|
|
|
required => 1, |
1700
|
|
|
|
|
|
|
); |
1701
|
|
|
|
|
|
|
|
1702
|
|
|
|
|
|
|
=head3 redis |
1703
|
|
|
|
|
|
|
|
1704
|
|
|
|
|
|
|
Existing L object or a hash reference with parameters to create a new one. |
1705
|
|
|
|
|
|
|
|
1706
|
|
|
|
|
|
|
=cut |
1707
|
|
|
|
|
|
|
has redis => ( |
1708
|
|
|
|
|
|
|
is => 'ro', |
1709
|
|
|
|
|
|
|
isa => 'Redis|Test::RedisServer|HashRef', |
1710
|
|
|
|
|
|
|
required => 1, |
1711
|
|
|
|
|
|
|
); |
1712
|
|
|
|
|
|
|
|
1713
|
|
|
|
|
|
|
=head3 reconnect_on_error |
1714
|
|
|
|
|
|
|
|
1715
|
|
|
|
|
|
|
Controls ability to force re-connection with Redis on error. |
1716
|
|
|
|
|
|
|
|
1717
|
|
|
|
|
|
|
=cut |
1718
|
|
|
|
|
|
|
has reconnect_on_error => ( |
1719
|
|
|
|
|
|
|
is => 'rw', |
1720
|
|
|
|
|
|
|
isa => 'Bool', |
1721
|
|
|
|
|
|
|
default => 0, |
1722
|
|
|
|
|
|
|
); |
1723
|
|
|
|
|
|
|
|
1724
|
|
|
|
|
|
|
=head3 connection_timeout |
1725
|
|
|
|
|
|
|
|
1726
|
|
|
|
|
|
|
Controls socket timeout for Redis server connection, number of seconds (can be fractional). |
1727
|
|
|
|
|
|
|
|
1728
|
|
|
|
|
|
|
NOTE: Changes external socket configuration. |
1729
|
|
|
|
|
|
|
|
1730
|
|
|
|
|
|
|
=cut |
1731
|
|
|
|
|
|
|
has connection_timeout => ( |
1732
|
|
|
|
|
|
|
is => 'rw', |
1733
|
|
|
|
|
|
|
isa => 'Maybe['.__PACKAGE__.'::NonNegNum]', |
1734
|
|
|
|
|
|
|
default => undef, |
1735
|
|
|
|
|
|
|
trigger => \&_connection_timeout_trigger, |
1736
|
|
|
|
|
|
|
); |
1737
|
|
|
|
|
|
|
|
1738
|
|
|
|
|
|
|
sub _connection_timeout_trigger { |
1739
|
0
|
|
|
0
|
|
|
my ( $self, $timeout, $old_timeout ) = @_; |
1740
|
|
|
|
|
|
|
|
1741
|
0
|
0
|
0
|
|
|
|
return if scalar( @_ ) == 2 && ( !defined( $timeout ) && !defined( $old_timeout ) ); |
|
|
|
0
|
|
|
|
|
1742
|
|
|
|
|
|
|
|
1743
|
0
|
0
|
|
|
|
|
if ( my $redis = $self->_redis ) { |
1744
|
0
|
0
|
|
|
|
|
my $socket = _INSTANCE( $redis->{sock}, 'IO::Socket' ) or _croak( 'Bad socket object' ); |
1745
|
|
|
|
|
|
|
# IO::Socket provides a way to set a timeout on the socket, |
1746
|
|
|
|
|
|
|
# but the timeout will be used only for connection, |
1747
|
|
|
|
|
|
|
# not for reading / writing operations. |
1748
|
0
|
|
|
|
|
|
$socket->timeout( $redis->{cnx_timeout} = $timeout ); |
1749
|
|
|
|
|
|
|
} |
1750
|
|
|
|
|
|
|
} |
1751
|
|
|
|
|
|
|
|
1752
|
|
|
|
|
|
|
=head3 operation_timeout |
1753
|
|
|
|
|
|
|
|
1754
|
|
|
|
|
|
|
Controls socket timeout for Redis server read and write operations, number of seconds (can be fractional). |
1755
|
|
|
|
|
|
|
|
1756
|
|
|
|
|
|
|
NOTE: Changes external socket configuration. |
1757
|
|
|
|
|
|
|
|
1758
|
|
|
|
|
|
|
=cut |
1759
|
|
|
|
|
|
|
has operation_timeout => ( |
1760
|
|
|
|
|
|
|
is => 'rw', |
1761
|
|
|
|
|
|
|
isa => 'Maybe['.__PACKAGE__.'::NonNegNum]', |
1762
|
|
|
|
|
|
|
default => undef, |
1763
|
|
|
|
|
|
|
trigger => \&_operation_timeout_trigger, |
1764
|
|
|
|
|
|
|
); |
1765
|
|
|
|
|
|
|
|
1766
|
|
|
|
|
|
|
sub _operation_timeout_trigger { |
1767
|
0
|
|
|
0
|
|
|
my ( $self, $timeout, $old_timeout ) = @_; |
1768
|
|
|
|
|
|
|
|
1769
|
0
|
0
|
0
|
|
|
|
return if scalar( @_ ) == 2 && ( !defined( $timeout ) && !defined( $old_timeout ) ); |
|
|
|
0
|
|
|
|
|
1770
|
|
|
|
|
|
|
|
1771
|
0
|
0
|
|
|
|
|
if ( my $redis = $self->_redis ) { |
1772
|
0
|
0
|
|
|
|
|
my $socket = _INSTANCE( $redis->{sock}, 'IO::Socket' ) or _croak( 'Bad socket object' ); |
1773
|
|
|
|
|
|
|
# IO::Socket::Timeout provides a way to set a timeout |
1774
|
|
|
|
|
|
|
# on read / write operations on an IO::Socket instance, |
1775
|
|
|
|
|
|
|
# or any IO::Socket::* modules, like IO::Socket::INET. |
1776
|
0
|
0
|
|
|
|
|
if ( defined $timeout ) { |
1777
|
0
|
|
|
|
|
|
$redis->{write_timeout} = $redis->{read_timeout} = $timeout; |
1778
|
0
|
|
|
|
|
|
$redis->_maybe_enable_timeouts( $socket ); |
1779
|
0
|
|
|
|
|
|
$socket->enable_timeout; |
1780
|
|
|
|
|
|
|
} else { |
1781
|
0
|
|
|
|
|
|
$redis->{write_timeout} = $redis->{read_timeout} = 0; |
1782
|
0
|
|
|
|
|
|
$redis->_maybe_enable_timeouts( $socket ); |
1783
|
0
|
|
|
|
|
|
$socket->disable_timeout; |
1784
|
|
|
|
|
|
|
} |
1785
|
|
|
|
|
|
|
} |
1786
|
|
|
|
|
|
|
} |
1787
|
|
|
|
|
|
|
|
1788
|
|
|
|
|
|
|
=head3 cleanup_bytes |
1789
|
|
|
|
|
|
|
|
1790
|
|
|
|
|
|
|
Accessor for C attribute - The minimum size, in bytes, |
1791
|
|
|
|
|
|
|
of the data to be released when performing memory cleanup. |
1792
|
|
|
|
|
|
|
Default 0. |
1793
|
|
|
|
|
|
|
|
1794
|
|
|
|
|
|
|
The C attribute is designed to reduce the release of memory |
1795
|
|
|
|
|
|
|
operations with frequent data changes. |
1796
|
|
|
|
|
|
|
|
1797
|
|
|
|
|
|
|
The C attribute value can be provided to L. |
1798
|
|
|
|
|
|
|
The method returns and sets the current value of the attribute. |
1799
|
|
|
|
|
|
|
|
1800
|
|
|
|
|
|
|
The C value must be less than or equal to C<'maxmemory'>. Otherwise |
1801
|
|
|
|
|
|
|
an error exception is thrown (C). |
1802
|
|
|
|
|
|
|
|
1803
|
|
|
|
|
|
|
=cut |
1804
|
|
|
|
|
|
|
has cleanup_bytes => ( |
1805
|
|
|
|
|
|
|
is => 'rw', |
1806
|
|
|
|
|
|
|
writer => '_set_cleanup_bytes', |
1807
|
|
|
|
|
|
|
isa => __PACKAGE__.'::NonNegInt', |
1808
|
|
|
|
|
|
|
default => 0, |
1809
|
|
|
|
|
|
|
trigger => sub { |
1810
|
|
|
|
|
|
|
my $self = shift; |
1811
|
|
|
|
|
|
|
!$self->_maxmemory || ( $self->cleanup_bytes <= $self->maxmemory || $self->_throw( $E_MISMATCH_ARG, 'cleanup_bytes' ) ); |
1812
|
|
|
|
|
|
|
}, |
1813
|
|
|
|
|
|
|
); |
1814
|
|
|
|
|
|
|
|
1815
|
|
|
|
|
|
|
=head3 cleanup_items |
1816
|
|
|
|
|
|
|
|
1817
|
|
|
|
|
|
|
The minimum number of the collection elements to be realesed |
1818
|
|
|
|
|
|
|
when performing memory cleanup. Default 100. |
1819
|
|
|
|
|
|
|
|
1820
|
|
|
|
|
|
|
The C attribute is designed to reduce number of times collection cleanup takes place. |
1821
|
|
|
|
|
|
|
Setting value too high may result in unwanted delays during operations with Redis. |
1822
|
|
|
|
|
|
|
|
1823
|
|
|
|
|
|
|
The C attribute value can be used in the L. |
1824
|
|
|
|
|
|
|
The method returns and sets the current value of the attribute. |
1825
|
|
|
|
|
|
|
|
1826
|
|
|
|
|
|
|
=cut |
1827
|
|
|
|
|
|
|
has cleanup_items => ( |
1828
|
|
|
|
|
|
|
is => 'rw', |
1829
|
|
|
|
|
|
|
writer => '_set_cleanup_items', |
1830
|
|
|
|
|
|
|
isa => __PACKAGE__.'::NonNegInt', |
1831
|
|
|
|
|
|
|
default => 100, |
1832
|
|
|
|
|
|
|
); |
1833
|
|
|
|
|
|
|
|
1834
|
|
|
|
|
|
|
=head3 max_datasize |
1835
|
|
|
|
|
|
|
|
1836
|
|
|
|
|
|
|
Accessor for the C attribute. |
1837
|
|
|
|
|
|
|
|
1838
|
|
|
|
|
|
|
The method returns the current value of the attribute if called without arguments. |
1839
|
|
|
|
|
|
|
|
1840
|
|
|
|
|
|
|
Non-negative integer value can be used to specify a new value to |
1841
|
|
|
|
|
|
|
the maximum size of the data introduced into the collection |
1842
|
|
|
|
|
|
|
(methods L and L). |
1843
|
|
|
|
|
|
|
|
1844
|
|
|
|
|
|
|
The C attribute value is used in the L |
1845
|
|
|
|
|
|
|
and operations data entry on the Redis server. |
1846
|
|
|
|
|
|
|
|
1847
|
|
|
|
|
|
|
The L uses the smaller of the values of 512MB and |
1848
|
|
|
|
|
|
|
C<'maxmemory'> limit from a F file. |
1849
|
|
|
|
|
|
|
|
1850
|
|
|
|
|
|
|
=cut |
1851
|
|
|
|
|
|
|
has max_datasize => ( |
1852
|
|
|
|
|
|
|
is => 'rw', |
1853
|
|
|
|
|
|
|
isa => __PACKAGE__.'::NonNegInt', |
1854
|
|
|
|
|
|
|
default => $MAX_DATASIZE, |
1855
|
|
|
|
|
|
|
lazy => 1, |
1856
|
|
|
|
|
|
|
trigger => sub { |
1857
|
|
|
|
|
|
|
my $self = shift; |
1858
|
|
|
|
|
|
|
$self->max_datasize <= ( $self->_maxmemory ? min( $self->_maxmemory, $MAX_DATASIZE ) : $MAX_DATASIZE ) |
1859
|
|
|
|
|
|
|
|| $self->_throw( $E_MISMATCH_ARG, 'max_datasize' ); |
1860
|
|
|
|
|
|
|
}, |
1861
|
|
|
|
|
|
|
); |
1862
|
|
|
|
|
|
|
|
1863
|
|
|
|
|
|
|
=head3 older_allowed |
1864
|
|
|
|
|
|
|
|
1865
|
|
|
|
|
|
|
Accessor for the C attribute which controls if adding an element |
1866
|
|
|
|
|
|
|
that is older than the last element removed from collection is allowed. |
1867
|
|
|
|
|
|
|
Default is C<0> (not allowed). |
1868
|
|
|
|
|
|
|
|
1869
|
|
|
|
|
|
|
The method returns the current value of the attribute. |
1870
|
|
|
|
|
|
|
The C attribute value is used in the L. |
1871
|
|
|
|
|
|
|
|
1872
|
|
|
|
|
|
|
=cut |
1873
|
|
|
|
|
|
|
has older_allowed => ( |
1874
|
|
|
|
|
|
|
is => 'rw', |
1875
|
|
|
|
|
|
|
isa => 'Bool', |
1876
|
|
|
|
|
|
|
default => 0, |
1877
|
|
|
|
|
|
|
); |
1878
|
|
|
|
|
|
|
|
1879
|
|
|
|
|
|
|
=head3 memory_reserve |
1880
|
|
|
|
|
|
|
|
1881
|
|
|
|
|
|
|
Accessor for the C attribute which specifies the amount of additional |
1882
|
|
|
|
|
|
|
memory reserved for metadata and bookkeeping. |
1883
|
|
|
|
|
|
|
Default C<0.05> (5%) of 'maxmemory'. |
1884
|
|
|
|
|
|
|
Not used when C<'maxmemory'> == 0 (it is not set in the F). |
1885
|
|
|
|
|
|
|
|
1886
|
|
|
|
|
|
|
Valid values must be between C<$MIN_MEMORY_RESERVE> and C<$MAX_MEMORY_RESERVE>. |
1887
|
|
|
|
|
|
|
|
1888
|
|
|
|
|
|
|
The method returns the current value of the attribute. |
1889
|
|
|
|
|
|
|
The C attribute value is used in the L. |
1890
|
|
|
|
|
|
|
|
1891
|
|
|
|
|
|
|
=cut |
1892
|
|
|
|
|
|
|
has memory_reserve => ( |
1893
|
|
|
|
|
|
|
is => 'rw', |
1894
|
|
|
|
|
|
|
writer => '_set_memory_reserve', |
1895
|
|
|
|
|
|
|
isa => 'Num', |
1896
|
|
|
|
|
|
|
default => $MIN_MEMORY_RESERVE, |
1897
|
|
|
|
|
|
|
trigger => sub { |
1898
|
|
|
|
|
|
|
my $self = shift; |
1899
|
|
|
|
|
|
|
my $memory_reserve = $self->memory_reserve; |
1900
|
|
|
|
|
|
|
( _NUMBER( $memory_reserve ) && $memory_reserve >= $MIN_MEMORY_RESERVE && $memory_reserve <= $MAX_MEMORY_RESERVE ) |
1901
|
|
|
|
|
|
|
|| $self->_throw( $E_MISMATCH_ARG, 'memory_reserve' ); |
1902
|
|
|
|
|
|
|
}, |
1903
|
|
|
|
|
|
|
); |
1904
|
|
|
|
|
|
|
|
1905
|
|
|
|
|
|
|
=head3 last_errorcode |
1906
|
|
|
|
|
|
|
|
1907
|
|
|
|
|
|
|
Get code of the last error. |
1908
|
|
|
|
|
|
|
|
1909
|
|
|
|
|
|
|
See the list of supported error codes in L section. |
1910
|
|
|
|
|
|
|
|
1911
|
|
|
|
|
|
|
=cut |
1912
|
|
|
|
|
|
|
has last_errorcode => ( |
1913
|
|
|
|
|
|
|
reader => 'last_errorcode', |
1914
|
|
|
|
|
|
|
writer => '_set_last_errorcode', |
1915
|
|
|
|
|
|
|
isa => 'Int', |
1916
|
|
|
|
|
|
|
default => 0, |
1917
|
|
|
|
|
|
|
); |
1918
|
|
|
|
|
|
|
|
1919
|
|
|
|
|
|
|
#-- public methods ------------------------------------------------------------- |
1920
|
|
|
|
|
|
|
|
1921
|
|
|
|
|
|
|
=head3 insert |
1922
|
|
|
|
|
|
|
|
1923
|
|
|
|
|
|
|
insert( $list_id, $data_id, $data, $data_time ) |
1924
|
|
|
|
|
|
|
|
1925
|
|
|
|
|
|
|
Example: |
1926
|
|
|
|
|
|
|
|
1927
|
|
|
|
|
|
|
$list_id = $coll->insert( 'Some List_id', 'Some Data_id', 'Some data' ); |
1928
|
|
|
|
|
|
|
|
1929
|
|
|
|
|
|
|
$list_id = $coll->insert( 'Another List_id', 'Data ID', 'More data', Time::HiRes::time() ); |
1930
|
|
|
|
|
|
|
|
1931
|
|
|
|
|
|
|
Insert data into the capped collection on the Redis server. |
1932
|
|
|
|
|
|
|
|
1933
|
|
|
|
|
|
|
Arguments: |
1934
|
|
|
|
|
|
|
|
1935
|
|
|
|
|
|
|
=over 3 |
1936
|
|
|
|
|
|
|
|
1937
|
|
|
|
|
|
|
=item C<$list_id> |
1938
|
|
|
|
|
|
|
|
1939
|
|
|
|
|
|
|
Mandatory, non-empty string: list ID. Must not contain C<':'>. |
1940
|
|
|
|
|
|
|
|
1941
|
|
|
|
|
|
|
The data will be inserted into the list with given ID, and the list |
1942
|
|
|
|
|
|
|
is created automatically if it does not exist yet. |
1943
|
|
|
|
|
|
|
|
1944
|
|
|
|
|
|
|
=item C<$data_id> |
1945
|
|
|
|
|
|
|
|
1946
|
|
|
|
|
|
|
Mandatory, non-empty string: data ID, unique within the list identified by C<$list_id> |
1947
|
|
|
|
|
|
|
argument. |
1948
|
|
|
|
|
|
|
|
1949
|
|
|
|
|
|
|
=item C<$data> |
1950
|
|
|
|
|
|
|
|
1951
|
|
|
|
|
|
|
Data value: a string. Data length should not exceed value of L attribute. |
1952
|
|
|
|
|
|
|
|
1953
|
|
|
|
|
|
|
=item C<$data_time> |
1954
|
|
|
|
|
|
|
|
1955
|
|
|
|
|
|
|
Optional data time, a non-negative number. If not specified, the current |
1956
|
|
|
|
|
|
|
value returned by C |
1957
|
|
|
|
|
|
|
returned by L module) are supported to have time |
1958
|
|
|
|
|
|
|
granularity of less than 1 second and stored with 4 decimal places. |
1959
|
|
|
|
|
|
|
|
1960
|
|
|
|
|
|
|
=back |
1961
|
|
|
|
|
|
|
|
1962
|
|
|
|
|
|
|
If collection is set to C and C<$data_time> less than time of the last removed |
1963
|
|
|
|
|
|
|
element (C - see C) then C is set to 0. |
1964
|
|
|
|
|
|
|
The L attribute value is used in the L. |
1965
|
|
|
|
|
|
|
|
1966
|
|
|
|
|
|
|
The method returns the ID of the data list to which the data was inserted (value of |
1967
|
|
|
|
|
|
|
the C<$list_id> argument). |
1968
|
|
|
|
|
|
|
|
1969
|
|
|
|
|
|
|
=cut |
1970
|
|
|
|
|
|
|
sub insert { |
1971
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
1972
|
0
|
|
|
|
|
|
my $list_id = shift; |
1973
|
0
|
|
|
|
|
|
my $data_id = shift; |
1974
|
0
|
|
|
|
|
|
my $data = shift; |
1975
|
0
|
|
0
|
|
|
|
my $data_time = shift // time; |
1976
|
|
|
|
|
|
|
|
1977
|
0
|
|
0
|
|
|
|
$data // $self->_throw( $E_MISMATCH_ARG, 'data' ); |
1978
|
0
|
0
|
0
|
|
|
|
( defined( _STRING( $data ) ) || $data eq '' ) || $self->_throw( $E_MISMATCH_ARG, 'data' ); |
1979
|
0
|
|
0
|
|
|
|
_STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
1980
|
0
|
0
|
|
|
|
|
$list_id !~ /:/ || $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
1981
|
0
|
0
|
|
|
|
|
defined( _STRING( $data_id ) ) || $self->_throw( $E_MISMATCH_ARG, 'data_id' ); |
1982
|
0
|
0
|
0
|
|
|
|
( defined( _NUMBER( $data_time ) ) && $data_time > 0 ) || $self->_throw( $E_MISMATCH_ARG, 'data_time' ); |
1983
|
|
|
|
|
|
|
|
1984
|
0
|
|
|
|
|
|
my $data_len = bytes::length( $data ); |
1985
|
0
|
0
|
|
|
|
|
( $data_len <= $self->max_datasize ) || $self->_throw( $E_DATA_TOO_LARGE ); |
1986
|
|
|
|
|
|
|
|
1987
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
1988
|
|
|
|
|
|
|
|
1989
|
0
|
|
|
|
|
|
my @ret = $self->_call_redis( |
1990
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'insert' ), |
1991
|
|
|
|
|
|
|
0, |
1992
|
|
|
|
|
|
|
$self->name, |
1993
|
|
|
|
|
|
|
$list_id, |
1994
|
|
|
|
|
|
|
$data_id, |
1995
|
|
|
|
|
|
|
$data, |
1996
|
|
|
|
|
|
|
$data_time, |
1997
|
|
|
|
|
|
|
# Recommend the inclusion of this option in the case of incomprehensible errors |
1998
|
|
|
|
|
|
|
$self->_DEBUG, |
1999
|
|
|
|
|
|
|
); |
2000
|
|
|
|
|
|
|
|
2001
|
0
|
|
|
|
|
|
my ( $error, $_last_cleanup_items, $_used_memory, $_total_bytes_deleted ) = @ret; |
2002
|
|
|
|
|
|
|
|
2003
|
0
|
0
|
0
|
|
|
|
if ( scalar( @ret ) == 4 && exists( $ERROR{ $error } ) && defined( _NONNEGINT( $_last_cleanup_items ) ) ) { |
|
|
|
0
|
|
|
|
|
2004
|
0
|
0
|
0
|
|
|
|
if ( $error == $E_NO_ERROR ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
2005
|
|
|
|
|
|
|
# Normal result: Nothing to do |
2006
|
|
|
|
|
|
|
} elsif ( $error == $E_COLLECTION_DELETED ) { |
2007
|
0
|
|
|
|
|
|
$self->_clear_sha1; |
2008
|
0
|
|
|
|
|
|
$self->_throw( $error ); |
2009
|
|
|
|
|
|
|
} elsif ( |
2010
|
|
|
|
|
|
|
$error == $E_DATA_ID_EXISTS |
2011
|
|
|
|
|
|
|
|| $error == $E_OLDER_THAN_ALLOWED |
2012
|
|
|
|
|
|
|
) { |
2013
|
0
|
|
|
|
|
|
$self->_throw( $error ); |
2014
|
|
|
|
|
|
|
} else { |
2015
|
0
|
|
|
|
|
|
$self->_throw( $error, 'Unexpected error' ); |
2016
|
|
|
|
|
|
|
} |
2017
|
|
|
|
|
|
|
} else { |
2018
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
2019
|
|
|
|
|
|
|
} |
2020
|
|
|
|
|
|
|
|
2021
|
0
|
0
|
|
|
|
|
return wantarray ? ( $list_id, $_last_cleanup_items, $_used_memory, $_total_bytes_deleted ) : $list_id; |
2022
|
|
|
|
|
|
|
} |
2023
|
|
|
|
|
|
|
|
2024
|
|
|
|
|
|
|
=head3 update |
2025
|
|
|
|
|
|
|
|
2026
|
|
|
|
|
|
|
update( $list_id, $data_id, $data, $new_data_time ) |
2027
|
|
|
|
|
|
|
|
2028
|
|
|
|
|
|
|
Example: |
2029
|
|
|
|
|
|
|
|
2030
|
|
|
|
|
|
|
if ( $coll->update( $list_id, $data_id, 'New data' ) ) { |
2031
|
|
|
|
|
|
|
say "Data updated successfully"; |
2032
|
|
|
|
|
|
|
} else { |
2033
|
|
|
|
|
|
|
say "The data is not updated"; |
2034
|
|
|
|
|
|
|
} |
2035
|
|
|
|
|
|
|
|
2036
|
|
|
|
|
|
|
Updates existing data item. |
2037
|
|
|
|
|
|
|
|
2038
|
|
|
|
|
|
|
Arguments: |
2039
|
|
|
|
|
|
|
|
2040
|
|
|
|
|
|
|
=over 3 |
2041
|
|
|
|
|
|
|
|
2042
|
|
|
|
|
|
|
=item C<$list_id> |
2043
|
|
|
|
|
|
|
|
2044
|
|
|
|
|
|
|
Mandatory, non-empty string: list ID. Must not contain C<':'>. |
2045
|
|
|
|
|
|
|
|
2046
|
|
|
|
|
|
|
=item C<$data_id> |
2047
|
|
|
|
|
|
|
|
2048
|
|
|
|
|
|
|
Mandatory, non-empty string: data ID, unique within the list identified by C<$list_id> |
2049
|
|
|
|
|
|
|
argument. |
2050
|
|
|
|
|
|
|
|
2051
|
|
|
|
|
|
|
=item C<$data> |
2052
|
|
|
|
|
|
|
|
2053
|
|
|
|
|
|
|
New data value: a string. Data length should not exceed value of L attribute. |
2054
|
|
|
|
|
|
|
|
2055
|
|
|
|
|
|
|
=item C<$new_data_time> |
2056
|
|
|
|
|
|
|
|
2057
|
|
|
|
|
|
|
Optional new data time, a non-negative number. If not specified, the existing |
2058
|
|
|
|
|
|
|
data time is preserved. |
2059
|
|
|
|
|
|
|
|
2060
|
|
|
|
|
|
|
=back |
2061
|
|
|
|
|
|
|
|
2062
|
|
|
|
|
|
|
If the collection is set to C and C<$new_data_time> less than time of the last |
2063
|
|
|
|
|
|
|
removed element (C - see L) then C is set to 0. |
2064
|
|
|
|
|
|
|
The L attribute value is used in the L. |
2065
|
|
|
|
|
|
|
|
2066
|
|
|
|
|
|
|
Method returns true if the data is updated or false if the list with the given ID does not exist or |
2067
|
|
|
|
|
|
|
is used an invalid data ID. |
2068
|
|
|
|
|
|
|
|
2069
|
|
|
|
|
|
|
Throws an exception on other errors. |
2070
|
|
|
|
|
|
|
|
2071
|
|
|
|
|
|
|
=cut |
2072
|
|
|
|
|
|
|
sub update { |
2073
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
2074
|
0
|
|
|
|
|
|
my $list_id = shift; |
2075
|
0
|
|
|
|
|
|
my $data_id = shift; |
2076
|
0
|
|
|
|
|
|
my $data = shift; |
2077
|
|
|
|
|
|
|
|
2078
|
0
|
|
0
|
|
|
|
$data // $self->_throw( $E_MISMATCH_ARG, 'data' ); |
2079
|
0
|
0
|
0
|
|
|
|
( defined( _STRING( $data ) ) || $data eq '' ) || $self->_throw( $E_MISMATCH_ARG, 'data' ); |
2080
|
0
|
|
0
|
|
|
|
_STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
2081
|
0
|
0
|
|
|
|
|
defined( _STRING( $data_id ) ) || $self->_throw( $E_MISMATCH_ARG, 'data_id' ); |
2082
|
|
|
|
|
|
|
|
2083
|
0
|
|
|
|
|
|
my $new_data_time; |
2084
|
0
|
0
|
|
|
|
|
if ( @_ ) { |
2085
|
0
|
|
|
|
|
|
$new_data_time = shift; |
2086
|
0
|
0
|
0
|
|
|
|
( defined( _NUMBER( $new_data_time ) ) && $new_data_time > 0 ) || $self->_throw( $E_MISMATCH_ARG, 'new_data_time' ); |
2087
|
|
|
|
|
|
|
} |
2088
|
|
|
|
|
|
|
|
2089
|
0
|
|
|
|
|
|
my $data_len = bytes::length( $data ); |
2090
|
0
|
0
|
|
|
|
|
( $data_len <= $self->max_datasize ) || $self->_throw( $E_DATA_TOO_LARGE ); |
2091
|
|
|
|
|
|
|
|
2092
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2093
|
|
|
|
|
|
|
|
2094
|
0
|
|
0
|
|
|
|
my @ret = $self->_call_redis( |
2095
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'update' ), |
2096
|
|
|
|
|
|
|
0, |
2097
|
|
|
|
|
|
|
$self->name, |
2098
|
|
|
|
|
|
|
$list_id, |
2099
|
|
|
|
|
|
|
$data_id, |
2100
|
|
|
|
|
|
|
$data, |
2101
|
|
|
|
|
|
|
$new_data_time // 0, |
2102
|
|
|
|
|
|
|
# Recommend the inclusion of this option in the case of incomprehensible errors |
2103
|
|
|
|
|
|
|
$self->_DEBUG, |
2104
|
|
|
|
|
|
|
); |
2105
|
|
|
|
|
|
|
|
2106
|
0
|
|
|
|
|
|
my ( $error, $_last_cleanup_items, $_used_memory, $_total_bytes_deleted ) = @ret; |
2107
|
|
|
|
|
|
|
|
2108
|
0
|
0
|
0
|
|
|
|
if ( scalar( @ret ) == 4 && exists( $ERROR{ $error } ) && defined( _NONNEGINT( $_last_cleanup_items ) ) ) { |
|
|
|
0
|
|
|
|
|
2109
|
0
|
0
|
0
|
|
|
|
if ( $error == $E_NO_ERROR ) { |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
2110
|
0
|
0
|
|
|
|
|
return wantarray ? ( 1, $_last_cleanup_items, $_used_memory, $_total_bytes_deleted ) : 1; |
2111
|
|
|
|
|
|
|
} elsif ( $error == $E_NONEXISTENT_DATA_ID ) { |
2112
|
0
|
|
|
|
|
|
return 0; |
2113
|
|
|
|
|
|
|
} elsif ( |
2114
|
|
|
|
|
|
|
$error == $E_COLLECTION_DELETED |
2115
|
|
|
|
|
|
|
|| $error == $E_DATA_ID_EXISTS |
2116
|
|
|
|
|
|
|
|| $error == $E_OLDER_THAN_ALLOWED |
2117
|
|
|
|
|
|
|
) { |
2118
|
0
|
|
|
|
|
|
$self->_clear_sha1; |
2119
|
0
|
|
|
|
|
|
$self->_throw( $error ); |
2120
|
|
|
|
|
|
|
} else { |
2121
|
0
|
|
|
|
|
|
$self->_throw( $error, 'Unexpected error' ); |
2122
|
|
|
|
|
|
|
} |
2123
|
|
|
|
|
|
|
} else { |
2124
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
2125
|
|
|
|
|
|
|
} |
2126
|
|
|
|
|
|
|
} |
2127
|
|
|
|
|
|
|
|
2128
|
|
|
|
|
|
|
=head3 upsert |
2129
|
|
|
|
|
|
|
|
2130
|
|
|
|
|
|
|
upsert( $list_id, $data_id, $data, $data_time ) |
2131
|
|
|
|
|
|
|
|
2132
|
|
|
|
|
|
|
Example: |
2133
|
|
|
|
|
|
|
|
2134
|
|
|
|
|
|
|
$list_id = $coll->upsert( 'Some List_id', 'Some Data_id', 'Some data' ); |
2135
|
|
|
|
|
|
|
|
2136
|
|
|
|
|
|
|
$list_id = $coll->upsert( 'Another List_id', 'Data ID', 'More data', Time::HiRes::time() ); |
2137
|
|
|
|
|
|
|
|
2138
|
|
|
|
|
|
|
If the list C<$list_id> does not contain data with C<$data_id>, |
2139
|
|
|
|
|
|
|
then it behaves like an L, |
2140
|
|
|
|
|
|
|
otherwise behaves like an L. |
2141
|
|
|
|
|
|
|
|
2142
|
|
|
|
|
|
|
The method returns the ID of the data list to which the data was inserted (value of |
2143
|
|
|
|
|
|
|
the C<$list_id> argument) as the L method. |
2144
|
|
|
|
|
|
|
|
2145
|
|
|
|
|
|
|
=cut |
2146
|
|
|
|
|
|
|
sub upsert { |
2147
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
2148
|
0
|
|
|
|
|
|
my $list_id = shift; |
2149
|
0
|
|
|
|
|
|
my $data_id = shift; |
2150
|
0
|
|
|
|
|
|
my $data = shift; |
2151
|
0
|
|
|
|
|
|
my $data_time = shift; |
2152
|
|
|
|
|
|
|
|
2153
|
0
|
|
0
|
|
|
|
$data // $self->_throw( $E_MISMATCH_ARG, 'data' ); |
2154
|
0
|
0
|
0
|
|
|
|
( defined( _STRING( $data ) ) || $data eq '' ) || $self->_throw( $E_MISMATCH_ARG, 'data' ); |
2155
|
0
|
|
0
|
|
|
|
_STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
2156
|
0
|
0
|
|
|
|
|
$list_id !~ /:/ || $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
2157
|
0
|
0
|
|
|
|
|
defined( _STRING( $data_id ) ) || $self->_throw( $E_MISMATCH_ARG, 'data_id' ); |
2158
|
0
|
0
|
0
|
|
|
|
!defined( $data_time ) || ( defined( _NUMBER( $data_time ) ) && $data_time > 0 ) || $self->_throw( $E_MISMATCH_ARG, 'data_time' ); |
|
|
|
0
|
|
|
|
|
2159
|
|
|
|
|
|
|
|
2160
|
0
|
|
|
|
|
|
my $data_len = bytes::length( $data ); |
2161
|
0
|
0
|
|
|
|
|
( $data_len <= $self->max_datasize ) || $self->_throw( $E_DATA_TOO_LARGE ); |
2162
|
|
|
|
|
|
|
|
2163
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2164
|
|
|
|
|
|
|
|
2165
|
0
|
|
0
|
|
|
|
my @ret = $self->_call_redis( |
2166
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'upsert' ), |
2167
|
|
|
|
|
|
|
0, |
2168
|
|
|
|
|
|
|
$self->name, |
2169
|
|
|
|
|
|
|
$list_id, |
2170
|
|
|
|
|
|
|
$data_id, |
2171
|
|
|
|
|
|
|
$data, |
2172
|
|
|
|
|
|
|
$data_time // -1, |
2173
|
|
|
|
|
|
|
# Recommend the inclusion of this option in the case of incomprehensible errors |
2174
|
|
|
|
|
|
|
$self->_DEBUG, |
2175
|
|
|
|
|
|
|
time, |
2176
|
|
|
|
|
|
|
); |
2177
|
|
|
|
|
|
|
|
2178
|
0
|
|
|
|
|
|
my ( $error, $cleanings ) = @ret; |
2179
|
|
|
|
|
|
|
|
2180
|
0
|
0
|
0
|
|
|
|
if ( scalar( @ret ) == 4 && exists( $ERROR{ $error } ) && defined( _NONNEGINT( $cleanings ) ) ) { |
|
|
|
0
|
|
|
|
|
2181
|
0
|
0
|
0
|
|
|
|
if ( $error == $E_NO_ERROR ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
2182
|
|
|
|
|
|
|
# Normal result: Nothing to do |
2183
|
|
|
|
|
|
|
} elsif ( $error == $E_COLLECTION_DELETED ) { |
2184
|
0
|
|
|
|
|
|
$self->_clear_sha1; |
2185
|
0
|
|
|
|
|
|
$self->_throw( $error ); |
2186
|
|
|
|
|
|
|
} elsif ( |
2187
|
|
|
|
|
|
|
$error == $E_DATA_ID_EXISTS |
2188
|
|
|
|
|
|
|
|| $error == $E_OLDER_THAN_ALLOWED |
2189
|
|
|
|
|
|
|
) { |
2190
|
0
|
|
|
|
|
|
$self->_throw( $error ); |
2191
|
|
|
|
|
|
|
} elsif ( $error == $E_NONEXISTENT_DATA_ID ) { |
2192
|
|
|
|
|
|
|
# Nothing to do |
2193
|
|
|
|
|
|
|
} else { |
2194
|
0
|
|
|
|
|
|
$self->_throw( $error, 'Unexpected error' ); |
2195
|
|
|
|
|
|
|
} |
2196
|
|
|
|
|
|
|
} else { |
2197
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
2198
|
|
|
|
|
|
|
} |
2199
|
|
|
|
|
|
|
|
2200
|
0
|
0
|
|
|
|
|
return wantarray ? ( $list_id, $cleanings ) : $list_id; # as insert |
2201
|
|
|
|
|
|
|
} |
2202
|
|
|
|
|
|
|
|
2203
|
|
|
|
|
|
|
=head3 receive |
2204
|
|
|
|
|
|
|
|
2205
|
|
|
|
|
|
|
receive( $list_id, $data_id ) |
2206
|
|
|
|
|
|
|
|
2207
|
|
|
|
|
|
|
Example: |
2208
|
|
|
|
|
|
|
|
2209
|
|
|
|
|
|
|
my @data = $coll->receive( $list_id ); |
2210
|
|
|
|
|
|
|
say "List '$list_id' has '$_'" foreach @data; |
2211
|
|
|
|
|
|
|
# or |
2212
|
|
|
|
|
|
|
my $list_len = $coll->receive( $list_id ); |
2213
|
|
|
|
|
|
|
say "List '$list_id' has '$list_len' item(s)"; |
2214
|
|
|
|
|
|
|
# or |
2215
|
|
|
|
|
|
|
my $data = $coll->receive( $list_id, $data_id ); |
2216
|
|
|
|
|
|
|
say "List '$list_id' has '$data_id'" if defined $data; |
2217
|
|
|
|
|
|
|
|
2218
|
|
|
|
|
|
|
If the C<$data_id> argument is not specified or is an empty string: |
2219
|
|
|
|
|
|
|
|
2220
|
|
|
|
|
|
|
=over 3 |
2221
|
|
|
|
|
|
|
|
2222
|
|
|
|
|
|
|
=item * |
2223
|
|
|
|
|
|
|
|
2224
|
|
|
|
|
|
|
In a list context, the method returns all the data from the list given by |
2225
|
|
|
|
|
|
|
the C<$list_id> identifier. |
2226
|
|
|
|
|
|
|
|
2227
|
|
|
|
|
|
|
Method returns an empty list if the list with the given ID does not exist. |
2228
|
|
|
|
|
|
|
|
2229
|
|
|
|
|
|
|
=item * |
2230
|
|
|
|
|
|
|
|
2231
|
|
|
|
|
|
|
In a scalar context, the method returns the length of the data list given by |
2232
|
|
|
|
|
|
|
the C<$list_id> identifier. |
2233
|
|
|
|
|
|
|
|
2234
|
|
|
|
|
|
|
=back |
2235
|
|
|
|
|
|
|
|
2236
|
|
|
|
|
|
|
If the C<$data_id> argument is specified: |
2237
|
|
|
|
|
|
|
|
2238
|
|
|
|
|
|
|
=over 3 |
2239
|
|
|
|
|
|
|
|
2240
|
|
|
|
|
|
|
=item * |
2241
|
|
|
|
|
|
|
|
2242
|
|
|
|
|
|
|
The method returns the specified element of the data list. |
2243
|
|
|
|
|
|
|
If the data with C<$data_id> ID does not exist, C is returned. |
2244
|
|
|
|
|
|
|
|
2245
|
|
|
|
|
|
|
=back |
2246
|
|
|
|
|
|
|
|
2247
|
|
|
|
|
|
|
=cut |
2248
|
|
|
|
|
|
|
sub receive { |
2249
|
0
|
|
|
0
|
1
|
|
my ( $self, $list_id, $data_id ) = @_; |
2250
|
|
|
|
|
|
|
|
2251
|
0
|
|
0
|
|
|
|
_STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
2252
|
|
|
|
|
|
|
|
2253
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2254
|
|
|
|
|
|
|
|
2255
|
0
|
0
|
|
|
|
|
return unless $self->list_exists( $list_id ); |
2256
|
|
|
|
|
|
|
|
2257
|
0
|
0
|
0
|
|
|
|
if ( defined( $data_id ) && $data_id ne '' ) { |
2258
|
0
|
|
0
|
|
|
|
_STRING( $data_id ) // $self->_throw( $E_MISMATCH_ARG, 'data_id' ); |
2259
|
0
|
|
|
|
|
|
return $self->_call_redis( |
2260
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'receive' ), |
2261
|
|
|
|
|
|
|
0, |
2262
|
|
|
|
|
|
|
$self->name, |
2263
|
|
|
|
|
|
|
$list_id, |
2264
|
|
|
|
|
|
|
'val', |
2265
|
|
|
|
|
|
|
$data_id, |
2266
|
|
|
|
|
|
|
); |
2267
|
|
|
|
|
|
|
} else { |
2268
|
0
|
0
|
|
|
|
|
if ( wantarray ) { |
2269
|
0
|
0
|
|
|
|
|
return $self->_call_redis( |
2270
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'receive' ), |
2271
|
|
|
|
|
|
|
0, |
2272
|
|
|
|
|
|
|
$self->name, |
2273
|
|
|
|
|
|
|
$list_id, |
2274
|
|
|
|
|
|
|
defined( $data_id ) ? 'all' : 'vals', |
2275
|
|
|
|
|
|
|
'', |
2276
|
|
|
|
|
|
|
); |
2277
|
|
|
|
|
|
|
} else { |
2278
|
0
|
|
|
|
|
|
return $self->_call_redis( |
2279
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'receive' ), |
2280
|
|
|
|
|
|
|
0, |
2281
|
|
|
|
|
|
|
$self->name, |
2282
|
|
|
|
|
|
|
$list_id, |
2283
|
|
|
|
|
|
|
'len', |
2284
|
|
|
|
|
|
|
'', |
2285
|
|
|
|
|
|
|
); |
2286
|
|
|
|
|
|
|
} |
2287
|
|
|
|
|
|
|
} |
2288
|
|
|
|
|
|
|
} |
2289
|
|
|
|
|
|
|
|
2290
|
|
|
|
|
|
|
=head3 pop_oldest |
2291
|
|
|
|
|
|
|
|
2292
|
|
|
|
|
|
|
The method retrieves the oldest data stored in the collection and removes it from |
2293
|
|
|
|
|
|
|
the collection. |
2294
|
|
|
|
|
|
|
|
2295
|
|
|
|
|
|
|
Returns a list of two elements. |
2296
|
|
|
|
|
|
|
The first element contains the identifier of the list from which the data was retrieved. |
2297
|
|
|
|
|
|
|
The second element contains the extracted data. |
2298
|
|
|
|
|
|
|
|
2299
|
|
|
|
|
|
|
The returned data item is removed from the collection. |
2300
|
|
|
|
|
|
|
|
2301
|
|
|
|
|
|
|
Method returns an empty list if the collection does not contain any data. |
2302
|
|
|
|
|
|
|
|
2303
|
|
|
|
|
|
|
The following examples illustrate uses of the C method: |
2304
|
|
|
|
|
|
|
|
2305
|
|
|
|
|
|
|
while ( my ( $list_id, $data ) = $coll->pop_oldest ) { |
2306
|
|
|
|
|
|
|
say "List '$list_id' had '$data'"; |
2307
|
|
|
|
|
|
|
} |
2308
|
|
|
|
|
|
|
|
2309
|
|
|
|
|
|
|
=cut |
2310
|
|
|
|
|
|
|
sub pop_oldest { |
2311
|
0
|
|
|
0
|
1
|
|
my ( $self ) = @_; |
2312
|
|
|
|
|
|
|
|
2313
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2314
|
|
|
|
|
|
|
|
2315
|
0
|
|
|
|
|
|
my @ret = $self->_call_redis( |
2316
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'pop_oldest' ), |
2317
|
|
|
|
|
|
|
0, |
2318
|
|
|
|
|
|
|
$self->name, |
2319
|
|
|
|
|
|
|
); |
2320
|
|
|
|
|
|
|
|
2321
|
0
|
|
|
|
|
|
my ( $error, $queue_exist, $to_delete_id, $to_delete_data ) = @ret; |
2322
|
|
|
|
|
|
|
|
2323
|
0
|
0
|
|
|
|
|
if ( exists $ERROR{ $error } ) { |
2324
|
0
|
0
|
|
|
|
|
$self->_clear_sha1 if $error == $E_COLLECTION_DELETED; |
2325
|
0
|
0
|
|
|
|
|
$self->_throw( $error ) if $error != $E_NO_ERROR; |
2326
|
|
|
|
|
|
|
} else { |
2327
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
2328
|
|
|
|
|
|
|
} |
2329
|
|
|
|
|
|
|
|
2330
|
0
|
0
|
|
|
|
|
if ( $queue_exist ) { |
2331
|
0
|
|
|
|
|
|
return( $to_delete_id, $to_delete_data ); |
2332
|
|
|
|
|
|
|
} else { |
2333
|
0
|
|
|
|
|
|
return; |
2334
|
|
|
|
|
|
|
} |
2335
|
|
|
|
|
|
|
} |
2336
|
|
|
|
|
|
|
|
2337
|
|
|
|
|
|
|
=head3 redis_config_ok |
2338
|
|
|
|
|
|
|
|
2339
|
|
|
|
|
|
|
redis_config_ok( redis => $server ) |
2340
|
|
|
|
|
|
|
|
2341
|
|
|
|
|
|
|
Example: |
2342
|
|
|
|
|
|
|
|
2343
|
|
|
|
|
|
|
say 'Redis server config ', $coll->redis_config_ok ? 'OK' : 'NOT OK'; |
2344
|
|
|
|
|
|
|
my $redis = Redis->new( server => "$server:$port" ); |
2345
|
|
|
|
|
|
|
say 'Redis server config ', |
2346
|
|
|
|
|
|
|
Redis::CappedCollection::redis_config_ok( redis => $redis ) |
2347
|
|
|
|
|
|
|
? 'OK' |
2348
|
|
|
|
|
|
|
: 'NOT OK' |
2349
|
|
|
|
|
|
|
; |
2350
|
|
|
|
|
|
|
|
2351
|
|
|
|
|
|
|
Check whether there is a Redis server config correct, |
2352
|
|
|
|
|
|
|
now that the 'maxmemory-policy' setting is 'noeviction'. |
2353
|
|
|
|
|
|
|
Returns true if config correct and false otherwise. |
2354
|
|
|
|
|
|
|
|
2355
|
|
|
|
|
|
|
It can be called as either the existing C object method or a class function. |
2356
|
|
|
|
|
|
|
|
2357
|
|
|
|
|
|
|
If invoked as the object method, C uses the C |
2358
|
|
|
|
|
|
|
attribute from the object as default. |
2359
|
|
|
|
|
|
|
|
2360
|
|
|
|
|
|
|
If invoked as the class function, C requires mandatory C |
2361
|
|
|
|
|
|
|
argument. |
2362
|
|
|
|
|
|
|
|
2363
|
|
|
|
|
|
|
This argument are in key-value pair as described for L method. |
2364
|
|
|
|
|
|
|
|
2365
|
|
|
|
|
|
|
An error exception is thrown (C) if an argument is not valid. |
2366
|
|
|
|
|
|
|
|
2367
|
|
|
|
|
|
|
=cut |
2368
|
|
|
|
|
|
|
sub redis_config_ok { |
2369
|
0
|
|
|
0
|
1
|
|
return _maxmemory_policy_ok( @_ ); |
2370
|
|
|
|
|
|
|
} |
2371
|
|
|
|
|
|
|
|
2372
|
|
|
|
|
|
|
=head3 collection_info |
2373
|
|
|
|
|
|
|
|
2374
|
|
|
|
|
|
|
collection_info( redis => $server, name => $name ) |
2375
|
|
|
|
|
|
|
|
2376
|
|
|
|
|
|
|
Example: |
2377
|
|
|
|
|
|
|
|
2378
|
|
|
|
|
|
|
my $info = $coll->collection_info; |
2379
|
|
|
|
|
|
|
say 'An existing collection uses ', $info->{cleanup_bytes}, " byte of 'cleanup_bytes', ", |
2380
|
|
|
|
|
|
|
$info->{items}, ' items are stored in ', $info->{lists}, ' lists'; |
2381
|
|
|
|
|
|
|
# or |
2382
|
|
|
|
|
|
|
my $info = Redis::CappedCollection::collection_info( |
2383
|
|
|
|
|
|
|
redis => $redis, # or redis => { server => "$server:$port" } |
2384
|
|
|
|
|
|
|
name => 'Collection name', |
2385
|
|
|
|
|
|
|
); |
2386
|
|
|
|
|
|
|
|
2387
|
|
|
|
|
|
|
Get collection information and status. |
2388
|
|
|
|
|
|
|
It can be called as either an existing C object method or a class function. |
2389
|
|
|
|
|
|
|
|
2390
|
|
|
|
|
|
|
C arguments are in key-value pairs. |
2391
|
|
|
|
|
|
|
Arguments description match the arguments description for L method: |
2392
|
|
|
|
|
|
|
|
2393
|
|
|
|
|
|
|
=over 3 |
2394
|
|
|
|
|
|
|
|
2395
|
|
|
|
|
|
|
=item C |
2396
|
|
|
|
|
|
|
|
2397
|
|
|
|
|
|
|
=item C |
2398
|
|
|
|
|
|
|
|
2399
|
|
|
|
|
|
|
=back |
2400
|
|
|
|
|
|
|
|
2401
|
|
|
|
|
|
|
If invoked as the object method, C, arguments are optional and |
2402
|
|
|
|
|
|
|
use corresponding object attributes as defaults. |
2403
|
|
|
|
|
|
|
|
2404
|
|
|
|
|
|
|
If called as a class methods, the arguments are mandatory. |
2405
|
|
|
|
|
|
|
|
2406
|
|
|
|
|
|
|
Returns a reference to a hash with the following elements: |
2407
|
|
|
|
|
|
|
|
2408
|
|
|
|
|
|
|
=over 3 |
2409
|
|
|
|
|
|
|
|
2410
|
|
|
|
|
|
|
=item * |
2411
|
|
|
|
|
|
|
|
2412
|
|
|
|
|
|
|
C - Number of lists in a collection. |
2413
|
|
|
|
|
|
|
|
2414
|
|
|
|
|
|
|
=item * |
2415
|
|
|
|
|
|
|
|
2416
|
|
|
|
|
|
|
C - Number of data items stored in the collection. |
2417
|
|
|
|
|
|
|
|
2418
|
|
|
|
|
|
|
=item * |
2419
|
|
|
|
|
|
|
|
2420
|
|
|
|
|
|
|
C - Time of the oldest data in the collection. |
2421
|
|
|
|
|
|
|
C if the collection does not contain data. |
2422
|
|
|
|
|
|
|
|
2423
|
|
|
|
|
|
|
=item * |
2424
|
|
|
|
|
|
|
|
2425
|
|
|
|
|
|
|
C - True if it is allowed to put data in collection that is older than the last element |
2426
|
|
|
|
|
|
|
removed from collection. |
2427
|
|
|
|
|
|
|
|
2428
|
|
|
|
|
|
|
=item * |
2429
|
|
|
|
|
|
|
|
2430
|
|
|
|
|
|
|
C - Memory reserve coefficient. |
2431
|
|
|
|
|
|
|
|
2432
|
|
|
|
|
|
|
=item * |
2433
|
|
|
|
|
|
|
|
2434
|
|
|
|
|
|
|
C - The minimum size, in bytes, |
2435
|
|
|
|
|
|
|
of the data to be released when performing memory cleanup. |
2436
|
|
|
|
|
|
|
|
2437
|
|
|
|
|
|
|
=item * |
2438
|
|
|
|
|
|
|
|
2439
|
|
|
|
|
|
|
C - The minimum number of the collection elements |
2440
|
|
|
|
|
|
|
to be realesed when performing memory cleanup. |
2441
|
|
|
|
|
|
|
|
2442
|
|
|
|
|
|
|
=item * |
2443
|
|
|
|
|
|
|
|
2444
|
|
|
|
|
|
|
C - Data structure version. |
2445
|
|
|
|
|
|
|
|
2446
|
|
|
|
|
|
|
=item * |
2447
|
|
|
|
|
|
|
|
2448
|
|
|
|
|
|
|
C - time of the last removed element from collection |
2449
|
|
|
|
|
|
|
or 0 if nothing was removed from collection yet. |
2450
|
|
|
|
|
|
|
|
2451
|
|
|
|
|
|
|
=back |
2452
|
|
|
|
|
|
|
|
2453
|
|
|
|
|
|
|
An error will cause the program to throw an exception (C) if an argument is not valid |
2454
|
|
|
|
|
|
|
or the collection does not exist. |
2455
|
|
|
|
|
|
|
|
2456
|
|
|
|
|
|
|
=cut |
2457
|
|
|
|
|
|
|
my @_collection_info_result_keys = qw( |
2458
|
|
|
|
|
|
|
error |
2459
|
|
|
|
|
|
|
lists |
2460
|
|
|
|
|
|
|
items |
2461
|
|
|
|
|
|
|
older_allowed |
2462
|
|
|
|
|
|
|
cleanup_bytes |
2463
|
|
|
|
|
|
|
cleanup_items |
2464
|
|
|
|
|
|
|
memory_reserve |
2465
|
|
|
|
|
|
|
data_version |
2466
|
|
|
|
|
|
|
last_removed_time |
2467
|
|
|
|
|
|
|
oldest_time |
2468
|
|
|
|
|
|
|
); |
2469
|
|
|
|
|
|
|
|
2470
|
|
|
|
|
|
|
sub collection_info { |
2471
|
0
|
|
|
0
|
1
|
|
my $results = {}; |
2472
|
0
|
|
|
|
|
|
my @ret; |
2473
|
0
|
0
|
0
|
|
|
|
if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
2474
|
0
|
|
|
|
|
|
my $self = shift; |
2475
|
|
|
|
|
|
|
|
2476
|
0
|
|
|
|
|
|
my %arguments = @_; |
2477
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%arguments, [] ); |
2478
|
|
|
|
|
|
|
|
2479
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2480
|
|
|
|
|
|
|
|
2481
|
0
|
|
|
|
|
|
@ret = $self->_call_redis( |
2482
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'collection_info' ), |
2483
|
|
|
|
|
|
|
0, |
2484
|
|
|
|
|
|
|
$self->name, |
2485
|
|
|
|
|
|
|
); |
2486
|
0
|
|
|
|
|
|
$results = _lists2hash( \@_collection_info_result_keys, \@ret ); |
2487
|
|
|
|
|
|
|
|
2488
|
0
|
|
|
|
|
|
my $error = $results->{error}; |
2489
|
|
|
|
|
|
|
|
2490
|
0
|
0
|
|
|
|
|
if ( exists $ERROR{ $error } ) { |
2491
|
0
|
0
|
|
|
|
|
$self->_clear_sha1 if $error == $E_COLLECTION_DELETED; |
2492
|
0
|
0
|
|
|
|
|
$self->_throw( $error ) if $error != $E_NO_ERROR; |
2493
|
|
|
|
|
|
|
} else { |
2494
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
2495
|
|
|
|
|
|
|
} |
2496
|
|
|
|
|
|
|
} else { |
2497
|
0
|
0
|
|
|
|
|
shift if _CLASSISA( $_[0], __PACKAGE__ ); # allow calling Foo->bar as well as Foo::bar |
2498
|
|
|
|
|
|
|
|
2499
|
0
|
|
|
|
|
|
my %arguments = @_; |
2500
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%arguments, [ 'redis', 'name' ] ); |
2501
|
|
|
|
|
|
|
|
2502
|
0
|
0
|
|
|
|
|
_croak( "'redis' argument is required" ) unless defined $arguments{redis}; |
2503
|
0
|
0
|
|
|
|
|
_croak( "'name' argument is required" ) unless defined $arguments{name}; |
2504
|
|
|
|
|
|
|
|
2505
|
0
|
|
|
|
|
|
my $redis = _get_redis( delete $arguments{redis} ); |
2506
|
0
|
|
|
|
|
|
my $name = delete $arguments{name}; |
2507
|
|
|
|
|
|
|
|
2508
|
0
|
0
|
|
|
|
|
_croak( 'Unknown arguments: ', join( ', ', keys %arguments ) ) if %arguments; |
2509
|
|
|
|
|
|
|
|
2510
|
0
|
|
|
|
|
|
@ret = _call_redis( |
2511
|
|
|
|
|
|
|
$redis, |
2512
|
|
|
|
|
|
|
_lua_script_cmd( $redis, 'collection_info' ), |
2513
|
|
|
|
|
|
|
0, |
2514
|
|
|
|
|
|
|
$name, |
2515
|
|
|
|
|
|
|
); |
2516
|
0
|
|
|
|
|
|
$results = _lists2hash( \@_collection_info_result_keys, \@ret ); |
2517
|
|
|
|
|
|
|
|
2518
|
0
|
|
|
|
|
|
my $error = $results->{error}; |
2519
|
|
|
|
|
|
|
|
2520
|
0
|
0
|
|
|
|
|
if ( exists $ERROR{ $error } ) { |
2521
|
0
|
0
|
|
|
|
|
_croak( format_message( "Collection '%s' info not received (%s)", $name, $ERROR{ $error } ) ) |
2522
|
|
|
|
|
|
|
if $error != $E_NO_ERROR; |
2523
|
|
|
|
|
|
|
} else { |
2524
|
0
|
|
|
|
|
|
_unknown_error( @ret ); |
2525
|
|
|
|
|
|
|
} |
2526
|
|
|
|
|
|
|
} |
2527
|
|
|
|
|
|
|
|
2528
|
0
|
|
|
|
|
|
my $oldest_time = $results->{oldest_time}; |
2529
|
0
|
0
|
0
|
|
|
|
!$oldest_time || defined( _NUMBER( $oldest_time ) ) || warn( format_message( 'oldest_time is not a number: %s', $oldest_time ) ); |
2530
|
|
|
|
|
|
|
|
2531
|
0
|
|
|
|
|
|
delete $results->{error}; |
2532
|
0
|
|
|
|
|
|
return $results; |
2533
|
|
|
|
|
|
|
} |
2534
|
|
|
|
|
|
|
|
2535
|
|
|
|
|
|
|
=head3 list_info |
2536
|
|
|
|
|
|
|
|
2537
|
|
|
|
|
|
|
list_info( $list_id ) |
2538
|
|
|
|
|
|
|
|
2539
|
|
|
|
|
|
|
Get data list information and status. |
2540
|
|
|
|
|
|
|
|
2541
|
|
|
|
|
|
|
C<$list_id> must be a non-empty string. |
2542
|
|
|
|
|
|
|
|
2543
|
|
|
|
|
|
|
Returns a reference to a hash with the following elements: |
2544
|
|
|
|
|
|
|
|
2545
|
|
|
|
|
|
|
=over 3 |
2546
|
|
|
|
|
|
|
|
2547
|
|
|
|
|
|
|
=item * |
2548
|
|
|
|
|
|
|
|
2549
|
|
|
|
|
|
|
C - Number of data items stored in the data list. |
2550
|
|
|
|
|
|
|
|
2551
|
|
|
|
|
|
|
=item * |
2552
|
|
|
|
|
|
|
|
2553
|
|
|
|
|
|
|
C - The time of the oldest data in the list. |
2554
|
|
|
|
|
|
|
C if the data list does not exist. |
2555
|
|
|
|
|
|
|
|
2556
|
|
|
|
|
|
|
=back |
2557
|
|
|
|
|
|
|
|
2558
|
|
|
|
|
|
|
=cut |
2559
|
|
|
|
|
|
|
my @_list_info_result_keys = qw( |
2560
|
|
|
|
|
|
|
error |
2561
|
|
|
|
|
|
|
items |
2562
|
|
|
|
|
|
|
oldest_time |
2563
|
|
|
|
|
|
|
); |
2564
|
|
|
|
|
|
|
|
2565
|
|
|
|
|
|
|
sub list_info { |
2566
|
0
|
|
|
0
|
1
|
|
my ( $self, $list_id ) = @_; |
2567
|
|
|
|
|
|
|
|
2568
|
0
|
|
0
|
|
|
|
_STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
2569
|
|
|
|
|
|
|
|
2570
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2571
|
|
|
|
|
|
|
|
2572
|
0
|
|
|
|
|
|
my @ret = $self->_call_redis( |
2573
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'list_info' ), |
2574
|
|
|
|
|
|
|
0, |
2575
|
|
|
|
|
|
|
$self->name, |
2576
|
|
|
|
|
|
|
$list_id, |
2577
|
|
|
|
|
|
|
); |
2578
|
0
|
|
|
|
|
|
my $results = _lists2hash( \@_list_info_result_keys, \@ret ); |
2579
|
|
|
|
|
|
|
|
2580
|
0
|
|
|
|
|
|
my $error = $results->{error}; |
2581
|
|
|
|
|
|
|
|
2582
|
0
|
0
|
|
|
|
|
if ( exists $ERROR{ $error } ) { |
2583
|
0
|
0
|
|
|
|
|
$self->_clear_sha1 if $error == $E_COLLECTION_DELETED; |
2584
|
0
|
0
|
|
|
|
|
$self->_throw( $error ) if $error != $E_NO_ERROR; |
2585
|
|
|
|
|
|
|
} else { |
2586
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
2587
|
|
|
|
|
|
|
} |
2588
|
|
|
|
|
|
|
|
2589
|
0
|
|
|
|
|
|
my $oldest_time = $results->{oldest_time}; |
2590
|
0
|
0
|
0
|
|
|
|
!$oldest_time || defined( _NUMBER( $oldest_time ) ) || warn( format_message( 'oldest_time is not a number: %s', $oldest_time ) ); |
2591
|
|
|
|
|
|
|
|
2592
|
0
|
|
|
|
|
|
delete $results->{error}; |
2593
|
0
|
|
|
|
|
|
return $results; |
2594
|
|
|
|
|
|
|
} |
2595
|
|
|
|
|
|
|
|
2596
|
|
|
|
|
|
|
=head3 oldest_time |
2597
|
|
|
|
|
|
|
|
2598
|
|
|
|
|
|
|
my $oldest_time = $coll->oldest_time; |
2599
|
|
|
|
|
|
|
|
2600
|
|
|
|
|
|
|
Get the time of the oldest data in the collection. |
2601
|
|
|
|
|
|
|
Returns C if the collection does not contain data. |
2602
|
|
|
|
|
|
|
|
2603
|
|
|
|
|
|
|
An error exception is thrown (C) if the collection does not exist. |
2604
|
|
|
|
|
|
|
|
2605
|
|
|
|
|
|
|
=cut |
2606
|
|
|
|
|
|
|
my @_oldest_time_result_keys = qw( |
2607
|
|
|
|
|
|
|
error |
2608
|
|
|
|
|
|
|
oldest_time |
2609
|
|
|
|
|
|
|
); |
2610
|
|
|
|
|
|
|
|
2611
|
|
|
|
|
|
|
sub oldest_time { |
2612
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
2613
|
|
|
|
|
|
|
|
2614
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2615
|
|
|
|
|
|
|
|
2616
|
0
|
|
|
|
|
|
my @ret = $self->_call_redis( |
2617
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'oldest_time' ), |
2618
|
|
|
|
|
|
|
0, |
2619
|
|
|
|
|
|
|
$self->name, |
2620
|
|
|
|
|
|
|
); |
2621
|
0
|
|
|
|
|
|
my $results = _lists2hash( \@_oldest_time_result_keys, \@ret ); |
2622
|
|
|
|
|
|
|
|
2623
|
0
|
|
|
|
|
|
my $error = $results->{error}; |
2624
|
|
|
|
|
|
|
|
2625
|
0
|
0
|
|
|
|
|
if ( exists $ERROR{ $error } ) { |
2626
|
0
|
0
|
|
|
|
|
$self->_clear_sha1 if $error == $E_COLLECTION_DELETED; |
2627
|
0
|
0
|
|
|
|
|
$self->_throw( $error ) if $error != $E_NO_ERROR; |
2628
|
|
|
|
|
|
|
} else { |
2629
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
2630
|
|
|
|
|
|
|
} |
2631
|
|
|
|
|
|
|
|
2632
|
0
|
|
|
|
|
|
my $oldest_time = $results->{oldest_time}; |
2633
|
0
|
0
|
0
|
|
|
|
!$oldest_time || defined( _NUMBER( $oldest_time ) ) || warn( format_message( 'oldest_time is not a number: %s', $oldest_time ) ); |
2634
|
|
|
|
|
|
|
|
2635
|
0
|
|
|
|
|
|
return $oldest_time; |
2636
|
|
|
|
|
|
|
} |
2637
|
|
|
|
|
|
|
|
2638
|
|
|
|
|
|
|
=head3 list_exists |
2639
|
|
|
|
|
|
|
|
2640
|
|
|
|
|
|
|
list_exists( $list_id ) |
2641
|
|
|
|
|
|
|
|
2642
|
|
|
|
|
|
|
Example: |
2643
|
|
|
|
|
|
|
|
2644
|
|
|
|
|
|
|
say "The collection has '$list_id' list" if $coll->list_exists( 'Some_id' ); |
2645
|
|
|
|
|
|
|
|
2646
|
|
|
|
|
|
|
Check whether there is a list in the collection with given |
2647
|
|
|
|
|
|
|
ID C<$list_id>. |
2648
|
|
|
|
|
|
|
|
2649
|
|
|
|
|
|
|
Returns true if the list exists and false otherwise. |
2650
|
|
|
|
|
|
|
|
2651
|
|
|
|
|
|
|
=cut |
2652
|
|
|
|
|
|
|
sub list_exists { |
2653
|
0
|
|
|
0
|
1
|
|
my ( $self, $list_id ) = @_; |
2654
|
|
|
|
|
|
|
|
2655
|
0
|
|
0
|
|
|
|
_STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
2656
|
|
|
|
|
|
|
|
2657
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2658
|
|
|
|
|
|
|
|
2659
|
0
|
|
|
|
|
|
return $self->_call_redis( 'EXISTS', $self->_data_list_key( $list_id ) ); |
2660
|
|
|
|
|
|
|
} |
2661
|
|
|
|
|
|
|
|
2662
|
|
|
|
|
|
|
=head3 collection_exists |
2663
|
|
|
|
|
|
|
|
2664
|
|
|
|
|
|
|
collection_exists( redis => $server, name => $name ) |
2665
|
|
|
|
|
|
|
|
2666
|
|
|
|
|
|
|
Example: |
2667
|
|
|
|
|
|
|
|
2668
|
|
|
|
|
|
|
say 'The collection ', $coll->name, ' exists' if $coll->collection_exists; |
2669
|
|
|
|
|
|
|
my $redis = Redis->new( server => "$server:$port" ); |
2670
|
|
|
|
|
|
|
say "The collection 'Some name' exists" |
2671
|
|
|
|
|
|
|
if Redis::CappedCollection::collection_exists( redis => $redis, name => 'Some name' ); |
2672
|
|
|
|
|
|
|
|
2673
|
|
|
|
|
|
|
Check whether there is a collection with given name. |
2674
|
|
|
|
|
|
|
Returns true if the collection exists and false otherwise. |
2675
|
|
|
|
|
|
|
|
2676
|
|
|
|
|
|
|
It can be called as either the existing C object method or a class function. |
2677
|
|
|
|
|
|
|
|
2678
|
|
|
|
|
|
|
If invoked as the object method, C uses C and C |
2679
|
|
|
|
|
|
|
attributes from the object as defaults. |
2680
|
|
|
|
|
|
|
|
2681
|
|
|
|
|
|
|
If invoked as the class function, C requires mandatory C and C |
2682
|
|
|
|
|
|
|
arguments. |
2683
|
|
|
|
|
|
|
|
2684
|
|
|
|
|
|
|
These arguments are in key-value pairs as described for L method. |
2685
|
|
|
|
|
|
|
|
2686
|
|
|
|
|
|
|
An error exception is thrown (C) if an argument is not valid. |
2687
|
|
|
|
|
|
|
|
2688
|
|
|
|
|
|
|
=cut |
2689
|
|
|
|
|
|
|
sub collection_exists { |
2690
|
0
|
|
|
0
|
1
|
|
my ( $self, $redis, $name ); |
2691
|
0
|
0
|
0
|
|
|
|
if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
2692
|
0
|
|
|
|
|
|
$self = shift; |
2693
|
0
|
|
|
|
|
|
$redis = $self->_redis; |
2694
|
0
|
|
|
|
|
|
$name = $self->name; |
2695
|
|
|
|
|
|
|
} else { |
2696
|
0
|
0
|
|
|
|
|
shift if _CLASSISA( $_[0], __PACKAGE__ ); # allow calling Foo->bar as well as Foo::bar |
2697
|
|
|
|
|
|
|
} |
2698
|
|
|
|
|
|
|
|
2699
|
0
|
|
|
|
|
|
my %arguments = @_; |
2700
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%arguments, [ 'redis', 'name' ] ); |
2701
|
|
|
|
|
|
|
|
2702
|
0
|
0
|
|
|
|
|
unless ( $self ) { |
2703
|
0
|
0
|
|
|
|
|
_croak( "'redis' argument is required" ) unless defined $arguments{redis}; |
2704
|
0
|
0
|
|
|
|
|
_croak( "'name' argument is required" ) unless defined $arguments{name}; |
2705
|
|
|
|
|
|
|
} |
2706
|
|
|
|
|
|
|
|
2707
|
0
|
0
|
|
|
|
|
$redis = _get_redis( $arguments{redis} ) unless $self; |
2708
|
0
|
0
|
|
|
|
|
$name = $arguments{name} if exists $arguments{name}; |
2709
|
|
|
|
|
|
|
|
2710
|
0
|
0
|
|
|
|
|
if ( $self ) { |
2711
|
0
|
|
|
|
|
|
return $self->_call_redis( 'EXISTS', _make_status_key( $name ) ); |
2712
|
|
|
|
|
|
|
} else { |
2713
|
0
|
|
|
|
|
|
return _call_redis( $redis, 'EXISTS', _make_status_key( $name ) ); |
2714
|
|
|
|
|
|
|
} |
2715
|
|
|
|
|
|
|
} |
2716
|
|
|
|
|
|
|
|
2717
|
|
|
|
|
|
|
=head3 lists |
2718
|
|
|
|
|
|
|
|
2719
|
|
|
|
|
|
|
lists( $pattern ) |
2720
|
|
|
|
|
|
|
|
2721
|
|
|
|
|
|
|
Example: |
2722
|
|
|
|
|
|
|
|
2723
|
|
|
|
|
|
|
say "The collection has '$_' list" foreach $coll->lists; |
2724
|
|
|
|
|
|
|
|
2725
|
|
|
|
|
|
|
Returns an array of list ID of lists stored in a collection. |
2726
|
|
|
|
|
|
|
Returns all list IDs matching C<$pattern> if C<$pattern> is not empty. |
2727
|
|
|
|
|
|
|
C<$patten> must be a non-empty string. |
2728
|
|
|
|
|
|
|
|
2729
|
|
|
|
|
|
|
Supported glob-style patterns: |
2730
|
|
|
|
|
|
|
|
2731
|
|
|
|
|
|
|
=over 3 |
2732
|
|
|
|
|
|
|
|
2733
|
|
|
|
|
|
|
=item * |
2734
|
|
|
|
|
|
|
|
2735
|
|
|
|
|
|
|
C matches C, C and C |
2736
|
|
|
|
|
|
|
|
2737
|
|
|
|
|
|
|
=item * |
2738
|
|
|
|
|
|
|
|
2739
|
|
|
|
|
|
|
C matches C and C |
2740
|
|
|
|
|
|
|
|
2741
|
|
|
|
|
|
|
=item * |
2742
|
|
|
|
|
|
|
|
2743
|
|
|
|
|
|
|
C matches C and C, but not C |
2744
|
|
|
|
|
|
|
|
2745
|
|
|
|
|
|
|
=back |
2746
|
|
|
|
|
|
|
|
2747
|
|
|
|
|
|
|
Use C<'\'> to escape special characters if you want to match them verbatim. |
2748
|
|
|
|
|
|
|
|
2749
|
|
|
|
|
|
|
Warning: consider C as a command that should only be used in production |
2750
|
|
|
|
|
|
|
environments with extreme care. Its performance is not optimal for large collections. |
2751
|
|
|
|
|
|
|
This command is intended for debugging and special operations. |
2752
|
|
|
|
|
|
|
Don't use C in your regular application code. |
2753
|
|
|
|
|
|
|
|
2754
|
|
|
|
|
|
|
In addition, it may cause an exception (C) if |
2755
|
|
|
|
|
|
|
the collection contains a very large number of lists |
2756
|
|
|
|
|
|
|
(C<'Error while reading from Redis server'>). |
2757
|
|
|
|
|
|
|
|
2758
|
|
|
|
|
|
|
=cut |
2759
|
|
|
|
|
|
|
sub lists { |
2760
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
2761
|
0
|
|
0
|
|
|
|
my $pattern = shift // '*'; |
2762
|
|
|
|
|
|
|
|
2763
|
0
|
|
0
|
|
|
|
_STRING( $pattern ) // $self->_throw( $E_MISMATCH_ARG, 'pattern' ); |
2764
|
|
|
|
|
|
|
|
2765
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2766
|
|
|
|
|
|
|
|
2767
|
0
|
|
|
|
|
|
my @keys; |
2768
|
|
|
|
|
|
|
try { |
2769
|
0
|
|
|
0
|
|
|
@keys = $self->_call_redis( 'KEYS', $self->_data_list_key( $pattern ) ); |
2770
|
|
|
|
|
|
|
} catch { |
2771
|
0
|
|
|
0
|
|
|
my $error = $_; |
2772
|
0
|
0
|
|
|
|
|
_croak( $error ) unless $self->last_errorcode == $E_REDIS_DID_NOT_RETURN_DATA; |
2773
|
0
|
|
|
|
|
|
}; |
2774
|
|
|
|
|
|
|
|
2775
|
0
|
|
|
|
|
|
return map { ( $_ =~ /:([^:]+)$/ )[0] } @keys; |
|
0
|
|
|
|
|
|
|
2776
|
|
|
|
|
|
|
} |
2777
|
|
|
|
|
|
|
|
2778
|
|
|
|
|
|
|
=head3 resize |
2779
|
|
|
|
|
|
|
|
2780
|
|
|
|
|
|
|
resize( redis => $server, name => $name, ... ) |
2781
|
|
|
|
|
|
|
|
2782
|
|
|
|
|
|
|
Example: |
2783
|
|
|
|
|
|
|
|
2784
|
|
|
|
|
|
|
$coll->resize( cleanup_bytes => 100_000 ); |
2785
|
|
|
|
|
|
|
my $redis = Redis->new( server => "$server:$port" ); |
2786
|
|
|
|
|
|
|
Redis::CappedCollection::resize( redis => $redis, name => 'Some name', older_allowed => 1 ); |
2787
|
|
|
|
|
|
|
|
2788
|
|
|
|
|
|
|
Use the C to change the values of the parameters of the collection. |
2789
|
|
|
|
|
|
|
It can be called as either the existing C object method or a class function. |
2790
|
|
|
|
|
|
|
|
2791
|
|
|
|
|
|
|
If invoked as the object method, C uses C and C attributes |
2792
|
|
|
|
|
|
|
from the object as defaults. |
2793
|
|
|
|
|
|
|
If invoked as the class function, C requires mandatory C and C |
2794
|
|
|
|
|
|
|
arguments. |
2795
|
|
|
|
|
|
|
|
2796
|
|
|
|
|
|
|
These arguments are in key-value pairs as described for L method. |
2797
|
|
|
|
|
|
|
|
2798
|
|
|
|
|
|
|
It is possible to change the following parameters: C, C, |
2799
|
|
|
|
|
|
|
C, C. One or more parameters are required. |
2800
|
|
|
|
|
|
|
|
2801
|
|
|
|
|
|
|
Returns the number of completed changes. |
2802
|
|
|
|
|
|
|
|
2803
|
|
|
|
|
|
|
An error exception is thrown (C) if an argument is not valid or the |
2804
|
|
|
|
|
|
|
collection does not exist. |
2805
|
|
|
|
|
|
|
|
2806
|
|
|
|
|
|
|
=cut |
2807
|
|
|
|
|
|
|
sub resize { |
2808
|
0
|
|
|
0
|
1
|
|
my ( $self, $redis, $name ); |
2809
|
0
|
0
|
0
|
|
|
|
if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
2810
|
0
|
|
|
|
|
|
$self = shift; |
2811
|
0
|
|
|
|
|
|
$redis = $self->_redis; |
2812
|
0
|
|
|
|
|
|
$name = $self->name; |
2813
|
|
|
|
|
|
|
} else { |
2814
|
0
|
0
|
|
|
|
|
shift if _CLASSISA( $_[0], __PACKAGE__ ); # allow calling Foo->bar as well as Foo::bar |
2815
|
|
|
|
|
|
|
} |
2816
|
|
|
|
|
|
|
|
2817
|
0
|
|
|
|
|
|
my %arguments = @_; |
2818
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%arguments, [ 'redis', 'name', @_status_parameters ] ); |
2819
|
|
|
|
|
|
|
|
2820
|
0
|
0
|
|
|
|
|
unless ( $self ) { |
2821
|
0
|
0
|
|
|
|
|
_croak( "'redis' argument is required" ) unless defined $arguments{redis}; |
2822
|
0
|
0
|
|
|
|
|
_croak( "'name' argument is required" ) unless defined $arguments{name}; |
2823
|
|
|
|
|
|
|
} |
2824
|
|
|
|
|
|
|
|
2825
|
0
|
0
|
|
|
|
|
$redis = _get_redis( $arguments{redis} ) unless $self; |
2826
|
0
|
0
|
|
|
|
|
$name = $arguments{name} if $arguments{name}; |
2827
|
|
|
|
|
|
|
|
2828
|
0
|
|
|
|
|
|
my $requested_changes = 0; |
2829
|
0
|
|
|
|
|
|
foreach my $parameter ( @_status_parameters ) { |
2830
|
0
|
0
|
|
|
|
|
++$requested_changes if exists $arguments{ $parameter }; |
2831
|
|
|
|
|
|
|
} |
2832
|
0
|
0
|
|
|
|
|
unless ( $requested_changes ) { |
2833
|
0
|
|
|
|
|
|
my $error = 'One or more parameters are required'; |
2834
|
0
|
0
|
|
|
|
|
if ( $self ) { |
2835
|
0
|
|
|
|
|
|
$self->_throw( $E_MISMATCH_ARG, $error ); |
2836
|
|
|
|
|
|
|
} else { |
2837
|
0
|
|
|
|
|
|
_croak( format_message( '%s : %s', $error, $ERROR{ $E_MISMATCH_ARG } ) ); |
2838
|
|
|
|
|
|
|
} |
2839
|
|
|
|
|
|
|
} |
2840
|
|
|
|
|
|
|
|
2841
|
0
|
|
|
|
|
|
my $resized = 0; |
2842
|
0
|
|
|
|
|
|
foreach my $parameter ( @_status_parameters ) { |
2843
|
0
|
0
|
|
|
|
|
if ( exists $arguments{ $parameter } ) { |
2844
|
0
|
0
|
0
|
|
|
|
if ( $parameter eq 'cleanup_bytes' || $parameter eq 'cleanup_items' ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
2845
|
|
|
|
|
|
|
_croak( "'$parameter' must be nonnegative integer" ) |
2846
|
0
|
0
|
|
|
|
|
unless _NONNEGINT( $arguments{ $parameter } ); |
2847
|
|
|
|
|
|
|
} elsif ( $parameter eq 'memory_reserve' ) { |
2848
|
0
|
|
|
|
|
|
my $memory_reserve = $arguments{ $parameter }; |
2849
|
0
|
0
|
0
|
|
|
|
_croak( format_message( "'%s' must have a valid value", $parameter ) ) |
|
|
|
0
|
|
|
|
|
2850
|
|
|
|
|
|
|
unless _NUMBER( $memory_reserve ) && $memory_reserve >= $MIN_MEMORY_RESERVE && $memory_reserve <= $MAX_MEMORY_RESERVE; |
2851
|
|
|
|
|
|
|
} elsif ( $parameter eq 'older_allowed' ) { |
2852
|
0
|
0
|
|
|
|
|
$arguments{ $parameter } = $arguments{ $parameter } ? 1 :0; |
2853
|
|
|
|
|
|
|
} |
2854
|
|
|
|
|
|
|
|
2855
|
0
|
|
|
|
|
|
my $ret = 0; |
2856
|
0
|
|
|
|
|
|
my $new_val = $arguments{ $parameter }; |
2857
|
0
|
0
|
|
|
|
|
if ( $self ) { |
2858
|
0
|
|
|
|
|
|
$ret = $self->_call_redis( 'HSET', _make_status_key( $self->name ), $parameter, $new_val ); |
2859
|
|
|
|
|
|
|
} else { |
2860
|
0
|
|
|
|
|
|
$ret = _call_redis( $redis, 'HSET', _make_status_key( $name ), $parameter, $new_val ); |
2861
|
|
|
|
|
|
|
} |
2862
|
|
|
|
|
|
|
|
2863
|
0
|
0
|
|
|
|
|
if ( $ret == 0 ) { # 0 if field already exists in the hash and the value was updated |
2864
|
0
|
0
|
|
|
|
|
if ( $self ) { |
2865
|
0
|
0
|
|
|
|
|
if ( $parameter eq 'cleanup_bytes' ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
2866
|
0
|
|
|
|
|
|
$self->_set_cleanup_bytes( $new_val ); |
2867
|
|
|
|
|
|
|
} elsif ( $parameter eq 'cleanup_items' ) { |
2868
|
0
|
|
|
|
|
|
$self->_set_cleanup_items( $new_val ); |
2869
|
|
|
|
|
|
|
} elsif ( $parameter eq 'memory_reserve' ) { |
2870
|
0
|
|
|
|
|
|
$self->_set_memory_reserve( $new_val ); |
2871
|
|
|
|
|
|
|
} else { |
2872
|
0
|
|
|
|
|
|
$self->$parameter( $new_val ); |
2873
|
|
|
|
|
|
|
} |
2874
|
|
|
|
|
|
|
} |
2875
|
0
|
|
|
|
|
|
++$resized; |
2876
|
|
|
|
|
|
|
} else { |
2877
|
0
|
|
|
|
|
|
my $msg = format_message( "Parameter %s not updated to '%s' for collection '%s'", $parameter, $new_val, $name ); |
2878
|
0
|
0
|
|
|
|
|
if ( $self ) { |
2879
|
0
|
|
|
|
|
|
$self->_throw( $E_COLLECTION_DELETED, $msg ); |
2880
|
|
|
|
|
|
|
} else { |
2881
|
0
|
|
|
|
|
|
_croak( "$msg (".$ERROR{ $E_COLLECTION_DELETED }.')' ); |
2882
|
|
|
|
|
|
|
} |
2883
|
|
|
|
|
|
|
} |
2884
|
|
|
|
|
|
|
} |
2885
|
|
|
|
|
|
|
} |
2886
|
|
|
|
|
|
|
|
2887
|
0
|
|
|
|
|
|
return $resized; |
2888
|
|
|
|
|
|
|
} |
2889
|
|
|
|
|
|
|
|
2890
|
|
|
|
|
|
|
=head3 drop_collection |
2891
|
|
|
|
|
|
|
|
2892
|
|
|
|
|
|
|
drop_collection( redis => $server, name => $name ) |
2893
|
|
|
|
|
|
|
|
2894
|
|
|
|
|
|
|
Example: |
2895
|
|
|
|
|
|
|
|
2896
|
|
|
|
|
|
|
$coll->drop_collection; |
2897
|
|
|
|
|
|
|
my $redis = Redis->new( server => "$server:$port" ); |
2898
|
|
|
|
|
|
|
Redis::CappedCollection::drop_collection( redis => $redis, name => 'Some name' ); |
2899
|
|
|
|
|
|
|
|
2900
|
|
|
|
|
|
|
Use the C to remove the entire collection from the redis server, |
2901
|
|
|
|
|
|
|
including all its data and metadata. |
2902
|
|
|
|
|
|
|
|
2903
|
|
|
|
|
|
|
Before using this method, make sure that the collection is not being used by other customers. |
2904
|
|
|
|
|
|
|
|
2905
|
|
|
|
|
|
|
It can be called as either the existing C object method or a class function. |
2906
|
|
|
|
|
|
|
If invoked as the class function, C requires mandatory C and C |
2907
|
|
|
|
|
|
|
arguments. |
2908
|
|
|
|
|
|
|
These arguments are in key-value pairs as described for L method. |
2909
|
|
|
|
|
|
|
|
2910
|
|
|
|
|
|
|
Warning: consider C as a command that should only be used in production |
2911
|
|
|
|
|
|
|
environments with extreme care. Its performance is not optimal for large collections. |
2912
|
|
|
|
|
|
|
This command is intended for debugging and special operations. |
2913
|
|
|
|
|
|
|
Avoid using C in your regular application code. |
2914
|
|
|
|
|
|
|
|
2915
|
|
|
|
|
|
|
C mat throw an exception (C) if |
2916
|
|
|
|
|
|
|
the collection contains a very large number of lists |
2917
|
|
|
|
|
|
|
(C<'Error while reading from Redis server'>). |
2918
|
|
|
|
|
|
|
|
2919
|
|
|
|
|
|
|
An error exception is thrown (C) if an argument is not valid. |
2920
|
|
|
|
|
|
|
|
2921
|
|
|
|
|
|
|
=cut |
2922
|
|
|
|
|
|
|
sub drop_collection { |
2923
|
0
|
|
|
0
|
1
|
|
my $ret; |
2924
|
0
|
0
|
0
|
|
|
|
if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
2925
|
0
|
|
|
|
|
|
my $self = shift; |
2926
|
|
|
|
|
|
|
|
2927
|
0
|
|
|
|
|
|
my %arguments = @_; |
2928
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%arguments, [] ); |
2929
|
|
|
|
|
|
|
|
2930
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2931
|
|
|
|
|
|
|
|
2932
|
0
|
|
|
|
|
|
$ret = $self->_call_redis( |
2933
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'drop_collection' ), |
2934
|
|
|
|
|
|
|
0, |
2935
|
|
|
|
|
|
|
$self->name, |
2936
|
|
|
|
|
|
|
); |
2937
|
|
|
|
|
|
|
|
2938
|
0
|
|
|
|
|
|
$self->_clear_name; |
2939
|
0
|
|
|
|
|
|
$self->_clear_sha1; |
2940
|
|
|
|
|
|
|
} else { |
2941
|
0
|
0
|
|
|
|
|
shift if _CLASSISA( $_[0], __PACKAGE__ ); # allow calling Foo->bar as well as Foo::bar |
2942
|
|
|
|
|
|
|
|
2943
|
0
|
|
|
|
|
|
my %arguments = @_; |
2944
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%arguments, [ 'redis', 'name' ] ); |
2945
|
|
|
|
|
|
|
|
2946
|
0
|
0
|
|
|
|
|
_croak( "'redis' argument is required" ) unless defined $arguments{redis}; |
2947
|
0
|
0
|
|
|
|
|
_croak( "'name' argument is required" ) unless defined $arguments{name}; |
2948
|
|
|
|
|
|
|
|
2949
|
0
|
|
|
|
|
|
my $redis = _get_redis( $arguments{redis} ); |
2950
|
0
|
|
|
|
|
|
my $name = $arguments{name}; |
2951
|
|
|
|
|
|
|
|
2952
|
0
|
|
|
|
|
|
$ret = _call_redis( |
2953
|
|
|
|
|
|
|
$redis, |
2954
|
|
|
|
|
|
|
_lua_script_cmd( $redis, 'drop_collection' ), |
2955
|
|
|
|
|
|
|
0, |
2956
|
|
|
|
|
|
|
$name, |
2957
|
|
|
|
|
|
|
); |
2958
|
|
|
|
|
|
|
} |
2959
|
|
|
|
|
|
|
|
2960
|
0
|
|
|
|
|
|
return $ret; |
2961
|
|
|
|
|
|
|
} |
2962
|
|
|
|
|
|
|
|
2963
|
|
|
|
|
|
|
=head3 drop_list |
2964
|
|
|
|
|
|
|
|
2965
|
|
|
|
|
|
|
drop_list( $list_id ) |
2966
|
|
|
|
|
|
|
|
2967
|
|
|
|
|
|
|
Use the C method to remove the entire specified list. |
2968
|
|
|
|
|
|
|
Method removes all the structures on the Redis server associated with |
2969
|
|
|
|
|
|
|
the specified list. |
2970
|
|
|
|
|
|
|
|
2971
|
|
|
|
|
|
|
C<$list_id> must be a non-empty string. |
2972
|
|
|
|
|
|
|
|
2973
|
|
|
|
|
|
|
Method returns true if the list is removed, or false otherwise. |
2974
|
|
|
|
|
|
|
|
2975
|
|
|
|
|
|
|
=cut |
2976
|
|
|
|
|
|
|
my @_drop_list_result_keys = qw( |
2977
|
|
|
|
|
|
|
error |
2978
|
|
|
|
|
|
|
list_removed |
2979
|
|
|
|
|
|
|
); |
2980
|
|
|
|
|
|
|
|
2981
|
|
|
|
|
|
|
sub drop_list { |
2982
|
0
|
|
|
0
|
1
|
|
my ( $self, $list_id ) = @_; |
2983
|
|
|
|
|
|
|
|
2984
|
0
|
|
0
|
|
|
|
_STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
2985
|
|
|
|
|
|
|
|
2986
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2987
|
|
|
|
|
|
|
|
2988
|
0
|
|
|
|
|
|
my @ret = $self->_call_redis( |
2989
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'drop_list' ), |
2990
|
|
|
|
|
|
|
0, |
2991
|
|
|
|
|
|
|
$self->name, |
2992
|
|
|
|
|
|
|
$list_id, |
2993
|
|
|
|
|
|
|
); |
2994
|
0
|
|
|
|
|
|
my $results = _lists2hash( \@_drop_list_result_keys, \@ret ); |
2995
|
|
|
|
|
|
|
|
2996
|
0
|
|
|
|
|
|
my $error = $results->{error}; |
2997
|
|
|
|
|
|
|
|
2998
|
0
|
0
|
|
|
|
|
if ( exists $ERROR{ $error } ) { |
2999
|
0
|
0
|
|
|
|
|
$self->_clear_sha1 if $error == $E_COLLECTION_DELETED; |
3000
|
0
|
0
|
|
|
|
|
$self->_throw( $error ) if $error != $E_NO_ERROR; |
3001
|
|
|
|
|
|
|
} else { |
3002
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
3003
|
|
|
|
|
|
|
} |
3004
|
|
|
|
|
|
|
|
3005
|
0
|
|
|
|
|
|
return $results->{list_removed}; |
3006
|
|
|
|
|
|
|
} |
3007
|
|
|
|
|
|
|
|
3008
|
|
|
|
|
|
|
=head3 clear_collection |
3009
|
|
|
|
|
|
|
|
3010
|
|
|
|
|
|
|
$coll->clear_collection; |
3011
|
|
|
|
|
|
|
|
3012
|
|
|
|
|
|
|
Use the C to remove the entire collection data from the redis server, |
3013
|
|
|
|
|
|
|
|
3014
|
|
|
|
|
|
|
Before using this method, make sure that the collection is not being used by other customers. |
3015
|
|
|
|
|
|
|
|
3016
|
|
|
|
|
|
|
Warning: consider C as a command that should only be used in production |
3017
|
|
|
|
|
|
|
environments with extreme care. Its performance is not optimal for large collections. |
3018
|
|
|
|
|
|
|
This command is intended for debugging and special operations. |
3019
|
|
|
|
|
|
|
Avoid using C in your regular application code. |
3020
|
|
|
|
|
|
|
|
3021
|
|
|
|
|
|
|
C mat throw an exception (C) if |
3022
|
|
|
|
|
|
|
the collection contains a very large number of lists |
3023
|
|
|
|
|
|
|
(C<'Error while reading from Redis server'>). |
3024
|
|
|
|
|
|
|
|
3025
|
|
|
|
|
|
|
=cut |
3026
|
|
|
|
|
|
|
sub clear_collection { |
3027
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
3028
|
|
|
|
|
|
|
|
3029
|
0
|
|
|
|
|
|
my $ret; |
3030
|
|
|
|
|
|
|
|
3031
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
3032
|
|
|
|
|
|
|
|
3033
|
0
|
|
|
|
|
|
$ret = $self->_call_redis( |
3034
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'clear_collection' ), |
3035
|
|
|
|
|
|
|
0, |
3036
|
|
|
|
|
|
|
$self->name, |
3037
|
|
|
|
|
|
|
); |
3038
|
|
|
|
|
|
|
|
3039
|
0
|
|
|
|
|
|
return $ret; |
3040
|
|
|
|
|
|
|
} |
3041
|
|
|
|
|
|
|
|
3042
|
|
|
|
|
|
|
=head3 ping |
3043
|
|
|
|
|
|
|
|
3044
|
|
|
|
|
|
|
$is_alive = $coll->ping; |
3045
|
|
|
|
|
|
|
|
3046
|
|
|
|
|
|
|
This command is used to test if a connection is still alive. |
3047
|
|
|
|
|
|
|
|
3048
|
|
|
|
|
|
|
Returns 1 if a connection is still alive or 0 otherwise. |
3049
|
|
|
|
|
|
|
|
3050
|
|
|
|
|
|
|
External connections to the server object (eg, C <$redis = Redis->new( ... );>), |
3051
|
|
|
|
|
|
|
and the collection object can continue to work after calling ping only if the method returned 1. |
3052
|
|
|
|
|
|
|
|
3053
|
|
|
|
|
|
|
If there is no connection to the Redis server (methods return 0), the connection to the server closes. |
3054
|
|
|
|
|
|
|
In this case, to continue working with the collection, |
3055
|
|
|
|
|
|
|
you must re-create the C object with the L method. |
3056
|
|
|
|
|
|
|
When using an external connection to the server, |
3057
|
|
|
|
|
|
|
to check the connection to the server you can use the C<$redis->echo( ... )> call. |
3058
|
|
|
|
|
|
|
This is useful to avoid closing the connection to the Redis server unintentionally. |
3059
|
|
|
|
|
|
|
|
3060
|
|
|
|
|
|
|
=cut |
3061
|
|
|
|
|
|
|
sub ping { |
3062
|
0
|
|
|
0
|
1
|
|
my ( $self ) = @_; |
3063
|
|
|
|
|
|
|
|
3064
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
3065
|
|
|
|
|
|
|
|
3066
|
0
|
|
|
|
|
|
my $ret; |
3067
|
|
|
|
|
|
|
try { |
3068
|
0
|
|
|
0
|
|
|
$ret = $self->_redis->ping; |
3069
|
|
|
|
|
|
|
} catch { |
3070
|
0
|
|
|
0
|
|
|
$self->_redis_exception( $_ ); |
3071
|
0
|
|
|
|
|
|
}; |
3072
|
|
|
|
|
|
|
|
3073
|
0
|
0
|
0
|
|
|
|
return( ( $ret // '' ) eq 'PONG' ? 1 : 0 ); |
3074
|
|
|
|
|
|
|
} |
3075
|
|
|
|
|
|
|
|
3076
|
|
|
|
|
|
|
=head3 quit |
3077
|
|
|
|
|
|
|
|
3078
|
|
|
|
|
|
|
$coll->quit; |
3079
|
|
|
|
|
|
|
|
3080
|
|
|
|
|
|
|
Close the connection with the redis server. |
3081
|
|
|
|
|
|
|
|
3082
|
|
|
|
|
|
|
It does not close the connection to the Redis server if it is an external connection provided |
3083
|
|
|
|
|
|
|
to collection constructor as existing L object. |
3084
|
|
|
|
|
|
|
When using an external connection (eg, C<$redis = Redis-> new (...);>), |
3085
|
|
|
|
|
|
|
to close the connection to the Redis server, call C<$redis->quit> after calling this method. |
3086
|
|
|
|
|
|
|
|
3087
|
|
|
|
|
|
|
=cut |
3088
|
|
|
|
|
|
|
sub quit { |
3089
|
0
|
|
|
0
|
1
|
|
my ( $self ) = @_; |
3090
|
|
|
|
|
|
|
|
3091
|
0
|
0
|
0
|
|
|
|
return if $] >= 5.14 && ${^GLOBAL_PHASE} eq 'DESTRUCT'; |
3092
|
|
|
|
|
|
|
|
3093
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
3094
|
0
|
|
|
|
|
|
$self->_clear_sha1; |
3095
|
0
|
0
|
|
|
|
|
unless ( $self->_use_external_connection ) { |
3096
|
|
|
|
|
|
|
try { |
3097
|
0
|
|
|
0
|
|
|
$self->_redis->quit; |
3098
|
|
|
|
|
|
|
} catch { |
3099
|
0
|
|
|
0
|
|
|
$self->_redis_exception( $_ ); |
3100
|
0
|
|
|
|
|
|
}; |
3101
|
|
|
|
|
|
|
} |
3102
|
|
|
|
|
|
|
|
3103
|
0
|
|
|
|
|
|
return; |
3104
|
|
|
|
|
|
|
} |
3105
|
|
|
|
|
|
|
|
3106
|
|
|
|
|
|
|
#-- private attributes --------------------------------------------------------- |
3107
|
|
|
|
|
|
|
|
3108
|
|
|
|
|
|
|
has _DEBUG => ( |
3109
|
|
|
|
|
|
|
is => 'rw', |
3110
|
|
|
|
|
|
|
init_arg => undef, |
3111
|
|
|
|
|
|
|
isa => 'Num', |
3112
|
|
|
|
|
|
|
default => 0, |
3113
|
|
|
|
|
|
|
); |
3114
|
|
|
|
|
|
|
|
3115
|
|
|
|
|
|
|
has _check_maxmemory => ( |
3116
|
|
|
|
|
|
|
is => 'ro', |
3117
|
|
|
|
|
|
|
init_arg => 'check_maxmemory', |
3118
|
|
|
|
|
|
|
isa => 'Bool', |
3119
|
|
|
|
|
|
|
default => 1, |
3120
|
|
|
|
|
|
|
); |
3121
|
|
|
|
|
|
|
|
3122
|
|
|
|
|
|
|
has _create_from_naked_new => ( |
3123
|
|
|
|
|
|
|
is => 'ro', |
3124
|
|
|
|
|
|
|
isa => 'Bool', |
3125
|
|
|
|
|
|
|
default => 1, |
3126
|
|
|
|
|
|
|
); |
3127
|
|
|
|
|
|
|
|
3128
|
|
|
|
|
|
|
has _create_from_open => ( |
3129
|
|
|
|
|
|
|
is => 'ro', |
3130
|
|
|
|
|
|
|
isa => 'Bool', |
3131
|
|
|
|
|
|
|
default => 0, |
3132
|
|
|
|
|
|
|
); |
3133
|
|
|
|
|
|
|
|
3134
|
|
|
|
|
|
|
has _use_external_connection => ( |
3135
|
|
|
|
|
|
|
is => 'rw', |
3136
|
|
|
|
|
|
|
isa => 'Bool', |
3137
|
|
|
|
|
|
|
default => 1, |
3138
|
|
|
|
|
|
|
); |
3139
|
|
|
|
|
|
|
|
3140
|
|
|
|
|
|
|
has _server => ( |
3141
|
|
|
|
|
|
|
is => 'rw', |
3142
|
|
|
|
|
|
|
isa => 'Str', |
3143
|
|
|
|
|
|
|
default => $DEFAULT_SERVER.':'.$DEFAULT_PORT, |
3144
|
|
|
|
|
|
|
trigger => sub { |
3145
|
|
|
|
|
|
|
my $self = shift; |
3146
|
|
|
|
|
|
|
$self->_server( $self->_server.':'.$DEFAULT_PORT ) |
3147
|
|
|
|
|
|
|
unless $self->_server =~ /:/; |
3148
|
|
|
|
|
|
|
}, |
3149
|
|
|
|
|
|
|
); |
3150
|
|
|
|
|
|
|
|
3151
|
|
|
|
|
|
|
has _redis => ( |
3152
|
|
|
|
|
|
|
is => 'rw', |
3153
|
|
|
|
|
|
|
# 'Maybe[Test::RedisServer]' to test only |
3154
|
|
|
|
|
|
|
isa => 'Maybe[Redis] | Maybe[Test::RedisServer]', |
3155
|
|
|
|
|
|
|
); |
3156
|
|
|
|
|
|
|
|
3157
|
|
|
|
|
|
|
has _maxmemory => ( |
3158
|
|
|
|
|
|
|
is => 'rw', |
3159
|
|
|
|
|
|
|
isa => __PACKAGE__.'::NonNegInt', |
3160
|
|
|
|
|
|
|
init_arg => undef, |
3161
|
|
|
|
|
|
|
); |
3162
|
|
|
|
|
|
|
|
3163
|
|
|
|
|
|
|
foreach my $attr_name ( qw( |
3164
|
|
|
|
|
|
|
_queue_key |
3165
|
|
|
|
|
|
|
_status_key |
3166
|
|
|
|
|
|
|
_data_keys |
3167
|
|
|
|
|
|
|
_time_keys |
3168
|
|
|
|
|
|
|
) ) { |
3169
|
|
|
|
|
|
|
has $attr_name => ( |
3170
|
|
|
|
|
|
|
is => 'rw', |
3171
|
|
|
|
|
|
|
isa => 'Str', |
3172
|
|
|
|
|
|
|
init_arg => undef, |
3173
|
|
|
|
|
|
|
); |
3174
|
|
|
|
|
|
|
} |
3175
|
|
|
|
|
|
|
|
3176
|
|
|
|
|
|
|
my $_lua_scripts = {}; |
3177
|
|
|
|
|
|
|
|
3178
|
|
|
|
|
|
|
#-- private functions ---------------------------------------------------------- |
3179
|
|
|
|
|
|
|
|
3180
|
|
|
|
|
|
|
sub _check_arguments_acceptability { |
3181
|
0
|
|
|
0
|
|
|
my ( $received_arguments, $acceptable_arguments ) = @_; |
3182
|
|
|
|
|
|
|
|
3183
|
0
|
|
|
|
|
|
my ( %legal_arguments, @unlegal_arguments ); |
3184
|
0
|
|
|
|
|
|
$legal_arguments{ $_ } = 1 foreach @$acceptable_arguments; |
3185
|
0
|
|
|
|
|
|
foreach my $argument ( keys %$received_arguments ) { |
3186
|
0
|
0
|
|
|
|
|
push @unlegal_arguments, $argument unless exists $legal_arguments{ $argument }; |
3187
|
|
|
|
|
|
|
} |
3188
|
|
|
|
|
|
|
|
3189
|
0
|
0
|
|
|
|
|
_croak( format_message( 'Unknown arguments: %s', \@unlegal_arguments ) ) if @unlegal_arguments; |
3190
|
|
|
|
|
|
|
|
3191
|
0
|
|
|
|
|
|
return; |
3192
|
|
|
|
|
|
|
} |
3193
|
|
|
|
|
|
|
|
3194
|
|
|
|
|
|
|
sub _maxmemory_policy_ok { |
3195
|
0
|
|
|
0
|
|
|
my ( $self, $redis ); |
3196
|
0
|
0
|
0
|
|
|
|
if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
3197
|
0
|
|
|
|
|
|
$self = shift; |
3198
|
0
|
|
|
|
|
|
$redis = $self->_redis; |
3199
|
|
|
|
|
|
|
} else { |
3200
|
0
|
0
|
|
|
|
|
shift if _CLASSISA( $_[0], __PACKAGE__ ); # allow calling Foo->bar as well as Foo::bar |
3201
|
|
|
|
|
|
|
} |
3202
|
|
|
|
|
|
|
|
3203
|
0
|
|
|
|
|
|
my %arguments = @_; |
3204
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%arguments, [ 'redis' ] ); |
3205
|
|
|
|
|
|
|
|
3206
|
0
|
|
|
|
|
|
my $maxmemory_policy; |
3207
|
0
|
0
|
|
|
|
|
if ( $self ) { |
3208
|
0
|
|
|
|
|
|
( undef, $maxmemory_policy ) = $self->_call_redis( 'CONFIG', 'GET', 'maxmemory-policy' ); |
3209
|
|
|
|
|
|
|
} else { |
3210
|
0
|
|
|
|
|
|
my $redis_argument = $arguments{redis}; |
3211
|
0
|
0
|
|
|
|
|
_croak( "'redis' argument is required" ) unless defined( $redis_argument ); |
3212
|
0
|
|
|
|
|
|
( undef, $maxmemory_policy ) = _call_redis( _get_redis( $redis_argument ), 'CONFIG', 'GET', 'maxmemory-policy' ) |
3213
|
|
|
|
|
|
|
} |
3214
|
|
|
|
|
|
|
|
3215
|
0
|
|
0
|
|
|
|
return( defined( $maxmemory_policy ) && $maxmemory_policy eq $USED_MEMORY_POLICY ); |
3216
|
|
|
|
|
|
|
} |
3217
|
|
|
|
|
|
|
|
3218
|
|
|
|
|
|
|
sub _lists2hash { |
3219
|
0
|
|
|
0
|
|
|
my ( $keys, $vals ) = @_; |
3220
|
|
|
|
|
|
|
|
3221
|
0
|
0
|
0
|
|
|
|
_croak( $ERROR{ $E_MISMATCH_ARG }." for internal function '_lists2hash'" ) |
|
|
|
0
|
|
|
|
|
3222
|
|
|
|
|
|
|
unless _ARRAY( $keys ) && _ARRAY0( $vals ) && scalar( @$keys ) >= scalar( @$vals ); |
3223
|
|
|
|
|
|
|
|
3224
|
0
|
|
|
|
|
|
my %hash; |
3225
|
0
|
|
|
|
|
|
for ( my $idx = 0; $idx < @$keys; $idx++ ) { |
3226
|
0
|
|
|
|
|
|
$hash{ $keys->[ $idx ] } = $vals->[ $idx ]; |
3227
|
|
|
|
|
|
|
} |
3228
|
|
|
|
|
|
|
|
3229
|
0
|
|
|
|
|
|
return \%hash; |
3230
|
|
|
|
|
|
|
} |
3231
|
|
|
|
|
|
|
|
3232
|
|
|
|
|
|
|
sub _process_unknown_error { |
3233
|
0
|
|
|
0
|
|
|
my ( $self, @args ) = @_; |
3234
|
|
|
|
|
|
|
|
3235
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_UNKNOWN_ERROR ); |
3236
|
0
|
0
|
|
|
|
|
_unknown_error( @args, $self->reconnect_on_error ? $self->_reconnect( $E_UNKNOWN_ERROR ) : () ); |
3237
|
|
|
|
|
|
|
} |
3238
|
|
|
|
|
|
|
|
3239
|
|
|
|
|
|
|
sub _unknown_error { |
3240
|
0
|
|
|
0
|
|
|
my @args = @_; |
3241
|
|
|
|
|
|
|
|
3242
|
0
|
|
|
|
|
|
_croak( format_message( '%s: %s', $ERROR{ $E_UNKNOWN_ERROR }, \@args ) ); |
3243
|
|
|
|
|
|
|
} |
3244
|
|
|
|
|
|
|
|
3245
|
|
|
|
|
|
|
sub _croak { |
3246
|
0
|
|
|
0
|
|
|
my @args = @_; |
3247
|
|
|
|
|
|
|
|
3248
|
0
|
0
|
|
|
|
|
if ( $DEBUG ) { |
3249
|
0
|
|
|
|
|
|
confess @args; |
3250
|
|
|
|
|
|
|
} else { |
3251
|
0
|
|
|
|
|
|
croak @args; |
3252
|
|
|
|
|
|
|
} |
3253
|
|
|
|
|
|
|
} |
3254
|
|
|
|
|
|
|
|
3255
|
|
|
|
|
|
|
sub _make_data_key { |
3256
|
0
|
|
|
0
|
|
|
my ( $name ) = @_; |
3257
|
0
|
|
|
|
|
|
return( $NAMESPACE.':D:'.$name ); |
3258
|
|
|
|
|
|
|
} |
3259
|
|
|
|
|
|
|
|
3260
|
|
|
|
|
|
|
sub _make_time_key { |
3261
|
0
|
|
|
0
|
|
|
my ( $name ) = @_; |
3262
|
0
|
|
|
|
|
|
return( $NAMESPACE.':T:'.$name ); |
3263
|
|
|
|
|
|
|
} |
3264
|
|
|
|
|
|
|
|
3265
|
|
|
|
|
|
|
sub _make_status_key { |
3266
|
0
|
|
|
0
|
|
|
my ( $name ) = @_; |
3267
|
0
|
|
|
|
|
|
return( $NAMESPACE.':S:'.$name ); |
3268
|
|
|
|
|
|
|
} |
3269
|
|
|
|
|
|
|
|
3270
|
|
|
|
|
|
|
sub _get_redis { |
3271
|
0
|
|
|
0
|
|
|
my ( $redis ) = @_; |
3272
|
|
|
|
|
|
|
|
3273
|
0
|
0
|
|
|
|
|
$redis = _redis_constructor( $redis ) |
3274
|
|
|
|
|
|
|
unless _INSTANCE( $redis, 'Redis' ); |
3275
|
|
|
|
|
|
|
|
3276
|
0
|
|
|
|
|
|
return $redis; |
3277
|
|
|
|
|
|
|
} |
3278
|
|
|
|
|
|
|
|
3279
|
|
|
|
|
|
|
#-- private methods ------------------------------------------------------------ |
3280
|
|
|
|
|
|
|
|
3281
|
|
|
|
|
|
|
# for testing only |
3282
|
|
|
|
|
|
|
sub _long_term_operation { |
3283
|
0
|
|
|
0
|
|
|
my ( $self, $return_as_insert ) = @_; |
3284
|
|
|
|
|
|
|
|
3285
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
3286
|
|
|
|
|
|
|
|
3287
|
0
|
0
|
|
|
|
|
my @ret = $self->_call_redis( |
3288
|
|
|
|
|
|
|
$self->_lua_script_cmd( '_long_term_operation' ), |
3289
|
|
|
|
|
|
|
0, |
3290
|
|
|
|
|
|
|
$self->name, |
3291
|
|
|
|
|
|
|
$return_as_insert ? 1 : 0, |
3292
|
|
|
|
|
|
|
); |
3293
|
|
|
|
|
|
|
|
3294
|
0
|
|
|
|
|
|
my ( $error ) = @ret; |
3295
|
|
|
|
|
|
|
|
3296
|
0
|
0
|
|
|
|
|
if ( $return_as_insert ) { |
3297
|
0
|
0
|
0
|
|
|
|
if ( scalar( @ret ) == 1 && exists( $ERROR{ $error } ) ) { |
3298
|
0
|
0
|
0
|
|
|
|
if ( $error == $E_NO_ERROR ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
3299
|
|
|
|
|
|
|
# Normal result: Nothing to do |
3300
|
|
|
|
|
|
|
} elsif ( $error == $E_COLLECTION_DELETED ) { |
3301
|
0
|
|
|
|
|
|
$self->_clear_sha1; |
3302
|
0
|
|
|
|
|
|
$self->_throw( $error ); |
3303
|
|
|
|
|
|
|
} elsif ( |
3304
|
|
|
|
|
|
|
$error == $E_DATA_ID_EXISTS |
3305
|
|
|
|
|
|
|
|| $error == $E_OLDER_THAN_ALLOWED |
3306
|
|
|
|
|
|
|
) { |
3307
|
0
|
|
|
|
|
|
$self->_throw( $error ); |
3308
|
|
|
|
|
|
|
} else { |
3309
|
0
|
|
|
|
|
|
$self->_throw( $error, 'Unexpected error' ); |
3310
|
|
|
|
|
|
|
} |
3311
|
|
|
|
|
|
|
} else { |
3312
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
3313
|
|
|
|
|
|
|
} |
3314
|
|
|
|
|
|
|
} else { |
3315
|
0
|
0
|
0
|
|
|
|
if ( scalar( @ret ) == 3 && exists( $ERROR{ $error } ) && $ret[2] eq '_long_term_operation' ) { |
|
|
|
0
|
|
|
|
|
3316
|
0
|
0
|
|
|
|
|
if ( $error == $E_NO_ERROR ) { |
3317
|
|
|
|
|
|
|
# Normal result: Nothing to do |
3318
|
|
|
|
|
|
|
} else { |
3319
|
0
|
|
|
|
|
|
$self->_throw( $error, 'Unexpected error' ); |
3320
|
|
|
|
|
|
|
} |
3321
|
|
|
|
|
|
|
} else { |
3322
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
3323
|
|
|
|
|
|
|
} |
3324
|
|
|
|
|
|
|
} |
3325
|
|
|
|
|
|
|
|
3326
|
0
|
|
|
|
|
|
return \@ret; |
3327
|
|
|
|
|
|
|
} |
3328
|
|
|
|
|
|
|
|
3329
|
|
|
|
|
|
|
sub _data_list_key { |
3330
|
0
|
|
|
0
|
|
|
my ( $self, $list_id ) = @_; |
3331
|
|
|
|
|
|
|
|
3332
|
0
|
|
|
|
|
|
return( $self->_data_keys.':'.$list_id ); |
3333
|
|
|
|
|
|
|
} |
3334
|
|
|
|
|
|
|
|
3335
|
|
|
|
|
|
|
sub _time_list_key { |
3336
|
0
|
|
|
0
|
|
|
my ( $self, $list_id ) = @_; |
3337
|
|
|
|
|
|
|
|
3338
|
0
|
|
|
|
|
|
return( $self->_time_keys.':'.$list_id ); |
3339
|
|
|
|
|
|
|
} |
3340
|
|
|
|
|
|
|
|
3341
|
|
|
|
|
|
|
sub _verify_collection { |
3342
|
0
|
|
|
0
|
|
|
my ( $self ) = @_; |
3343
|
|
|
|
|
|
|
|
3344
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
3345
|
|
|
|
|
|
|
|
3346
|
0
|
0
|
0
|
|
|
|
my ( $status_exist, $older_allowed, $cleanup_bytes, $cleanup_items, $memory_reserve, $data_version ) = $self->_call_redis( |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
3347
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'verify_collection' ), |
3348
|
|
|
|
|
|
|
0, |
3349
|
|
|
|
|
|
|
$self->name, |
3350
|
|
|
|
|
|
|
$self->older_allowed ? 1 : 0, |
3351
|
|
|
|
|
|
|
$self->cleanup_bytes || 0, |
3352
|
|
|
|
|
|
|
$self->cleanup_items || 0, |
3353
|
|
|
|
|
|
|
$self->memory_reserve || $MIN_MEMORY_RESERVE, |
3354
|
|
|
|
|
|
|
); |
3355
|
|
|
|
|
|
|
|
3356
|
0
|
0
|
|
|
|
|
if ( $status_exist ) { |
3357
|
0
|
0
|
|
|
|
|
$self->cleanup_bytes( $cleanup_bytes ) unless $self->cleanup_bytes; |
3358
|
0
|
0
|
|
|
|
|
$self->cleanup_items( $cleanup_items ) unless $self->cleanup_items; |
3359
|
0
|
0
|
|
|
|
|
$older_allowed == $self->older_allowed or $self->_throw( $E_MISMATCH_ARG, 'older_allowed' ); |
3360
|
0
|
0
|
|
|
|
|
$cleanup_bytes == $self->cleanup_bytes or $self->_throw( $E_MISMATCH_ARG, 'cleanup_bytes' ); |
3361
|
0
|
0
|
|
|
|
|
$cleanup_items == $self->cleanup_items or $self->_throw( $E_MISMATCH_ARG, 'cleanup_items' ); |
3362
|
0
|
0
|
|
|
|
|
$memory_reserve == $self->memory_reserve or $self->_throw( $E_MISMATCH_ARG, 'memory_reserve' ); |
3363
|
0
|
0
|
|
|
|
|
$data_version == $DATA_VERSION or $self->_throw( $E_INCOMP_DATA_VERSION ); |
3364
|
|
|
|
|
|
|
} |
3365
|
|
|
|
|
|
|
} |
3366
|
|
|
|
|
|
|
|
3367
|
|
|
|
|
|
|
sub _reconnect { |
3368
|
0
|
|
|
0
|
|
|
my $self = shift; |
3369
|
0
|
|
0
|
|
|
|
my $err = shift // 0; |
3370
|
0
|
|
|
|
|
|
my $msg = shift; |
3371
|
|
|
|
|
|
|
|
3372
|
0
|
|
|
|
|
|
my $err_msg = ''; |
3373
|
0
|
0
|
0
|
|
|
|
if ( |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
3374
|
|
|
|
|
|
|
!$err || ( |
3375
|
|
|
|
|
|
|
$err != $E_MISMATCH_ARG |
3376
|
|
|
|
|
|
|
&& $err != $E_DATA_TOO_LARGE |
3377
|
|
|
|
|
|
|
&& $err != $E_MAXMEMORY_LIMIT |
3378
|
|
|
|
|
|
|
&& $err != $E_MAXMEMORY_POLICY |
3379
|
|
|
|
|
|
|
) |
3380
|
|
|
|
|
|
|
) { |
3381
|
|
|
|
|
|
|
try { |
3382
|
0
|
|
|
0
|
|
|
$self->_redis->connect; |
3383
|
|
|
|
|
|
|
} catch { |
3384
|
0
|
|
|
0
|
|
|
my $error = $_; |
3385
|
0
|
|
|
|
|
|
$err_msg = "(Not reconnected: $error)"; |
3386
|
0
|
|
|
|
|
|
}; |
3387
|
|
|
|
|
|
|
} |
3388
|
|
|
|
|
|
|
|
3389
|
0
|
0
|
|
|
|
|
if ( $err_msg ) { |
3390
|
0
|
0
|
|
|
|
|
$msg = defined( $msg ) |
|
|
0
|
|
|
|
|
|
3391
|
|
|
|
|
|
|
? ( $msg ? "$msg " : '' )."($err_msg)" |
3392
|
|
|
|
|
|
|
: $err_msg; |
3393
|
|
|
|
|
|
|
} |
3394
|
|
|
|
|
|
|
|
3395
|
0
|
|
|
|
|
|
return $msg; |
3396
|
|
|
|
|
|
|
} |
3397
|
|
|
|
|
|
|
|
3398
|
|
|
|
|
|
|
sub _throw { |
3399
|
0
|
|
|
0
|
|
|
my ( $self, $err, $prefix ) = @_; |
3400
|
|
|
|
|
|
|
|
3401
|
0
|
0
|
|
|
|
|
$prefix = $self->_reconnect( $err, $prefix ) if $self->reconnect_on_error; |
3402
|
|
|
|
|
|
|
|
3403
|
0
|
0
|
|
|
|
|
if ( exists $ERROR{ $err } ) { |
3404
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $err ); |
3405
|
0
|
0
|
|
|
|
|
_croak( format_message( '%s%s', ( $prefix ? "$prefix : " : '' ), $ERROR{ $err } ) ); |
3406
|
|
|
|
|
|
|
} else { |
3407
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_UNKNOWN_ERROR ); |
3408
|
0
|
0
|
|
|
|
|
_croak( format_message( '%s: %s%s', $ERROR{ $E_UNKNOWN_ERROR }, ( $prefix ? "$prefix : " : '' ), format_message( '%s', $err ) ) ); |
3409
|
|
|
|
|
|
|
} |
3410
|
|
|
|
|
|
|
} |
3411
|
|
|
|
|
|
|
|
3412
|
|
|
|
|
|
|
{ |
3413
|
|
|
|
|
|
|
my ( $_running_script_name, $_running_script_body ); |
3414
|
|
|
|
|
|
|
|
3415
|
|
|
|
|
|
|
sub _lua_script_cmd { |
3416
|
0
|
|
|
0
|
|
|
my ( $self, $redis ); |
3417
|
0
|
0
|
|
|
|
|
if ( _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
3418
|
0
|
|
|
|
|
|
$self = shift; |
3419
|
0
|
|
|
|
|
|
$redis = $self->_redis; |
3420
|
|
|
|
|
|
|
} else { # allow calling Foo::bar |
3421
|
0
|
|
|
|
|
|
$redis = shift; |
3422
|
|
|
|
|
|
|
} |
3423
|
|
|
|
|
|
|
|
3424
|
0
|
|
|
|
|
|
$_running_script_name = shift; |
3425
|
0
|
|
|
|
|
|
$_running_script_body = $lua_script_body{ $_running_script_name }; |
3426
|
|
|
|
|
|
|
|
3427
|
0
|
|
|
|
|
|
my $sha1 = $_lua_scripts->{ $redis }->{ $_running_script_name }; |
3428
|
0
|
0
|
|
|
|
|
unless ( $sha1 ) { |
3429
|
0
|
|
|
|
|
|
$sha1 = $_lua_scripts->{ $redis }->{ $_running_script_name } = sha1_hex( $_running_script_body ); |
3430
|
0
|
|
|
|
|
|
my $ret; |
3431
|
0
|
0
|
|
|
|
|
if ( $self ) { |
3432
|
0
|
|
|
|
|
|
$ret = ( $self->_call_redis( 'SCRIPT', 'EXISTS', $sha1 ) )[0]; |
3433
|
|
|
|
|
|
|
} else { |
3434
|
0
|
|
|
|
|
|
$ret = ( _call_redis( $redis, 'SCRIPT', 'EXISTS', $sha1 ) )[0]; |
3435
|
|
|
|
|
|
|
} |
3436
|
0
|
0
|
|
|
|
|
return( 'EVAL', $_running_script_body ) |
3437
|
|
|
|
|
|
|
unless $ret; |
3438
|
|
|
|
|
|
|
} |
3439
|
0
|
|
|
|
|
|
return( 'EVALSHA', $sha1 ); |
3440
|
|
|
|
|
|
|
} |
3441
|
|
|
|
|
|
|
|
3442
|
|
|
|
|
|
|
sub _redis_exception { |
3443
|
0
|
|
|
0
|
|
|
my $self; |
3444
|
0
|
0
|
|
|
|
|
$self = shift if _INSTANCE( $_[0], __PACKAGE__ ); # allow calling $obj->bar |
3445
|
0
|
|
|
|
|
|
my ( $error ) = @_; # allow calling Foo::bar |
3446
|
|
|
|
|
|
|
|
3447
|
0
|
|
|
|
|
|
my $err_msg = ''; |
3448
|
0
|
0
|
|
|
|
|
if ( $self ) { |
3449
|
|
|
|
|
|
|
# Use the error messages from Redis.pm |
3450
|
0
|
0
|
0
|
|
|
|
if ( |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
3451
|
|
|
|
|
|
|
$error =~ /^Could not connect to Redis server at / |
3452
|
|
|
|
|
|
|
|| $error =~ /^Can't close socket: / |
3453
|
|
|
|
|
|
|
|| $error =~ /^Not connected to any server/ |
3454
|
|
|
|
|
|
|
# Maybe for pub/sub only |
3455
|
|
|
|
|
|
|
|| $error =~ /^Error while reading from Redis server: / |
3456
|
|
|
|
|
|
|
|| $error =~ /^Redis server closed connection/ |
3457
|
|
|
|
|
|
|
) { |
3458
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NETWORK ); |
3459
|
|
|
|
|
|
|
|
3460
|
|
|
|
|
|
|
# For connection problem |
3461
|
0
|
0
|
|
|
|
|
$err_msg = $self->_reconnect( $E_UNKNOWN_ERROR, $err_msg ) if $self->reconnect_on_error; |
3462
|
|
|
|
|
|
|
} elsif ( |
3463
|
|
|
|
|
|
|
$error =~ /^\[[^]]+\]\s+-?\Q$REDIS_MEMORY_ERROR_MSG\E/i |
3464
|
|
|
|
|
|
|
|| $error =~ /^\[[^]]+\]\s+-?\Q$REDIS_ERROR_CODE $ERROR{ $E_MAXMEMORY_LIMIT }\E/i |
3465
|
|
|
|
|
|
|
|| $error =~ /^\[[^]]+\]\s+-NOSCRIPT No matching script. Please use EVAL./ |
3466
|
|
|
|
|
|
|
) { |
3467
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_MAXMEMORY_LIMIT ); |
3468
|
0
|
|
|
|
|
|
$self->_clear_sha1; |
3469
|
|
|
|
|
|
|
|
3470
|
|
|
|
|
|
|
# No connection problem |
3471
|
|
|
|
|
|
|
} elsif ( $error =~ /^\[[^]]+\]\s+BUSY Redis is busy running a script/ ){ |
3472
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_UNKNOWN_ERROR ); |
3473
|
|
|
|
|
|
|
|
3474
|
|
|
|
|
|
|
# No connection problem - must wait... |
3475
|
|
|
|
|
|
|
} else { # external ALRM processing here |
3476
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_REDIS ); |
3477
|
|
|
|
|
|
|
|
3478
|
|
|
|
|
|
|
# For possible connection problems |
3479
|
0
|
0
|
|
|
|
|
$err_msg = $self->_reconnect( $E_UNKNOWN_ERROR, $err_msg ) if $self->reconnect_on_error; |
3480
|
|
|
|
|
|
|
} |
3481
|
|
|
|
|
|
|
} else { |
3482
|
|
|
|
|
|
|
# nothing to do now |
3483
|
|
|
|
|
|
|
} |
3484
|
|
|
|
|
|
|
|
3485
|
0
|
0
|
|
|
|
|
if ( $error =~ /\] ERR Error (?:running|compiling) script/ ) { |
3486
|
0
|
|
|
|
|
|
$error .= "\nLua script '$_running_script_name':\n$_running_script_body"; |
3487
|
|
|
|
|
|
|
} |
3488
|
0
|
|
|
|
|
|
_croak( format_message( '%s %s', $error, $err_msg ) ); |
3489
|
|
|
|
|
|
|
} |
3490
|
|
|
|
|
|
|
} |
3491
|
|
|
|
|
|
|
|
3492
|
|
|
|
|
|
|
sub _clear_sha1 { |
3493
|
0
|
|
|
0
|
|
|
my ( $self ) = @_; |
3494
|
|
|
|
|
|
|
|
3495
|
0
|
0
|
|
|
|
|
delete( $_lua_scripts->{ $self->_redis } ) if $self->_redis; |
3496
|
|
|
|
|
|
|
} |
3497
|
|
|
|
|
|
|
|
3498
|
|
|
|
|
|
|
sub _redis_constructor { |
3499
|
0
|
|
|
0
|
|
|
my ( $self, $redis, $redis_parameters ); |
3500
|
0
|
0
|
0
|
|
|
|
if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
3501
|
0
|
|
|
|
|
|
$self = shift; |
3502
|
0
|
|
|
|
|
|
$redis_parameters = shift; |
3503
|
|
|
|
|
|
|
|
3504
|
0
|
0
|
|
|
|
|
if ( _HASH0( $redis_parameters ) ) { |
3505
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
3506
|
|
|
|
|
|
|
$redis = try { |
3507
|
0
|
|
|
0
|
|
|
Redis->new( %$redis_parameters ); |
3508
|
|
|
|
|
|
|
} catch { |
3509
|
0
|
|
|
0
|
|
|
my $error = $_; |
3510
|
0
|
|
|
|
|
|
$self->_redis_exception( format_message( '%s; (redis_parameters = %s)', $error, _parameters_2_str( $redis_parameters ) ) ); |
3511
|
0
|
|
|
|
|
|
}; |
3512
|
|
|
|
|
|
|
} else { |
3513
|
0
|
|
|
|
|
|
$redis = $self->_redis; |
3514
|
|
|
|
|
|
|
} |
3515
|
|
|
|
|
|
|
} else { # allow calling Foo::bar |
3516
|
0
|
0
|
|
|
|
|
$redis_parameters = _HASH0( shift ) or _croak( $ERROR{ $E_MISMATCH_ARG } ); |
3517
|
|
|
|
|
|
|
$redis = try { |
3518
|
0
|
|
|
0
|
|
|
Redis->new( %$redis_parameters ); |
3519
|
|
|
|
|
|
|
} catch { |
3520
|
0
|
|
|
0
|
|
|
my $error = $_; |
3521
|
0
|
|
|
|
|
|
_croak( format_message( "'Redis' exception: %s; (redis_parameters = %s)", $error, _parameters_2_str( $redis_parameters ) ) ); |
3522
|
0
|
|
|
|
|
|
}; |
3523
|
|
|
|
|
|
|
} |
3524
|
|
|
|
|
|
|
|
3525
|
0
|
|
|
|
|
|
return $redis; |
3526
|
|
|
|
|
|
|
} |
3527
|
|
|
|
|
|
|
|
3528
|
|
|
|
|
|
|
sub _parameters_2_str { |
3529
|
0
|
|
|
0
|
|
|
my ( $parameters_hash_ref ) = @_; |
3530
|
|
|
|
|
|
|
|
3531
|
0
|
|
|
|
|
|
my %parameters_hash = ( %$parameters_hash_ref ); |
3532
|
0
|
0
|
|
|
|
|
$parameters_hash{password} =~ s/./*/g if defined $parameters_hash{password}; |
3533
|
|
|
|
|
|
|
|
3534
|
0
|
|
|
|
|
|
return format_message( '%s', \%parameters_hash ); |
3535
|
|
|
|
|
|
|
} |
3536
|
|
|
|
|
|
|
|
3537
|
|
|
|
|
|
|
# Keep in mind the default 'redis.conf' values: |
3538
|
|
|
|
|
|
|
# Close the connection after a client is idle for N seconds (0 to disable) |
3539
|
|
|
|
|
|
|
# timeout 300 |
3540
|
|
|
|
|
|
|
|
3541
|
|
|
|
|
|
|
# Send a request to Redis |
3542
|
|
|
|
|
|
|
sub _call_redis { |
3543
|
0
|
|
|
0
|
|
|
my ( $self, $redis ); |
3544
|
0
|
0
|
|
|
|
|
if ( _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
3545
|
0
|
|
|
|
|
|
$self = shift; |
3546
|
|
|
|
|
|
|
|
3547
|
0
|
0
|
0
|
|
|
|
if ( $self->reconnect_on_error && !$self->ping ) { |
3548
|
0
|
|
|
|
|
|
my $err_msg = $self->_reconnect(); |
3549
|
0
|
0
|
|
|
|
|
_croak( format_message( '%s: %s', $ERROR{$E_REDIS}, $err_msg ) ) if $err_msg; |
3550
|
|
|
|
|
|
|
} |
3551
|
|
|
|
|
|
|
|
3552
|
0
|
|
|
|
|
|
$redis = $self->_redis; |
3553
|
|
|
|
|
|
|
} else { # allow calling Foo::bar |
3554
|
0
|
|
|
|
|
|
$redis = shift; |
3555
|
|
|
|
|
|
|
} |
3556
|
0
|
|
|
|
|
|
my $method = shift; |
3557
|
|
|
|
|
|
|
|
3558
|
0
|
0
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ) if $self; |
3559
|
|
|
|
|
|
|
|
3560
|
0
|
|
|
|
|
|
my @return; |
3561
|
0
|
|
|
|
|
|
my @args = @_; |
3562
|
|
|
|
|
|
|
try { |
3563
|
0
|
0
|
|
0
|
|
|
@return = $redis->$method( map { ref( $_ ) ? $$_ : $_ } @args ); |
|
0
|
|
|
|
|
|
|
3564
|
|
|
|
|
|
|
} catch { |
3565
|
0
|
|
|
0
|
|
|
my $error = $_; |
3566
|
0
|
0
|
|
|
|
|
if ( $self ) { |
3567
|
0
|
|
|
|
|
|
$self->_redis_exception( $error ); |
3568
|
|
|
|
|
|
|
} else { |
3569
|
0
|
|
|
|
|
|
_redis_exception( $error ); |
3570
|
|
|
|
|
|
|
} |
3571
|
0
|
|
|
|
|
|
}; |
3572
|
|
|
|
|
|
|
|
3573
|
0
|
0
|
|
|
|
|
unless ( scalar @return ) { |
3574
|
0
|
0
|
|
|
|
|
$self->_set_last_errorcode( $E_REDIS_DID_NOT_RETURN_DATA ) |
3575
|
|
|
|
|
|
|
if $self; |
3576
|
0
|
|
|
|
|
|
_croak( $ERROR{ $E_REDIS_DID_NOT_RETURN_DATA } ); |
3577
|
|
|
|
|
|
|
} |
3578
|
|
|
|
|
|
|
|
3579
|
0
|
0
|
|
|
|
|
return wantarray ? @return : $return[0]; |
3580
|
|
|
|
|
|
|
} |
3581
|
|
|
|
|
|
|
|
3582
|
|
|
|
|
|
|
sub DESTROY { |
3583
|
|
|
|
|
|
|
my ( $self ) = @_; |
3584
|
|
|
|
|
|
|
|
3585
|
|
|
|
|
|
|
$self->clear_sha1; |
3586
|
|
|
|
|
|
|
} |
3587
|
|
|
|
|
|
|
|
3588
|
|
|
|
|
|
|
#-- Closes and cleans up ------------------------------------------------------- |
3589
|
|
|
|
|
|
|
|
3590
|
98
|
|
|
98
|
|
546
|
no Mouse::Util::TypeConstraints; |
|
98
|
|
|
|
|
119
|
|
|
98
|
|
|
|
|
794
|
|
3591
|
98
|
|
|
98
|
|
11254
|
no Mouse; # keywords are removed from the package |
|
98
|
|
|
|
|
111
|
|
|
98
|
|
|
|
|
352
|
|
3592
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable(); |
3593
|
|
|
|
|
|
|
|
3594
|
|
|
|
|
|
|
=head2 DIAGNOSTICS |
3595
|
|
|
|
|
|
|
|
3596
|
|
|
|
|
|
|
All recognizable errors in C set corresponding value |
3597
|
|
|
|
|
|
|
into the L and throw an exception (C). |
3598
|
|
|
|
|
|
|
Unidentified errors also throw exceptions but L is not set. |
3599
|
|
|
|
|
|
|
|
3600
|
|
|
|
|
|
|
In addition to errors in the L module, detected errors are |
3601
|
|
|
|
|
|
|
L$E_MISMATCH_ARG>, L$E_DATA_TOO_LARGE>, L$E_MAXMEMORY_POLICY>, L$E_COLLECTION_DELETED>, |
3602
|
|
|
|
|
|
|
L$E_DATA_ID_EXISTS>, L$E_OLDER_THAN_ALLOWED>, L$E_NONEXISTENT_DATA_ID>, |
3603
|
|
|
|
|
|
|
L$E_INCOMP_DATA_VERSION>, L$E_REDIS_DID_NOT_RETURN_DATA>, L$E_UNKNOWN_ERROR>. |
3604
|
|
|
|
|
|
|
|
3605
|
|
|
|
|
|
|
The user has the choice: |
3606
|
|
|
|
|
|
|
|
3607
|
|
|
|
|
|
|
=over 3 |
3608
|
|
|
|
|
|
|
|
3609
|
|
|
|
|
|
|
=item * |
3610
|
|
|
|
|
|
|
|
3611
|
|
|
|
|
|
|
Use the module methods and independently analyze the situation without the use |
3612
|
|
|
|
|
|
|
of L. |
3613
|
|
|
|
|
|
|
|
3614
|
|
|
|
|
|
|
=item * |
3615
|
|
|
|
|
|
|
|
3616
|
|
|
|
|
|
|
Piece of code wrapped in C and analyze L |
3617
|
|
|
|
|
|
|
(look at the L"An Example"> section). |
3618
|
|
|
|
|
|
|
|
3619
|
|
|
|
|
|
|
=back |
3620
|
|
|
|
|
|
|
|
3621
|
|
|
|
|
|
|
=head2 Debug mode |
3622
|
|
|
|
|
|
|
|
3623
|
|
|
|
|
|
|
An error exception is thrown with C if the package variable C<$DEBUG> set to true. |
3624
|
|
|
|
|
|
|
|
3625
|
|
|
|
|
|
|
=head2 An Example |
3626
|
|
|
|
|
|
|
|
3627
|
|
|
|
|
|
|
An example of error handling. |
3628
|
|
|
|
|
|
|
|
3629
|
|
|
|
|
|
|
use 5.010; |
3630
|
|
|
|
|
|
|
use strict; |
3631
|
|
|
|
|
|
|
use warnings; |
3632
|
|
|
|
|
|
|
|
3633
|
|
|
|
|
|
|
#-- Common --------------------------------------------------------- |
3634
|
|
|
|
|
|
|
use Redis::CappedCollection qw( |
3635
|
|
|
|
|
|
|
$DEFAULT_SERVER |
3636
|
|
|
|
|
|
|
$DEFAULT_PORT |
3637
|
|
|
|
|
|
|
|
3638
|
|
|
|
|
|
|
$E_NO_ERROR |
3639
|
|
|
|
|
|
|
$E_MISMATCH_ARG |
3640
|
|
|
|
|
|
|
$E_DATA_TOO_LARGE |
3641
|
|
|
|
|
|
|
$E_NETWORK |
3642
|
|
|
|
|
|
|
$E_MAXMEMORY_LIMIT |
3643
|
|
|
|
|
|
|
$E_MAXMEMORY_POLICY |
3644
|
|
|
|
|
|
|
$E_COLLECTION_DELETED |
3645
|
|
|
|
|
|
|
$E_REDIS |
3646
|
|
|
|
|
|
|
); |
3647
|
|
|
|
|
|
|
|
3648
|
|
|
|
|
|
|
# Error handling |
3649
|
|
|
|
|
|
|
sub exception { |
3650
|
|
|
|
|
|
|
my $coll = shift; |
3651
|
|
|
|
|
|
|
my $err = shift; |
3652
|
|
|
|
|
|
|
|
3653
|
|
|
|
|
|
|
die $err unless $coll; |
3654
|
|
|
|
|
|
|
if ( $coll->last_errorcode == $E_NO_ERROR ) { |
3655
|
|
|
|
|
|
|
# For example, to ignore |
3656
|
|
|
|
|
|
|
return unless $err; |
3657
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_MISMATCH_ARG ) { |
3658
|
|
|
|
|
|
|
# Necessary to correct the code |
3659
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_DATA_TOO_LARGE ) { |
3660
|
|
|
|
|
|
|
# Limit data length |
3661
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_NETWORK ) { |
3662
|
|
|
|
|
|
|
# For example, sleep |
3663
|
|
|
|
|
|
|
#sleep 60; |
3664
|
|
|
|
|
|
|
# and return code to repeat the operation |
3665
|
|
|
|
|
|
|
#return 'to repeat'; |
3666
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_MAXMEMORY_LIMIT ) { |
3667
|
|
|
|
|
|
|
# For example, return code to restart the server |
3668
|
|
|
|
|
|
|
#return 'to restart the redis server'; |
3669
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_MAXMEMORY_POLICY ) { |
3670
|
|
|
|
|
|
|
# Correct Redis server 'maxmemory-policy' setting |
3671
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_COLLECTION_DELETED ) { |
3672
|
|
|
|
|
|
|
# For example, return code to ignore |
3673
|
|
|
|
|
|
|
#return "to ignore $err"; |
3674
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_REDIS ) { |
3675
|
|
|
|
|
|
|
# Independently analyze the $err |
3676
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_DATA_ID_EXISTS ) { |
3677
|
|
|
|
|
|
|
# For example, return code to reinsert the data |
3678
|
|
|
|
|
|
|
#return "to reinsert with new data ID"; |
3679
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_OLDER_THAN_ALLOWED ) { |
3680
|
|
|
|
|
|
|
# Independently analyze the situation |
3681
|
|
|
|
|
|
|
} else { |
3682
|
|
|
|
|
|
|
# Unknown error code |
3683
|
|
|
|
|
|
|
} |
3684
|
|
|
|
|
|
|
die $err if $err; |
3685
|
|
|
|
|
|
|
} |
3686
|
|
|
|
|
|
|
|
3687
|
|
|
|
|
|
|
my ( $list_id, $coll, @data ); |
3688
|
|
|
|
|
|
|
|
3689
|
|
|
|
|
|
|
eval { |
3690
|
|
|
|
|
|
|
$coll = Redis::CappedCollection->create( |
3691
|
|
|
|
|
|
|
redis => $DEFAULT_SERVER.':'.$DEFAULT_PORT, |
3692
|
|
|
|
|
|
|
name => 'Some name', |
3693
|
|
|
|
|
|
|
); |
3694
|
|
|
|
|
|
|
}; |
3695
|
|
|
|
|
|
|
exception( $coll, $@ ) if $@; |
3696
|
|
|
|
|
|
|
say "'", $coll->name, "' collection created."; |
3697
|
|
|
|
|
|
|
|
3698
|
|
|
|
|
|
|
#-- Producer ------------------------------------------------------- |
3699
|
|
|
|
|
|
|
#-- New data |
3700
|
|
|
|
|
|
|
|
3701
|
|
|
|
|
|
|
eval { |
3702
|
|
|
|
|
|
|
$list_id = $coll->insert( |
3703
|
|
|
|
|
|
|
'Some List_id', # list id |
3704
|
|
|
|
|
|
|
123, # data id |
3705
|
|
|
|
|
|
|
'Some data', |
3706
|
|
|
|
|
|
|
); |
3707
|
|
|
|
|
|
|
say "Added data in a list with '", $list_id, "' id" ); |
3708
|
|
|
|
|
|
|
|
3709
|
|
|
|
|
|
|
# Change the "zero" element of the list with the ID $list_id |
3710
|
|
|
|
|
|
|
if ( $coll->update( $list_id, 0, 'New data' ) ) { |
3711
|
|
|
|
|
|
|
say 'Data updated successfully'; |
3712
|
|
|
|
|
|
|
} else { |
3713
|
|
|
|
|
|
|
say 'Failed to update element'; |
3714
|
|
|
|
|
|
|
} |
3715
|
|
|
|
|
|
|
}; |
3716
|
|
|
|
|
|
|
exception( $coll, $@ ) if $@; |
3717
|
|
|
|
|
|
|
|
3718
|
|
|
|
|
|
|
#-- Consumer ------------------------------------------------------- |
3719
|
|
|
|
|
|
|
#-- Fetching the data |
3720
|
|
|
|
|
|
|
|
3721
|
|
|
|
|
|
|
eval { |
3722
|
|
|
|
|
|
|
@data = $coll->receive( $list_id ); |
3723
|
|
|
|
|
|
|
say "List '$list_id' has '$_'" foreach @data; |
3724
|
|
|
|
|
|
|
# or to obtain records in the order they were placed |
3725
|
|
|
|
|
|
|
while ( my ( $list_id, $data ) = $coll->pop_oldest ) { |
3726
|
|
|
|
|
|
|
say "List '$list_id' had '$data'"; |
3727
|
|
|
|
|
|
|
} |
3728
|
|
|
|
|
|
|
}; |
3729
|
|
|
|
|
|
|
exception( $coll, $@ ) if $@; |
3730
|
|
|
|
|
|
|
|
3731
|
|
|
|
|
|
|
#-- Utility -------------------------------------------------------- |
3732
|
|
|
|
|
|
|
#-- Getting statistics |
3733
|
|
|
|
|
|
|
|
3734
|
|
|
|
|
|
|
my ( $lists, $items ); |
3735
|
|
|
|
|
|
|
eval { |
3736
|
|
|
|
|
|
|
my $info = $coll->collection_info; |
3737
|
|
|
|
|
|
|
say 'An existing collection uses ', $info->{cleanup_bytes}, " byte of 'cleanup_bytes', ", |
3738
|
|
|
|
|
|
|
'in ', $info->{items}, ' items are placed in ', |
3739
|
|
|
|
|
|
|
$info->{lists}, ' lists'; |
3740
|
|
|
|
|
|
|
|
3741
|
|
|
|
|
|
|
say "The collection has '$list_id' list" |
3742
|
|
|
|
|
|
|
if $coll->list_exists( 'Some_id' ); |
3743
|
|
|
|
|
|
|
}; |
3744
|
|
|
|
|
|
|
exception( $coll, $@ ) if $@; |
3745
|
|
|
|
|
|
|
|
3746
|
|
|
|
|
|
|
#-- Closes and cleans up ------------------------------------------- |
3747
|
|
|
|
|
|
|
|
3748
|
|
|
|
|
|
|
eval { |
3749
|
|
|
|
|
|
|
$coll->quit; |
3750
|
|
|
|
|
|
|
|
3751
|
|
|
|
|
|
|
# Before use, make sure that the collection |
3752
|
|
|
|
|
|
|
# is not being used by other clients |
3753
|
|
|
|
|
|
|
#$coll->drop_collection; |
3754
|
|
|
|
|
|
|
}; |
3755
|
|
|
|
|
|
|
exception( $coll, $@ ) if $@; |
3756
|
|
|
|
|
|
|
|
3757
|
|
|
|
|
|
|
=head2 CappedCollection data structure |
3758
|
|
|
|
|
|
|
|
3759
|
|
|
|
|
|
|
Using currently selected database (default = 0). |
3760
|
|
|
|
|
|
|
|
3761
|
|
|
|
|
|
|
CappedCollection package creates the following data structures on Redis: |
3762
|
|
|
|
|
|
|
|
3763
|
|
|
|
|
|
|
#-- To store collection status: |
3764
|
|
|
|
|
|
|
# HASH Namespace:S:Collection_id |
3765
|
|
|
|
|
|
|
# For example: |
3766
|
|
|
|
|
|
|
$ redis-cli |
3767
|
|
|
|
|
|
|
redis 127.0.0.1:6379> KEYS C:S:* |
3768
|
|
|
|
|
|
|
1) "C:S:Some collection name" |
3769
|
|
|
|
|
|
|
# | | | |
3770
|
|
|
|
|
|
|
# | +-------+ +------------+ |
3771
|
|
|
|
|
|
|
# | | | |
3772
|
|
|
|
|
|
|
# Namespace | | |
3773
|
|
|
|
|
|
|
# Fixed symbol of a properties hash | |
3774
|
|
|
|
|
|
|
# Capped Collection id |
3775
|
|
|
|
|
|
|
... |
3776
|
|
|
|
|
|
|
redis 127.0.0.1:6379> HGETALL "C:S:Some collection name" |
3777
|
|
|
|
|
|
|
1) "lists" # hash key |
3778
|
|
|
|
|
|
|
2) "1" # the key value |
3779
|
|
|
|
|
|
|
3) "items" # hash key |
3780
|
|
|
|
|
|
|
4) "1" # the key value |
3781
|
|
|
|
|
|
|
5) "older_allowed" # hash key |
3782
|
|
|
|
|
|
|
6) "0" # the key value |
3783
|
|
|
|
|
|
|
7) "cleanup_bytes" # hash key |
3784
|
|
|
|
|
|
|
8) "0" # the key value |
3785
|
|
|
|
|
|
|
9) "cleanup_items" # hash key |
3786
|
|
|
|
|
|
|
10) "100" # the key value |
3787
|
|
|
|
|
|
|
11) "memory_reserve" # hash key |
3788
|
|
|
|
|
|
|
12) "0.05" # the key value |
3789
|
|
|
|
|
|
|
13) "data_version" # hash key |
3790
|
|
|
|
|
|
|
14) "3" # the key value |
3791
|
|
|
|
|
|
|
15) "last_removed_time" # hash key |
3792
|
|
|
|
|
|
|
16) "0" # the key value |
3793
|
|
|
|
|
|
|
... |
3794
|
|
|
|
|
|
|
|
3795
|
|
|
|
|
|
|
#-- To store collection queue: |
3796
|
|
|
|
|
|
|
# ZSET Namespace:Q:Collection_id |
3797
|
|
|
|
|
|
|
# For example: |
3798
|
|
|
|
|
|
|
redis 127.0.0.1:6379> KEYS C:Q:* |
3799
|
|
|
|
|
|
|
1) "C:Q:Some collection name" |
3800
|
|
|
|
|
|
|
# | | | |
3801
|
|
|
|
|
|
|
# | +------+ +-----------+ |
3802
|
|
|
|
|
|
|
# | | | |
3803
|
|
|
|
|
|
|
# Namespace | | |
3804
|
|
|
|
|
|
|
# Fixed symbol of a queue | |
3805
|
|
|
|
|
|
|
# Capped Collection id |
3806
|
|
|
|
|
|
|
... |
3807
|
|
|
|
|
|
|
redis 127.0.0.1:6379> ZRANGE "C:Q:Some collection name" 0 -1 WITHSCORES |
3808
|
|
|
|
|
|
|
1) "Some list id" ----------+ |
3809
|
|
|
|
|
|
|
2) "1348252575.6651001" | |
3810
|
|
|
|
|
|
|
# | | |
3811
|
|
|
|
|
|
|
# Score: oldest data_time | |
3812
|
|
|
|
|
|
|
# Member: Data List id |
3813
|
|
|
|
|
|
|
... |
3814
|
|
|
|
|
|
|
|
3815
|
|
|
|
|
|
|
#-- To store CappedCollection data: |
3816
|
|
|
|
|
|
|
# HASH Namespace:I:Collection_id:DataList_id |
3817
|
|
|
|
|
|
|
# HASH Namespace:D:Collection_id:DataList_id |
3818
|
|
|
|
|
|
|
# If the amount of data in the list is greater than 1 |
3819
|
|
|
|
|
|
|
# ZSET Namespace:T:Collection_id:DataList_id |
3820
|
|
|
|
|
|
|
# For example: |
3821
|
|
|
|
|
|
|
redis 127.0.0.1:6379> KEYS C:[DT]:* |
3822
|
|
|
|
|
|
|
1) "C:D:Some collection name:Some list id" |
3823
|
|
|
|
|
|
|
# If the amount of data in the list is greater than 1 |
3824
|
|
|
|
|
|
|
2) "C:T:Some collection name:Some list id" |
3825
|
|
|
|
|
|
|
# | | | | |
3826
|
|
|
|
|
|
|
# | +-----+ +-------+ + ---------+ |
3827
|
|
|
|
|
|
|
# | | | | |
3828
|
|
|
|
|
|
|
# Namespace | | | |
3829
|
|
|
|
|
|
|
# Fixed symbol of a list of data | | |
3830
|
|
|
|
|
|
|
# Capped Collection id | |
3831
|
|
|
|
|
|
|
# Data list id |
3832
|
|
|
|
|
|
|
... |
3833
|
|
|
|
|
|
|
redis 127.0.0.1:6379> HGETALL "C:D:Some collection name:Some list id" |
3834
|
|
|
|
|
|
|
1) "0" # hash key: Data id |
3835
|
|
|
|
|
|
|
2) "Some stuff" # the key value: Data |
3836
|
|
|
|
|
|
|
... |
3837
|
|
|
|
|
|
|
# If the amount of data in the list is greater than 1 |
3838
|
|
|
|
|
|
|
redis 127.0.0.1:6379> ZRANGE "C:T:Some collection name:Some list id" 0 -1 WITHSCORES |
3839
|
|
|
|
|
|
|
1) "0" ---------------+ |
3840
|
|
|
|
|
|
|
2) "1348252575.5906" | |
3841
|
|
|
|
|
|
|
# | | |
3842
|
|
|
|
|
|
|
# Score: data_time | |
3843
|
|
|
|
|
|
|
# Member: Data id |
3844
|
|
|
|
|
|
|
... |
3845
|
|
|
|
|
|
|
|
3846
|
|
|
|
|
|
|
=head1 DEPENDENCIES |
3847
|
|
|
|
|
|
|
|
3848
|
|
|
|
|
|
|
In order to install and use this package Perl version 5.010 or better is |
3849
|
|
|
|
|
|
|
required. Redis::CappedCollection module depends on other packages |
3850
|
|
|
|
|
|
|
that are distributed separately from Perl. We recommend the following packages |
3851
|
|
|
|
|
|
|
to be installed before installing Redis::CappedCollection : |
3852
|
|
|
|
|
|
|
|
3853
|
|
|
|
|
|
|
Const::Fast |
3854
|
|
|
|
|
|
|
Digest::SHA1 |
3855
|
|
|
|
|
|
|
Mouse |
3856
|
|
|
|
|
|
|
Params::Util |
3857
|
|
|
|
|
|
|
Redis |
3858
|
|
|
|
|
|
|
Try::Tiny |
3859
|
|
|
|
|
|
|
|
3860
|
|
|
|
|
|
|
The Redis::CappedCollection module has the following optional dependencies: |
3861
|
|
|
|
|
|
|
|
3862
|
|
|
|
|
|
|
Data::UUID |
3863
|
|
|
|
|
|
|
JSON::XS |
3864
|
|
|
|
|
|
|
Net::EmptyPort |
3865
|
|
|
|
|
|
|
Test::Exception |
3866
|
|
|
|
|
|
|
Test::NoWarnings |
3867
|
|
|
|
|
|
|
Test::RedisServer |
3868
|
|
|
|
|
|
|
|
3869
|
|
|
|
|
|
|
If the optional modules are missing, some "prereq" tests are skipped. |
3870
|
|
|
|
|
|
|
|
3871
|
|
|
|
|
|
|
The installation of the missing dependencies can either be accomplished |
3872
|
|
|
|
|
|
|
through your OS package manager or through CPAN (or downloading the source |
3873
|
|
|
|
|
|
|
for all dependencies and compiling them manually). |
3874
|
|
|
|
|
|
|
|
3875
|
|
|
|
|
|
|
=head1 BUGS AND LIMITATIONS |
3876
|
|
|
|
|
|
|
|
3877
|
|
|
|
|
|
|
Redis server version 2.8 or higher is required. |
3878
|
|
|
|
|
|
|
|
3879
|
|
|
|
|
|
|
The use of C in the F file could lead to |
3880
|
|
|
|
|
|
|
a serious (and hard to detect) problem as Redis server may delete |
3881
|
|
|
|
|
|
|
the collection element. Therefore the C does not work with |
3882
|
|
|
|
|
|
|
mode C in the F. |
3883
|
|
|
|
|
|
|
|
3884
|
|
|
|
|
|
|
It may not be possible to use this module with the cluster of Redis servers |
3885
|
|
|
|
|
|
|
because full name of some Redis keys may not be known at the time of the call |
3886
|
|
|
|
|
|
|
the Redis Lua script (C<'EVAL'> or C<'EVALSHA'> command). |
3887
|
|
|
|
|
|
|
So the Redis server may not be able to correctly forward the request |
3888
|
|
|
|
|
|
|
to the appropriate node in the cluster. |
3889
|
|
|
|
|
|
|
|
3890
|
|
|
|
|
|
|
We strongly recommend setting C option in the F file. |
3891
|
|
|
|
|
|
|
|
3892
|
|
|
|
|
|
|
Old data with the same time will be forced out in no specific order. |
3893
|
|
|
|
|
|
|
|
3894
|
|
|
|
|
|
|
The collection API does not support deleting a single data item. |
3895
|
|
|
|
|
|
|
|
3896
|
|
|
|
|
|
|
UTF-8 data should be serialized before passing to C for storing in Redis. |
3897
|
|
|
|
|
|
|
|
3898
|
|
|
|
|
|
|
According to L documentation: |
3899
|
|
|
|
|
|
|
|
3900
|
|
|
|
|
|
|
=over 3 |
3901
|
|
|
|
|
|
|
|
3902
|
|
|
|
|
|
|
=item * |
3903
|
|
|
|
|
|
|
|
3904
|
|
|
|
|
|
|
This module consider that any data sent to the Redis server is a raw octets string, |
3905
|
|
|
|
|
|
|
even if it has utf8 flag set. |
3906
|
|
|
|
|
|
|
And it doesn't do anything when getting data from the Redis server. |
3907
|
|
|
|
|
|
|
|
3908
|
|
|
|
|
|
|
TODO: implement tests for |
3909
|
|
|
|
|
|
|
|
3910
|
|
|
|
|
|
|
=over 3 |
3911
|
|
|
|
|
|
|
|
3912
|
|
|
|
|
|
|
=item * |
3913
|
|
|
|
|
|
|
|
3914
|
|
|
|
|
|
|
memory errors (working with internal ROLLBACK commands) |
3915
|
|
|
|
|
|
|
|
3916
|
|
|
|
|
|
|
=item * |
3917
|
|
|
|
|
|
|
|
3918
|
|
|
|
|
|
|
working when maxmemory = 0 (in the F file) |
3919
|
|
|
|
|
|
|
|
3920
|
|
|
|
|
|
|
=back |
3921
|
|
|
|
|
|
|
|
3922
|
|
|
|
|
|
|
WARNING: According to C function in F : |
3923
|
|
|
|
|
|
|
|
3924
|
|
|
|
|
|
|
/* 32 bit instances are limited to 4GB of address space, so if there is |
3925
|
|
|
|
|
|
|
* no explicit limit in the user provided configuration we set a limit |
3926
|
|
|
|
|
|
|
* at 3 GB using maxmemory with 'noeviction' policy'. This avoids |
3927
|
|
|
|
|
|
|
* useless crashes of the Redis instance for out of memory. */ |
3928
|
|
|
|
|
|
|
|
3929
|
|
|
|
|
|
|
The C module was written, tested, and found working |
3930
|
|
|
|
|
|
|
on recent Linux distributions. |
3931
|
|
|
|
|
|
|
|
3932
|
|
|
|
|
|
|
There are no known bugs in this package. |
3933
|
|
|
|
|
|
|
|
3934
|
|
|
|
|
|
|
Please report problems to the L"AUTHOR">. |
3935
|
|
|
|
|
|
|
|
3936
|
|
|
|
|
|
|
Patches are welcome. |
3937
|
|
|
|
|
|
|
|
3938
|
|
|
|
|
|
|
=back |
3939
|
|
|
|
|
|
|
|
3940
|
|
|
|
|
|
|
=head1 MORE DOCUMENTATION |
3941
|
|
|
|
|
|
|
|
3942
|
|
|
|
|
|
|
All modules contain detailed information on the interfaces they provide. |
3943
|
|
|
|
|
|
|
|
3944
|
|
|
|
|
|
|
=head1 SEE ALSO |
3945
|
|
|
|
|
|
|
|
3946
|
|
|
|
|
|
|
The basic operation of the Redis::CappedCollection package module: |
3947
|
|
|
|
|
|
|
|
3948
|
|
|
|
|
|
|
L - Object interface to create |
3949
|
|
|
|
|
|
|
a collection, addition of data and data manipulation. |
3950
|
|
|
|
|
|
|
|
3951
|
|
|
|
|
|
|
L - String manipulation utilities. |
3952
|
|
|
|
|
|
|
|
3953
|
|
|
|
|
|
|
L - Perl binding for Redis database. |
3954
|
|
|
|
|
|
|
|
3955
|
|
|
|
|
|
|
=head1 SOURCE CODE |
3956
|
|
|
|
|
|
|
|
3957
|
|
|
|
|
|
|
Redis::CappedCollection is hosted on GitHub: |
3958
|
|
|
|
|
|
|
L |
3959
|
|
|
|
|
|
|
|
3960
|
|
|
|
|
|
|
=head1 AUTHOR |
3961
|
|
|
|
|
|
|
|
3962
|
|
|
|
|
|
|
Sergey Gladkov, Esgladkov@trackingsoft.comE |
3963
|
|
|
|
|
|
|
|
3964
|
|
|
|
|
|
|
=head1 CONTRIBUTORS |
3965
|
|
|
|
|
|
|
|
3966
|
|
|
|
|
|
|
Alexander Solovey |
3967
|
|
|
|
|
|
|
|
3968
|
|
|
|
|
|
|
Jeremy Jordan |
3969
|
|
|
|
|
|
|
|
3970
|
|
|
|
|
|
|
Vlad Marchenko |
3971
|
|
|
|
|
|
|
|
3972
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
3973
|
|
|
|
|
|
|
|
3974
|
|
|
|
|
|
|
Copyright (C) 2012-2016 by TrackingSoft LLC. |
3975
|
|
|
|
|
|
|
|
3976
|
|
|
|
|
|
|
This package is free software; you can redistribute it and/or modify it under |
3977
|
|
|
|
|
|
|
the same terms as Perl itself. See I at |
3978
|
|
|
|
|
|
|
L. |
3979
|
|
|
|
|
|
|
|
3980
|
|
|
|
|
|
|
This program is |
3981
|
|
|
|
|
|
|
distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; |
3982
|
|
|
|
|
|
|
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
3983
|
|
|
|
|
|
|
PARTICULAR PURPOSE. |
3984
|
|
|
|
|
|
|
|
3985
|
|
|
|
|
|
|
=cut |
3986
|
|
|
|
|
|
|
|
3987
|
|
|
|
|
|
|
__DATA__ |