line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Number::Phone::UK::DBM::Deep; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
## |
4
|
|
|
|
|
|
|
# Number::Phone::UK::DBM::Deep |
5
|
|
|
|
|
|
|
# |
6
|
|
|
|
|
|
|
# Description: |
7
|
|
|
|
|
|
|
# Multi-level database module for storing hash trees, arrays and simple |
8
|
|
|
|
|
|
|
# key/value pairs into FTP-able, cross-platform binary database files. |
9
|
|
|
|
|
|
|
# |
10
|
|
|
|
|
|
|
# Type `perldoc Number::Phone::UK::DBM::Deep` for complete documentation. |
11
|
|
|
|
|
|
|
# |
12
|
|
|
|
|
|
|
# Usage Examples: |
13
|
|
|
|
|
|
|
# my %db; |
14
|
|
|
|
|
|
|
# tie %db, 'Number::Phone::UK::DBM::Deep', 'my_database.db'; # standard tie() method |
15
|
|
|
|
|
|
|
# |
16
|
|
|
|
|
|
|
# my $db = new Number::Phone::UK::DBM::Deep( 'my_database.db' ); # preferred OO method |
17
|
|
|
|
|
|
|
# |
18
|
|
|
|
|
|
|
# $db->{my_scalar} = 'hello world'; |
19
|
|
|
|
|
|
|
# $db->{my_hash} = { larry => 'genius', hashes => 'fast' }; |
20
|
|
|
|
|
|
|
# $db->{my_array} = [ 1, 2, 3, time() ]; |
21
|
|
|
|
|
|
|
# $db->{my_complex} = [ 'hello', { perl => 'rules' }, 42, 99 ]; |
22
|
|
|
|
|
|
|
# push @{$db->{my_array}}, 'another value'; |
23
|
|
|
|
|
|
|
# my @key_list = keys %{$db->{my_hash}}; |
24
|
|
|
|
|
|
|
# print "This module " . $db->{my_complex}->[1]->{perl} . "!\n"; |
25
|
|
|
|
|
|
|
# |
26
|
|
|
|
|
|
|
# Copyright: |
27
|
|
|
|
|
|
|
# (c) 2002-2006 Joseph Huckaby. All Rights Reserved. |
28
|
|
|
|
|
|
|
# This program is free software; you can redistribute it and/or |
29
|
|
|
|
|
|
|
# modify it under the same terms as Perl itself. |
30
|
|
|
|
|
|
|
## |
31
|
|
|
|
|
|
|
|
32
|
1
|
|
|
1
|
|
4
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
32
|
|
33
|
|
|
|
|
|
|
|
34
|
1
|
|
|
1
|
|
2
|
use Fcntl qw( :DEFAULT :flock :seek ); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
393
|
|
35
|
1
|
|
|
1
|
|
3
|
use Digest::MD5 (); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
14
|
|
36
|
1
|
|
|
1
|
|
3
|
use Scalar::Util (); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
14
|
|
37
|
|
|
|
|
|
|
|
38
|
1
|
|
|
1
|
|
3
|
use vars qw( $VERSION ); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
5034
|
|
39
|
|
|
|
|
|
|
$VERSION = q(0.983); |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
## |
42
|
|
|
|
|
|
|
# Set to 4 and 'N' for 32-bit offset tags (default). Theoretical limit of 4 GB per file. |
43
|
|
|
|
|
|
|
# (Perl must be compiled with largefile support for files > 2 GB) |
44
|
|
|
|
|
|
|
# |
45
|
|
|
|
|
|
|
# Set to 8 and 'Q' for 64-bit offsets. Theoretical limit of 16 XB per file. |
46
|
|
|
|
|
|
|
# (Perl must be compiled with largefile and 64-bit long support) |
47
|
|
|
|
|
|
|
## |
48
|
|
|
|
|
|
|
#my $LONG_SIZE = 4; |
49
|
|
|
|
|
|
|
#my $LONG_PACK = 'N'; |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
## |
52
|
|
|
|
|
|
|
# Set to 4 and 'N' for 32-bit data length prefixes. Limit of 4 GB for each key/value. |
53
|
|
|
|
|
|
|
# Upgrading this is possible (see above) but probably not necessary. If you need |
54
|
|
|
|
|
|
|
# more than 4 GB for a single key or value, this module is really not for you :-) |
55
|
|
|
|
|
|
|
## |
56
|
|
|
|
|
|
|
#my $DATA_LENGTH_SIZE = 4; |
57
|
|
|
|
|
|
|
#my $DATA_LENGTH_PACK = 'N'; |
58
|
|
|
|
|
|
|
our ($LONG_SIZE, $LONG_PACK, $DATA_LENGTH_SIZE, $DATA_LENGTH_PACK); |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
## |
61
|
|
|
|
|
|
|
# Maximum number of buckets per list before another level of indexing is done. |
62
|
|
|
|
|
|
|
# Increase this value for slightly greater speed, but larger database files. |
63
|
|
|
|
|
|
|
# DO NOT decrease this value below 16, due to risk of recursive reindex overrun. |
64
|
|
|
|
|
|
|
## |
65
|
|
|
|
|
|
|
my $MAX_BUCKETS = 16; |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
## |
68
|
|
|
|
|
|
|
# Better not adjust anything below here, unless you're me :-) |
69
|
|
|
|
|
|
|
## |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
## |
72
|
|
|
|
|
|
|
# Setup digest function for keys |
73
|
|
|
|
|
|
|
## |
74
|
|
|
|
|
|
|
our ($DIGEST_FUNC, $HASH_SIZE); |
75
|
|
|
|
|
|
|
#my $DIGEST_FUNC = \&Digest::MD5::md5; |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
## |
78
|
|
|
|
|
|
|
# Precalculate index and bucket sizes based on values above. |
79
|
|
|
|
|
|
|
## |
80
|
|
|
|
|
|
|
#my $HASH_SIZE = 16; |
81
|
|
|
|
|
|
|
my ($INDEX_SIZE, $BUCKET_SIZE, $BUCKET_LIST_SIZE); |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
set_digest(); |
84
|
|
|
|
|
|
|
#set_pack(); |
85
|
|
|
|
|
|
|
#_precalc_sizes(); |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
## |
88
|
|
|
|
|
|
|
# Setup file and tag signatures. These should never change. |
89
|
|
|
|
|
|
|
## |
90
|
|
|
|
|
|
|
sub SIG_FILE () { 'DPDB' } |
91
|
|
|
|
|
|
|
sub SIG_HASH () { 'H' } |
92
|
|
|
|
|
|
|
sub SIG_ARRAY () { 'A' } |
93
|
|
|
|
|
|
|
sub SIG_NULL () { 'N' } |
94
|
|
|
|
|
|
|
sub SIG_DATA () { 'D' } |
95
|
|
|
|
|
|
|
sub SIG_INDEX () { 'I' } |
96
|
|
|
|
|
|
|
sub SIG_BLIST () { 'B' } |
97
|
|
|
|
|
|
|
sub SIG_SIZE () { 1 } |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
## |
100
|
|
|
|
|
|
|
# Setup constants for users to pass to new() |
101
|
|
|
|
|
|
|
## |
102
|
|
|
|
|
|
|
sub TYPE_HASH () { SIG_HASH } |
103
|
|
|
|
|
|
|
sub TYPE_ARRAY () { SIG_ARRAY } |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
sub _get_args { |
106
|
24
|
|
|
24
|
|
17
|
my $proto = shift; |
107
|
|
|
|
|
|
|
|
108
|
24
|
|
|
|
|
21
|
my $args; |
109
|
24
|
50
|
|
|
|
27
|
if (scalar(@_) > 1) { |
|
|
0
|
|
|
|
|
|
110
|
24
|
50
|
|
|
|
37
|
if ( @_ % 2 ) { |
111
|
0
|
|
|
|
|
0
|
$proto->_throw_error( "Odd number of parameters to " . (caller(1))[2] ); |
112
|
|
|
|
|
|
|
} |
113
|
24
|
|
|
|
|
35
|
$args = {@_}; |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
elsif ( ref $_[0] ) { |
116
|
0
|
0
|
|
|
|
0
|
unless ( eval { local $SIG{'__DIE__'}; %{$_[0]} || 1 } ) { |
|
0
|
0
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
117
|
0
|
|
|
|
|
0
|
$proto->_throw_error( "Not a hashref in args to " . (caller(1))[2] ); |
118
|
|
|
|
|
|
|
} |
119
|
0
|
|
|
|
|
0
|
$args = $_[0]; |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
else { |
122
|
0
|
|
|
|
|
0
|
$args = { file => shift }; |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
24
|
|
|
|
|
35
|
return $args; |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
sub new { |
129
|
|
|
|
|
|
|
## |
130
|
|
|
|
|
|
|
# Class constructor method for Perl OO interface. |
131
|
|
|
|
|
|
|
# Calls tie() and returns blessed reference to tied hash or array, |
132
|
|
|
|
|
|
|
# providing a hybrid OO/tie interface. |
133
|
|
|
|
|
|
|
## |
134
|
12
|
|
|
12
|
1
|
11
|
my $class = shift; |
135
|
12
|
|
|
|
|
20
|
my $args = $class->_get_args( @_ ); |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
## |
138
|
|
|
|
|
|
|
# Check if we want a tied hash or array. |
139
|
|
|
|
|
|
|
## |
140
|
12
|
|
|
|
|
10
|
my $self; |
141
|
12
|
50
|
66
|
|
|
38
|
if (defined($args->{type}) && $args->{type} eq TYPE_ARRAY) { |
142
|
0
|
|
|
|
|
0
|
$class = 'Number::Phone::UK::DBM::Deep::Array'; |
143
|
0
|
|
|
|
|
0
|
require Number::Phone::UK::DBM::Deep::Array; |
144
|
0
|
|
|
|
|
0
|
tie @$self, $class, %$args; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
else { |
147
|
12
|
|
|
|
|
11
|
$class = 'Number::Phone::UK::DBM::Deep::Hash'; |
148
|
12
|
|
|
|
|
472
|
require Number::Phone::UK::DBM::Deep::Hash; |
149
|
12
|
|
|
|
|
49
|
tie %$self, $class, %$args; |
150
|
|
|
|
|
|
|
} |
151
|
|
|
|
|
|
|
|
152
|
12
|
|
|
|
|
22
|
return bless $self, $class; |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
sub _init { |
156
|
|
|
|
|
|
|
## |
157
|
|
|
|
|
|
|
# Setup $self and bless into this class. |
158
|
|
|
|
|
|
|
## |
159
|
12
|
|
|
12
|
|
11
|
my $class = shift; |
160
|
12
|
|
|
|
|
7
|
my $args = shift; |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
# These are the defaults to be optionally overridden below |
163
|
12
|
|
|
|
|
20
|
my $self = bless { |
164
|
|
|
|
|
|
|
type => TYPE_HASH, |
165
|
|
|
|
|
|
|
base_offset => length(SIG_FILE), |
166
|
|
|
|
|
|
|
}, $class; |
167
|
|
|
|
|
|
|
|
168
|
12
|
|
|
|
|
24
|
foreach my $param ( keys %$self ) { |
169
|
24
|
100
|
|
|
|
30
|
next unless exists $args->{$param}; |
170
|
23
|
|
|
|
|
25
|
$self->{$param} = delete $args->{$param} |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
# locking implicitly enables autoflush |
174
|
12
|
50
|
|
|
|
20
|
if ($args->{locking}) { $args->{autoflush} = 1; } |
|
0
|
|
|
|
|
0
|
|
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
$self->{root} = exists $args->{root} |
177
|
|
|
|
|
|
|
? $args->{root} |
178
|
12
|
100
|
|
|
|
23
|
: Number::Phone::UK::DBM::Deep::_::Root->new( $args ); |
179
|
|
|
|
|
|
|
|
180
|
12
|
50
|
|
|
|
15
|
if (!defined($self->_fh)) { $self->_open(); } |
|
0
|
|
|
|
|
0
|
|
181
|
|
|
|
|
|
|
|
182
|
12
|
|
|
|
|
21
|
return $self; |
183
|
|
|
|
|
|
|
} |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
sub TIEHASH { |
186
|
0
|
|
|
0
|
|
0
|
shift; |
187
|
0
|
|
|
|
|
0
|
require Number::Phone::UK::DBM::Deep::Hash; |
188
|
0
|
|
|
|
|
0
|
return Number::Phone::UK::DBM::Deep::Hash->TIEHASH( @_ ); |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
sub TIEARRAY { |
192
|
0
|
|
|
0
|
|
0
|
shift; |
193
|
0
|
|
|
|
|
0
|
require Number::Phone::UK::DBM::Deep::Array; |
194
|
0
|
|
|
|
|
0
|
return Number::Phone::UK::DBM::Deep::Array->TIEARRAY( @_ ); |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
#XXX Unneeded now ... |
198
|
|
|
|
|
|
|
#sub DESTROY { |
199
|
|
|
|
|
|
|
#} |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
sub _open { |
202
|
|
|
|
|
|
|
## |
203
|
|
|
|
|
|
|
# Open a fh to the database, create if nonexistent. |
204
|
|
|
|
|
|
|
# Make sure file signature matches Number::Phone::UK::DBM::Deep spec. |
205
|
|
|
|
|
|
|
## |
206
|
0
|
|
|
0
|
|
0
|
my $self = $_[0]->_get_self; |
207
|
|
|
|
|
|
|
|
208
|
0
|
|
|
|
|
0
|
local($/,$\); |
209
|
|
|
|
|
|
|
|
210
|
0
|
0
|
|
|
|
0
|
if (defined($self->_fh)) { $self->_close(); } |
|
0
|
|
|
|
|
0
|
|
211
|
|
|
|
|
|
|
|
212
|
0
|
|
|
|
|
0
|
my $flags = O_RDWR | O_CREAT | O_BINARY; |
213
|
|
|
|
|
|
|
|
214
|
0
|
|
|
|
|
0
|
my $fh; |
215
|
|
|
|
|
|
|
sysopen( $fh, $self->_root->{file}, $flags ) |
216
|
0
|
0
|
|
|
|
0
|
or $self->_throw_error( "Cannot sysopen file: " . $self->_root->{file} . ": $!" ); |
217
|
|
|
|
|
|
|
|
218
|
0
|
|
|
|
|
0
|
$self->_root->{fh} = $fh; |
219
|
|
|
|
|
|
|
|
220
|
0
|
0
|
|
|
|
0
|
if ($self->_root->{autoflush}) { |
221
|
0
|
|
|
|
|
0
|
my $old = select $fh; |
222
|
0
|
|
|
|
|
0
|
$|=1; |
223
|
0
|
|
|
|
|
0
|
select $old; |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
|
226
|
0
|
|
|
|
|
0
|
seek($fh, 0 + $self->_root->{file_offset}, SEEK_SET); |
227
|
|
|
|
|
|
|
|
228
|
0
|
|
|
|
|
0
|
my $signature; |
229
|
0
|
|
|
|
|
0
|
my $bytes_read = read( $fh, $signature, length(SIG_FILE)); |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
## |
232
|
|
|
|
|
|
|
# File is empty -- write signature and master index |
233
|
|
|
|
|
|
|
## |
234
|
0
|
0
|
|
|
|
0
|
if (!$bytes_read) { |
235
|
0
|
|
|
|
|
0
|
seek($fh, 0 + $self->_root->{file_offset}, SEEK_SET); |
236
|
0
|
|
|
|
|
0
|
print( $fh SIG_FILE); |
237
|
0
|
|
|
|
|
0
|
$self->_create_tag($self->_base_offset, $self->_type, chr(0) x $INDEX_SIZE); |
238
|
|
|
|
|
|
|
|
239
|
0
|
|
|
|
|
0
|
my $plain_key = "[base]"; |
240
|
0
|
|
|
|
|
0
|
print( $fh pack($DATA_LENGTH_PACK, length($plain_key)) . $plain_key ); |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
# Flush the filehandle |
243
|
0
|
|
|
|
|
0
|
my $old_fh = select $fh; |
244
|
0
|
|
|
|
|
0
|
my $old_af = $|; $| = 1; $| = $old_af; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
245
|
0
|
|
|
|
|
0
|
select $old_fh; |
246
|
|
|
|
|
|
|
|
247
|
0
|
|
|
|
|
0
|
my @stats = stat($fh); |
248
|
0
|
|
|
|
|
0
|
$self->_root->{inode} = $stats[1]; |
249
|
0
|
|
|
|
|
0
|
$self->_root->{end} = $stats[7]; |
250
|
|
|
|
|
|
|
|
251
|
0
|
|
|
|
|
0
|
return 1; |
252
|
|
|
|
|
|
|
} |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
## |
255
|
|
|
|
|
|
|
# Check signature was valid |
256
|
|
|
|
|
|
|
## |
257
|
0
|
0
|
|
|
|
0
|
unless ($signature eq SIG_FILE) { |
258
|
0
|
|
|
|
|
0
|
$self->_close(); |
259
|
0
|
|
|
|
|
0
|
return $self->_throw_error("Signature not found -- file is not a Deep DB"); |
260
|
|
|
|
|
|
|
} |
261
|
|
|
|
|
|
|
|
262
|
0
|
|
|
|
|
0
|
my @stats = stat($fh); |
263
|
0
|
|
|
|
|
0
|
$self->_root->{inode} = $stats[1]; |
264
|
0
|
|
|
|
|
0
|
$self->_root->{end} = $stats[7]; |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
## |
267
|
|
|
|
|
|
|
# Get our type from master index signature |
268
|
|
|
|
|
|
|
## |
269
|
0
|
|
|
|
|
0
|
my $tag = $self->_load_tag($self->_base_offset); |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
#XXX We probably also want to store the hash algorithm name and not assume anything |
272
|
|
|
|
|
|
|
#XXX The cool thing would be to allow a different hashing algorithm at every level |
273
|
|
|
|
|
|
|
|
274
|
0
|
0
|
|
|
|
0
|
if (!$tag) { |
275
|
0
|
|
|
|
|
0
|
return $self->_throw_error("Corrupted file, no master index record"); |
276
|
|
|
|
|
|
|
} |
277
|
0
|
0
|
|
|
|
0
|
if ($self->{type} ne $tag->{signature}) { |
278
|
0
|
|
|
|
|
0
|
return $self->_throw_error("File type mismatch"); |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
|
281
|
0
|
|
|
|
|
0
|
return 1; |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
sub _close { |
285
|
|
|
|
|
|
|
## |
286
|
|
|
|
|
|
|
# Close database fh |
287
|
|
|
|
|
|
|
## |
288
|
0
|
|
|
0
|
|
0
|
my $self = $_[0]->_get_self; |
289
|
0
|
0
|
|
|
|
0
|
close $self->_root->{fh} if $self->_root->{fh}; |
290
|
0
|
|
|
|
|
0
|
$self->_root->{fh} = undef; |
291
|
|
|
|
|
|
|
} |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
sub _create_tag { |
294
|
|
|
|
|
|
|
## |
295
|
|
|
|
|
|
|
# Given offset, signature and content, create tag and write to disk |
296
|
|
|
|
|
|
|
## |
297
|
0
|
|
|
0
|
|
0
|
my ($self, $offset, $sig, $content) = @_; |
298
|
0
|
|
|
|
|
0
|
my $size = length($content); |
299
|
|
|
|
|
|
|
|
300
|
0
|
|
|
|
|
0
|
local($/,$\); |
301
|
|
|
|
|
|
|
|
302
|
0
|
|
|
|
|
0
|
my $fh = $self->_fh; |
303
|
|
|
|
|
|
|
|
304
|
0
|
|
|
|
|
0
|
seek($fh, $offset + $self->_root->{file_offset}, SEEK_SET); |
305
|
0
|
|
|
|
|
0
|
print( $fh $sig . pack($DATA_LENGTH_PACK, $size) . $content ); |
306
|
|
|
|
|
|
|
|
307
|
0
|
0
|
|
|
|
0
|
if ($offset == $self->_root->{end}) { |
308
|
0
|
|
|
|
|
0
|
$self->_root->{end} += SIG_SIZE + $DATA_LENGTH_SIZE + $size; |
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
return { |
312
|
0
|
|
|
|
|
0
|
signature => $sig, |
313
|
|
|
|
|
|
|
size => $size, |
314
|
|
|
|
|
|
|
offset => $offset + SIG_SIZE + $DATA_LENGTH_SIZE, |
315
|
|
|
|
|
|
|
content => $content |
316
|
|
|
|
|
|
|
}; |
317
|
|
|
|
|
|
|
} |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
sub _load_tag { |
320
|
|
|
|
|
|
|
## |
321
|
|
|
|
|
|
|
# Given offset, load single tag and return signature, size and data |
322
|
|
|
|
|
|
|
## |
323
|
47
|
|
|
47
|
|
34
|
my $self = shift; |
324
|
47
|
|
|
|
|
23
|
my $offset = shift; |
325
|
|
|
|
|
|
|
|
326
|
47
|
|
|
|
|
87
|
local($/,$\); |
327
|
|
|
|
|
|
|
|
328
|
47
|
|
|
|
|
47
|
my $fh = $self->_fh; |
329
|
|
|
|
|
|
|
|
330
|
47
|
|
|
|
|
56
|
seek($fh, $offset + $self->_root->{file_offset}, SEEK_SET); |
331
|
47
|
50
|
|
|
|
228
|
if (eof $fh) { return undef; } |
|
0
|
|
|
|
|
0
|
|
332
|
|
|
|
|
|
|
|
333
|
47
|
|
|
|
|
39
|
my $b; |
334
|
47
|
|
|
|
|
77
|
read( $fh, $b, SIG_SIZE + $DATA_LENGTH_SIZE ); |
335
|
47
|
|
|
|
|
123
|
my ($sig, $size) = unpack( "A $DATA_LENGTH_PACK", $b ); |
336
|
|
|
|
|
|
|
|
337
|
47
|
|
|
|
|
28
|
my $buffer; |
338
|
47
|
|
|
|
|
91
|
read( $fh, $buffer, $size); |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
return { |
341
|
47
|
|
|
|
|
177
|
signature => $sig, |
342
|
|
|
|
|
|
|
size => $size, |
343
|
|
|
|
|
|
|
offset => $offset + SIG_SIZE + $DATA_LENGTH_SIZE, |
344
|
|
|
|
|
|
|
content => $buffer |
345
|
|
|
|
|
|
|
}; |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
sub _index_lookup { |
349
|
|
|
|
|
|
|
## |
350
|
|
|
|
|
|
|
# Given index tag, lookup single entry in index and return . |
351
|
|
|
|
|
|
|
## |
352
|
27
|
|
|
27
|
|
18
|
my $self = shift; |
353
|
27
|
|
|
|
|
22
|
my ($tag, $index) = @_; |
354
|
|
|
|
|
|
|
|
355
|
27
|
|
|
|
|
43
|
my $location = unpack($LONG_PACK, substr($tag->{content}, $index * $LONG_SIZE, $LONG_SIZE) ); |
356
|
27
|
50
|
|
|
|
33
|
if (!$location) { return; } |
|
0
|
|
|
|
|
0
|
|
357
|
|
|
|
|
|
|
|
358
|
27
|
|
|
|
|
33
|
return $self->_load_tag( $location ); |
359
|
|
|
|
|
|
|
} |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
sub _add_bucket { |
362
|
|
|
|
|
|
|
## |
363
|
|
|
|
|
|
|
# Adds one key/value pair to bucket list, given offset, MD5 digest of key, |
364
|
|
|
|
|
|
|
# plain (undigested) key and value. |
365
|
|
|
|
|
|
|
## |
366
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
367
|
0
|
|
|
|
|
0
|
my ($tag, $md5, $plain_key, $value) = @_; |
368
|
0
|
|
|
|
|
0
|
my $keys = $tag->{content}; |
369
|
0
|
|
|
|
|
0
|
my $location = 0; |
370
|
0
|
|
|
|
|
0
|
my $result = 2; |
371
|
|
|
|
|
|
|
|
372
|
0
|
|
|
|
|
0
|
local($/,$\); |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
# This verifies that only supported values will be stored. |
375
|
|
|
|
|
|
|
{ |
376
|
0
|
|
|
|
|
0
|
my $r = Scalar::Util::reftype( $value ); |
|
0
|
|
|
|
|
0
|
|
377
|
0
|
0
|
|
|
|
0
|
last if !defined $r; |
378
|
|
|
|
|
|
|
|
379
|
0
|
0
|
|
|
|
0
|
last if $r eq 'HASH'; |
380
|
0
|
0
|
|
|
|
0
|
last if $r eq 'ARRAY'; |
381
|
|
|
|
|
|
|
|
382
|
0
|
|
|
|
|
0
|
$self->_throw_error( |
383
|
|
|
|
|
|
|
"Storage of variables of type '$r' is not supported." |
384
|
|
|
|
|
|
|
); |
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
|
387
|
0
|
|
|
|
|
0
|
my $root = $self->_root; |
388
|
|
|
|
|
|
|
|
389
|
0
|
|
|
|
|
0
|
my $is_dbm_deep = eval { local $SIG{'__DIE__'}; $value->isa( 'Number::Phone::UK::DBM::Deep' ) }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
390
|
0
|
|
0
|
|
|
0
|
my $internal_ref = $is_dbm_deep && ($value->_root eq $root); |
391
|
|
|
|
|
|
|
|
392
|
0
|
|
|
|
|
0
|
my $fh = $self->_fh; |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
## |
395
|
|
|
|
|
|
|
# Iterate through buckets, seeing if this is a new entry or a replace. |
396
|
|
|
|
|
|
|
## |
397
|
0
|
|
|
|
|
0
|
for (my $i=0; $i<$MAX_BUCKETS; $i++) { |
398
|
0
|
|
|
|
|
0
|
my $subloc = unpack($LONG_PACK, substr($keys, ($i * $BUCKET_SIZE) + $HASH_SIZE, $LONG_SIZE)); |
399
|
0
|
0
|
|
|
|
0
|
if (!$subloc) { |
400
|
|
|
|
|
|
|
## |
401
|
|
|
|
|
|
|
# Found empty bucket (end of list). Populate and exit loop. |
402
|
|
|
|
|
|
|
## |
403
|
0
|
|
|
|
|
0
|
$result = 2; |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
$location = $internal_ref |
406
|
|
|
|
|
|
|
? $value->_base_offset |
407
|
0
|
0
|
|
|
|
0
|
: $root->{end}; |
408
|
|
|
|
|
|
|
|
409
|
0
|
|
|
|
|
0
|
seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE) + $root->{file_offset}, SEEK_SET); |
410
|
0
|
|
|
|
|
0
|
print( $fh $md5 . pack($LONG_PACK, $location) ); |
411
|
0
|
|
|
|
|
0
|
last; |
412
|
|
|
|
|
|
|
} |
413
|
|
|
|
|
|
|
|
414
|
0
|
|
|
|
|
0
|
my $key = substr($keys, $i * $BUCKET_SIZE, $HASH_SIZE); |
415
|
0
|
0
|
|
|
|
0
|
if ($md5 eq $key) { |
416
|
|
|
|
|
|
|
## |
417
|
|
|
|
|
|
|
# Found existing bucket with same key. Replace with new value. |
418
|
|
|
|
|
|
|
## |
419
|
0
|
|
|
|
|
0
|
$result = 1; |
420
|
|
|
|
|
|
|
|
421
|
0
|
0
|
|
|
|
0
|
if ($internal_ref) { |
422
|
0
|
|
|
|
|
0
|
$location = $value->_base_offset; |
423
|
0
|
|
|
|
|
0
|
seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE) + $root->{file_offset}, SEEK_SET); |
424
|
0
|
|
|
|
|
0
|
print( $fh $md5 . pack($LONG_PACK, $location) ); |
425
|
0
|
|
|
|
|
0
|
return $result; |
426
|
|
|
|
|
|
|
} |
427
|
|
|
|
|
|
|
|
428
|
0
|
|
|
|
|
0
|
seek($fh, $subloc + SIG_SIZE + $root->{file_offset}, SEEK_SET); |
429
|
0
|
|
|
|
|
0
|
my $size; |
430
|
0
|
|
|
|
|
0
|
read( $fh, $size, $DATA_LENGTH_SIZE); $size = unpack($DATA_LENGTH_PACK, $size); |
|
0
|
|
|
|
|
0
|
|
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
## |
433
|
|
|
|
|
|
|
# If value is a hash, array, or raw value with equal or less size, we can |
434
|
|
|
|
|
|
|
# reuse the same content area of the database. Otherwise, we have to create |
435
|
|
|
|
|
|
|
# a new content area at the EOF. |
436
|
|
|
|
|
|
|
## |
437
|
0
|
|
|
|
|
0
|
my $actual_length; |
438
|
0
|
|
0
|
|
|
0
|
my $r = Scalar::Util::reftype( $value ) || ''; |
439
|
0
|
0
|
0
|
|
|
0
|
if ( $r eq 'HASH' || $r eq 'ARRAY' ) { |
440
|
0
|
|
|
|
|
0
|
$actual_length = $INDEX_SIZE; |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
# if autobless is enabled, must also take into consideration |
443
|
|
|
|
|
|
|
# the class name, as it is stored along with key/value. |
444
|
0
|
0
|
|
|
|
0
|
if ( $root->{autobless} ) { |
445
|
0
|
|
|
|
|
0
|
my $value_class = Scalar::Util::blessed($value); |
446
|
0
|
0
|
0
|
|
|
0
|
if ( defined $value_class && !$value->isa('Number::Phone::UK::DBM::Deep') ) { |
447
|
0
|
|
|
|
|
0
|
$actual_length += length($value_class); |
448
|
|
|
|
|
|
|
} |
449
|
|
|
|
|
|
|
} |
450
|
|
|
|
|
|
|
} |
451
|
0
|
|
|
|
|
0
|
else { $actual_length = length($value); } |
452
|
|
|
|
|
|
|
|
453
|
0
|
0
|
0
|
|
|
0
|
if ($actual_length <= ($size || 0)) { |
454
|
0
|
|
|
|
|
0
|
$location = $subloc; |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
else { |
457
|
0
|
|
|
|
|
0
|
$location = $root->{end}; |
458
|
0
|
|
|
|
|
0
|
seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE) + $HASH_SIZE + $root->{file_offset}, SEEK_SET); |
459
|
0
|
|
|
|
|
0
|
print( $fh pack($LONG_PACK, $location) ); |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
|
462
|
0
|
|
|
|
|
0
|
last; |
463
|
|
|
|
|
|
|
} |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
## |
467
|
|
|
|
|
|
|
# If this is an internal reference, return now. |
468
|
|
|
|
|
|
|
# No need to write value or plain key |
469
|
|
|
|
|
|
|
## |
470
|
0
|
0
|
|
|
|
0
|
if ($internal_ref) { |
471
|
0
|
|
|
|
|
0
|
return $result; |
472
|
|
|
|
|
|
|
} |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
## |
475
|
|
|
|
|
|
|
# If bucket didn't fit into list, split into a new index level |
476
|
|
|
|
|
|
|
## |
477
|
0
|
0
|
|
|
|
0
|
if (!$location) { |
478
|
0
|
|
|
|
|
0
|
seek($fh, $tag->{ref_loc} + $root->{file_offset}, SEEK_SET); |
479
|
0
|
|
|
|
|
0
|
print( $fh pack($LONG_PACK, $root->{end}) ); |
480
|
|
|
|
|
|
|
|
481
|
0
|
|
|
|
|
0
|
my $index_tag = $self->_create_tag($root->{end}, SIG_INDEX, chr(0) x $INDEX_SIZE); |
482
|
0
|
|
|
|
|
0
|
my @offsets = (); |
483
|
|
|
|
|
|
|
|
484
|
0
|
|
|
|
|
0
|
$keys .= $md5 . pack($LONG_PACK, 0); |
485
|
|
|
|
|
|
|
|
486
|
0
|
|
|
|
|
0
|
for (my $i=0; $i<=$MAX_BUCKETS; $i++) { |
487
|
0
|
|
|
|
|
0
|
my $key = substr($keys, $i * $BUCKET_SIZE, $HASH_SIZE); |
488
|
0
|
0
|
|
|
|
0
|
if ($key) { |
489
|
0
|
|
|
|
|
0
|
my $old_subloc = unpack($LONG_PACK, substr($keys, ($i * $BUCKET_SIZE) + $HASH_SIZE, $LONG_SIZE)); |
490
|
0
|
|
|
|
|
0
|
my $num = ord(substr($key, $tag->{ch} + 1, 1)); |
491
|
|
|
|
|
|
|
|
492
|
0
|
0
|
|
|
|
0
|
if ($offsets[$num]) { |
493
|
0
|
|
|
|
|
0
|
my $offset = $offsets[$num] + SIG_SIZE + $DATA_LENGTH_SIZE; |
494
|
0
|
|
|
|
|
0
|
seek($fh, $offset + $root->{file_offset}, SEEK_SET); |
495
|
0
|
|
|
|
|
0
|
my $subkeys; |
496
|
0
|
|
|
|
|
0
|
read( $fh, $subkeys, $BUCKET_LIST_SIZE); |
497
|
|
|
|
|
|
|
|
498
|
0
|
|
|
|
|
0
|
for (my $k=0; $k<$MAX_BUCKETS; $k++) { |
499
|
0
|
|
|
|
|
0
|
my $subloc = unpack($LONG_PACK, substr($subkeys, ($k * $BUCKET_SIZE) + $HASH_SIZE, $LONG_SIZE)); |
500
|
0
|
0
|
|
|
|
0
|
if (!$subloc) { |
501
|
0
|
|
|
|
|
0
|
seek($fh, $offset + ($k * $BUCKET_SIZE) + $root->{file_offset}, SEEK_SET); |
502
|
0
|
|
0
|
|
|
0
|
print( $fh $key . pack($LONG_PACK, $old_subloc || $root->{end}) ); |
503
|
0
|
|
|
|
|
0
|
last; |
504
|
|
|
|
|
|
|
} |
505
|
|
|
|
|
|
|
} # k loop |
506
|
|
|
|
|
|
|
} |
507
|
|
|
|
|
|
|
else { |
508
|
0
|
|
|
|
|
0
|
$offsets[$num] = $root->{end}; |
509
|
0
|
|
|
|
|
0
|
seek($fh, $index_tag->{offset} + ($num * $LONG_SIZE) + $root->{file_offset}, SEEK_SET); |
510
|
0
|
|
|
|
|
0
|
print( $fh pack($LONG_PACK, $root->{end}) ); |
511
|
|
|
|
|
|
|
|
512
|
0
|
|
|
|
|
0
|
my $blist_tag = $self->_create_tag($root->{end}, SIG_BLIST, chr(0) x $BUCKET_LIST_SIZE); |
513
|
|
|
|
|
|
|
|
514
|
0
|
|
|
|
|
0
|
seek($fh, $blist_tag->{offset} + $root->{file_offset}, SEEK_SET); |
515
|
0
|
|
0
|
|
|
0
|
print( $fh $key . pack($LONG_PACK, $old_subloc || $root->{end}) ); |
516
|
|
|
|
|
|
|
} |
517
|
|
|
|
|
|
|
} # key is real |
518
|
|
|
|
|
|
|
} # i loop |
519
|
|
|
|
|
|
|
|
520
|
0
|
|
0
|
|
|
0
|
$location ||= $root->{end}; |
521
|
|
|
|
|
|
|
} # re-index bucket list |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
## |
524
|
|
|
|
|
|
|
# Seek to content area and store signature, value and plaintext key |
525
|
|
|
|
|
|
|
## |
526
|
0
|
0
|
|
|
|
0
|
if ($location) { |
527
|
0
|
|
|
|
|
0
|
my $content_length; |
528
|
0
|
|
|
|
|
0
|
seek($fh, $location + $root->{file_offset}, SEEK_SET); |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
## |
531
|
|
|
|
|
|
|
# Write signature based on content type, set content length and write actual value. |
532
|
|
|
|
|
|
|
## |
533
|
0
|
|
0
|
|
|
0
|
my $r = Scalar::Util::reftype($value) || ''; |
534
|
0
|
0
|
|
|
|
0
|
if ($r eq 'HASH') { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
535
|
0
|
0
|
0
|
|
|
0
|
if ( !$internal_ref && tied %{$value} ) { |
|
0
|
|
|
|
|
0
|
|
536
|
0
|
|
|
|
|
0
|
return $self->_throw_error("Cannot store a tied value"); |
537
|
|
|
|
|
|
|
} |
538
|
0
|
|
|
|
|
0
|
print( $fh TYPE_HASH ); |
539
|
0
|
|
|
|
|
0
|
print( $fh pack($DATA_LENGTH_PACK, $INDEX_SIZE) . chr(0) x $INDEX_SIZE ); |
540
|
0
|
|
|
|
|
0
|
$content_length = $INDEX_SIZE; |
541
|
|
|
|
|
|
|
} |
542
|
|
|
|
|
|
|
elsif ($r eq 'ARRAY') { |
543
|
0
|
0
|
0
|
|
|
0
|
if ( !$internal_ref && tied @{$value} ) { |
|
0
|
|
|
|
|
0
|
|
544
|
0
|
|
|
|
|
0
|
return $self->_throw_error("Cannot store a tied value"); |
545
|
|
|
|
|
|
|
} |
546
|
0
|
|
|
|
|
0
|
print( $fh TYPE_ARRAY ); |
547
|
0
|
|
|
|
|
0
|
print( $fh pack($DATA_LENGTH_PACK, $INDEX_SIZE) . chr(0) x $INDEX_SIZE ); |
548
|
0
|
|
|
|
|
0
|
$content_length = $INDEX_SIZE; |
549
|
|
|
|
|
|
|
} |
550
|
|
|
|
|
|
|
elsif (!defined($value)) { |
551
|
0
|
|
|
|
|
0
|
print( $fh SIG_NULL ); |
552
|
0
|
|
|
|
|
0
|
print( $fh pack($DATA_LENGTH_PACK, 0) ); |
553
|
0
|
|
|
|
|
0
|
$content_length = 0; |
554
|
|
|
|
|
|
|
} |
555
|
|
|
|
|
|
|
else { |
556
|
0
|
|
|
|
|
0
|
print( $fh SIG_DATA ); |
557
|
0
|
|
|
|
|
0
|
print( $fh pack($DATA_LENGTH_PACK, length($value)) . $value ); |
558
|
0
|
|
|
|
|
0
|
$content_length = length($value); |
559
|
|
|
|
|
|
|
} |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
## |
562
|
|
|
|
|
|
|
# Plain key is stored AFTER value, as keys are typically fetched less often. |
563
|
|
|
|
|
|
|
## |
564
|
0
|
|
|
|
|
0
|
print( $fh pack($DATA_LENGTH_PACK, length($plain_key)) . $plain_key ); |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
## |
567
|
|
|
|
|
|
|
# If value is blessed, preserve class name |
568
|
|
|
|
|
|
|
## |
569
|
0
|
0
|
|
|
|
0
|
if ( $root->{autobless} ) { |
570
|
0
|
|
|
|
|
0
|
my $value_class = Scalar::Util::blessed($value); |
571
|
0
|
0
|
0
|
|
|
0
|
if ( defined $value_class && $value_class ne 'Number::Phone::UK::DBM::Deep' ) { |
572
|
|
|
|
|
|
|
## |
573
|
|
|
|
|
|
|
# Blessed ref -- will restore later |
574
|
|
|
|
|
|
|
## |
575
|
0
|
|
|
|
|
0
|
print( $fh chr(1) ); |
576
|
0
|
|
|
|
|
0
|
print( $fh pack($DATA_LENGTH_PACK, length($value_class)) . $value_class ); |
577
|
0
|
|
|
|
|
0
|
$content_length += 1; |
578
|
0
|
|
|
|
|
0
|
$content_length += $DATA_LENGTH_SIZE + length($value_class); |
579
|
|
|
|
|
|
|
} |
580
|
|
|
|
|
|
|
else { |
581
|
0
|
|
|
|
|
0
|
print( $fh chr(0) ); |
582
|
0
|
|
|
|
|
0
|
$content_length += 1; |
583
|
|
|
|
|
|
|
} |
584
|
|
|
|
|
|
|
} |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
## |
587
|
|
|
|
|
|
|
# If this is a new content area, advance EOF counter |
588
|
|
|
|
|
|
|
## |
589
|
0
|
0
|
|
|
|
0
|
if ($location == $root->{end}) { |
590
|
0
|
|
|
|
|
0
|
$root->{end} += SIG_SIZE; |
591
|
0
|
|
|
|
|
0
|
$root->{end} += $DATA_LENGTH_SIZE + $content_length; |
592
|
0
|
|
|
|
|
0
|
$root->{end} += $DATA_LENGTH_SIZE + length($plain_key); |
593
|
|
|
|
|
|
|
} |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
## |
596
|
|
|
|
|
|
|
# If content is a hash or array, create new child Number::Phone::UK::DBM::Deep object and |
597
|
|
|
|
|
|
|
# pass each key or element to it. |
598
|
|
|
|
|
|
|
## |
599
|
0
|
0
|
|
|
|
0
|
if ($r eq 'HASH') { |
|
|
0
|
|
|
|
|
|
600
|
0
|
|
|
|
|
0
|
my %x = %$value; |
601
|
0
|
|
|
|
|
0
|
tie %$value, 'Number::Phone::UK::DBM::Deep', { |
602
|
|
|
|
|
|
|
type => TYPE_HASH, |
603
|
|
|
|
|
|
|
base_offset => $location, |
604
|
|
|
|
|
|
|
root => $root, |
605
|
|
|
|
|
|
|
}; |
606
|
0
|
|
|
|
|
0
|
%$value = %x; |
607
|
|
|
|
|
|
|
} |
608
|
|
|
|
|
|
|
elsif ($r eq 'ARRAY') { |
609
|
0
|
|
|
|
|
0
|
my @x = @$value; |
610
|
0
|
|
|
|
|
0
|
tie @$value, 'Number::Phone::UK::DBM::Deep', { |
611
|
|
|
|
|
|
|
type => TYPE_ARRAY, |
612
|
|
|
|
|
|
|
base_offset => $location, |
613
|
|
|
|
|
|
|
root => $root, |
614
|
|
|
|
|
|
|
}; |
615
|
0
|
|
|
|
|
0
|
@$value = @x; |
616
|
|
|
|
|
|
|
} |
617
|
|
|
|
|
|
|
|
618
|
0
|
|
|
|
|
0
|
return $result; |
619
|
|
|
|
|
|
|
} |
620
|
|
|
|
|
|
|
|
621
|
0
|
|
|
|
|
0
|
return $self->_throw_error("Fatal error: indexing failed -- possibly due to corruption in file"); |
622
|
|
|
|
|
|
|
} |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
sub _get_bucket_value { |
625
|
|
|
|
|
|
|
## |
626
|
|
|
|
|
|
|
# Fetch single value given tag and MD5 digested key. |
627
|
|
|
|
|
|
|
## |
628
|
15
|
|
|
15
|
|
12
|
my $self = shift; |
629
|
15
|
|
|
|
|
14
|
my ($tag, $md5) = @_; |
630
|
15
|
|
|
|
|
10
|
my $keys = $tag->{content}; |
631
|
|
|
|
|
|
|
|
632
|
15
|
|
|
|
|
26
|
local($/,$\); |
633
|
|
|
|
|
|
|
|
634
|
15
|
|
|
|
|
16
|
my $fh = $self->_fh; |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
## |
637
|
|
|
|
|
|
|
# Iterate through buckets, looking for a key match |
638
|
|
|
|
|
|
|
## |
639
|
|
|
|
|
|
|
BUCKET: |
640
|
15
|
|
|
|
|
28
|
for (my $i=0; $i<$MAX_BUCKETS; $i++) { |
641
|
21
|
|
|
|
|
25
|
my $key = substr($keys, $i * $BUCKET_SIZE, $HASH_SIZE); |
642
|
21
|
|
|
|
|
31
|
my $subloc = unpack($LONG_PACK, substr($keys, ($i * $BUCKET_SIZE) + $HASH_SIZE, $LONG_SIZE)); |
643
|
|
|
|
|
|
|
|
644
|
21
|
50
|
|
|
|
29
|
if (!$subloc) { |
645
|
|
|
|
|
|
|
## |
646
|
|
|
|
|
|
|
# Hit end of list, no match |
647
|
|
|
|
|
|
|
## |
648
|
0
|
|
|
|
|
0
|
return; |
649
|
|
|
|
|
|
|
} |
650
|
|
|
|
|
|
|
|
651
|
21
|
100
|
|
|
|
24
|
if ( $md5 ne $key ) { |
652
|
6
|
|
|
|
|
9
|
next BUCKET; |
653
|
|
|
|
|
|
|
} |
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
## |
656
|
|
|
|
|
|
|
# Found match -- seek to offset and read signature |
657
|
|
|
|
|
|
|
## |
658
|
15
|
|
|
|
|
14
|
my $signature; |
659
|
15
|
|
|
|
|
15
|
seek($fh, $subloc + $self->_root->{file_offset}, SEEK_SET); |
660
|
15
|
|
|
|
|
65
|
read( $fh, $signature, SIG_SIZE); |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
## |
663
|
|
|
|
|
|
|
# If value is a hash or array, return new Number::Phone::UK::DBM::Deep object with correct offset |
664
|
|
|
|
|
|
|
## |
665
|
15
|
100
|
66
|
|
|
44
|
if (($signature eq TYPE_HASH) || ($signature eq TYPE_ARRAY)) { |
|
|
50
|
|
|
|
|
|
666
|
11
|
|
|
|
|
16
|
my $obj = Number::Phone::UK::DBM::Deep->new( |
667
|
|
|
|
|
|
|
type => $signature, |
668
|
|
|
|
|
|
|
base_offset => $subloc, |
669
|
|
|
|
|
|
|
root => $self->_root |
670
|
|
|
|
|
|
|
); |
671
|
|
|
|
|
|
|
|
672
|
11
|
50
|
|
|
|
12
|
if ($self->_root->{autobless}) { |
673
|
|
|
|
|
|
|
## |
674
|
|
|
|
|
|
|
# Skip over value and plain key to see if object needs |
675
|
|
|
|
|
|
|
# to be re-blessed |
676
|
|
|
|
|
|
|
## |
677
|
0
|
|
|
|
|
0
|
seek($fh, $DATA_LENGTH_SIZE + $INDEX_SIZE, SEEK_CUR); |
678
|
|
|
|
|
|
|
|
679
|
0
|
|
|
|
|
0
|
my $size; |
680
|
0
|
|
|
|
|
0
|
read( $fh, $size, $DATA_LENGTH_SIZE); $size = unpack($DATA_LENGTH_PACK, $size); |
|
0
|
|
|
|
|
0
|
|
681
|
0
|
0
|
|
|
|
0
|
if ($size) { seek($fh, $size, SEEK_CUR); } |
|
0
|
|
|
|
|
0
|
|
682
|
|
|
|
|
|
|
|
683
|
0
|
|
|
|
|
0
|
my $bless_bit; |
684
|
0
|
|
|
|
|
0
|
read( $fh, $bless_bit, 1); |
685
|
0
|
0
|
|
|
|
0
|
if (ord($bless_bit)) { |
686
|
|
|
|
|
|
|
## |
687
|
|
|
|
|
|
|
# Yes, object needs to be re-blessed |
688
|
|
|
|
|
|
|
## |
689
|
0
|
|
|
|
|
0
|
my $class_name; |
690
|
0
|
|
|
|
|
0
|
read( $fh, $size, $DATA_LENGTH_SIZE); $size = unpack($DATA_LENGTH_PACK, $size); |
|
0
|
|
|
|
|
0
|
|
691
|
0
|
0
|
|
|
|
0
|
if ($size) { read( $fh, $class_name, $size); } |
|
0
|
|
|
|
|
0
|
|
692
|
0
|
0
|
|
|
|
0
|
if ($class_name) { $obj = bless( $obj, $class_name ); } |
|
0
|
|
|
|
|
0
|
|
693
|
|
|
|
|
|
|
} |
694
|
|
|
|
|
|
|
} |
695
|
|
|
|
|
|
|
|
696
|
11
|
|
|
|
|
31
|
return $obj; |
697
|
|
|
|
|
|
|
} |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
## |
700
|
|
|
|
|
|
|
# Otherwise return actual value |
701
|
|
|
|
|
|
|
## |
702
|
|
|
|
|
|
|
elsif ($signature eq SIG_DATA) { |
703
|
4
|
|
|
|
|
1
|
my $size; |
704
|
4
|
|
|
|
|
4
|
my $value = ''; |
705
|
4
|
|
|
|
|
5
|
read( $fh, $size, $DATA_LENGTH_SIZE); $size = unpack($DATA_LENGTH_PACK, $size); |
|
4
|
|
|
|
|
5
|
|
706
|
4
|
50
|
|
|
|
6
|
if ($size) { read( $fh, $value, $size); } |
|
4
|
|
|
|
|
27
|
|
707
|
4
|
|
|
|
|
11
|
return $value; |
708
|
|
|
|
|
|
|
} |
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
## |
711
|
|
|
|
|
|
|
# Key exists, but content is null |
712
|
|
|
|
|
|
|
## |
713
|
0
|
|
|
|
|
0
|
else { return; } |
714
|
|
|
|
|
|
|
} # i loop |
715
|
|
|
|
|
|
|
|
716
|
0
|
|
|
|
|
0
|
return; |
717
|
|
|
|
|
|
|
} |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
sub _delete_bucket { |
720
|
|
|
|
|
|
|
## |
721
|
|
|
|
|
|
|
# Delete single key/value pair given tag and MD5 digested key. |
722
|
|
|
|
|
|
|
## |
723
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
724
|
0
|
|
|
|
|
0
|
my ($tag, $md5) = @_; |
725
|
0
|
|
|
|
|
0
|
my $keys = $tag->{content}; |
726
|
|
|
|
|
|
|
|
727
|
0
|
|
|
|
|
0
|
local($/,$\); |
728
|
|
|
|
|
|
|
|
729
|
0
|
|
|
|
|
0
|
my $fh = $self->_fh; |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
## |
732
|
|
|
|
|
|
|
# Iterate through buckets, looking for a key match |
733
|
|
|
|
|
|
|
## |
734
|
|
|
|
|
|
|
BUCKET: |
735
|
0
|
|
|
|
|
0
|
for (my $i=0; $i<$MAX_BUCKETS; $i++) { |
736
|
0
|
|
|
|
|
0
|
my $key = substr($keys, $i * $BUCKET_SIZE, $HASH_SIZE); |
737
|
0
|
|
|
|
|
0
|
my $subloc = unpack($LONG_PACK, substr($keys, ($i * $BUCKET_SIZE) + $HASH_SIZE, $LONG_SIZE)); |
738
|
|
|
|
|
|
|
|
739
|
0
|
0
|
|
|
|
0
|
if (!$subloc) { |
740
|
|
|
|
|
|
|
## |
741
|
|
|
|
|
|
|
# Hit end of list, no match |
742
|
|
|
|
|
|
|
## |
743
|
0
|
|
|
|
|
0
|
return; |
744
|
|
|
|
|
|
|
} |
745
|
|
|
|
|
|
|
|
746
|
0
|
0
|
|
|
|
0
|
if ( $md5 ne $key ) { |
747
|
0
|
|
|
|
|
0
|
next BUCKET; |
748
|
|
|
|
|
|
|
} |
749
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
## |
751
|
|
|
|
|
|
|
# Matched key -- delete bucket and return |
752
|
|
|
|
|
|
|
## |
753
|
0
|
|
|
|
|
0
|
seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE) + $self->_root->{file_offset}, SEEK_SET); |
754
|
0
|
|
|
|
|
0
|
print( $fh substr($keys, ($i+1) * $BUCKET_SIZE ) ); |
755
|
0
|
|
|
|
|
0
|
print( $fh chr(0) x $BUCKET_SIZE ); |
756
|
|
|
|
|
|
|
|
757
|
0
|
|
|
|
|
0
|
return 1; |
758
|
|
|
|
|
|
|
} # i loop |
759
|
|
|
|
|
|
|
|
760
|
0
|
|
|
|
|
0
|
return; |
761
|
|
|
|
|
|
|
} |
762
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
sub _bucket_exists { |
764
|
|
|
|
|
|
|
## |
765
|
|
|
|
|
|
|
# Check existence of single key given tag and MD5 digested key. |
766
|
|
|
|
|
|
|
## |
767
|
5
|
|
|
5
|
|
2
|
my $self = shift; |
768
|
5
|
|
|
|
|
6
|
my ($tag, $md5) = @_; |
769
|
5
|
|
|
|
|
4
|
my $keys = $tag->{content}; |
770
|
|
|
|
|
|
|
|
771
|
|
|
|
|
|
|
## |
772
|
|
|
|
|
|
|
# Iterate through buckets, looking for a key match |
773
|
|
|
|
|
|
|
## |
774
|
|
|
|
|
|
|
BUCKET: |
775
|
5
|
|
|
|
|
10
|
for (my $i=0; $i<$MAX_BUCKETS; $i++) { |
776
|
16
|
|
|
|
|
16
|
my $key = substr($keys, $i * $BUCKET_SIZE, $HASH_SIZE); |
777
|
16
|
|
|
|
|
15
|
my $subloc = unpack($LONG_PACK, substr($keys, ($i * $BUCKET_SIZE) + $HASH_SIZE, $LONG_SIZE)); |
778
|
|
|
|
|
|
|
|
779
|
16
|
100
|
|
|
|
19
|
if (!$subloc) { |
780
|
|
|
|
|
|
|
## |
781
|
|
|
|
|
|
|
# Hit end of list, no match |
782
|
|
|
|
|
|
|
## |
783
|
4
|
|
|
|
|
18
|
return; |
784
|
|
|
|
|
|
|
} |
785
|
|
|
|
|
|
|
|
786
|
12
|
100
|
|
|
|
15
|
if ( $md5 ne $key ) { |
787
|
11
|
|
|
|
|
17
|
next BUCKET; |
788
|
|
|
|
|
|
|
} |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
## |
791
|
|
|
|
|
|
|
# Matched key -- return true |
792
|
|
|
|
|
|
|
## |
793
|
1
|
|
|
|
|
3
|
return 1; |
794
|
|
|
|
|
|
|
} # i loop |
795
|
|
|
|
|
|
|
|
796
|
0
|
|
|
|
|
0
|
return; |
797
|
|
|
|
|
|
|
} |
798
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
sub _find_bucket_list { |
800
|
|
|
|
|
|
|
## |
801
|
|
|
|
|
|
|
# Locate offset for bucket list, given digested key |
802
|
|
|
|
|
|
|
## |
803
|
20
|
|
|
20
|
|
11
|
my $self = shift; |
804
|
20
|
|
|
|
|
15
|
my $md5 = shift; |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
## |
807
|
|
|
|
|
|
|
# Locate offset for bucket list using digest index system |
808
|
|
|
|
|
|
|
## |
809
|
20
|
|
|
|
|
16
|
my $ch = 0; |
810
|
20
|
|
|
|
|
23
|
my $tag = $self->_load_tag($self->_base_offset); |
811
|
20
|
50
|
|
|
|
33
|
if (!$tag) { return; } |
|
0
|
|
|
|
|
0
|
|
812
|
|
|
|
|
|
|
|
813
|
20
|
|
|
|
|
38
|
while ($tag->{signature} ne SIG_BLIST) { |
814
|
27
|
|
|
|
|
52
|
$tag = $self->_index_lookup($tag, ord(substr($md5, $ch, 1))); |
815
|
27
|
50
|
|
|
|
58
|
if (!$tag) { return; } |
|
0
|
|
|
|
|
0
|
|
816
|
27
|
|
|
|
|
38
|
$ch++; |
817
|
|
|
|
|
|
|
} |
818
|
|
|
|
|
|
|
|
819
|
20
|
|
|
|
|
23
|
return $tag; |
820
|
|
|
|
|
|
|
} |
821
|
|
|
|
|
|
|
|
822
|
|
|
|
|
|
|
sub _traverse_index { |
823
|
|
|
|
|
|
|
## |
824
|
|
|
|
|
|
|
# Scan index and recursively step into deeper levels, looking for next key. |
825
|
|
|
|
|
|
|
## |
826
|
0
|
|
|
0
|
|
0
|
my ($self, $offset, $ch, $force_return_next) = @_; |
827
|
0
|
0
|
|
|
|
0
|
$force_return_next = undef unless $force_return_next; |
828
|
|
|
|
|
|
|
|
829
|
0
|
|
|
|
|
0
|
local($/,$\); |
830
|
|
|
|
|
|
|
|
831
|
0
|
|
|
|
|
0
|
my $tag = $self->_load_tag( $offset ); |
832
|
|
|
|
|
|
|
|
833
|
0
|
|
|
|
|
0
|
my $fh = $self->_fh; |
834
|
|
|
|
|
|
|
|
835
|
0
|
0
|
|
|
|
0
|
if ($tag->{signature} ne SIG_BLIST) { |
|
|
0
|
|
|
|
|
|
836
|
0
|
|
|
|
|
0
|
my $content = $tag->{content}; |
837
|
0
|
|
|
|
|
0
|
my $start; |
838
|
0
|
0
|
|
|
|
0
|
if ($self->{return_next}) { $start = 0; } |
|
0
|
|
|
|
|
0
|
|
839
|
0
|
|
|
|
|
0
|
else { $start = ord(substr($self->{prev_md5}, $ch, 1)); } |
840
|
|
|
|
|
|
|
|
841
|
0
|
|
|
|
|
0
|
for (my $index = $start; $index < 256; $index++) { |
842
|
0
|
|
|
|
|
0
|
my $subloc = unpack($LONG_PACK, substr($content, $index * $LONG_SIZE, $LONG_SIZE) ); |
843
|
0
|
0
|
|
|
|
0
|
if ($subloc) { |
844
|
0
|
|
|
|
|
0
|
my $result = $self->_traverse_index( $subloc, $ch + 1, $force_return_next ); |
845
|
0
|
0
|
|
|
|
0
|
if (defined($result)) { return $result; } |
|
0
|
|
|
|
|
0
|
|
846
|
|
|
|
|
|
|
} |
847
|
|
|
|
|
|
|
} # index loop |
848
|
|
|
|
|
|
|
|
849
|
0
|
|
|
|
|
0
|
$self->{return_next} = 1; |
850
|
|
|
|
|
|
|
} # tag is an index |
851
|
|
|
|
|
|
|
|
852
|
|
|
|
|
|
|
elsif ($tag->{signature} eq SIG_BLIST) { |
853
|
0
|
|
|
|
|
0
|
my $keys = $tag->{content}; |
854
|
0
|
0
|
|
|
|
0
|
if ($force_return_next) { $self->{return_next} = 1; } |
|
0
|
|
|
|
|
0
|
|
855
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
## |
857
|
|
|
|
|
|
|
# Iterate through buckets, looking for a key match |
858
|
|
|
|
|
|
|
## |
859
|
0
|
|
|
|
|
0
|
for (my $i=0; $i<$MAX_BUCKETS; $i++) { |
860
|
0
|
|
|
|
|
0
|
my $key = substr($keys, $i * $BUCKET_SIZE, $HASH_SIZE); |
861
|
0
|
|
|
|
|
0
|
my $subloc = unpack($LONG_PACK, substr($keys, ($i * $BUCKET_SIZE) + $HASH_SIZE, $LONG_SIZE)); |
862
|
|
|
|
|
|
|
|
863
|
0
|
0
|
|
|
|
0
|
if (!$subloc) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
864
|
|
|
|
|
|
|
## |
865
|
|
|
|
|
|
|
# End of bucket list -- return to outer loop |
866
|
|
|
|
|
|
|
## |
867
|
0
|
|
|
|
|
0
|
$self->{return_next} = 1; |
868
|
0
|
|
|
|
|
0
|
last; |
869
|
|
|
|
|
|
|
} |
870
|
|
|
|
|
|
|
elsif ($key eq $self->{prev_md5}) { |
871
|
|
|
|
|
|
|
## |
872
|
|
|
|
|
|
|
# Located previous key -- return next one found |
873
|
|
|
|
|
|
|
## |
874
|
0
|
|
|
|
|
0
|
$self->{return_next} = 1; |
875
|
0
|
|
|
|
|
0
|
next; |
876
|
|
|
|
|
|
|
} |
877
|
|
|
|
|
|
|
elsif ($self->{return_next}) { |
878
|
|
|
|
|
|
|
## |
879
|
|
|
|
|
|
|
# Seek to bucket location and skip over signature |
880
|
|
|
|
|
|
|
## |
881
|
0
|
|
|
|
|
0
|
seek($fh, $subloc + SIG_SIZE + $self->_root->{file_offset}, SEEK_SET); |
882
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
## |
884
|
|
|
|
|
|
|
# Skip over value to get to plain key |
885
|
|
|
|
|
|
|
## |
886
|
0
|
|
|
|
|
0
|
my $size; |
887
|
0
|
|
|
|
|
0
|
read( $fh, $size, $DATA_LENGTH_SIZE); $size = unpack($DATA_LENGTH_PACK, $size); |
|
0
|
|
|
|
|
0
|
|
888
|
0
|
0
|
|
|
|
0
|
if ($size) { seek($fh, $size, SEEK_CUR); } |
|
0
|
|
|
|
|
0
|
|
889
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
## |
891
|
|
|
|
|
|
|
# Read in plain key and return as scalar |
892
|
|
|
|
|
|
|
## |
893
|
0
|
|
|
|
|
0
|
my $plain_key; |
894
|
0
|
|
|
|
|
0
|
read( $fh, $size, $DATA_LENGTH_SIZE); $size = unpack($DATA_LENGTH_PACK, $size); |
|
0
|
|
|
|
|
0
|
|
895
|
0
|
0
|
|
|
|
0
|
if ($size) { read( $fh, $plain_key, $size); } |
|
0
|
|
|
|
|
0
|
|
896
|
|
|
|
|
|
|
|
897
|
0
|
|
|
|
|
0
|
return $plain_key; |
898
|
|
|
|
|
|
|
} |
899
|
|
|
|
|
|
|
} # bucket loop |
900
|
|
|
|
|
|
|
|
901
|
0
|
|
|
|
|
0
|
$self->{return_next} = 1; |
902
|
|
|
|
|
|
|
} # tag is a bucket list |
903
|
|
|
|
|
|
|
|
904
|
0
|
|
|
|
|
0
|
return; |
905
|
|
|
|
|
|
|
} |
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
sub _get_next_key { |
908
|
|
|
|
|
|
|
## |
909
|
|
|
|
|
|
|
# Locate next key, given digested previous one |
910
|
|
|
|
|
|
|
## |
911
|
0
|
|
|
0
|
|
0
|
my $self = $_[0]->_get_self; |
912
|
|
|
|
|
|
|
|
913
|
0
|
0
|
|
|
|
0
|
$self->{prev_md5} = $_[1] ? $_[1] : undef; |
914
|
0
|
|
|
|
|
0
|
$self->{return_next} = 0; |
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
## |
917
|
|
|
|
|
|
|
# If the previous key was not specifed, start at the top and |
918
|
|
|
|
|
|
|
# return the first one found. |
919
|
|
|
|
|
|
|
## |
920
|
0
|
0
|
|
|
|
0
|
if (!$self->{prev_md5}) { |
921
|
0
|
|
|
|
|
0
|
$self->{prev_md5} = chr(0) x $HASH_SIZE; |
922
|
0
|
|
|
|
|
0
|
$self->{return_next} = 1; |
923
|
|
|
|
|
|
|
} |
924
|
|
|
|
|
|
|
|
925
|
0
|
|
|
|
|
0
|
return $self->_traverse_index( $self->_base_offset, 0 ); |
926
|
|
|
|
|
|
|
} |
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
sub lock { |
929
|
|
|
|
|
|
|
## |
930
|
|
|
|
|
|
|
# If db locking is set, flock() the db file. If called multiple |
931
|
|
|
|
|
|
|
# times before unlock(), then the same number of unlocks() must |
932
|
|
|
|
|
|
|
# be called before the lock is released. |
933
|
|
|
|
|
|
|
## |
934
|
20
|
|
|
20
|
1
|
30
|
my $self = $_[0]->_get_self; |
935
|
20
|
|
|
|
|
18
|
my $type = $_[1]; |
936
|
20
|
50
|
|
|
|
26
|
$type = LOCK_EX unless defined $type; |
937
|
|
|
|
|
|
|
|
938
|
20
|
50
|
|
|
|
22
|
if (!defined($self->_fh)) { return; } |
|
0
|
|
|
|
|
0
|
|
939
|
|
|
|
|
|
|
|
940
|
20
|
50
|
|
|
|
46
|
if ($self->_root->{locking}) { |
941
|
0
|
0
|
|
|
|
0
|
if (!$self->_root->{locked}) { |
942
|
0
|
|
|
|
|
0
|
flock($self->_fh, $type); |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
# refresh end counter in case file has changed size |
945
|
0
|
|
|
|
|
0
|
my @stats = stat($self->_root->{file}); |
946
|
0
|
|
|
|
|
0
|
$self->_root->{end} = $stats[7]; |
947
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
# double-check file inode, in case another process |
949
|
|
|
|
|
|
|
# has optimize()d our file while we were waiting. |
950
|
0
|
0
|
|
|
|
0
|
if ($stats[1] != $self->_root->{inode}) { |
951
|
0
|
|
|
|
|
0
|
$self->_open(); # re-open |
952
|
0
|
|
|
|
|
0
|
flock($self->_fh, $type); # re-lock |
953
|
0
|
|
|
|
|
0
|
$self->_root->{end} = (stat($self->_fh))[7]; # re-end |
954
|
|
|
|
|
|
|
} |
955
|
|
|
|
|
|
|
} |
956
|
0
|
|
|
|
|
0
|
$self->_root->{locked}++; |
957
|
|
|
|
|
|
|
|
958
|
0
|
|
|
|
|
0
|
return 1; |
959
|
|
|
|
|
|
|
} |
960
|
|
|
|
|
|
|
|
961
|
20
|
|
|
|
|
18
|
return; |
962
|
|
|
|
|
|
|
} |
963
|
|
|
|
|
|
|
|
964
|
|
|
|
|
|
|
sub unlock { |
965
|
|
|
|
|
|
|
## |
966
|
|
|
|
|
|
|
# If db locking is set, unlock the db file. See note in lock() |
967
|
|
|
|
|
|
|
# regarding calling lock() multiple times. |
968
|
|
|
|
|
|
|
## |
969
|
20
|
|
|
20
|
1
|
37
|
my $self = $_[0]->_get_self; |
970
|
|
|
|
|
|
|
|
971
|
20
|
50
|
|
|
|
23
|
if (!defined($self->_fh)) { return; } |
|
0
|
|
|
|
|
0
|
|
972
|
|
|
|
|
|
|
|
973
|
20
|
50
|
33
|
|
|
26
|
if ($self->_root->{locking} && $self->_root->{locked} > 0) { |
974
|
0
|
|
|
|
|
0
|
$self->_root->{locked}--; |
975
|
0
|
0
|
|
|
|
0
|
if (!$self->_root->{locked}) { flock($self->_fh, LOCK_UN); } |
|
0
|
|
|
|
|
0
|
|
976
|
|
|
|
|
|
|
|
977
|
0
|
|
|
|
|
0
|
return 1; |
978
|
|
|
|
|
|
|
} |
979
|
|
|
|
|
|
|
|
980
|
20
|
|
|
|
|
17
|
return; |
981
|
|
|
|
|
|
|
} |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
sub _copy_value { |
984
|
0
|
|
|
0
|
|
0
|
my $self = shift->_get_self; |
985
|
0
|
|
|
|
|
0
|
my ($spot, $value) = @_; |
986
|
|
|
|
|
|
|
|
987
|
0
|
0
|
|
|
|
0
|
if ( !ref $value ) { |
|
|
0
|
|
|
|
|
|
988
|
0
|
|
|
|
|
0
|
${$spot} = $value; |
|
0
|
|
|
|
|
0
|
|
989
|
|
|
|
|
|
|
} |
990
|
0
|
|
|
|
|
0
|
elsif ( eval { local $SIG{__DIE__}; $value->isa( 'Number::Phone::UK::DBM::Deep' ) } ) { |
|
0
|
|
|
|
|
0
|
|
991
|
0
|
|
|
|
|
0
|
my $type = $value->_type; |
992
|
0
|
0
|
|
|
|
0
|
${$spot} = $type eq TYPE_HASH ? {} : []; |
|
0
|
|
|
|
|
0
|
|
993
|
0
|
|
|
|
|
0
|
$value->_copy_node( ${$spot} ); |
|
0
|
|
|
|
|
0
|
|
994
|
|
|
|
|
|
|
} |
995
|
|
|
|
|
|
|
else { |
996
|
0
|
|
|
|
|
0
|
my $r = Scalar::Util::reftype( $value ); |
997
|
0
|
|
|
|
|
0
|
my $c = Scalar::Util::blessed( $value ); |
998
|
0
|
0
|
|
|
|
0
|
if ( $r eq 'ARRAY' ) { |
999
|
0
|
|
|
|
|
0
|
${$spot} = [ @{$value} ]; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
1000
|
|
|
|
|
|
|
} |
1001
|
|
|
|
|
|
|
else { |
1002
|
0
|
|
|
|
|
0
|
${$spot} = { %{$value} }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
1003
|
|
|
|
|
|
|
} |
1004
|
0
|
0
|
|
|
|
0
|
${$spot} = bless ${$spot}, $c |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
1005
|
|
|
|
|
|
|
if defined $c; |
1006
|
|
|
|
|
|
|
} |
1007
|
|
|
|
|
|
|
|
1008
|
0
|
|
|
|
|
0
|
return 1; |
1009
|
|
|
|
|
|
|
} |
1010
|
|
|
|
|
|
|
|
1011
|
|
|
|
|
|
|
sub _copy_node { |
1012
|
|
|
|
|
|
|
## |
1013
|
|
|
|
|
|
|
# Copy single level of keys or elements to new DB handle. |
1014
|
|
|
|
|
|
|
# Recurse for nested structures |
1015
|
|
|
|
|
|
|
## |
1016
|
0
|
|
|
0
|
|
0
|
my $self = shift->_get_self; |
1017
|
0
|
|
|
|
|
0
|
my ($db_temp) = @_; |
1018
|
|
|
|
|
|
|
|
1019
|
0
|
0
|
|
|
|
0
|
if ($self->_type eq TYPE_HASH) { |
1020
|
0
|
|
|
|
|
0
|
my $key = $self->first_key(); |
1021
|
0
|
|
|
|
|
0
|
while ($key) { |
1022
|
0
|
|
|
|
|
0
|
my $value = $self->get($key); |
1023
|
0
|
|
|
|
|
0
|
$self->_copy_value( \$db_temp->{$key}, $value ); |
1024
|
0
|
|
|
|
|
0
|
$key = $self->next_key($key); |
1025
|
|
|
|
|
|
|
} |
1026
|
|
|
|
|
|
|
} |
1027
|
|
|
|
|
|
|
else { |
1028
|
0
|
|
|
|
|
0
|
my $length = $self->length(); |
1029
|
0
|
|
|
|
|
0
|
for (my $index = 0; $index < $length; $index++) { |
1030
|
0
|
|
|
|
|
0
|
my $value = $self->get($index); |
1031
|
0
|
|
|
|
|
0
|
$self->_copy_value( \$db_temp->[$index], $value ); |
1032
|
|
|
|
|
|
|
} |
1033
|
|
|
|
|
|
|
} |
1034
|
|
|
|
|
|
|
|
1035
|
0
|
|
|
|
|
0
|
return 1; |
1036
|
|
|
|
|
|
|
} |
1037
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
sub export { |
1039
|
|
|
|
|
|
|
## |
1040
|
|
|
|
|
|
|
# Recursively export into standard Perl hashes and arrays. |
1041
|
|
|
|
|
|
|
## |
1042
|
0
|
|
|
0
|
1
|
0
|
my $self = $_[0]->_get_self; |
1043
|
|
|
|
|
|
|
|
1044
|
0
|
|
|
|
|
0
|
my $temp; |
1045
|
0
|
0
|
|
|
|
0
|
if ($self->_type eq TYPE_HASH) { $temp = {}; } |
|
0
|
0
|
|
|
|
0
|
|
1046
|
0
|
|
|
|
|
0
|
elsif ($self->_type eq TYPE_ARRAY) { $temp = []; } |
1047
|
|
|
|
|
|
|
|
1048
|
0
|
|
|
|
|
0
|
$self->lock(); |
1049
|
0
|
|
|
|
|
0
|
$self->_copy_node( $temp ); |
1050
|
0
|
|
|
|
|
0
|
$self->unlock(); |
1051
|
|
|
|
|
|
|
|
1052
|
0
|
|
|
|
|
0
|
return $temp; |
1053
|
|
|
|
|
|
|
} |
1054
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
sub import { |
1056
|
|
|
|
|
|
|
## |
1057
|
|
|
|
|
|
|
# Recursively import Perl hash/array structure |
1058
|
|
|
|
|
|
|
## |
1059
|
|
|
|
|
|
|
#XXX This use of ref() seems to be ok |
1060
|
1
|
50
|
|
1
|
|
3
|
if (!ref($_[0])) { return; } # Perl calls import() on use -- ignore |
|
1
|
|
|
|
|
52
|
|
1061
|
|
|
|
|
|
|
|
1062
|
0
|
|
|
|
|
0
|
my $self = $_[0]->_get_self; |
1063
|
0
|
|
|
|
|
0
|
my $struct = $_[1]; |
1064
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
#XXX This use of ref() seems to be ok |
1066
|
0
|
0
|
|
|
|
0
|
if (!ref($struct)) { |
1067
|
|
|
|
|
|
|
## |
1068
|
|
|
|
|
|
|
# struct is not a reference, so just import based on our type |
1069
|
|
|
|
|
|
|
## |
1070
|
0
|
|
|
|
|
0
|
shift @_; |
1071
|
|
|
|
|
|
|
|
1072
|
0
|
0
|
|
|
|
0
|
if ($self->_type eq TYPE_HASH) { $struct = {@_}; } |
|
0
|
0
|
|
|
|
0
|
|
1073
|
0
|
|
|
|
|
0
|
elsif ($self->_type eq TYPE_ARRAY) { $struct = [@_]; } |
1074
|
|
|
|
|
|
|
} |
1075
|
|
|
|
|
|
|
|
1076
|
0
|
|
0
|
|
|
0
|
my $r = Scalar::Util::reftype($struct) || ''; |
1077
|
0
|
0
|
0
|
|
|
0
|
if ($r eq "HASH" && $self->_type eq TYPE_HASH) { |
|
|
0
|
0
|
|
|
|
|
1078
|
0
|
|
|
|
|
0
|
foreach my $key (keys %$struct) { $self->put($key, $struct->{$key}); } |
|
0
|
|
|
|
|
0
|
|
1079
|
|
|
|
|
|
|
} |
1080
|
|
|
|
|
|
|
elsif ($r eq "ARRAY" && $self->_type eq TYPE_ARRAY) { |
1081
|
0
|
|
|
|
|
0
|
$self->push( @$struct ); |
1082
|
|
|
|
|
|
|
} |
1083
|
|
|
|
|
|
|
else { |
1084
|
0
|
|
|
|
|
0
|
return $self->_throw_error("Cannot import: type mismatch"); |
1085
|
|
|
|
|
|
|
} |
1086
|
|
|
|
|
|
|
|
1087
|
0
|
|
|
|
|
0
|
return 1; |
1088
|
|
|
|
|
|
|
} |
1089
|
|
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
sub optimize { |
1091
|
|
|
|
|
|
|
## |
1092
|
|
|
|
|
|
|
# Rebuild entire database into new file, then move |
1093
|
|
|
|
|
|
|
# it back on top of original. |
1094
|
|
|
|
|
|
|
## |
1095
|
0
|
|
|
0
|
1
|
0
|
my $self = $_[0]->_get_self; |
1096
|
|
|
|
|
|
|
|
1097
|
|
|
|
|
|
|
#XXX Need to create a new test for this |
1098
|
|
|
|
|
|
|
# if ($self->_root->{links} > 1) { |
1099
|
|
|
|
|
|
|
# return $self->_throw_error("Cannot optimize: reference count is greater than 1"); |
1100
|
|
|
|
|
|
|
# } |
1101
|
|
|
|
|
|
|
|
1102
|
|
|
|
|
|
|
my $db_temp = Number::Phone::UK::DBM::Deep->new( |
1103
|
0
|
|
|
|
|
0
|
file => $self->_root->{file} . '.tmp', |
1104
|
|
|
|
|
|
|
type => $self->_type |
1105
|
|
|
|
|
|
|
); |
1106
|
0
|
0
|
|
|
|
0
|
if (!$db_temp) { |
1107
|
0
|
|
|
|
|
0
|
return $self->_throw_error("Cannot optimize: failed to open temp file: $!"); |
1108
|
|
|
|
|
|
|
} |
1109
|
|
|
|
|
|
|
|
1110
|
0
|
|
|
|
|
0
|
$self->lock(); |
1111
|
0
|
|
|
|
|
0
|
$self->_copy_node( $db_temp ); |
1112
|
0
|
|
|
|
|
0
|
undef $db_temp; |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
## |
1115
|
|
|
|
|
|
|
# Attempt to copy user, group and permissions over to new file |
1116
|
|
|
|
|
|
|
## |
1117
|
0
|
|
|
|
|
0
|
my @stats = stat($self->_fh); |
1118
|
0
|
|
|
|
|
0
|
my $perms = $stats[2] & 07777; |
1119
|
0
|
|
|
|
|
0
|
my $uid = $stats[4]; |
1120
|
0
|
|
|
|
|
0
|
my $gid = $stats[5]; |
1121
|
0
|
|
|
|
|
0
|
chown( $uid, $gid, $self->_root->{file} . '.tmp' ); |
1122
|
0
|
|
|
|
|
0
|
chmod( $perms, $self->_root->{file} . '.tmp' ); |
1123
|
|
|
|
|
|
|
|
1124
|
|
|
|
|
|
|
# q.v. perlport for more information on this variable |
1125
|
0
|
0
|
0
|
|
|
0
|
if ( $^O eq 'MSWin32' || $^O eq 'cygwin' ) { |
1126
|
|
|
|
|
|
|
## |
1127
|
|
|
|
|
|
|
# Potential race condition when optmizing on Win32 with locking. |
1128
|
|
|
|
|
|
|
# The Windows filesystem requires that the filehandle be closed |
1129
|
|
|
|
|
|
|
# before it is overwritten with rename(). This could be redone |
1130
|
|
|
|
|
|
|
# with a soft copy. |
1131
|
|
|
|
|
|
|
## |
1132
|
0
|
|
|
|
|
0
|
$self->unlock(); |
1133
|
0
|
|
|
|
|
0
|
$self->_close(); |
1134
|
|
|
|
|
|
|
} |
1135
|
|
|
|
|
|
|
|
1136
|
0
|
0
|
|
|
|
0
|
if (!rename $self->_root->{file} . '.tmp', $self->_root->{file}) { |
1137
|
0
|
|
|
|
|
0
|
unlink $self->_root->{file} . '.tmp'; |
1138
|
0
|
|
|
|
|
0
|
$self->unlock(); |
1139
|
0
|
|
|
|
|
0
|
return $self->_throw_error("Optimize failed: Cannot copy temp file over original: $!"); |
1140
|
|
|
|
|
|
|
} |
1141
|
|
|
|
|
|
|
|
1142
|
0
|
|
|
|
|
0
|
$self->unlock(); |
1143
|
0
|
|
|
|
|
0
|
$self->_close(); |
1144
|
0
|
|
|
|
|
0
|
$self->_open(); |
1145
|
|
|
|
|
|
|
|
1146
|
0
|
|
|
|
|
0
|
return 1; |
1147
|
|
|
|
|
|
|
} |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
sub clone { |
1150
|
|
|
|
|
|
|
## |
1151
|
|
|
|
|
|
|
# Make copy of object and return |
1152
|
|
|
|
|
|
|
## |
1153
|
0
|
|
|
0
|
1
|
0
|
my $self = $_[0]->_get_self; |
1154
|
|
|
|
|
|
|
|
1155
|
0
|
|
|
|
|
0
|
return Number::Phone::UK::DBM::Deep->new( |
1156
|
|
|
|
|
|
|
type => $self->_type, |
1157
|
|
|
|
|
|
|
base_offset => $self->_base_offset, |
1158
|
|
|
|
|
|
|
root => $self->_root |
1159
|
|
|
|
|
|
|
); |
1160
|
|
|
|
|
|
|
} |
1161
|
|
|
|
|
|
|
|
1162
|
|
|
|
|
|
|
{ |
1163
|
|
|
|
|
|
|
my %is_legal_filter = map { |
1164
|
|
|
|
|
|
|
$_ => ~~1, |
1165
|
|
|
|
|
|
|
} qw( |
1166
|
|
|
|
|
|
|
store_key store_value |
1167
|
|
|
|
|
|
|
fetch_key fetch_value |
1168
|
|
|
|
|
|
|
); |
1169
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
sub set_filter { |
1171
|
|
|
|
|
|
|
## |
1172
|
|
|
|
|
|
|
# Setup filter function for storing or fetching the key or value |
1173
|
|
|
|
|
|
|
## |
1174
|
0
|
|
|
0
|
1
|
0
|
my $self = $_[0]->_get_self; |
1175
|
0
|
|
|
|
|
0
|
my $type = lc $_[1]; |
1176
|
0
|
0
|
|
|
|
0
|
my $func = $_[2] ? $_[2] : undef; |
1177
|
|
|
|
|
|
|
|
1178
|
0
|
0
|
|
|
|
0
|
if ( $is_legal_filter{$type} ) { |
1179
|
0
|
|
|
|
|
0
|
$self->_root->{"filter_$type"} = $func; |
1180
|
0
|
|
|
|
|
0
|
return 1; |
1181
|
|
|
|
|
|
|
} |
1182
|
|
|
|
|
|
|
|
1183
|
0
|
|
|
|
|
0
|
return; |
1184
|
|
|
|
|
|
|
} |
1185
|
|
|
|
|
|
|
} |
1186
|
|
|
|
|
|
|
|
1187
|
|
|
|
|
|
|
## |
1188
|
|
|
|
|
|
|
# Accessor methods |
1189
|
|
|
|
|
|
|
## |
1190
|
|
|
|
|
|
|
|
1191
|
|
|
|
|
|
|
sub _root { |
1192
|
|
|
|
|
|
|
## |
1193
|
|
|
|
|
|
|
# Get access to the root structure |
1194
|
|
|
|
|
|
|
## |
1195
|
282
|
|
|
282
|
|
385
|
my $self = $_[0]->_get_self; |
1196
|
282
|
|
|
|
|
550
|
return $self->{root}; |
1197
|
|
|
|
|
|
|
} |
1198
|
|
|
|
|
|
|
|
1199
|
|
|
|
|
|
|
sub _fh { |
1200
|
|
|
|
|
|
|
## |
1201
|
|
|
|
|
|
|
# Get access to the raw fh |
1202
|
|
|
|
|
|
|
## |
1203
|
|
|
|
|
|
|
#XXX It will be useful, though, when we split out HASH and ARRAY |
1204
|
134
|
|
|
134
|
|
191
|
my $self = $_[0]->_get_self; |
1205
|
134
|
|
|
|
|
147
|
return $self->_root->{fh}; |
1206
|
|
|
|
|
|
|
} |
1207
|
|
|
|
|
|
|
|
1208
|
|
|
|
|
|
|
sub _type { |
1209
|
|
|
|
|
|
|
## |
1210
|
|
|
|
|
|
|
# Get type of current node (TYPE_HASH or TYPE_ARRAY) |
1211
|
|
|
|
|
|
|
## |
1212
|
0
|
|
|
0
|
|
0
|
my $self = $_[0]->_get_self; |
1213
|
0
|
|
|
|
|
0
|
return $self->{type}; |
1214
|
|
|
|
|
|
|
} |
1215
|
|
|
|
|
|
|
|
1216
|
|
|
|
|
|
|
sub _base_offset { |
1217
|
|
|
|
|
|
|
## |
1218
|
|
|
|
|
|
|
# Get base_offset of current node (TYPE_HASH or TYPE_ARRAY) |
1219
|
|
|
|
|
|
|
## |
1220
|
20
|
|
|
20
|
|
26
|
my $self = $_[0]->_get_self; |
1221
|
20
|
|
|
|
|
29
|
return $self->{base_offset}; |
1222
|
|
|
|
|
|
|
} |
1223
|
|
|
|
|
|
|
|
1224
|
|
|
|
|
|
|
sub error { |
1225
|
|
|
|
|
|
|
## |
1226
|
|
|
|
|
|
|
# Get last error string, or undef if no error |
1227
|
|
|
|
|
|
|
## |
1228
|
|
|
|
|
|
|
return $_[0] |
1229
|
|
|
|
|
|
|
? ( $_[0]->_get_self->{root}->{error} or undef ) |
1230
|
1
|
50
|
50
|
1
|
1
|
3
|
: $@; |
1231
|
|
|
|
|
|
|
} |
1232
|
|
|
|
|
|
|
|
1233
|
|
|
|
|
|
|
## |
1234
|
|
|
|
|
|
|
# Utility methods |
1235
|
|
|
|
|
|
|
## |
1236
|
|
|
|
|
|
|
|
1237
|
|
|
|
|
|
|
sub _throw_error { |
1238
|
|
|
|
|
|
|
## |
1239
|
|
|
|
|
|
|
# Store error string in self |
1240
|
|
|
|
|
|
|
## |
1241
|
0
|
|
|
0
|
|
0
|
my $error_text = $_[1]; |
1242
|
|
|
|
|
|
|
|
1243
|
0
|
0
|
|
|
|
0
|
if ( Scalar::Util::blessed $_[0] ) { |
1244
|
0
|
|
|
|
|
0
|
my $self = $_[0]->_get_self; |
1245
|
0
|
|
|
|
|
0
|
$self->_root->{error} = $error_text; |
1246
|
|
|
|
|
|
|
|
1247
|
0
|
0
|
|
|
|
0
|
unless ($self->_root->{debug}) { |
1248
|
0
|
|
|
|
|
0
|
die "Number::Phone::UK::DBM::Deep: $error_text\n"; |
1249
|
|
|
|
|
|
|
} |
1250
|
|
|
|
|
|
|
|
1251
|
0
|
|
|
|
|
0
|
warn "Number::Phone::UK::DBM::Deep: $error_text\n"; |
1252
|
0
|
|
|
|
|
0
|
return; |
1253
|
|
|
|
|
|
|
} |
1254
|
|
|
|
|
|
|
else { |
1255
|
0
|
|
|
|
|
0
|
die "Number::Phone::UK::DBM::Deep: $error_text\n"; |
1256
|
|
|
|
|
|
|
} |
1257
|
|
|
|
|
|
|
} |
1258
|
|
|
|
|
|
|
|
1259
|
|
|
|
|
|
|
sub clear_error { |
1260
|
|
|
|
|
|
|
## |
1261
|
|
|
|
|
|
|
# Clear error state |
1262
|
|
|
|
|
|
|
## |
1263
|
0
|
|
|
0
|
1
|
0
|
my $self = $_[0]->_get_self; |
1264
|
|
|
|
|
|
|
|
1265
|
0
|
|
|
|
|
0
|
undef $self->_root->{error}; |
1266
|
|
|
|
|
|
|
} |
1267
|
|
|
|
|
|
|
|
1268
|
|
|
|
|
|
|
sub _precalc_sizes { |
1269
|
|
|
|
|
|
|
## |
1270
|
|
|
|
|
|
|
# Precalculate index, bucket and bucket list sizes |
1271
|
|
|
|
|
|
|
## |
1272
|
|
|
|
|
|
|
|
1273
|
|
|
|
|
|
|
#XXX I don't like this ... |
1274
|
2
|
100
|
|
2
|
|
6
|
set_pack() unless defined $LONG_SIZE; |
1275
|
|
|
|
|
|
|
|
1276
|
2
|
|
|
|
|
2
|
$INDEX_SIZE = 256 * $LONG_SIZE; |
1277
|
2
|
|
|
|
|
2
|
$BUCKET_SIZE = $HASH_SIZE + $LONG_SIZE; |
1278
|
2
|
|
|
|
|
2
|
$BUCKET_LIST_SIZE = $MAX_BUCKETS * $BUCKET_SIZE; |
1279
|
|
|
|
|
|
|
} |
1280
|
|
|
|
|
|
|
|
1281
|
|
|
|
|
|
|
sub set_pack { |
1282
|
|
|
|
|
|
|
## |
1283
|
|
|
|
|
|
|
# Set pack/unpack modes (see file header for more) |
1284
|
|
|
|
|
|
|
## |
1285
|
1
|
|
|
1
|
1
|
1
|
my ($long_s, $long_p, $data_s, $data_p) = @_; |
1286
|
|
|
|
|
|
|
|
1287
|
1
|
50
|
|
|
|
2
|
$LONG_SIZE = $long_s ? $long_s : 4; |
1288
|
1
|
50
|
|
|
|
2
|
$LONG_PACK = $long_p ? $long_p : 'N'; |
1289
|
|
|
|
|
|
|
|
1290
|
1
|
50
|
|
|
|
2
|
$DATA_LENGTH_SIZE = $data_s ? $data_s : 4; |
1291
|
1
|
50
|
|
|
|
1
|
$DATA_LENGTH_PACK = $data_p ? $data_p : 'N'; |
1292
|
|
|
|
|
|
|
|
1293
|
1
|
|
|
|
|
3
|
_precalc_sizes(); |
1294
|
|
|
|
|
|
|
} |
1295
|
|
|
|
|
|
|
|
1296
|
|
|
|
|
|
|
sub set_digest { |
1297
|
|
|
|
|
|
|
## |
1298
|
|
|
|
|
|
|
# Set key digest function (default is MD5) |
1299
|
|
|
|
|
|
|
## |
1300
|
1
|
|
|
1
|
1
|
2
|
my ($digest_func, $hash_size) = @_; |
1301
|
|
|
|
|
|
|
|
1302
|
1
|
50
|
|
|
|
4
|
$DIGEST_FUNC = $digest_func ? $digest_func : \&Digest::MD5::md5; |
1303
|
1
|
50
|
|
|
|
3
|
$HASH_SIZE = $hash_size ? $hash_size : 16; |
1304
|
|
|
|
|
|
|
|
1305
|
1
|
|
|
|
|
4
|
_precalc_sizes(); |
1306
|
|
|
|
|
|
|
} |
1307
|
|
|
|
|
|
|
|
1308
|
|
|
|
|
|
|
sub _is_writable { |
1309
|
0
|
|
|
0
|
|
0
|
my $fh = shift; |
1310
|
0
|
|
|
|
|
0
|
(O_WRONLY | O_RDWR) & fcntl( $fh, F_GETFL, my $slush = 0); |
1311
|
|
|
|
|
|
|
} |
1312
|
|
|
|
|
|
|
|
1313
|
|
|
|
|
|
|
#sub _is_readable { |
1314
|
|
|
|
|
|
|
# my $fh = shift; |
1315
|
|
|
|
|
|
|
# (O_RDONLY | O_RDWR) & fcntl( $fh, F_GETFL, my $slush = 0); |
1316
|
|
|
|
|
|
|
#} |
1317
|
|
|
|
|
|
|
|
1318
|
|
|
|
|
|
|
## |
1319
|
|
|
|
|
|
|
# tie() methods (hashes and arrays) |
1320
|
|
|
|
|
|
|
## |
1321
|
|
|
|
|
|
|
|
1322
|
|
|
|
|
|
|
sub STORE { |
1323
|
|
|
|
|
|
|
## |
1324
|
|
|
|
|
|
|
# Store single hash key/value or array element in database. |
1325
|
|
|
|
|
|
|
## |
1326
|
0
|
|
|
0
|
|
0
|
my $self = $_[0]->_get_self; |
1327
|
0
|
|
|
|
|
0
|
my $key = $_[1]; |
1328
|
|
|
|
|
|
|
|
1329
|
0
|
|
|
|
|
0
|
local($/,$\); |
1330
|
|
|
|
|
|
|
|
1331
|
|
|
|
|
|
|
# User may be storing a hash, in which case we do not want it run |
1332
|
|
|
|
|
|
|
# through the filtering system |
1333
|
|
|
|
|
|
|
my $value = ($self->_root->{filter_store_value} && !ref($_[2])) |
1334
|
0
|
0
|
0
|
|
|
0
|
? $self->_root->{filter_store_value}->($_[2]) |
1335
|
|
|
|
|
|
|
: $_[2]; |
1336
|
|
|
|
|
|
|
|
1337
|
0
|
|
|
|
|
0
|
my $md5 = $DIGEST_FUNC->($key); |
1338
|
|
|
|
|
|
|
|
1339
|
|
|
|
|
|
|
## |
1340
|
|
|
|
|
|
|
# Make sure file is open |
1341
|
|
|
|
|
|
|
## |
1342
|
0
|
0
|
0
|
|
|
0
|
if (!defined($self->_fh) && !$self->_open()) { |
1343
|
0
|
|
|
|
|
0
|
return; |
1344
|
|
|
|
|
|
|
} |
1345
|
|
|
|
|
|
|
|
1346
|
0
|
0
|
0
|
|
|
0
|
if ( $^O ne 'MSWin32' && !_is_writable( $self->_fh ) ) { |
1347
|
0
|
|
|
|
|
0
|
$self->_throw_error( 'Cannot write to a readonly filehandle' ); |
1348
|
|
|
|
|
|
|
} |
1349
|
|
|
|
|
|
|
|
1350
|
|
|
|
|
|
|
## |
1351
|
|
|
|
|
|
|
# Request exclusive lock for writing |
1352
|
|
|
|
|
|
|
## |
1353
|
0
|
|
|
|
|
0
|
$self->lock( LOCK_EX ); |
1354
|
|
|
|
|
|
|
|
1355
|
0
|
|
|
|
|
0
|
my $fh = $self->_fh; |
1356
|
|
|
|
|
|
|
|
1357
|
|
|
|
|
|
|
## |
1358
|
|
|
|
|
|
|
# Locate offset for bucket list using digest index system |
1359
|
|
|
|
|
|
|
## |
1360
|
0
|
|
|
|
|
0
|
my $tag = $self->_load_tag($self->_base_offset); |
1361
|
0
|
0
|
|
|
|
0
|
if (!$tag) { |
1362
|
0
|
|
|
|
|
0
|
$tag = $self->_create_tag($self->_base_offset, SIG_INDEX, chr(0) x $INDEX_SIZE); |
1363
|
|
|
|
|
|
|
} |
1364
|
|
|
|
|
|
|
|
1365
|
0
|
|
|
|
|
0
|
my $ch = 0; |
1366
|
0
|
|
|
|
|
0
|
while ($tag->{signature} ne SIG_BLIST) { |
1367
|
0
|
|
|
|
|
0
|
my $num = ord(substr($md5, $ch, 1)); |
1368
|
|
|
|
|
|
|
|
1369
|
0
|
|
|
|
|
0
|
my $ref_loc = $tag->{offset} + ($num * $LONG_SIZE); |
1370
|
0
|
|
|
|
|
0
|
my $new_tag = $self->_index_lookup($tag, $num); |
1371
|
|
|
|
|
|
|
|
1372
|
0
|
0
|
|
|
|
0
|
if (!$new_tag) { |
1373
|
0
|
|
|
|
|
0
|
seek($fh, $ref_loc + $self->_root->{file_offset}, SEEK_SET); |
1374
|
0
|
|
|
|
|
0
|
print( $fh pack($LONG_PACK, $self->_root->{end}) ); |
1375
|
|
|
|
|
|
|
|
1376
|
0
|
|
|
|
|
0
|
$tag = $self->_create_tag($self->_root->{end}, SIG_BLIST, chr(0) x $BUCKET_LIST_SIZE); |
1377
|
|
|
|
|
|
|
|
1378
|
0
|
|
|
|
|
0
|
$tag->{ref_loc} = $ref_loc; |
1379
|
0
|
|
|
|
|
0
|
$tag->{ch} = $ch; |
1380
|
|
|
|
|
|
|
|
1381
|
0
|
|
|
|
|
0
|
last; |
1382
|
|
|
|
|
|
|
} |
1383
|
|
|
|
|
|
|
else { |
1384
|
0
|
|
|
|
|
0
|
$tag = $new_tag; |
1385
|
|
|
|
|
|
|
|
1386
|
0
|
|
|
|
|
0
|
$tag->{ref_loc} = $ref_loc; |
1387
|
0
|
|
|
|
|
0
|
$tag->{ch} = $ch; |
1388
|
|
|
|
|
|
|
} |
1389
|
0
|
|
|
|
|
0
|
$ch++; |
1390
|
|
|
|
|
|
|
} |
1391
|
|
|
|
|
|
|
|
1392
|
|
|
|
|
|
|
## |
1393
|
|
|
|
|
|
|
# Add key/value to bucket list |
1394
|
|
|
|
|
|
|
## |
1395
|
0
|
|
|
|
|
0
|
my $result = $self->_add_bucket( $tag, $md5, $key, $value ); |
1396
|
|
|
|
|
|
|
|
1397
|
0
|
|
|
|
|
0
|
$self->unlock(); |
1398
|
|
|
|
|
|
|
|
1399
|
0
|
|
|
|
|
0
|
return $result; |
1400
|
|
|
|
|
|
|
} |
1401
|
|
|
|
|
|
|
|
1402
|
|
|
|
|
|
|
sub FETCH { |
1403
|
|
|
|
|
|
|
## |
1404
|
|
|
|
|
|
|
# Fetch single value or element given plain key or array index |
1405
|
|
|
|
|
|
|
## |
1406
|
15
|
|
|
15
|
|
22
|
my $self = shift->_get_self; |
1407
|
15
|
|
|
|
|
10
|
my $key = shift; |
1408
|
|
|
|
|
|
|
|
1409
|
|
|
|
|
|
|
## |
1410
|
|
|
|
|
|
|
# Make sure file is open |
1411
|
|
|
|
|
|
|
## |
1412
|
15
|
50
|
|
|
|
15
|
if (!defined($self->_fh)) { $self->_open(); } |
|
0
|
|
|
|
|
0
|
|
1413
|
|
|
|
|
|
|
|
1414
|
15
|
|
|
|
|
36
|
my $md5 = $DIGEST_FUNC->($key); |
1415
|
|
|
|
|
|
|
|
1416
|
|
|
|
|
|
|
## |
1417
|
|
|
|
|
|
|
# Request shared lock for reading |
1418
|
|
|
|
|
|
|
## |
1419
|
15
|
|
|
|
|
23
|
$self->lock( LOCK_SH ); |
1420
|
|
|
|
|
|
|
|
1421
|
15
|
|
|
|
|
18
|
my $tag = $self->_find_bucket_list( $md5 ); |
1422
|
15
|
50
|
|
|
|
20
|
if (!$tag) { |
1423
|
0
|
|
|
|
|
0
|
$self->unlock(); |
1424
|
0
|
|
|
|
|
0
|
return; |
1425
|
|
|
|
|
|
|
} |
1426
|
|
|
|
|
|
|
|
1427
|
|
|
|
|
|
|
## |
1428
|
|
|
|
|
|
|
# Get value from bucket list |
1429
|
|
|
|
|
|
|
## |
1430
|
15
|
|
|
|
|
24
|
my $result = $self->_get_bucket_value( $tag, $md5 ); |
1431
|
|
|
|
|
|
|
|
1432
|
15
|
|
|
|
|
22
|
$self->unlock(); |
1433
|
|
|
|
|
|
|
|
1434
|
|
|
|
|
|
|
#XXX What is ref() checking here? |
1435
|
|
|
|
|
|
|
#YYY Filters only apply on scalar values, so the ref check is making |
1436
|
|
|
|
|
|
|
#YYY sure the fetched bucket is a scalar, not a child hash or array. |
1437
|
|
|
|
|
|
|
return ($result && !ref($result) && $self->_root->{filter_fetch_value}) |
1438
|
15
|
50
|
66
|
|
|
93
|
? $self->_root->{filter_fetch_value}->($result) |
1439
|
|
|
|
|
|
|
: $result; |
1440
|
|
|
|
|
|
|
} |
1441
|
|
|
|
|
|
|
|
1442
|
|
|
|
|
|
|
sub DELETE { |
1443
|
|
|
|
|
|
|
## |
1444
|
|
|
|
|
|
|
# Delete single key/value pair or element given plain key or array index |
1445
|
|
|
|
|
|
|
## |
1446
|
0
|
|
|
0
|
|
0
|
my $self = $_[0]->_get_self; |
1447
|
0
|
|
|
|
|
0
|
my $key = $_[1]; |
1448
|
|
|
|
|
|
|
|
1449
|
0
|
|
|
|
|
0
|
my $md5 = $DIGEST_FUNC->($key); |
1450
|
|
|
|
|
|
|
|
1451
|
|
|
|
|
|
|
## |
1452
|
|
|
|
|
|
|
# Make sure file is open |
1453
|
|
|
|
|
|
|
## |
1454
|
0
|
0
|
|
|
|
0
|
if (!defined($self->_fh)) { $self->_open(); } |
|
0
|
|
|
|
|
0
|
|
1455
|
|
|
|
|
|
|
|
1456
|
|
|
|
|
|
|
## |
1457
|
|
|
|
|
|
|
# Request exclusive lock for writing |
1458
|
|
|
|
|
|
|
## |
1459
|
0
|
|
|
|
|
0
|
$self->lock( LOCK_EX ); |
1460
|
|
|
|
|
|
|
|
1461
|
0
|
|
|
|
|
0
|
my $tag = $self->_find_bucket_list( $md5 ); |
1462
|
0
|
0
|
|
|
|
0
|
if (!$tag) { |
1463
|
0
|
|
|
|
|
0
|
$self->unlock(); |
1464
|
0
|
|
|
|
|
0
|
return; |
1465
|
|
|
|
|
|
|
} |
1466
|
|
|
|
|
|
|
|
1467
|
|
|
|
|
|
|
## |
1468
|
|
|
|
|
|
|
# Delete bucket |
1469
|
|
|
|
|
|
|
## |
1470
|
0
|
|
|
|
|
0
|
my $value = $self->_get_bucket_value( $tag, $md5 ); |
1471
|
0
|
0
|
0
|
|
|
0
|
if ($value && !ref($value) && $self->_root->{filter_fetch_value}) { |
|
|
|
0
|
|
|
|
|
1472
|
0
|
|
|
|
|
0
|
$value = $self->_root->{filter_fetch_value}->($value); |
1473
|
|
|
|
|
|
|
} |
1474
|
|
|
|
|
|
|
|
1475
|
0
|
|
|
|
|
0
|
my $result = $self->_delete_bucket( $tag, $md5 ); |
1476
|
|
|
|
|
|
|
|
1477
|
|
|
|
|
|
|
## |
1478
|
|
|
|
|
|
|
# If this object is an array and the key deleted was on the end of the stack, |
1479
|
|
|
|
|
|
|
# decrement the length variable. |
1480
|
|
|
|
|
|
|
## |
1481
|
|
|
|
|
|
|
|
1482
|
0
|
|
|
|
|
0
|
$self->unlock(); |
1483
|
|
|
|
|
|
|
|
1484
|
0
|
|
|
|
|
0
|
return $value; |
1485
|
|
|
|
|
|
|
} |
1486
|
|
|
|
|
|
|
|
1487
|
|
|
|
|
|
|
sub EXISTS { |
1488
|
|
|
|
|
|
|
## |
1489
|
|
|
|
|
|
|
# Check if a single key or element exists given plain key or array index |
1490
|
|
|
|
|
|
|
## |
1491
|
5
|
|
|
5
|
|
8
|
my $self = $_[0]->_get_self; |
1492
|
5
|
|
|
|
|
3
|
my $key = $_[1]; |
1493
|
|
|
|
|
|
|
|
1494
|
5
|
|
|
|
|
14
|
my $md5 = $DIGEST_FUNC->($key); |
1495
|
|
|
|
|
|
|
|
1496
|
|
|
|
|
|
|
## |
1497
|
|
|
|
|
|
|
# Make sure file is open |
1498
|
|
|
|
|
|
|
## |
1499
|
5
|
50
|
|
|
|
6
|
if (!defined($self->_fh)) { $self->_open(); } |
|
0
|
|
|
|
|
0
|
|
1500
|
|
|
|
|
|
|
|
1501
|
|
|
|
|
|
|
## |
1502
|
|
|
|
|
|
|
# Request shared lock for reading |
1503
|
|
|
|
|
|
|
## |
1504
|
5
|
|
|
|
|
5
|
$self->lock( LOCK_SH ); |
1505
|
|
|
|
|
|
|
|
1506
|
5
|
|
|
|
|
6
|
my $tag = $self->_find_bucket_list( $md5 ); |
1507
|
|
|
|
|
|
|
|
1508
|
|
|
|
|
|
|
## |
1509
|
|
|
|
|
|
|
# For some reason, the built-in exists() function returns '' for false |
1510
|
|
|
|
|
|
|
## |
1511
|
5
|
50
|
|
|
|
7
|
if (!$tag) { |
1512
|
0
|
|
|
|
|
0
|
$self->unlock(); |
1513
|
0
|
|
|
|
|
0
|
return ''; |
1514
|
|
|
|
|
|
|
} |
1515
|
|
|
|
|
|
|
|
1516
|
|
|
|
|
|
|
## |
1517
|
|
|
|
|
|
|
# Check if bucket exists and return 1 or '' |
1518
|
|
|
|
|
|
|
## |
1519
|
5
|
|
100
|
|
|
11
|
my $result = $self->_bucket_exists( $tag, $md5 ) || ''; |
1520
|
|
|
|
|
|
|
|
1521
|
5
|
|
|
|
|
8
|
$self->unlock(); |
1522
|
|
|
|
|
|
|
|
1523
|
5
|
|
|
|
|
12
|
return $result; |
1524
|
|
|
|
|
|
|
} |
1525
|
|
|
|
|
|
|
|
1526
|
|
|
|
|
|
|
sub CLEAR { |
1527
|
|
|
|
|
|
|
## |
1528
|
|
|
|
|
|
|
# Clear all keys from hash, or all elements from array. |
1529
|
|
|
|
|
|
|
## |
1530
|
0
|
|
|
0
|
|
0
|
my $self = $_[0]->_get_self; |
1531
|
|
|
|
|
|
|
|
1532
|
|
|
|
|
|
|
## |
1533
|
|
|
|
|
|
|
# Make sure file is open |
1534
|
|
|
|
|
|
|
## |
1535
|
0
|
0
|
|
|
|
0
|
if (!defined($self->_fh)) { $self->_open(); } |
|
0
|
|
|
|
|
0
|
|
1536
|
|
|
|
|
|
|
|
1537
|
|
|
|
|
|
|
## |
1538
|
|
|
|
|
|
|
# Request exclusive lock for writing |
1539
|
|
|
|
|
|
|
## |
1540
|
0
|
|
|
|
|
0
|
$self->lock( LOCK_EX ); |
1541
|
|
|
|
|
|
|
|
1542
|
0
|
|
|
|
|
0
|
my $fh = $self->_fh; |
1543
|
|
|
|
|
|
|
|
1544
|
0
|
|
|
|
|
0
|
seek($fh, $self->_base_offset + $self->_root->{file_offset}, SEEK_SET); |
1545
|
0
|
0
|
|
|
|
0
|
if (eof $fh) { |
1546
|
0
|
|
|
|
|
0
|
$self->unlock(); |
1547
|
0
|
|
|
|
|
0
|
return; |
1548
|
|
|
|
|
|
|
} |
1549
|
|
|
|
|
|
|
|
1550
|
0
|
|
|
|
|
0
|
$self->_create_tag($self->_base_offset, $self->_type, chr(0) x $INDEX_SIZE); |
1551
|
|
|
|
|
|
|
|
1552
|
0
|
|
|
|
|
0
|
$self->unlock(); |
1553
|
|
|
|
|
|
|
|
1554
|
0
|
|
|
|
|
0
|
return 1; |
1555
|
|
|
|
|
|
|
} |
1556
|
|
|
|
|
|
|
|
1557
|
|
|
|
|
|
|
## |
1558
|
|
|
|
|
|
|
# Public method aliases |
1559
|
|
|
|
|
|
|
## |
1560
|
0
|
|
|
0
|
1
|
0
|
sub put { (shift)->STORE( @_ ) } |
1561
|
0
|
|
|
0
|
1
|
0
|
sub store { (shift)->STORE( @_ ) } |
1562
|
0
|
|
|
0
|
1
|
0
|
sub get { (shift)->FETCH( @_ ) } |
1563
|
0
|
|
|
0
|
1
|
0
|
sub fetch { (shift)->FETCH( @_ ) } |
1564
|
0
|
|
|
0
|
1
|
0
|
sub delete { (shift)->DELETE( @_ ) } |
1565
|
0
|
|
|
0
|
1
|
0
|
sub exists { (shift)->EXISTS( @_ ) } |
1566
|
0
|
|
|
0
|
1
|
0
|
sub clear { (shift)->CLEAR( @_ ) } |
1567
|
|
|
|
|
|
|
|
1568
|
|
|
|
|
|
|
package Number::Phone::UK::DBM::Deep::_::Root; |
1569
|
|
|
|
|
|
|
|
1570
|
|
|
|
|
|
|
sub new { |
1571
|
1
|
|
|
1
|
|
1
|
my $class = shift; |
1572
|
1
|
|
|
|
|
1
|
my ($args) = @_; |
1573
|
|
|
|
|
|
|
|
1574
|
1
|
|
|
|
|
6
|
my $self = bless { |
1575
|
|
|
|
|
|
|
file => undef, |
1576
|
|
|
|
|
|
|
fh => undef, |
1577
|
|
|
|
|
|
|
file_offset => 0, |
1578
|
|
|
|
|
|
|
end => 0, |
1579
|
|
|
|
|
|
|
autoflush => undef, |
1580
|
|
|
|
|
|
|
locking => undef, |
1581
|
|
|
|
|
|
|
debug => undef, |
1582
|
|
|
|
|
|
|
filter_store_key => undef, |
1583
|
|
|
|
|
|
|
filter_store_value => undef, |
1584
|
|
|
|
|
|
|
filter_fetch_key => undef, |
1585
|
|
|
|
|
|
|
filter_fetch_value => undef, |
1586
|
|
|
|
|
|
|
autobless => undef, |
1587
|
|
|
|
|
|
|
locked => 0, |
1588
|
|
|
|
|
|
|
%$args, |
1589
|
|
|
|
|
|
|
}, $class; |
1590
|
|
|
|
|
|
|
|
1591
|
1
|
50
|
33
|
|
|
10
|
if ( $self->{fh} && !$self->{file_offset} ) { |
1592
|
1
|
|
|
|
|
2
|
$self->{file_offset} = tell( $self->{fh} ); |
1593
|
|
|
|
|
|
|
} |
1594
|
|
|
|
|
|
|
|
1595
|
1
|
|
|
|
|
2
|
return $self; |
1596
|
|
|
|
|
|
|
} |
1597
|
|
|
|
|
|
|
|
1598
|
|
|
|
|
|
|
sub DESTROY { |
1599
|
0
|
|
|
0
|
|
|
my $self = shift; |
1600
|
0
|
0
|
|
|
|
|
return unless $self; |
1601
|
|
|
|
|
|
|
|
1602
|
0
|
0
|
|
|
|
|
close $self->{fh} if $self->{fh}; |
1603
|
|
|
|
|
|
|
|
1604
|
0
|
|
|
|
|
|
return; |
1605
|
|
|
|
|
|
|
} |
1606
|
|
|
|
|
|
|
|
1607
|
|
|
|
|
|
|
1; |
1608
|
|
|
|
|
|
|
|
1609
|
|
|
|
|
|
|
__END__ |