line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package MMapDB; |
2
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
47547
|
use 5.008008; |
|
2
|
|
|
|
|
8
|
|
|
2
|
|
|
|
|
78
|
|
4
|
2
|
|
|
2
|
|
10
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
61
|
|
5
|
2
|
|
|
2
|
|
7
|
use warnings; |
|
2
|
|
|
|
|
7
|
|
|
2
|
|
|
|
|
74
|
|
6
|
2
|
|
|
2
|
|
7
|
no warnings qw/uninitialized/; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
65
|
|
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
9
|
|
|
|
|
|
|
# keep this in mind |
10
|
2
|
|
|
2
|
|
1708
|
use integer; |
|
2
|
|
|
|
|
17
|
|
|
2
|
|
|
|
|
9
|
|
11
|
|
|
|
|
|
|
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
12
|
|
|
|
|
|
|
|
13
|
2
|
|
|
2
|
|
55
|
use Fcntl qw/:seek :flock/; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
278
|
|
14
|
2
|
|
|
2
|
|
10
|
use File::Spec; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
43
|
|
15
|
2
|
|
|
2
|
|
1601
|
use File::Map qw/map_handle protect/; |
|
2
|
|
|
|
|
15845
|
|
|
2
|
|
|
|
|
11
|
|
16
|
2
|
|
|
2
|
|
265
|
use Exporter qw/import/; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
53
|
|
17
|
2
|
|
|
2
|
|
433121
|
use Encode (); |
|
2
|
|
|
|
|
57985
|
|
|
2
|
|
|
|
|
388
|
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
{ # limit visibility of "our"/"my" variables |
20
|
|
|
|
|
|
|
our $VERSION = '0.15'; |
21
|
|
|
|
|
|
|
our %EXPORT_TAGS= |
22
|
|
|
|
|
|
|
( |
23
|
|
|
|
|
|
|
mode =>[qw/DATAMODE_NORMAL DATAMODE_SIMPLE/], |
24
|
|
|
|
|
|
|
error=>[qw/E_READONLY E_TWICE E_TRANSACTION E_FULL E_DUPLICATE |
25
|
|
|
|
|
|
|
E_OPEN E_READ E_WRITE E_CLOSE E_RENAME E_TRUNCATE E_LOCK |
26
|
|
|
|
|
|
|
E_RANGE E_NOT_IMPLEMENTED/], |
27
|
|
|
|
|
|
|
); |
28
|
|
|
|
|
|
|
my %seen; |
29
|
|
|
|
|
|
|
undef @seen{map {@$_} values %EXPORT_TAGS}; |
30
|
|
|
|
|
|
|
our @EXPORT_OK=keys %seen; |
31
|
|
|
|
|
|
|
$EXPORT_TAGS{all}=\@EXPORT_OK; |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
require XSLoader; |
34
|
|
|
|
|
|
|
XSLoader::load('MMapDB', $VERSION); |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
our @attributes; |
37
|
|
|
|
|
|
|
BEGIN { |
38
|
|
|
|
|
|
|
# define attributes and implement accessor methods |
39
|
|
|
|
|
|
|
# !! keep in sync with MMapDB.xs !! |
40
|
2
|
|
|
2
|
|
12
|
@attributes=(qw/filename readonly intfmt _data _intsize _stringfmt |
41
|
|
|
|
|
|
|
_stringtbl mainidx _ididx main_index id_index |
42
|
|
|
|
|
|
|
_nextid _idmap _tmpfh _tmpname _stringfh _stringmap |
43
|
|
|
|
|
|
|
_strpos lockfile flags dbformat_in dbformat_out |
44
|
|
|
|
|
|
|
_stringfmt_out stringmap_prealloc _stringmap_end |
45
|
|
|
|
|
|
|
index_prealloc _index_end _tmpmap |
46
|
|
|
|
|
|
|
/); |
47
|
2
|
|
|
|
|
12
|
for( my $i=0; $i<@attributes; $i++ ) { |
48
|
56
|
|
|
|
|
62
|
my $method_num=$i; |
49
|
|
|
|
|
|
|
## no critic |
50
|
2
|
|
|
2
|
|
19
|
no strict 'refs'; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
145
|
|
51
|
56
|
|
|
50736
|
|
706
|
*{__PACKAGE__.'::'.$attributes[$method_num]}= |
|
50736
|
|
|
|
|
112467
|
|
52
|
56
|
|
|
|
|
276
|
sub : lvalue {$_[0]->[$method_num]}; |
53
|
|
|
|
|
|
|
## use critic |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
} |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
my @dbformats=qw/MMDB MMDC/; |
59
|
|
|
|
|
|
|
my %dbformats=do { my $i=0; map {($_=>$i++)} @dbformats }; |
60
|
|
|
|
|
|
|
|
61
|
2
|
|
|
2
|
|
19603
|
BEGIN { |
62
|
|
|
|
|
|
|
use constant { |
63
|
2
|
|
|
|
|
1087
|
FORMATVERSION => 0, # magic number position (in bytes) |
64
|
|
|
|
|
|
|
INTFMT => 4, # INTFMT byte position (in bytes) |
65
|
|
|
|
|
|
|
BASEOFFSET => 8, |
66
|
|
|
|
|
|
|
MAINIDX => 0, # (in words (units of _intsize bytes)) |
67
|
|
|
|
|
|
|
IDIDX => 1, # (in words) |
68
|
|
|
|
|
|
|
NEXTID => 2, # (in words) |
69
|
|
|
|
|
|
|
STRINGTBL => 3, # (in words) |
70
|
|
|
|
|
|
|
DATASTART => 4, # (in words) |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
DBFMT0 => 0, # MMDB format |
73
|
|
|
|
|
|
|
DBFMT1 => 1, # MMDC format with utf8 support |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
# iterator questions |
76
|
|
|
|
|
|
|
IT_NTH =>0, # reposition iterator |
77
|
|
|
|
|
|
|
IT_CUR =>1, # what is the current index |
78
|
|
|
|
|
|
|
IT_NELEM =>2, # how many elements does it iterate over |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
DATAMODE_NORMAL=>0, |
81
|
|
|
|
|
|
|
DATAMODE_SIMPLE=>1, |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
E_READONLY => \'database is read-only', |
84
|
|
|
|
|
|
|
E_TWICE => \'can\'t insert the same ID twice', |
85
|
|
|
|
|
|
|
E_TRANSACTION => \'there is already an active transaction', |
86
|
|
|
|
|
|
|
E_FULL => \'can\'t allocate ID', |
87
|
|
|
|
|
|
|
E_DUPLICATE => \'data records cannot be mixed up with subindices', |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
E_OPEN => \'can\'t open file', |
90
|
|
|
|
|
|
|
E_READ => \'can\'t read from file', |
91
|
|
|
|
|
|
|
E_WRITE => \'can\'t write to file', |
92
|
|
|
|
|
|
|
E_CLOSE => \'file could not be closed', |
93
|
|
|
|
|
|
|
E_RENAME => \'can\'t rename file', |
94
|
|
|
|
|
|
|
E_SEEK => \'can\'t move file pointer', |
95
|
|
|
|
|
|
|
E_TRUNCATE => \'can\'t truncate file', |
96
|
|
|
|
|
|
|
E_LOCK => \'can\'t (un)lock lockfile', |
97
|
|
|
|
|
|
|
E_RANGE => \'attempt move iterator out of its range', |
98
|
|
|
|
|
|
|
E_NOT_IMPLEMENTED => \'function not implemented', |
99
|
2
|
|
|
2
|
|
12
|
}; |
|
2
|
|
|
|
|
14
|
|
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
#sub D { |
103
|
|
|
|
|
|
|
# use Data::Dumper; |
104
|
|
|
|
|
|
|
# local $Data::Dumper::Useqq=1; |
105
|
|
|
|
|
|
|
# warn Dumper @_; |
106
|
|
|
|
|
|
|
#} |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
sub _putdata { |
109
|
3343
|
|
|
3343
|
|
8993
|
my ($I, $pos, $fmt, @param)=@_; |
110
|
|
|
|
|
|
|
|
111
|
3343
|
|
|
|
|
6628
|
my $pstr=pack $fmt, @param; |
112
|
3343
|
|
|
|
|
6490
|
my $map=$I->_tmpmap; |
113
|
3343
|
100
|
|
|
|
7400
|
if( $pos+length($pstr)>length $$map ) { |
114
|
16
|
|
|
|
|
48
|
my $prea=$I->index_prealloc; |
115
|
16
|
|
|
|
|
43
|
my $need=$prea*(($pos+length($pstr)+$prea-1)/$prea); |
116
|
16
|
|
|
|
|
32
|
eval { |
117
|
16
|
|
|
|
|
38
|
my $fh=$I->_tmpfh; |
118
|
16
|
50
|
33
|
|
|
1161
|
sysseek $fh, $need, SEEK_SET and |
119
|
|
|
|
|
|
|
truncate $fh, $need and |
120
|
|
|
|
|
|
|
map_handle $$map, $fh, '+>', 0, $need; |
121
|
|
|
|
|
|
|
}; |
122
|
16
|
50
|
|
|
|
4028
|
$I->_e(E_OPEN) if $@; |
123
|
|
|
|
|
|
|
} |
124
|
3343
|
|
|
|
|
7464
|
substr $$map, $pos, length($pstr), $pstr; |
125
|
3343
|
|
|
|
|
9511
|
return length($pstr); |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
sub set_intfmt { |
129
|
7
|
|
|
7
|
1
|
18
|
my ($I, $fmt)=@_; |
130
|
|
|
|
|
|
|
|
131
|
7
|
50
|
|
|
|
19
|
$fmt='N' unless $fmt; |
132
|
|
|
|
|
|
|
|
133
|
7
|
|
|
|
|
11
|
my %allowed; undef @allowed{qw/L N J Q/}; |
|
7
|
|
|
|
|
28
|
|
134
|
7
|
50
|
|
|
|
24
|
return unless exists $allowed{$fmt}; |
135
|
|
|
|
|
|
|
|
136
|
7
|
|
|
|
|
22
|
$I->intfmt=$fmt; |
137
|
7
|
|
|
|
|
33
|
$I->_intsize=length pack($fmt, 0); |
138
|
|
|
|
|
|
|
|
139
|
7
|
100
|
|
|
|
18
|
if( $I->dbformat_in>DBFMT0 ) { |
140
|
|
|
|
|
|
|
# new format with utf8 support |
141
|
6
|
|
|
|
|
13
|
$I->_stringfmt=$I->intfmt.'/a*C x!'.$I->_intsize; |
142
|
|
|
|
|
|
|
} else { |
143
|
1
|
|
|
|
|
3
|
$I->_stringfmt=$I->intfmt.'/a* x!'.$I->_intsize; |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
7
|
100
|
|
|
|
18
|
if( $I->dbformat_out>DBFMT0 ) { |
147
|
|
|
|
|
|
|
# new format with utf8 support |
148
|
6
|
|
|
|
|
15
|
$I->_stringfmt_out=$I->intfmt.'/a*C x!'.$I->_intsize; |
149
|
|
|
|
|
|
|
} else { |
150
|
1
|
|
|
|
|
3
|
$I->_stringfmt_out=$I->intfmt.'/a* x!'.$I->_intsize; |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
|
153
|
7
|
|
|
|
|
22
|
return 1; |
154
|
|
|
|
|
|
|
} |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
sub new { |
157
|
1
|
|
|
1
|
1
|
233
|
my ($parent, @param)=@_; |
158
|
1
|
|
|
|
|
2
|
my $I; |
159
|
|
|
|
|
|
|
|
160
|
1
|
50
|
|
|
|
4
|
if (ref $parent) { |
161
|
0
|
|
|
|
|
0
|
$I=bless [@$parent]=>ref($parent); |
162
|
0
|
|
|
|
|
0
|
for my $k (qw/_nextid _idmap _tmpfh _tmpname _stringfh _stringmap |
163
|
|
|
|
|
|
|
_strpos main_index id_index/) { |
164
|
0
|
|
|
|
|
0
|
undef $I->$k; |
165
|
|
|
|
|
|
|
} |
166
|
0
|
0
|
|
|
|
0
|
if( defined $I->_data ) { |
167
|
|
|
|
|
|
|
# parameters: PARENT POS DATAMODE |
168
|
0
|
|
|
|
|
0
|
tie %{$I->main_index=+{}}, 'MMapDB::Index', $I, $I->mainidx, 0; |
|
0
|
|
|
|
|
0
|
|
169
|
0
|
|
|
|
|
0
|
tie %{$I->id_index=+{}}, 'MMapDB::IDIndex', $I, undef, 0; |
|
0
|
|
|
|
|
0
|
|
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
} else { |
172
|
1
|
|
|
|
|
2
|
$I=bless []=>$parent; |
173
|
1
|
|
|
|
|
4
|
$I->set_intfmt('N'); |
174
|
1
|
|
|
|
|
3
|
$I->flags=0; |
175
|
1
|
|
|
|
|
5
|
$I->dbformat_in=$#dbformats; # use the newest by default |
176
|
1
|
|
|
|
|
3
|
$I->dbformat_out=$#dbformats; # use the newest by default |
177
|
|
|
|
|
|
|
} |
178
|
1
|
|
|
|
|
3
|
$I->stringmap_prealloc=1024*1024*10; # 10MB |
179
|
1
|
|
|
|
|
4
|
$I->index_prealloc=1024*1024*10; # 10MB |
180
|
|
|
|
|
|
|
|
181
|
1
|
50
|
|
|
|
4
|
if( @param==1 ) { |
182
|
0
|
|
|
|
|
0
|
$I->filename=$param[0]; |
183
|
|
|
|
|
|
|
} else { |
184
|
1
|
|
|
|
|
6
|
while( my ($k, $v)=splice @param, 0, 2 ) { |
185
|
1
|
50
|
|
|
|
17
|
$I->$k=$v if $k=$I->can($k); |
186
|
|
|
|
|
|
|
} |
187
|
1
|
50
|
|
|
|
3
|
$I->set_intfmt($I->intfmt) unless $I->intfmt eq 'N'; |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
|
190
|
1
|
|
|
|
|
3
|
return $I; |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
sub is_valid { |
194
|
0
|
|
|
0
|
1
|
0
|
my ($I)=@_; |
195
|
|
|
|
|
|
|
|
196
|
0
|
0
|
|
|
|
0
|
return unless $I->_data; |
197
|
|
|
|
|
|
|
# the INTFMT field serves 2 ways: |
198
|
|
|
|
|
|
|
# 1) it specifies the used integer format |
199
|
|
|
|
|
|
|
# 2) it works as VALID flag. commit() write a NULL byte here |
200
|
|
|
|
|
|
|
# to mark the old file as invalid. |
201
|
|
|
|
|
|
|
# we must reconnect if our cached fmt does not match. |
202
|
0
|
|
|
|
|
0
|
return substr( ${$I->_data}, INTFMT, 1 ) eq $I->intfmt; |
|
0
|
|
|
|
|
0
|
|
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
sub start { |
206
|
4
|
|
|
4
|
1
|
9
|
my ($I)=@_; |
207
|
|
|
|
|
|
|
|
208
|
4
|
50
|
|
|
|
13
|
$I->_e(E_TRANSACTION) if defined $I->_tmpfh; |
209
|
|
|
|
|
|
|
|
210
|
4
|
|
|
|
|
6
|
my $retry=5; |
211
|
4
|
50
|
|
|
|
14
|
RETRY: { |
212
|
4
|
|
|
|
|
5
|
return unless $retry--; |
213
|
2
|
|
|
|
|
5
|
$I->stop if (defined $I->_data and |
214
|
4
|
100
|
66
|
|
|
9
|
substr( ${$I->_data}, INTFMT, 1 ) ne $I->intfmt); |
215
|
|
|
|
|
|
|
|
216
|
4
|
50
|
|
|
|
9
|
unless( $I->_data ) { |
217
|
4
|
|
|
|
|
8
|
my ($dummy, $fmt); |
218
|
0
|
|
|
|
|
0
|
my $fh; |
219
|
4
|
50
|
|
|
|
11
|
if( $I->readonly ) { |
220
|
0
|
0
|
|
|
|
0
|
open $fh, '<', $I->filename or return; |
221
|
|
|
|
|
|
|
} else { |
222
|
4
|
100
|
|
|
|
21
|
open $fh, '+<', $I->filename or return; |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
# Map the main data always read-only. If we are in writable mode |
226
|
|
|
|
|
|
|
# map only the header page again writable. |
227
|
3
|
|
|
|
|
9
|
eval { |
228
|
3
|
|
|
|
|
18
|
map_handle $dummy, $fh, '<'; |
229
|
|
|
|
|
|
|
}; |
230
|
3
|
|
|
|
|
9821
|
close $fh; |
231
|
3
|
50
|
|
|
|
12
|
return if $@; # perhaps throw something here |
232
|
3
|
50
|
|
|
|
11
|
return unless length $dummy; |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
# check magic number |
235
|
3
|
50
|
|
|
|
32
|
return unless exists $dbformats{substr($dummy, FORMATVERSION, 4)}; |
236
|
3
|
|
|
|
|
27
|
$I->dbformat_out=$I->dbformat_in= |
237
|
|
|
|
|
|
|
$dbformats{substr($dummy, FORMATVERSION, 4)}; |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# read integer format |
240
|
3
|
|
|
|
|
20
|
$fmt=unpack 'x4a', $dummy; |
241
|
3
|
50
|
|
|
|
12
|
if( $fmt eq "\0" ) { |
242
|
0
|
|
|
|
|
0
|
select undef, undef, undef, 0.1; |
243
|
0
|
|
|
|
|
0
|
redo RETRY; |
244
|
|
|
|
|
|
|
} |
245
|
3
|
50
|
|
|
|
14
|
return unless $I->set_intfmt($fmt); |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
# read the byte just after the format character |
248
|
3
|
|
|
|
|
15
|
$I->flags=unpack 'x5C', $dummy; |
249
|
|
|
|
|
|
|
|
250
|
3
|
|
|
|
|
9
|
$I->_data=\$dummy; # now mapped |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
# read main index position |
253
|
3
|
|
|
|
|
5
|
$I->mainidx=unpack('x'.(BASEOFFSET+MAINIDX*$I->_intsize).$I->intfmt, |
254
|
3
|
|
|
|
|
9
|
${$I->_data}); |
255
|
3
|
|
|
|
|
8
|
$I->_ididx=unpack('x'.(BASEOFFSET+IDIDX*$I->_intsize).$I->intfmt, |
256
|
3
|
|
|
|
|
9
|
${$I->_data}); |
257
|
3
|
|
|
|
|
6
|
$I->_stringtbl=unpack('x'.(BASEOFFSET+STRINGTBL*$I->_intsize). |
258
|
3
|
|
|
|
|
9
|
$I->intfmt, ${$I->_data}); |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
# parameters: PARENT POS DATAMODE |
261
|
3
|
|
|
|
|
7
|
tie %{$I->main_index=+{}}, 'MMapDB::Index', $I, $I->mainidx, 0; |
|
3
|
|
|
|
|
8
|
|
262
|
3
|
|
|
|
|
4
|
tie %{$I->id_index=+{}}, 'MMapDB::IDIndex', $I, undef, 0; |
|
3
|
|
|
|
|
8
|
|
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
3
|
|
|
|
|
13937
|
return $I; |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
sub stop { |
270
|
4
|
|
|
4
|
1
|
6
|
my ($I)=@_; |
271
|
|
|
|
|
|
|
|
272
|
4
|
50
|
|
|
|
13
|
$I->_e(E_TRANSACTION) if defined $I->_tmpfh; |
273
|
|
|
|
|
|
|
|
274
|
4
|
100
|
|
|
|
13
|
return $I unless defined $I->_data; |
275
|
|
|
|
|
|
|
|
276
|
3
|
|
|
|
|
9
|
for my $k (qw/_data _stringtbl mainidx _ididx/) { |
277
|
12
|
|
|
|
|
34
|
undef $I->$k; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
|
280
|
3
|
|
|
|
|
92
|
untie %{$I->main_index}; undef $I->main_index; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
11
|
|
281
|
3
|
|
|
|
|
8
|
untie %{$I->id_index}; undef $I->id_index; |
|
3
|
|
|
|
|
22
|
|
|
3
|
|
|
|
|
8
|
|
282
|
|
|
|
|
|
|
|
283
|
3
|
|
|
|
|
7
|
return $I; |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
sub index_iterator { |
287
|
0
|
|
|
0
|
1
|
0
|
my ($I, $pos, $nth)=@_; |
288
|
|
|
|
|
|
|
|
289
|
0
|
|
|
|
|
0
|
my $data=$I->_data; |
290
|
0
|
0
|
0
|
0
|
|
0
|
return sub {} unless $data and defined $pos; |
|
0
|
|
|
|
|
0
|
|
291
|
0
|
|
|
|
|
0
|
my $fmt=$I->intfmt; |
292
|
0
|
|
|
|
|
0
|
my $isz=$I->_intsize; |
293
|
0
|
|
|
|
|
0
|
my ($nrecords, $recordlen)=unpack 'x'.$pos.$fmt.'2', $$data; |
294
|
0
|
0
|
|
|
|
0
|
die E_RANGE if $nth>$nrecords; |
295
|
0
|
|
|
|
|
0
|
$recordlen*=$isz; |
296
|
0
|
|
|
|
|
0
|
my ($cur, $end)=($pos+2*$isz+$nth*$recordlen, |
297
|
|
|
|
|
|
|
$pos+2*$isz+$nrecords*$recordlen); |
298
|
0
|
|
|
|
|
0
|
my $stroff=$I->_stringtbl; |
299
|
0
|
|
|
|
|
0
|
my $sfmt=$I->_stringfmt; |
300
|
0
|
|
|
|
|
0
|
my $dbfmt=$I->dbformat_in; |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
my $it=MMapDB::Iterator->new |
303
|
|
|
|
|
|
|
( sub { |
304
|
0
|
0
|
|
0
|
|
0
|
if( @_ ) { |
305
|
0
|
|
|
|
|
0
|
for( my $i=0; $i<@_; $i++ ) { |
306
|
0
|
0
|
|
|
|
0
|
if( $_[$i]==IT_NTH ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
307
|
0
|
|
|
|
|
0
|
my $nth=$_[++$i]; |
308
|
0
|
|
|
|
|
0
|
$nth=$pos+2*$isz+$nth*$recordlen; |
309
|
0
|
0
|
0
|
|
|
0
|
die E_RANGE unless( $pos+2*$isz<=$nth and $nth<=$end ); |
310
|
0
|
|
|
|
|
0
|
$cur=$nth; |
311
|
|
|
|
|
|
|
# return in VOID context |
312
|
0
|
0
|
|
|
|
0
|
return unless defined wantarray; |
313
|
|
|
|
|
|
|
} elsif( $_[$i]==IT_CUR ) { |
314
|
0
|
|
|
|
|
0
|
return ($cur-2*$isz-$pos)/$recordlen; |
315
|
|
|
|
|
|
|
} elsif( $_[$i]==IT_NELEM ) { |
316
|
0
|
|
|
|
|
0
|
return ($end-2*$isz-$pos)/$recordlen; |
317
|
|
|
|
|
|
|
} |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
} |
320
|
0
|
0
|
|
|
|
0
|
return if $cur>=$end; |
321
|
0
|
|
|
|
|
0
|
my ($key, $npos)=unpack 'x'.$cur.$fmt.'2', $$data; |
322
|
0
|
|
|
|
|
0
|
my @list=unpack 'x'.($cur+2*$isz).$fmt.$npos, $$data; |
323
|
0
|
|
|
|
|
0
|
$cur+=$recordlen; |
324
|
0
|
0
|
|
|
|
0
|
if( $dbfmt>DBFMT0 ) { |
325
|
0
|
|
|
|
|
0
|
my ($str, $utf8)=unpack('x'.($stroff+$key).$sfmt, $$data); |
326
|
0
|
0
|
|
|
|
0
|
Encode::_utf8_on($str) if( $utf8 ); |
327
|
0
|
|
|
|
|
0
|
return ($str, @list); |
328
|
|
|
|
|
|
|
} else { |
329
|
0
|
|
|
|
|
0
|
return (unpack('x'.($stroff+$key).$sfmt, $$data), @list); |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
} |
332
|
0
|
|
|
|
|
0
|
); |
333
|
|
|
|
|
|
|
|
334
|
0
|
0
|
|
|
|
0
|
return wantarray ? ($it, $nrecords) : $it; |
335
|
|
|
|
|
|
|
} |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
sub id_index_iterator { |
338
|
0
|
|
|
0
|
1
|
0
|
my ($I)=@_; |
339
|
|
|
|
|
|
|
|
340
|
0
|
|
|
|
|
0
|
my $data=$I->_data; |
341
|
0
|
0
|
|
0
|
|
0
|
return sub {} unless $data; |
|
0
|
|
|
|
|
0
|
|
342
|
0
|
|
|
|
|
0
|
my $pos=$I->_ididx; |
343
|
0
|
|
|
|
|
0
|
my ($nrecords)=unpack 'x'.$pos.$I->intfmt, $$data; |
344
|
0
|
|
|
|
|
0
|
my $isz=$I->_intsize; |
345
|
0
|
|
|
|
|
0
|
my $recordlen=2*$isz; |
346
|
0
|
|
|
|
|
0
|
my ($cur, $end)=($pos+$isz, |
347
|
|
|
|
|
|
|
$pos+$isz+$nrecords*$recordlen); |
348
|
0
|
|
|
|
|
0
|
my $fmt=$I->intfmt.'2'; |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
my $it=MMapDB::Iterator->new |
351
|
|
|
|
|
|
|
( sub { |
352
|
0
|
0
|
|
0
|
|
0
|
if( @_ ) { |
353
|
0
|
|
|
|
|
0
|
for( my $i=0; $i<@_; $i++ ) { |
354
|
0
|
0
|
|
|
|
0
|
if( $_[$i]==IT_NTH ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
355
|
0
|
|
|
|
|
0
|
my $nth=$_[++$i]; |
356
|
0
|
|
|
|
|
0
|
$nth=$pos+$isz+$nth*$recordlen; |
357
|
0
|
0
|
0
|
|
|
0
|
die E_RANGE unless( $pos+$isz<=$nth and $nth<=$end ); |
358
|
0
|
|
|
|
|
0
|
$cur=$nth; |
359
|
|
|
|
|
|
|
# return in VOID context |
360
|
0
|
0
|
|
|
|
0
|
return unless defined wantarray; |
361
|
|
|
|
|
|
|
} elsif( $_[$i]==IT_CUR ) { |
362
|
0
|
|
|
|
|
0
|
return ($cur-$isz-$pos)/$recordlen; |
363
|
|
|
|
|
|
|
} elsif( $_[$i]==IT_NELEM ) { |
364
|
0
|
|
|
|
|
0
|
return ($end-$isz-$pos)/$recordlen; |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
} |
367
|
|
|
|
|
|
|
} |
368
|
0
|
0
|
|
|
|
0
|
return if $cur>=$end; |
369
|
0
|
|
|
|
|
0
|
my @l=unpack 'x'.$cur.$fmt, $$data; |
370
|
0
|
|
|
|
|
0
|
$cur+=$recordlen; |
371
|
0
|
|
|
|
|
0
|
return @l; |
372
|
|
|
|
|
|
|
} |
373
|
0
|
|
|
|
|
0
|
); |
374
|
|
|
|
|
|
|
|
375
|
0
|
0
|
|
|
|
0
|
return wantarray ? ($it, $nrecords) : $it; |
376
|
|
|
|
|
|
|
} |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
sub is_datapos { |
379
|
0
|
|
|
0
|
1
|
0
|
my ($I, $pos)=@_; |
380
|
0
|
|
|
|
|
0
|
return $pos<$I->mainidx; |
381
|
|
|
|
|
|
|
} |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
sub datamode : lvalue { |
384
|
0
|
|
|
0
|
1
|
0
|
tied(%{$_[0]->main_index})->datamode; |
|
0
|
|
|
|
|
0
|
|
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
sub id_datamode : lvalue { |
388
|
0
|
|
|
0
|
1
|
0
|
tied(%{$_[0]->id_index})->datamode; |
|
0
|
|
|
|
|
0
|
|
389
|
|
|
|
|
|
|
} |
390
|
|
|
|
|
|
|
|
391
|
0
|
|
|
0
|
|
0
|
sub _e {$_[0]->_rollback; die $_[1]} |
|
0
|
|
|
|
|
0
|
|
392
|
1520
|
50
|
|
1520
|
|
3104
|
sub _ct {$_[0]->_tmpfh or die E_TRANSACTION} |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
sub begin { |
395
|
3
|
|
|
3
|
1
|
15
|
my ($I, $dbfmt)=@_; |
396
|
|
|
|
|
|
|
|
397
|
3
|
50
|
|
|
|
9
|
$I->_e(E_TRANSACTION) if defined $I->_tmpfh; |
398
|
|
|
|
|
|
|
|
399
|
3
|
50
|
|
|
|
7
|
die E_READONLY if $I->readonly; |
400
|
|
|
|
|
|
|
|
401
|
3
|
50
|
|
|
|
8
|
if( defined $I->lockfile ) { |
402
|
|
|
|
|
|
|
# open lockfile |
403
|
0
|
0
|
|
|
|
0
|
unless( ref $I->lockfile ) { |
404
|
0
|
0
|
|
|
|
0
|
open my $fh, '>', $I->lockfile or die E_OPEN; |
405
|
0
|
|
|
|
|
0
|
$I->lockfile=$fh; |
406
|
|
|
|
|
|
|
} |
407
|
0
|
0
|
|
|
|
0
|
flock $I->lockfile, LOCK_EX or die E_LOCK; |
408
|
|
|
|
|
|
|
} |
409
|
|
|
|
|
|
|
|
410
|
3
|
50
|
|
|
|
14
|
if (defined $dbfmt) { |
411
|
0
|
0
|
|
|
|
0
|
$I->dbformat_out=($dbfmt==-1 ? $#dbformats : $dbfmt); |
412
|
|
|
|
|
|
|
} |
413
|
3
|
|
|
|
|
12
|
$I->set_intfmt($I->intfmt); # adjust string format |
414
|
|
|
|
|
|
|
|
415
|
3
|
|
|
|
|
9
|
$I->_tmpname=$I->filename.'.'.$$; |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
{ |
418
|
|
|
|
|
|
|
# open stringtbl tmpfile |
419
|
3
|
50
|
|
|
|
6
|
open my $fh, '+>', $I->_tmpname.'.strings' or die E_OPEN; |
|
3
|
|
|
|
|
15
|
|
420
|
3
|
|
|
|
|
10
|
$I->_stringfh=$fh; |
421
|
3
|
|
|
|
|
8
|
$I->_stringmap=\my $strings; |
422
|
3
|
|
|
|
|
5
|
eval { |
423
|
3
|
50
|
33
|
|
|
9
|
sysseek $fh, $I->stringmap_prealloc, SEEK_SET and |
424
|
|
|
|
|
|
|
truncate $fh, $I->stringmap_prealloc and |
425
|
|
|
|
|
|
|
map_handle $strings, $fh, '+>', 0, $I->stringmap_prealloc; |
426
|
|
|
|
|
|
|
}; |
427
|
3
|
50
|
|
|
|
623
|
die E_OPEN if $@; |
428
|
3
|
|
|
|
|
10
|
$I->_stringmap_end=0; |
429
|
|
|
|
|
|
|
} |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
{ |
432
|
|
|
|
|
|
|
# open tmpfile |
433
|
3
|
50
|
|
|
|
5
|
open my $fh, '+>', $I->_tmpname or die E_OPEN; |
|
3
|
|
|
|
|
14
|
|
434
|
3
|
|
|
|
|
8
|
$I->_tmpfh=$fh; # this starts the transaction |
435
|
3
|
|
|
|
|
3
|
$I->_tmpmap=\do{my $map=''}; |
|
3
|
|
|
|
|
10
|
|
436
|
|
|
|
|
|
|
|
437
|
3
|
|
|
|
|
15
|
$I->_putdata(0, 'a4aC', $dbformats[$I->dbformat_out], $I->intfmt, |
438
|
|
|
|
|
|
|
$I->flags & 0xff); |
439
|
3
|
|
|
|
|
10
|
$I->_index_end=BASEOFFSET+DATASTART*$I->_intsize; |
440
|
|
|
|
|
|
|
} |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
# and copy every *valid* entry from the old file |
443
|
|
|
|
|
|
|
# create _idmap on the way |
444
|
3
|
|
|
|
|
8
|
$I->_idmap={}; |
445
|
3
|
|
|
|
|
9
|
$I->_strpos=[]; |
446
|
3
|
|
|
|
|
11
|
for( my $it=$I->iterator; my ($pos)=$it->(); ) { |
447
|
258
|
|
|
|
|
1508
|
$I->insert($I->data_record($pos)); |
448
|
|
|
|
|
|
|
} |
449
|
3
|
100
|
|
|
|
6
|
if( $I->_data ) { |
450
|
2
|
|
|
|
|
5
|
$I->_nextid=unpack('x'.(BASEOFFSET+NEXTID*$I->_intsize).$I->intfmt, |
451
|
2
|
|
|
|
|
5
|
${$I->_data}); |
452
|
|
|
|
|
|
|
} else { |
453
|
1
|
|
|
|
|
2
|
$I->_nextid=1; |
454
|
|
|
|
|
|
|
} |
455
|
|
|
|
|
|
|
|
456
|
3
|
|
|
|
|
31
|
return $I; |
457
|
|
|
|
|
|
|
} |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
# The interator() below hops over the mmapped area. This one works on the file. |
460
|
|
|
|
|
|
|
# It can be used only within a begin/commit cycle. |
461
|
|
|
|
|
|
|
sub _fiterator { |
462
|
3
|
|
|
3
|
|
25
|
my ($I, $end)=@_; |
463
|
|
|
|
|
|
|
|
464
|
3
|
|
|
|
|
11
|
my $map=$I->_tmpmap; |
465
|
3
|
|
|
|
|
11
|
my $pos=BASEOFFSET+$I->_intsize*DATASTART; |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
return sub { |
468
|
1261
|
100
|
|
|
|
2386
|
LOOP: { |
469
|
1261
|
|
|
1261
|
|
1436
|
return if $pos>=$end; |
470
|
1258
|
|
|
|
|
1396
|
my $elpos=$pos; |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
# valid id nkeys key1...keyn sort data |
473
|
|
|
|
|
|
|
# read (valid, id, nkeys) |
474
|
1258
|
|
|
|
|
2899
|
my ($valid, $id, $nkeys)=unpack 'x'.$pos.$I->intfmt.'3', $$map; |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
# move iterator position |
477
|
|
|
|
|
|
|
# 5: valid, id, nkeys ... sort, data |
478
|
1258
|
|
|
|
|
2380
|
$pos+=$I->_intsize*(5+$nkeys); |
479
|
1258
|
50
|
|
|
|
2511
|
redo LOOP unless ($valid); |
480
|
|
|
|
|
|
|
|
481
|
1258
|
|
|
|
|
2249
|
my @l=unpack 'x'.($elpos+3*$I->_intsize).$I->intfmt.($nkeys+2), $$map; |
482
|
1258
|
|
|
|
|
1956
|
my $data=pop @l; |
483
|
1258
|
|
|
|
|
1512
|
my $sort=pop @l; |
484
|
|
|
|
|
|
|
|
485
|
1258
|
|
|
|
|
5088
|
return ([\@l, $sort, $data, $id], $elpos); |
486
|
|
|
|
|
|
|
} |
487
|
3
|
|
|
|
|
31
|
}; |
488
|
|
|
|
|
|
|
} |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
sub _really_write_index { |
491
|
28
|
|
|
28
|
|
49
|
my ($I, $map, $level)=@_; |
492
|
|
|
|
|
|
|
|
493
|
28
|
|
|
|
|
36
|
my $recordlen=1; # in ints: (1): for subindexes there is one |
494
|
|
|
|
|
|
|
# position to store |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
# find the max. number of positions we have to store |
497
|
28
|
|
|
|
|
157
|
foreach my $v (values %$map) { |
498
|
523
|
100
|
|
|
|
1000
|
if( ref($v) eq 'ARRAY' ) { |
499
|
|
|
|
|
|
|
# list of data records |
500
|
498
|
100
|
|
|
|
1089
|
$recordlen=@$v if @$v>$recordlen; |
501
|
|
|
|
|
|
|
} |
502
|
|
|
|
|
|
|
# else: recordlen is initialized with 1. So for subindexes there is |
503
|
|
|
|
|
|
|
# nothing to do |
504
|
|
|
|
|
|
|
} |
505
|
|
|
|
|
|
|
# each record comes with a header of 2 integers, the key position in the |
506
|
|
|
|
|
|
|
# string table and the actual position count of the record. So we have to |
507
|
|
|
|
|
|
|
# add 2 to $recordlen. |
508
|
28
|
|
|
|
|
53
|
$recordlen+=2; |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
# the index itself has a 2 integer header, the recordlen and the number |
511
|
|
|
|
|
|
|
# of index records that belong to the index. |
512
|
28
|
|
|
|
|
91
|
my $indexsize=(2+$recordlen*keys(%$map))*$I->_intsize; # in bytes |
513
|
|
|
|
|
|
|
|
514
|
28
|
|
|
|
|
61
|
my $pos=$I->_index_end; |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
# make room |
517
|
28
|
|
|
|
|
58
|
$I->_index_end=$pos+$indexsize; |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
# and write subindices after this index |
520
|
28
|
|
|
|
|
51
|
my $strings=$I->_stringmap; |
521
|
28
|
|
|
|
|
60
|
my $sfmt=$I->_stringfmt_out; |
522
|
28
|
|
|
|
|
50
|
my $dbfmt=$I->dbformat_out; |
523
|
28
|
|
|
|
|
88
|
foreach my $v (values %$map) { |
524
|
523
|
100
|
|
|
|
1153
|
if( ref($v) eq 'HASH' ) { |
525
|
|
|
|
|
|
|
# convert the subindex into a position list |
526
|
25
|
|
|
|
|
85
|
$v=[$I->_really_write_index($v, $level+1)]; |
527
|
|
|
|
|
|
|
} else { |
528
|
|
|
|
|
|
|
# here we already have a position list but it still contains |
529
|
|
|
|
|
|
|
# sorting ids. |
530
|
1258
|
|
|
|
|
12234
|
@$v=map { |
531
|
965
|
|
|
|
|
1525
|
$_->[1]; |
532
|
|
|
|
|
|
|
} sort { |
533
|
|
|
|
|
|
|
$a->[0] cmp $b->[0]; |
534
|
|
|
|
|
|
|
} map { |
535
|
|
|
|
|
|
|
# fetch sort string from string table |
536
|
498
|
50
|
|
|
|
764
|
if( $dbfmt>DBFMT0 ) { |
|
1258
|
|
|
|
|
2032
|
|
537
|
1258
|
|
|
|
|
4878
|
my ($str, $utf8)=unpack('x'.$_->[0].$sfmt, $$strings); |
538
|
1258
|
50
|
|
|
|
2659
|
Encode::_utf8_on($str) if $utf8; |
539
|
1258
|
|
|
|
|
3983
|
[$str, $_->[1]]; |
540
|
|
|
|
|
|
|
} else { |
541
|
0
|
|
|
|
|
0
|
[unpack('x'.$_->[0].$sfmt, $$strings), $_->[1]]; |
542
|
|
|
|
|
|
|
} |
543
|
|
|
|
|
|
|
} @$v; |
544
|
|
|
|
|
|
|
} |
545
|
|
|
|
|
|
|
} |
546
|
|
|
|
|
|
|
|
547
|
28
|
|
|
|
|
86
|
my $fmt=$I->intfmt; |
548
|
28
|
|
|
|
|
38
|
my $written=$pos; |
549
|
28
|
|
|
|
|
126
|
$written+=$I->_putdata($written, $fmt.'2', 0+keys(%$map), $recordlen); |
550
|
|
|
|
|
|
|
|
551
|
28
|
|
|
|
|
57
|
$fmt.=$recordlen; |
552
|
|
|
|
|
|
|
# write the records |
553
|
28
|
|
|
|
|
146
|
foreach my $key (map { |
|
523
|
|
|
|
|
767
|
|
554
|
1683
|
|
|
|
|
2108
|
$_->[0] |
555
|
|
|
|
|
|
|
} sort { |
556
|
|
|
|
|
|
|
$a->[1] cmp $b->[1]; |
557
|
|
|
|
|
|
|
} map { |
558
|
523
|
50
|
|
|
|
873
|
if( $dbfmt>DBFMT0 ) { |
559
|
523
|
|
|
|
|
1538
|
my ($str, $utf8)=unpack('x'.$_.$sfmt, $$strings); |
560
|
523
|
50
|
|
|
|
1062
|
Encode::_utf8_on($str) if $utf8; |
561
|
523
|
|
|
|
|
1262
|
[$_, $str]; |
562
|
|
|
|
|
|
|
} else { |
563
|
0
|
|
|
|
|
0
|
[$_, unpack('x'.$_.$sfmt, $$strings)]; |
564
|
|
|
|
|
|
|
} |
565
|
|
|
|
|
|
|
} keys %$map) { |
566
|
523
|
|
|
|
|
851
|
my $v=$map->{$key}; |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
#D($key, $v); |
569
|
|
|
|
|
|
|
#warn "$prefix> idx rec: ".unpack('H*', pack($fmt, $key, 0+@$v, @$v))."\n"; |
570
|
|
|
|
|
|
|
|
571
|
523
|
|
|
|
|
1254
|
$written+=$I->_putdata($written, $fmt, $key, 0+@$v, @$v); |
572
|
|
|
|
|
|
|
} |
573
|
|
|
|
|
|
|
|
574
|
28
|
|
|
|
|
521
|
return $pos; |
575
|
|
|
|
|
|
|
} |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
sub _write_index { |
578
|
3
|
|
|
3
|
|
8
|
my ($I)=@_; |
579
|
|
|
|
|
|
|
|
580
|
3
|
|
|
|
|
5
|
my %map; |
581
|
3
|
|
|
|
|
10
|
for( my $it=$I->_fiterator($I->_index_end); my ($el, $pos)=$it->(); ) { |
582
|
1258
|
|
|
|
|
9831
|
my $m=\%map; |
583
|
1258
|
|
|
|
|
1560
|
my @k=@{$el->[0]}; |
|
1258
|
|
|
|
|
4405
|
|
584
|
1258
|
|
66
|
|
|
5144
|
while(@k>1 and ref($m) eq 'HASH') { |
585
|
1000
|
|
|
|
|
1363
|
my $k=shift @k; |
586
|
1000
|
100
|
|
|
|
2231
|
$m->{$k}={} unless exists $m->{$k}; |
587
|
1000
|
|
|
|
|
2805
|
$m=$m->{$k}; |
588
|
|
|
|
|
|
|
} |
589
|
1258
|
50
|
|
|
|
3057
|
$I->_e(E_DUPLICATE) unless ref($m) eq 'HASH'; |
590
|
1258
|
100
|
|
|
|
3993
|
$m->{$k[0]}=[] unless defined $m->{$k[0]}; |
591
|
1258
|
50
|
|
|
|
12073
|
$I->_e(E_DUPLICATE) unless ref($m->{$k[0]}) eq 'ARRAY'; |
592
|
|
|
|
|
|
|
# Actually we want to save only positions but they must be ordered. |
593
|
|
|
|
|
|
|
# So either keep the order field together with the position here to |
594
|
|
|
|
|
|
|
# sort it later or do sort of ordered insert here. |
595
|
|
|
|
|
|
|
# The former is simpler. So it's it. |
596
|
1258
|
|
|
|
|
1395
|
push @{$m->{$k[0]}}, [$el->[1], $pos]; |
|
1258
|
|
|
|
|
6684
|
|
597
|
|
|
|
|
|
|
} |
598
|
|
|
|
|
|
|
|
599
|
3
|
|
|
|
|
19
|
return $I->_really_write_index(\%map, 0); |
600
|
|
|
|
|
|
|
} |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
sub _write_id_index { |
603
|
3
|
|
|
3
|
|
6
|
my ($I)=@_; |
604
|
|
|
|
|
|
|
|
605
|
3
|
|
|
|
|
11
|
my $map=$I->_idmap; |
606
|
3
|
|
|
|
|
8
|
my $fmt=$I->intfmt; |
607
|
|
|
|
|
|
|
|
608
|
3
|
|
|
|
|
8
|
my $pos=$I->_index_end; |
609
|
3
|
|
|
|
|
4
|
my $written=$pos; |
610
|
3
|
|
|
|
|
11
|
$written+=$I->_putdata($written, $fmt, 0+keys(%$map)); |
611
|
|
|
|
|
|
|
|
612
|
3
|
|
|
|
|
8
|
$fmt.='2'; |
613
|
|
|
|
|
|
|
# write the records |
614
|
3
|
|
|
|
|
452
|
foreach my $key (sort {$a <=> $b} keys %$map) { |
|
10463
|
|
|
|
|
9376
|
|
615
|
1258
|
|
|
|
|
2132
|
my $v=$map->{$key}; |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
#warn "id> idx rec: ".unpack('H*', pack($fmt, $key, $v))."\n"; |
618
|
|
|
|
|
|
|
|
619
|
1258
|
|
|
|
|
2266
|
$written+=$I->_putdata($written, $fmt, $key, $v); |
620
|
|
|
|
|
|
|
} |
621
|
3
|
|
|
|
|
147
|
$I->_index_end=$written; |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
#warn sprintf "id> index written @ %#x\n", $pos; |
624
|
|
|
|
|
|
|
|
625
|
3
|
|
|
|
|
8
|
return $pos; |
626
|
|
|
|
|
|
|
} |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
sub invalidate { |
629
|
3
|
|
|
3
|
1
|
5
|
my ($I)=@_; |
630
|
3
|
50
|
|
|
|
12
|
$I->_e(E_READONLY) if $I->readonly; |
631
|
3
|
100
|
|
|
|
8
|
return unless defined $I->_data; |
632
|
2
|
|
|
|
|
4
|
protect ${$I->_data}, '+<'; |
|
2
|
|
|
|
|
6
|
|
633
|
2
|
|
|
|
|
4
|
substr( ${$I->_data}, INTFMT, 1, "\0" ); |
|
2
|
|
|
|
|
7
|
|
634
|
2
|
|
|
|
|
5
|
protect ${$I->_data}, '<'; |
|
2
|
|
|
|
|
5
|
|
635
|
|
|
|
|
|
|
} |
636
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
sub commit { |
638
|
3
|
|
|
3
|
1
|
10
|
my ($I, $dont_invalidate)=@_; |
639
|
|
|
|
|
|
|
|
640
|
3
|
|
|
|
|
8
|
$I->_ct; |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
# write NEXTID |
643
|
3
|
|
|
|
|
12
|
$I->_putdata(BASEOFFSET+NEXTID*$I->_intsize, $I->intfmt, $I->_nextid); |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
# write MAINIDX and IDIDX |
646
|
3
|
|
|
|
|
13
|
my $mainidx=$I->_write_index; |
647
|
3
|
|
|
|
|
29
|
my $ididx=$I->_write_id_index; |
648
|
|
|
|
|
|
|
|
649
|
3
|
|
|
|
|
38
|
$I->_putdata(BASEOFFSET+MAINIDX*$I->_intsize, $I->intfmt, $mainidx); |
650
|
3
|
|
|
|
|
8
|
$I->_putdata(BASEOFFSET+IDIDX*$I->_intsize, $I->intfmt, $ididx); |
651
|
3
|
|
|
|
|
10
|
$I->_putdata(BASEOFFSET+STRINGTBL*$I->_intsize, $I->intfmt, $I->_index_end); |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
# now copy the string table |
654
|
3
|
|
|
|
|
8
|
my $fh=$I->_tmpfh; |
655
|
3
|
|
|
|
|
10
|
my $strings=$I->_stringmap; |
656
|
3
|
|
|
|
|
9
|
my $map=$I->_tmpmap; |
657
|
3
|
|
|
|
|
10
|
my $need=$I->_index_end+$I->_stringmap_end; |
658
|
3
|
100
|
|
|
|
12
|
if( $need>length $$map ) { |
659
|
1
|
|
|
|
|
4
|
eval { |
660
|
1
|
50
|
33
|
|
|
62
|
sysseek $fh, $need, SEEK_SET and |
661
|
|
|
|
|
|
|
truncate $fh, $need and |
662
|
|
|
|
|
|
|
map_handle $$map, $fh, '+>', 0, $need; |
663
|
|
|
|
|
|
|
}; |
664
|
1
|
50
|
|
|
|
198
|
$I->_e(E_OPEN) if $@; |
665
|
|
|
|
|
|
|
} |
666
|
3
|
|
|
|
|
9
|
substr($$map, $I->_index_end, $I->_stringmap_end, |
667
|
|
|
|
|
|
|
substr($$strings, 0, $I->_stringmap_end)); |
668
|
3
|
|
|
|
|
218
|
truncate $fh, $need; |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
#warn "mainidx=$mainidx, ididx=$ididx, strtbl=$strtbl\n"; |
671
|
|
|
|
|
|
|
|
672
|
3
|
|
|
|
|
11
|
undef $I->_idmap; |
673
|
3
|
|
|
|
|
384
|
undef $I->_strpos; |
674
|
3
|
|
|
|
|
211
|
undef $I->_stringmap; |
675
|
|
|
|
|
|
|
|
676
|
3
|
50
|
|
|
|
11
|
close $I->_stringfh or $I->_e(E_CLOSE); |
677
|
3
|
|
|
|
|
8
|
undef $I->_stringfh; |
678
|
3
|
|
|
|
|
19
|
unlink $I->_tmpname.'.strings'; |
679
|
|
|
|
|
|
|
|
680
|
3
|
|
|
|
|
11
|
undef $I->_tmpmap; |
681
|
3
|
50
|
|
|
|
20
|
close $fh or $I->_e(E_CLOSE); |
682
|
3
|
|
|
|
|
9
|
undef $I->_tmpfh; |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
# rename is (at least on Linux) an atomic operation |
685
|
3
|
50
|
|
|
|
10
|
rename $I->_tmpname, $I->filename or $I->_e(E_RENAME); |
686
|
|
|
|
|
|
|
|
687
|
3
|
50
|
|
|
|
18
|
$I->invalidate unless $dont_invalidate; |
688
|
|
|
|
|
|
|
|
689
|
3
|
50
|
|
|
|
9
|
if( $I->lockfile ) { |
690
|
0
|
0
|
|
|
|
0
|
flock $I->lockfile, LOCK_UN or die E_LOCK; |
691
|
|
|
|
|
|
|
} |
692
|
|
|
|
|
|
|
|
693
|
3
|
|
|
|
|
14
|
$I->start; |
694
|
|
|
|
|
|
|
} |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
sub _rollback { |
697
|
0
|
|
|
0
|
|
0
|
my ($I)=@_; |
698
|
|
|
|
|
|
|
|
699
|
0
|
|
|
|
|
0
|
close $I->_tmpfh; |
700
|
0
|
|
|
|
|
0
|
undef $I->_tmpfh; |
701
|
0
|
|
|
|
|
0
|
unlink $I->_tmpname; |
702
|
|
|
|
|
|
|
|
703
|
0
|
|
|
|
|
0
|
close $I->_stringfh; |
704
|
0
|
|
|
|
|
0
|
undef $I->_stringfh; |
705
|
0
|
|
|
|
|
0
|
unlink $I->_tmpname.'.strings'; |
706
|
|
|
|
|
|
|
|
707
|
0
|
|
|
|
|
0
|
undef $I->_stringmap; |
708
|
0
|
|
|
|
|
0
|
undef $I->_strpos; |
709
|
0
|
|
|
|
|
0
|
undef $I->_idmap; |
710
|
|
|
|
|
|
|
|
711
|
0
|
|
|
|
|
0
|
$I->_stringfmt_out=$I->_stringfmt; |
712
|
0
|
|
|
|
|
0
|
$I->dbformat_out=$I->dbformat_in; |
713
|
|
|
|
|
|
|
|
714
|
0
|
0
|
|
|
|
0
|
if( $I->lockfile ) { |
715
|
0
|
0
|
|
|
|
0
|
flock $I->lockfile, LOCK_UN or die E_LOCK; |
716
|
|
|
|
|
|
|
} |
717
|
|
|
|
|
|
|
} |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
sub rollback { |
720
|
0
|
|
|
0
|
1
|
0
|
my ($I)=@_; |
721
|
|
|
|
|
|
|
|
722
|
0
|
0
|
|
|
|
0
|
$I->_e(E_TRANSACTION) unless defined $I->_tmpfh; |
723
|
0
|
|
|
|
|
0
|
$I->_rollback; |
724
|
|
|
|
|
|
|
} |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
sub DESTROY { |
727
|
1
|
|
|
1
|
|
573
|
my ($I)=@_; |
728
|
|
|
|
|
|
|
|
729
|
1
|
50
|
|
|
|
3
|
$I->_rollback if defined $I->_tmpfh; |
730
|
1
|
|
|
|
|
5
|
$I->stop; |
731
|
|
|
|
|
|
|
} |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
sub backup { |
734
|
0
|
|
|
0
|
1
|
0
|
my ($I, $fn)=@_; |
735
|
|
|
|
|
|
|
|
736
|
0
|
|
|
|
|
0
|
$I->start; |
737
|
|
|
|
|
|
|
|
738
|
0
|
0
|
|
|
|
0
|
my $backup=$I->new(filename=>(defined $fn ? $fn : $I->filename.'.BACKUP')); |
739
|
|
|
|
|
|
|
|
740
|
0
|
|
|
|
|
0
|
$backup->begin; |
741
|
0
|
|
|
|
|
0
|
$backup->commit(1); |
742
|
|
|
|
|
|
|
} |
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
sub restore { |
745
|
0
|
|
|
0
|
1
|
0
|
my ($I, $fn)=@_; |
746
|
|
|
|
|
|
|
|
747
|
0
|
|
|
|
|
0
|
$I->start; |
748
|
0
|
0
|
|
|
|
0
|
$fn=$I->filename.'.BACKUP' unless defined $fn; |
749
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
# rename is (at least on Linux) an atomic operation |
751
|
0
|
0
|
|
|
|
0
|
rename $fn, $I->filename or die E_RENAME; |
752
|
0
|
|
|
|
|
0
|
$I->invalidate; |
753
|
|
|
|
|
|
|
|
754
|
0
|
|
|
|
|
0
|
return $I->start; |
755
|
|
|
|
|
|
|
} |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
# Returns the position of $key in the stringtable |
758
|
|
|
|
|
|
|
# If $key is not found it is inserted. @{$I->_strpos} is kept ordered. |
759
|
|
|
|
|
|
|
# So, we can do a binary search. |
760
|
|
|
|
|
|
|
# This implements something very similar to a HASH. So, why not use a HASH? |
761
|
|
|
|
|
|
|
# A HASH is kept completely in core and the memory is not returned to the |
762
|
|
|
|
|
|
|
# operating system when finished. The number of strings in the database |
763
|
|
|
|
|
|
|
# can become quite large. So if a long running process updates the database |
764
|
|
|
|
|
|
|
# only once it will consume much memory for nothing. To avoid this we map |
765
|
|
|
|
|
|
|
# the string table currently under construction in a separate file that |
766
|
|
|
|
|
|
|
# is mmapped into the address space of this process and keep here only |
767
|
|
|
|
|
|
|
# a list of pointer into this area. When the transaction is committed the |
768
|
|
|
|
|
|
|
# memory is returned to the OS. But on the other hand we need fast access. |
769
|
|
|
|
|
|
|
# This is achieved by the binary search. |
770
|
|
|
|
|
|
|
sub _string2pos { |
771
|
5548
|
|
|
5548
|
|
8186
|
my ($I, $key)=@_; |
772
|
|
|
|
|
|
|
|
773
|
5548
|
|
|
|
|
10144
|
my $fmt=$I->_stringfmt_out; |
774
|
5548
|
|
|
|
|
9681
|
my $dbfmt=$I->dbformat_out; |
775
|
|
|
|
|
|
|
|
776
|
5548
|
50
|
|
|
|
11360
|
Encode::_utf8_off($key) if $dbfmt==DBFMT0; |
777
|
|
|
|
|
|
|
|
778
|
5548
|
|
|
|
|
9687
|
my $strings=$I->_stringmap; |
779
|
5548
|
|
|
|
|
9670
|
my $poslist=$I->_strpos; |
780
|
|
|
|
|
|
|
|
781
|
5548
|
|
|
|
|
10641
|
my ($low, $high)=(0, 0+@$poslist); |
782
|
|
|
|
|
|
|
#warn "_string2pos($key): low=$low, high=$high\n"; |
783
|
|
|
|
|
|
|
|
784
|
5548
|
|
|
|
|
5975
|
my ($cur, $rel, $curstr); |
785
|
5548
|
|
|
|
|
10906
|
while( $low<$high ) { |
786
|
48535
|
|
|
|
|
54001
|
$cur=($high+$low)/2; # "use integer" is active, see above |
787
|
48535
|
50
|
|
|
|
74672
|
if( $dbfmt>DBFMT0 ) { |
788
|
48535
|
|
|
|
|
175999
|
($curstr, my $utf8)=unpack 'x'.$poslist->[$cur].$fmt, $$strings; |
789
|
48535
|
50
|
|
|
|
113766
|
Encode::_utf8_on($curstr) if $utf8; |
790
|
|
|
|
|
|
|
} else { |
791
|
0
|
|
|
|
|
0
|
$curstr=unpack 'x'.$poslist->[$cur].$fmt, $$strings; |
792
|
|
|
|
|
|
|
} |
793
|
|
|
|
|
|
|
#warn " --> looking at $curstr: low=$low, high=$high, cur=$cur\n"; |
794
|
48535
|
|
|
|
|
66884
|
$rel=($curstr cmp $key); |
795
|
48535
|
100
|
|
|
|
81145
|
if( $rel<0 ) { |
|
|
100
|
|
|
|
|
|
796
|
|
|
|
|
|
|
#warn " --> moving low: $low ==> ".($cur+1)."\n"; |
797
|
29982
|
|
|
|
|
59137
|
$low=$cur+1; |
798
|
|
|
|
|
|
|
} elsif( $rel>0 ) { |
799
|
|
|
|
|
|
|
#warn " --> moving high: $high ==> ".($cur)."\n"; |
800
|
|
|
|
|
|
|
# don't try to optimize here: $high=$cur-1 will not work in border cases |
801
|
16064
|
|
|
|
|
31562
|
$high=$cur; |
802
|
|
|
|
|
|
|
} else { |
803
|
|
|
|
|
|
|
#warn " --> BINGO\n"; |
804
|
2489
|
|
|
|
|
8198
|
return $poslist->[$cur]; |
805
|
|
|
|
|
|
|
} |
806
|
|
|
|
|
|
|
} |
807
|
|
|
|
|
|
|
#warn " --> NOT FOUND\n"; |
808
|
3059
|
|
|
|
|
6818
|
my $pos=$I->_stringmap_end; |
809
|
3059
|
|
|
|
|
6529
|
splice @$poslist, $low, 0, $pos; |
810
|
|
|
|
|
|
|
#warn " --> inserting $pos into poslist at $low ==> @$poslist\n"; |
811
|
|
|
|
|
|
|
|
812
|
3059
|
|
|
|
|
3251
|
my $newstr; |
813
|
3059
|
50
|
|
|
|
6194
|
if( $dbfmt>DBFMT0 ) { |
814
|
3059
|
50
|
|
|
|
6924
|
if( Encode::is_utf8($key) ) { |
815
|
0
|
|
|
|
|
0
|
$newstr=pack($fmt, Encode::encode_utf8($key), 1); |
816
|
|
|
|
|
|
|
} else { |
817
|
3059
|
|
|
|
|
9760
|
$newstr=pack($fmt, $key, 0); |
818
|
|
|
|
|
|
|
} |
819
|
|
|
|
|
|
|
} else { |
820
|
0
|
|
|
|
|
0
|
$newstr=pack($fmt, $key); |
821
|
|
|
|
|
|
|
} |
822
|
|
|
|
|
|
|
|
823
|
3059
|
100
|
|
|
|
7144
|
if( $pos+length($newstr)>length $$strings ) { |
824
|
|
|
|
|
|
|
# remap |
825
|
10
|
|
|
|
|
31
|
my $prea=$I->stringmap_prealloc; |
826
|
10
|
|
|
|
|
24
|
my $need=$prea*(($pos+length($newstr)+$prea-1)/$prea); |
827
|
10
|
|
|
|
|
20
|
eval { |
828
|
10
|
|
|
|
|
28
|
my $fh=$I->_stringfh; |
829
|
10
|
50
|
33
|
|
|
1334
|
sysseek $fh, $need, SEEK_SET and |
830
|
|
|
|
|
|
|
truncate $fh, $need and |
831
|
|
|
|
|
|
|
map_handle $$strings, $fh, '+>', 0, $need; |
832
|
|
|
|
|
|
|
}; |
833
|
10
|
50
|
|
|
|
2437
|
$I->_e(E_OPEN) if $@; |
834
|
|
|
|
|
|
|
} |
835
|
|
|
|
|
|
|
|
836
|
3059
|
|
|
|
|
7132
|
substr $$strings, $pos, length($newstr), $newstr; |
837
|
3059
|
|
|
|
|
6470
|
$I->_stringmap_end=$pos+length($newstr); |
838
|
|
|
|
|
|
|
|
839
|
3059
|
|
|
|
|
11126
|
return $pos; |
840
|
|
|
|
|
|
|
} |
841
|
|
|
|
|
|
|
|
842
|
|
|
|
|
|
|
sub insert { |
843
|
1516
|
|
|
1516
|
1
|
23301
|
my ($I, $rec)=@_; |
844
|
|
|
|
|
|
|
#my ($I, $key, $sort, $data, $id)=@_; |
845
|
|
|
|
|
|
|
|
846
|
|
|
|
|
|
|
#use Data::Dumper; $Data::Dumper::Useqq=1; warn Dumper $rec; |
847
|
|
|
|
|
|
|
|
848
|
1516
|
|
|
|
|
2903
|
$I->_ct; |
849
|
|
|
|
|
|
|
|
850
|
1516
|
50
|
|
|
|
3976
|
$rec->[0]=[$rec->[0]] unless ref $rec->[0]; |
851
|
1516
|
|
|
|
|
1945
|
for my $v (@{$rec}[1,2]) { |
|
1516
|
|
|
|
|
3178
|
|
852
|
3032
|
50
|
|
|
|
6867
|
$v='' unless defined $v; |
853
|
|
|
|
|
|
|
} |
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
# create new ID if necessary |
856
|
1516
|
|
|
|
|
2733
|
my $id=$rec->[3]; |
857
|
1516
|
|
|
|
|
2834
|
my $idmap=$I->_idmap; |
858
|
1516
|
100
|
|
|
|
3048
|
if( defined $id ) { |
859
|
258
|
50
|
|
|
|
664
|
$I->_e(E_TWICE) if exists $idmap->{$id}; |
860
|
|
|
|
|
|
|
} else { |
861
|
1258
|
|
|
|
|
2089
|
$id=$I->_nextid; |
862
|
1258
|
|
|
|
|
3286
|
undef $idmap->{$id}; # allocate it |
863
|
|
|
|
|
|
|
|
864
|
2
|
|
|
2
|
|
31
|
my $mask=do{no integer; unpack( $I->intfmt, pack $I->intfmt, -1 )>>1}; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
20
|
|
|
1258
|
|
|
|
|
1362
|
|
|
1258
|
|
|
|
|
2338
|
|
865
|
1258
|
|
|
|
|
1877
|
my $nid=($id+1)&$mask; |
866
|
1258
|
50
|
|
|
|
2550
|
$nid=1 if $nid==0; |
867
|
1258
|
|
|
|
|
3339
|
while(exists $idmap->{$nid}) { |
868
|
0
|
0
|
|
|
|
0
|
$nid=($nid+1)&$mask; $nid=1 if $nid==0; |
|
0
|
|
|
|
|
0
|
|
869
|
0
|
0
|
|
|
|
0
|
$I->_e(E_FULL) if $nid==$id; |
870
|
|
|
|
|
|
|
} |
871
|
1258
|
|
|
|
|
2443
|
$I->_nextid=$nid; |
872
|
|
|
|
|
|
|
} |
873
|
|
|
|
|
|
|
|
874
|
1516
|
|
|
|
|
3012
|
my $pos=$I->_index_end; |
875
|
1516
|
|
|
|
|
5986
|
$I->_index_end+=$I->_putdata($pos, $I->intfmt.'*', 1, $id, 0+@{$rec->[0]}, |
|
5548
|
|
|
|
|
10931
|
|
876
|
1516
|
|
|
|
|
2819
|
map {$I->_string2pos($_)} |
877
|
1516
|
|
|
|
|
2669
|
@{$rec->[0]}, @{$rec}[1,2]); |
|
1516
|
|
|
|
|
2610
|
|
878
|
|
|
|
|
|
|
|
879
|
1516
|
|
|
|
|
3942
|
$idmap->{$id}=$pos; |
880
|
|
|
|
|
|
|
|
881
|
1516
|
|
|
|
|
6468
|
return ($id, $pos); |
882
|
|
|
|
|
|
|
} |
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
sub delete_by_id { |
885
|
0
|
|
|
0
|
1
|
0
|
my ($I, $id, $return_element)=@_; |
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
# warn "delete_by_id($id)\n"; |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
# no such id |
890
|
0
|
0
|
|
|
|
0
|
return unless exists $I->_idmap->{$id}; |
891
|
|
|
|
|
|
|
|
892
|
0
|
|
|
|
|
0
|
my $map=$I->_tmpmap; |
893
|
0
|
|
|
|
|
0
|
my $idmap=$I->_idmap; |
894
|
0
|
|
|
|
|
0
|
my $pos; |
895
|
|
|
|
|
|
|
|
896
|
0
|
0
|
|
|
|
0
|
return unless defined($pos=delete $idmap->{$id}); |
897
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
# read VALID, ID, NKEYS |
899
|
0
|
|
|
|
|
0
|
my ($valid, $elid, $nkeys)=unpack 'x'.$pos.$I->intfmt.'3', $$map; |
900
|
|
|
|
|
|
|
|
901
|
0
|
0
|
|
|
|
0
|
return unless $valid; |
902
|
0
|
0
|
|
|
|
0
|
return unless $id==$elid; # XXX: should'nt that be an E_CORRUPT |
903
|
|
|
|
|
|
|
|
904
|
0
|
|
|
|
|
0
|
my $rc=1; |
905
|
0
|
0
|
|
|
|
0
|
if( $return_element ) { |
906
|
0
|
|
|
|
|
0
|
my $strings=$I->_stringmap; |
907
|
0
|
|
|
|
|
0
|
my $sfmt=$I->_stringfmt_out; |
908
|
0
|
|
|
|
|
0
|
my $dbfmt=$I->dbformat_out; |
909
|
|
|
|
|
|
|
my @l=map { |
910
|
0
|
0
|
|
|
|
0
|
if( $dbfmt>DBFMT0 ) { |
|
0
|
|
|
|
|
0
|
|
911
|
0
|
|
|
|
|
0
|
my ($str, $utf8)=unpack('x'.$_.$sfmt, $$strings); |
912
|
0
|
0
|
|
|
|
0
|
Encode::_utf8_on($str) if $utf8; |
913
|
0
|
|
|
|
|
0
|
$str; |
914
|
|
|
|
|
|
|
} else { |
915
|
0
|
|
|
|
|
0
|
unpack('x'.$_.$sfmt, $$strings); |
916
|
|
|
|
|
|
|
} |
917
|
|
|
|
|
|
|
} unpack('x'.($pos+3*$I->_intsize).$I->intfmt.($nkeys+2), $$map); |
918
|
|
|
|
|
|
|
|
919
|
0
|
|
|
|
|
0
|
my $rdata=pop @l; |
920
|
0
|
|
|
|
|
0
|
my $rsort=pop @l; |
921
|
|
|
|
|
|
|
|
922
|
0
|
|
|
|
|
0
|
$rc=[\@l, $rsort, $rdata, $id]; |
923
|
|
|
|
|
|
|
} |
924
|
|
|
|
|
|
|
|
925
|
0
|
|
|
|
|
0
|
$I->_putdata($pos, $I->intfmt, 0); # invalidate the record |
926
|
|
|
|
|
|
|
|
927
|
0
|
|
|
|
|
0
|
return $rc; |
928
|
|
|
|
|
|
|
} |
929
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
sub clear { |
931
|
1
|
|
|
1
|
1
|
3
|
my ($I)=@_; |
932
|
|
|
|
|
|
|
|
933
|
1
|
|
|
|
|
3
|
$I->_ct; |
934
|
|
|
|
|
|
|
|
935
|
1
|
|
|
|
|
3
|
$I->_index_end=BASEOFFSET+DATASTART*$I->_intsize; |
936
|
1
|
|
|
|
|
4
|
$I->_stringmap_end=0; |
937
|
|
|
|
|
|
|
|
938
|
1
|
|
|
|
|
3
|
$I->_idmap={}; |
939
|
1
|
|
|
|
|
75
|
$I->_strpos=[]; |
940
|
|
|
|
|
|
|
|
941
|
1
|
|
|
|
|
41
|
return; |
942
|
|
|
|
|
|
|
} |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
# sub xdata_record { |
945
|
|
|
|
|
|
|
# my ($I, $pos)=@_; |
946
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
# return unless $pos>0 and $pos<$I->mainidx; |
948
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
# # valid id nkeys key1...keyn sort data |
950
|
|
|
|
|
|
|
# my ($id, $nkeys)=unpack('x'.($pos+$I->_intsize).' '.$I->intfmt.'3', |
951
|
|
|
|
|
|
|
# ${$I->_data}); |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
# my $off=$I->_stringtbl; |
954
|
|
|
|
|
|
|
# my $data=$I->_data; |
955
|
|
|
|
|
|
|
# my $sfmt=$I->_stringfmt; |
956
|
|
|
|
|
|
|
# my @l=map { |
957
|
|
|
|
|
|
|
# unpack('x'.($off+$_).$sfmt, $$data); |
958
|
|
|
|
|
|
|
# } unpack('x'.($pos+3*$I->_intsize).' '.$I->intfmt.($nkeys+2), $$data); |
959
|
|
|
|
|
|
|
|
960
|
|
|
|
|
|
|
# my $rdata=pop @l; |
961
|
|
|
|
|
|
|
# my $rsort=pop @l; |
962
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
# #warn "data_record: keys=[@l], sort=$rsort, data=$rdata, id=$id\n"; |
964
|
|
|
|
|
|
|
# return [\@l, $rsort, $rdata, $id]; |
965
|
|
|
|
|
|
|
# } |
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
sub iterator { |
968
|
3
|
|
|
3
|
1
|
5
|
my ($I, $show_invalid)=@_; |
969
|
|
|
|
|
|
|
|
970
|
3
|
100
|
|
1
|
|
7
|
return sub {} unless $I->_data; |
|
1
|
|
|
|
|
4
|
|
971
|
|
|
|
|
|
|
|
972
|
2
|
|
|
|
|
6
|
my $pos=BASEOFFSET+DATASTART*$I->_intsize; |
973
|
2
|
|
|
|
|
7
|
my $end=$I->mainidx; |
974
|
|
|
|
|
|
|
|
975
|
|
|
|
|
|
|
return MMapDB::Iterator->new |
976
|
|
|
|
|
|
|
(sub { |
977
|
260
|
50
|
|
260
|
|
521
|
die E_NOT_IMPLEMENTED if @_; |
978
|
260
|
100
|
|
|
|
558
|
LOOP: { |
979
|
260
|
|
|
|
|
306
|
return if $pos>=$end; |
980
|
|
|
|
|
|
|
|
981
|
|
|
|
|
|
|
# valid id nkeys key1...keyn sort data |
982
|
258
|
|
|
|
|
462
|
my ($valid, undef, $nkeys)= |
983
|
258
|
|
|
|
|
835
|
unpack 'x'.$pos.' '.$I->intfmt.'3', ${$I->_data}; |
984
|
|
|
|
|
|
|
|
985
|
258
|
50
|
25
|
|
|
1107
|
if( $valid xor $show_invalid ) { |
986
|
258
|
|
|
|
|
367
|
my $rc=$pos; |
987
|
258
|
|
|
|
|
482
|
$pos+=$I->_intsize*($nkeys+5); # 5=(valid id nkeys sort data) |
988
|
258
|
|
|
|
|
936
|
return $rc; |
989
|
|
|
|
|
|
|
} |
990
|
0
|
|
|
|
|
0
|
$pos+=$I->_intsize*($nkeys+5); # 5=(valid id nkeys sort data) |
991
|
0
|
|
|
|
|
0
|
redo LOOP; |
992
|
|
|
|
|
|
|
} |
993
|
2
|
|
|
|
|
21
|
}); |
994
|
|
|
|
|
|
|
} |
995
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
package MMapDB::Iterator; |
997
|
|
|
|
|
|
|
|
998
|
2
|
|
|
2
|
|
2973
|
use strict; |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
542
|
|
999
|
|
|
|
|
|
|
|
1000
|
|
|
|
|
|
|
sub new { |
1001
|
2
|
|
|
2
|
|
5
|
my ($class, $func)=@_; |
1002
|
2
|
|
33
|
|
|
10
|
$class=ref($class) || $class; |
1003
|
2
|
|
|
|
|
16
|
return bless $func=>$class; |
1004
|
|
|
|
|
|
|
} |
1005
|
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
sub nth { |
1007
|
0
|
|
|
0
|
|
0
|
return $_[0]->(MMapDB::IT_NTH, $_[1]); |
1008
|
|
|
|
|
|
|
} |
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
sub cur { |
1011
|
0
|
|
|
0
|
|
0
|
return $_[0]->(MMapDB::IT_CUR); |
1012
|
|
|
|
|
|
|
} |
1013
|
|
|
|
|
|
|
|
1014
|
|
|
|
|
|
|
sub nelem { |
1015
|
0
|
|
|
0
|
|
0
|
return $_[0]->(MMapDB::IT_NELEM); |
1016
|
|
|
|
|
|
|
} |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
####################################################################### |
1019
|
|
|
|
|
|
|
# High Level Accessor Classes |
1020
|
|
|
|
|
|
|
####################################################################### |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
{ |
1023
|
|
|
|
|
|
|
package |
1024
|
|
|
|
|
|
|
MMapDB::_base; |
1025
|
|
|
|
|
|
|
|
1026
|
2
|
|
|
2
|
|
12
|
use strict; |
|
2
|
|
|
|
|
52
|
|
|
2
|
|
|
|
|
105
|
|
1027
|
2
|
|
|
2
|
|
12
|
use Carp qw/croak/; |
|
2
|
|
|
|
|
84
|
|
|
2
|
|
|
|
|
155
|
|
1028
|
2
|
|
|
2
|
|
11
|
use Scalar::Util (); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
208
|
|
1029
|
2
|
|
|
2
|
|
12
|
use Exporter qw/import/; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
100
|
|
1030
|
|
|
|
|
|
|
|
1031
|
|
|
|
|
|
|
use constant ({ |
1032
|
2
|
|
|
|
|
286
|
PARENT=>0, |
1033
|
|
|
|
|
|
|
POS=>1, |
1034
|
|
|
|
|
|
|
DATAMODE=>2, |
1035
|
|
|
|
|
|
|
ITERATOR=>3, |
1036
|
|
|
|
|
|
|
SHADOW=>4, |
1037
|
2
|
|
|
2
|
|
13
|
}); |
|
2
|
|
|
|
|
10
|
|
1038
|
2
|
|
|
2
|
|
1424
|
BEGIN {our @EXPORT=(qw!PARENT POS DATAMODE ITERATOR SHADOW!)}; |
1039
|
|
|
|
|
|
|
|
1040
|
|
|
|
|
|
|
sub new { |
1041
|
6
|
|
|
6
|
|
15
|
my ($class, @param)=@_; |
1042
|
6
|
|
33
|
|
|
26
|
$class=ref($class) || $class; |
1043
|
6
|
50
|
|
|
|
15
|
$param[DATAMODE]=0 unless defined $param[DATAMODE]; |
1044
|
6
|
|
|
|
|
22
|
Scalar::Util::weaken $param[0]; |
1045
|
6
|
|
|
|
|
29
|
return bless \@param=>$class; |
1046
|
|
|
|
|
|
|
} |
1047
|
|
|
|
|
|
|
|
1048
|
0
|
|
|
0
|
|
|
sub readonly {croak "Modification of a read-only value attempted";} |
1049
|
|
|
|
|
|
|
|
1050
|
0
|
|
|
0
|
|
|
sub datamode : lvalue {$_[0]->[DATAMODE]} |
1051
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
BEGIN { |
1053
|
2
|
|
|
2
|
|
8
|
*TIEHASH=\&new; |
1054
|
|
|
|
|
|
|
# STORE must be allowed to support constructs like this (with aliases): |
1055
|
|
|
|
|
|
|
# map { |
1056
|
|
|
|
|
|
|
# local $_; |
1057
|
|
|
|
|
|
|
# } values %{$db->main_index}; |
1058
|
|
|
|
|
|
|
# or |
1059
|
|
|
|
|
|
|
# for (values %{$db->main_index}) { |
1060
|
|
|
|
|
|
|
# local $_; |
1061
|
|
|
|
|
|
|
# } |
1062
|
|
|
|
|
|
|
*STORE=sub { |
1063
|
0
|
|
|
0
|
|
0
|
my ($I, $key, $value)=@_; |
1064
|
0
|
|
|
|
|
0
|
my $el; |
1065
|
0
|
|
|
|
|
0
|
my $ll=MMapDB::_localizing(); |
1066
|
|
|
|
|
|
|
# Carp::cluck "PL_localizing=$ll"; |
1067
|
|
|
|
|
|
|
|
1068
|
0
|
|
0
|
|
|
0
|
$el=($I->[SHADOW]||={}); |
1069
|
0
|
|
|
|
|
0
|
my $sh; |
1070
|
0
|
0
|
0
|
|
|
0
|
if( $ll==0 and $sh=$el->{$key} ) { # is already localized |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1071
|
|
|
|
|
|
|
# warn " ==> already shadowed"; |
1072
|
0
|
|
|
|
|
0
|
$sh->[1]=$value; |
1073
|
|
|
|
|
|
|
} elsif( $ll==1 ) { |
1074
|
|
|
|
|
|
|
# warn " ==> shadowing"; |
1075
|
0
|
|
0
|
|
|
0
|
$sh=($el->{$key}||=[]); |
1076
|
0
|
|
|
|
|
0
|
$sh->[0]++; |
1077
|
0
|
|
|
|
|
0
|
$sh->[1]=$value; |
1078
|
|
|
|
|
|
|
} elsif( $ll==2 ) { |
1079
|
0
|
0
|
|
|
|
0
|
if( --$sh->[0] ) { |
1080
|
|
|
|
|
|
|
# warn " ==> decremented shadow counter"; |
1081
|
0
|
|
|
|
|
0
|
$sh->[1]=$value; |
1082
|
|
|
|
|
|
|
} else { |
1083
|
|
|
|
|
|
|
# warn " ==> deleting shadow"; |
1084
|
0
|
|
|
|
|
0
|
delete $el->{$key}; |
1085
|
|
|
|
|
|
|
} |
1086
|
|
|
|
|
|
|
} else { |
1087
|
|
|
|
|
|
|
# warn " ==> ro"; |
1088
|
0
|
|
|
|
|
0
|
goto &readonly; |
1089
|
|
|
|
|
|
|
} |
1090
|
2
|
|
|
|
|
21
|
}; |
1091
|
2
|
|
|
|
|
5
|
*DELETE=\&readonly; |
1092
|
2
|
|
|
|
|
6
|
*CLEAR=\&readonly; |
1093
|
|
|
|
|
|
|
|
1094
|
2
|
|
|
|
|
4
|
*TIEARRAY=\&new; |
1095
|
|
|
|
|
|
|
#*STORE=sub {}; |
1096
|
2
|
|
|
|
|
4
|
*STORESIZE=\&readonly; |
1097
|
2
|
|
|
|
|
4
|
*EXTEND=\&readonly; |
1098
|
|
|
|
|
|
|
#*DELETE=\&readonly; |
1099
|
|
|
|
|
|
|
#*CLEAR=\&readonly; |
1100
|
2
|
|
|
|
|
4
|
*PUSH=\&readonly; |
1101
|
2
|
|
|
|
|
4
|
*UNSHIFT=\&readonly; |
1102
|
2
|
|
|
|
|
4
|
*POP=\&readonly; |
1103
|
2
|
|
|
|
|
4
|
*SHIFT=\&readonly; |
1104
|
2
|
|
|
|
|
87
|
*SPLICE=\&readonly; |
1105
|
|
|
|
|
|
|
} |
1106
|
|
|
|
|
|
|
} |
1107
|
|
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
####################################################################### |
1109
|
|
|
|
|
|
|
# Normal Index Accessor |
1110
|
|
|
|
|
|
|
####################################################################### |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
{ |
1113
|
|
|
|
|
|
|
package MMapDB::Index; |
1114
|
|
|
|
|
|
|
|
1115
|
2
|
|
|
2
|
|
13
|
use strict; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
172
|
|
1116
|
2
|
|
|
2
|
|
1723
|
BEGIN {MMapDB::_base->import} |
1117
|
|
|
|
|
|
|
{our @ISA=qw/MMapDB::_base/} |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
sub FETCH { |
1120
|
0
|
|
|
0
|
|
|
my ($I, $key)=@_; |
1121
|
|
|
|
|
|
|
|
1122
|
|
|
|
|
|
|
{ |
1123
|
0
|
|
|
|
|
|
my $shel; |
|
0
|
|
|
|
|
|
|
1124
|
0
|
0
|
0
|
|
|
|
$shel=$I->[SHADOW] and |
|
|
|
0
|
|
|
|
|
1125
|
|
|
|
|
|
|
keys %$shel and |
1126
|
|
|
|
|
|
|
$shel=$shel->{$key} and |
1127
|
|
|
|
|
|
|
return $shel->[1]; |
1128
|
|
|
|
|
|
|
} |
1129
|
|
|
|
|
|
|
|
1130
|
0
|
|
|
|
|
|
my @el=$I->[PARENT]->index_lookup($I->[POS], $key); |
1131
|
|
|
|
|
|
|
|
1132
|
0
|
0
|
|
|
|
|
return unless @el; |
1133
|
|
|
|
|
|
|
|
1134
|
0
|
|
|
|
|
|
my $rc; |
1135
|
|
|
|
|
|
|
|
1136
|
0
|
0
|
0
|
|
|
|
if( @el==1 and $el[0]>=$I->[PARENT]->mainidx ) { |
1137
|
|
|
|
|
|
|
# another index |
1138
|
0
|
|
|
|
|
|
tie %{$rc={}}, ref($I), $I->[PARENT], $el[0], $I->[DATAMODE]; |
|
0
|
|
|
|
|
|
|
1139
|
|
|
|
|
|
|
} else { |
1140
|
0
|
|
|
|
|
|
tie @{$rc=[]}, 'MMapDB::Data', $I->[PARENT], \@el, $I->[DATAMODE]; |
|
0
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
} |
1142
|
|
|
|
|
|
|
|
1143
|
0
|
|
|
|
|
|
return $rc; |
1144
|
|
|
|
|
|
|
} |
1145
|
|
|
|
|
|
|
|
1146
|
|
|
|
|
|
|
sub EXISTS { |
1147
|
0
|
|
|
0
|
|
|
my ($I, $key)=@_; |
1148
|
0
|
0
|
|
|
|
|
return $I->[PARENT]->index_lookup($I->[POS], $key) ? 1 : undef; |
1149
|
|
|
|
|
|
|
} |
1150
|
|
|
|
|
|
|
|
1151
|
|
|
|
|
|
|
sub FIRSTKEY { |
1152
|
0
|
|
|
0
|
|
|
my ($I)=@_; |
1153
|
0
|
|
|
|
|
|
my @el=($I->[ITERATOR]=$I->[PARENT]->index_iterator($I->[POS]))->(); |
1154
|
0
|
0
|
|
|
|
|
return @el ? $el[0] : (); |
1155
|
|
|
|
|
|
|
} |
1156
|
|
|
|
|
|
|
|
1157
|
|
|
|
|
|
|
sub NEXTKEY { |
1158
|
0
|
|
|
0
|
|
|
my ($I)=@_; |
1159
|
0
|
|
|
|
|
|
my @el=$I->[ITERATOR]->(); |
1160
|
0
|
0
|
|
|
|
|
return @el ? $el[0] : (); |
1161
|
|
|
|
|
|
|
} |
1162
|
|
|
|
|
|
|
|
1163
|
|
|
|
|
|
|
sub SCALAR { |
1164
|
0
|
|
|
0
|
|
|
my ($I)=@_; |
1165
|
0
|
0
|
|
|
|
|
my $pos=defined $I->[POS] ? $I->[POS] : $I->[PARENT]->_ididx; |
1166
|
0
|
|
|
|
|
|
my $n=unpack 'x'.$pos.$I->[PARENT]->intfmt,${$I->[PARENT]->_data}; |
|
0
|
|
|
|
|
|
|
1167
|
0
|
0
|
|
|
|
|
return $n==0 ? $n : "$n/$n"; |
1168
|
|
|
|
|
|
|
} |
1169
|
|
|
|
|
|
|
} |
1170
|
|
|
|
|
|
|
|
1171
|
|
|
|
|
|
|
####################################################################### |
1172
|
|
|
|
|
|
|
# ID Index Accessor |
1173
|
|
|
|
|
|
|
####################################################################### |
1174
|
|
|
|
|
|
|
|
1175
|
|
|
|
|
|
|
{ |
1176
|
|
|
|
|
|
|
package MMapDB::IDIndex; |
1177
|
|
|
|
|
|
|
|
1178
|
2
|
|
|
2
|
|
11
|
use strict; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
137
|
|
1179
|
2
|
|
|
2
|
|
1423
|
BEGIN {MMapDB::_base->import} |
1180
|
|
|
|
|
|
|
{our @ISA=qw/MMapDB::Index/} |
1181
|
|
|
|
|
|
|
|
1182
|
|
|
|
|
|
|
sub FETCH { |
1183
|
|
|
|
|
|
|
{ |
1184
|
0
|
|
|
0
|
|
|
my $shel; |
|
0
|
|
|
|
|
|
|
1185
|
0
|
0
|
0
|
|
|
|
$shel=$_[0]->[SHADOW] and |
|
|
|
0
|
|
|
|
|
1186
|
|
|
|
|
|
|
keys %$shel and |
1187
|
|
|
|
|
|
|
$shel=$shel->{$_[1]} and |
1188
|
|
|
|
|
|
|
return $shel->[1]; |
1189
|
|
|
|
|
|
|
} |
1190
|
|
|
|
|
|
|
|
1191
|
0
|
0
|
|
|
|
|
if( $_[0]->[DATAMODE]==MMapDB::DATAMODE_SIMPLE ) { |
1192
|
0
|
|
|
|
|
|
$_[0]->[PARENT]->data_value($_[0]->[PARENT]->id_index_lookup($_[1])); |
1193
|
|
|
|
|
|
|
} else { |
1194
|
0
|
|
|
|
|
|
$_[0]->[PARENT]->data_record($_[0]->[PARENT]->id_index_lookup($_[1])); |
1195
|
|
|
|
|
|
|
} |
1196
|
|
|
|
|
|
|
} |
1197
|
|
|
|
|
|
|
|
1198
|
|
|
|
|
|
|
sub EXISTS { |
1199
|
0
|
|
|
0
|
|
|
my ($I, $key)=@_; |
1200
|
0
|
0
|
|
|
|
|
return $I->[PARENT]->id_index_lookup($key) ? 1 : undef; |
1201
|
|
|
|
|
|
|
} |
1202
|
|
|
|
|
|
|
|
1203
|
|
|
|
|
|
|
sub FIRSTKEY { |
1204
|
0
|
|
|
0
|
|
|
my ($I)=@_; |
1205
|
0
|
|
|
|
|
|
my @el=($I->[ITERATOR]=$I->[PARENT]->id_index_iterator)->(); |
1206
|
0
|
0
|
|
|
|
|
return @el ? $el[0] : (); |
1207
|
|
|
|
|
|
|
} |
1208
|
|
|
|
|
|
|
} |
1209
|
|
|
|
|
|
|
|
1210
|
|
|
|
|
|
|
####################################################################### |
1211
|
|
|
|
|
|
|
# Data Accessor |
1212
|
|
|
|
|
|
|
####################################################################### |
1213
|
|
|
|
|
|
|
|
1214
|
|
|
|
|
|
|
{ |
1215
|
|
|
|
|
|
|
package MMapDB::Data; |
1216
|
|
|
|
|
|
|
|
1217
|
2
|
|
|
2
|
|
12
|
use strict; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
136
|
|
1218
|
2
|
|
|
2
|
|
1040
|
BEGIN {MMapDB::_base->import} |
1219
|
|
|
|
|
|
|
{our @ISA=qw/MMapDB::_base/} |
1220
|
|
|
|
|
|
|
|
1221
|
|
|
|
|
|
|
sub FETCH { |
1222
|
0
|
|
|
0
|
|
|
my ($I, $idx)=@_; |
1223
|
|
|
|
|
|
|
|
1224
|
|
|
|
|
|
|
{ |
1225
|
0
|
|
|
|
|
|
my $shel; |
|
0
|
|
|
|
|
|
|
1226
|
0
|
0
|
0
|
|
|
|
$shel=$I->[SHADOW] and |
|
|
|
0
|
|
|
|
|
1227
|
|
|
|
|
|
|
keys %$shel and |
1228
|
|
|
|
|
|
|
$shel=$shel->{$idx} and |
1229
|
|
|
|
|
|
|
return $shel->[1]; |
1230
|
|
|
|
|
|
|
} |
1231
|
|
|
|
|
|
|
|
1232
|
0
|
0
|
|
|
|
|
return unless @{$I->[POS]}>$idx; |
|
0
|
|
|
|
|
|
|
1233
|
0
|
0
|
|
|
|
|
if( $I->[DATAMODE]==MMapDB::DATAMODE_SIMPLE ) { |
1234
|
0
|
|
|
|
|
|
return $I->[PARENT]->data_value($I->[POS]->[$idx]); |
1235
|
|
|
|
|
|
|
} else { |
1236
|
0
|
|
|
|
|
|
return $I->[PARENT]->data_record($I->[POS]->[$idx]); |
1237
|
|
|
|
|
|
|
} |
1238
|
|
|
|
|
|
|
} |
1239
|
|
|
|
|
|
|
|
1240
|
0
|
|
|
0
|
|
|
sub FETCHSIZE {scalar @{$_[0]->[POS]}} |
|
0
|
|
|
|
|
|
|
1241
|
|
|
|
|
|
|
|
1242
|
0
|
|
|
0
|
|
|
sub EXISTS {@{$_[0]->[POS]}>$_[1]} |
|
0
|
|
|
|
|
|
|
1243
|
|
|
|
|
|
|
} |
1244
|
|
|
|
|
|
|
|
1245
|
|
|
|
|
|
|
1; |
1246
|
|
|
|
|
|
|
__END__ |