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