line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
|
=head1 NAME |
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
XBase::Index - base class for the index files for dbf |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
=cut |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
package XBase::Index; |
9
|
6
|
|
|
6
|
|
2190
|
use strict; |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
351
|
|
10
|
6
|
|
|
6
|
|
32
|
use vars qw( @ISA $DEBUG $VERSION $VERBOSE $BIGEND ); |
|
6
|
|
|
|
|
10
|
|
|
6
|
|
|
|
|
718
|
|
11
|
6
|
|
|
6
|
|
32
|
use XBase::Base; |
|
6
|
|
|
|
|
12
|
|
|
6
|
|
|
|
|
14646
|
|
12
|
|
|
|
|
|
|
@ISA = qw( XBase::Base ); |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
$VERSION = '1.05'; |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
$DEBUG = 0; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
$VERBOSE = 0 unless defined $VERBOSE; |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# We will setup global variable to denote the byte order (endian) |
21
|
|
|
|
|
|
|
my $packed = pack('d', 1); |
22
|
|
|
|
|
|
|
if ($packed eq "\077\360\000\000\000\000\000\000") { |
23
|
|
|
|
|
|
|
$BIGEND = 1; |
24
|
|
|
|
|
|
|
} elsif ($packed eq "\000\000\000\000\000\000\360\077") { |
25
|
|
|
|
|
|
|
$BIGEND = 0; |
26
|
|
|
|
|
|
|
} else { |
27
|
|
|
|
|
|
|
die "XBase::Index: your architecture is not supported.\n"; |
28
|
|
|
|
|
|
|
} |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
# Open appropriate index file and create object according to suffix |
31
|
|
|
|
|
|
|
sub new { |
32
|
16
|
|
|
16
|
1
|
40
|
my ($class, $file) = (shift, shift); |
33
|
16
|
|
|
|
|
42
|
my @opts = @_; |
34
|
16
|
50
|
|
|
|
51
|
print "XBase::Index::new($class, $file, @_)\n" if $XBase::Index::VERBOSE; |
35
|
16
|
100
|
|
|
|
51
|
if (ref $class) { @opts = ('dbf', $class, @opts); } |
|
6
|
|
|
|
|
16
|
|
36
|
16
|
|
|
|
|
100
|
my ($ext) = ($file =~ /\.(...)$/); |
37
|
16
|
|
|
|
|
36
|
$ext = lc $ext; |
38
|
|
|
|
|
|
|
|
39
|
16
|
100
|
66
|
|
|
161
|
if ($ext eq 'sdbm' or $ext eq 'pag' or $ext eq 'dir') { |
|
|
|
66
|
|
|
|
|
40
|
1
|
|
|
|
|
5
|
require XBase::SDBM; |
41
|
1
|
|
|
|
|
2
|
$ext = 'SDBM'; |
42
|
|
|
|
|
|
|
} |
43
|
|
|
|
|
|
|
|
44
|
16
|
|
|
|
|
1598
|
my $object = eval "new XBase::$ext \$file, \@opts"; |
45
|
16
|
50
|
|
|
|
167
|
return $object if defined $object; |
46
|
|
|
|
|
|
|
|
47
|
0
|
0
|
|
|
|
0
|
__PACKAGE__->Error("Error loading index: unknown extension\n") if $@; |
48
|
0
|
|
|
|
|
0
|
return; |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
# For XBase::*x object, a record is one page, object XBase::*x::Page here |
52
|
|
|
|
|
|
|
sub get_record { |
53
|
227
|
|
|
227
|
0
|
302
|
my ($self, $num) = @_; |
54
|
227
|
100
|
|
|
|
1645
|
return $self->{'pages_cache'}{$num} |
55
|
|
|
|
|
|
|
if defined $self->{'pages_cache'}{$num}; |
56
|
|
|
|
|
|
|
|
57
|
45
|
|
|
|
|
97
|
my $newpage = (ref $self) . '::Page::new'; |
58
|
45
|
|
|
|
|
190
|
my $page = $self->$newpage($num); |
59
|
|
|
|
|
|
|
|
60
|
45
|
50
|
|
|
|
123
|
if (defined $page) { |
61
|
45
|
|
|
|
|
214
|
$self->{'pages_cache'}{$num} = $page; |
62
|
|
|
|
|
|
|
|
63
|
45
|
|
|
|
|
150
|
local $^W = 0; |
64
|
45
|
50
|
|
|
|
112
|
print "Page $page->{'num'}:\tkeys: @{[ map { s/\s+$//; $_; } @{$page->{'keys'}}]}\n\tvalues: @{$page->{'values'}}\n" if $DEBUG; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
65
|
45
|
50
|
66
|
|
|
271
|
print "\tlefts: @{$page->{'lefts'}}\n" if defined $page->{'lefts'} and $DEBUG; |
|
0
|
|
|
|
|
0
|
|
66
|
|
|
|
|
|
|
} |
67
|
45
|
|
|
|
|
99
|
$page; |
68
|
|
|
|
|
|
|
} |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
# Get next (value, record number in dbf) pair |
71
|
|
|
|
|
|
|
# The important values of the index object are 'level' holding the |
72
|
|
|
|
|
|
|
# current level of the "cursor", 'pages' holding an array of pages |
73
|
|
|
|
|
|
|
# currently open for each level and 'rows' with an array of current row |
74
|
|
|
|
|
|
|
# in each level |
75
|
|
|
|
|
|
|
sub fetch { |
76
|
1366
|
|
|
1366
|
0
|
1784
|
my $self = shift; |
77
|
1366
|
|
|
|
|
1445
|
my ($level, $page, $row, $key, $val, $left); |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
# cycle while we get to the leaf record or otherwise get |
80
|
|
|
|
|
|
|
# a real value, not a pointer to lower page |
81
|
1366
|
|
|
|
|
2509
|
while (not defined $val) |
82
|
|
|
|
|
|
|
{ |
83
|
1534
|
|
|
|
|
2218
|
$level = $self->{'level'}; |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
# if we do not have level, let's start from zero |
86
|
1534
|
100
|
|
|
|
2850
|
if (not defined $level) { |
87
|
14
|
|
|
|
|
31
|
$level = $self->{'level'} = 0; |
88
|
14
|
|
|
|
|
67
|
$page = $self->get_record($self->{'start_page'}); |
89
|
14
|
50
|
|
|
|
116
|
if (not defined $page) { |
90
|
0
|
|
|
|
|
0
|
$self->Error("Index corrupt: $self: no root page $self->{'start_page'}\n"); |
91
|
0
|
|
|
|
|
0
|
return; |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
# and initialize 'pages' and 'rows' |
94
|
14
|
|
|
|
|
38
|
$self->{'pages'} = [ $page ]; |
95
|
14
|
|
|
|
|
50
|
$self->{'rows'} = []; |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
# get current page for this level |
99
|
1534
|
|
|
|
|
3014
|
$page = $self->{'pages'}[$level]; |
100
|
1534
|
50
|
|
|
|
2586
|
if (not defined $page) { |
101
|
0
|
|
|
|
|
0
|
$self->Error("Index corrupt: $self: page for level $level lost in normal course\n"); |
102
|
0
|
|
|
|
|
0
|
return; |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# get current row for current level and increase it |
106
|
|
|
|
|
|
|
# (or setup to zero) |
107
|
1534
|
|
|
|
|
2035
|
my $row = $self->{'rows'}[$level]; |
108
|
1534
|
100
|
|
|
|
2510
|
if (not defined $row) { |
109
|
142
|
|
|
|
|
235
|
$row = $self->{'rows'}[$level] = 0; |
110
|
|
|
|
|
|
|
} else { |
111
|
1392
|
|
|
|
|
2153
|
$self->{'rows'}[$level] = ++$row; |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
# get the (key, value, pointer) from the page |
115
|
1534
|
|
|
|
|
3041
|
($key, $val, $left) = $page->get_key_val_left($row); |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
# there is another page to walk |
118
|
1534
|
100
|
|
|
|
3721
|
if (defined $left) { |
119
|
|
|
|
|
|
|
# go deeper |
120
|
118
|
|
|
|
|
134
|
$level++; |
121
|
118
|
|
|
|
|
137
|
my $oldpage = $page; |
122
|
|
|
|
|
|
|
# load the next page |
123
|
118
|
|
|
|
|
229
|
$page = $self->get_record($left); |
124
|
118
|
50
|
|
|
|
242
|
if (not defined $page) { |
125
|
0
|
|
|
|
|
0
|
$self->Error("Index corrupt: $self: no page $left, ref'd from $oldpage, row $row, level $level\n"); |
126
|
0
|
|
|
|
|
0
|
return; |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
# and put it into the structure |
129
|
118
|
|
|
|
|
194
|
$self->{'pages'}[$level] = $page; |
130
|
118
|
|
|
|
|
166
|
$self->{'rows'}[$level] = undef; |
131
|
118
|
|
|
|
|
152
|
$self->{'level'} = $level; |
132
|
|
|
|
|
|
|
# and even if some index structures allow the |
133
|
|
|
|
|
|
|
# value in the same row as record, we want to |
134
|
|
|
|
|
|
|
# skip it when going down |
135
|
118
|
|
|
|
|
121
|
$val = undef; |
136
|
118
|
|
|
|
|
271
|
next; |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
# if we're lucky and got the value, return it |
139
|
1416
|
100
|
|
|
|
2198
|
if (defined $val) { |
140
|
1212
|
|
|
|
|
10457
|
return ($key, $val); |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
# we neither got link to lower page, nor the value |
143
|
|
|
|
|
|
|
# so it means we are backtracking the structure one |
144
|
|
|
|
|
|
|
# (or more) levels back |
145
|
|
|
|
|
|
|
else { |
146
|
204
|
|
|
|
|
295
|
$self->{'level'} = --$level; # go up the levels |
147
|
204
|
100
|
|
|
|
596
|
return if $level < 0; # do not fall over |
148
|
153
|
|
|
|
|
238
|
$page = $self->{'pages'}[$level]; |
149
|
153
|
50
|
|
|
|
253
|
if (not defined $page) |
150
|
|
|
|
|
|
|
{ |
151
|
0
|
|
|
|
|
0
|
$self->Error("Index corrupt: $self: page for level $level lost when backtracking\n"); |
152
|
0
|
|
|
|
|
0
|
return; |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
### next unless defined $page; |
155
|
153
|
|
|
|
|
206
|
$row = $self->{'rows'}[$level]; |
156
|
153
|
|
|
|
|
293
|
my ($backkey, $backval, $backleft) = $page->get_key_val_left($row); |
157
|
|
|
|
|
|
|
# this is a hook for ntx files where we do not |
158
|
|
|
|
|
|
|
# want to miss a values that are stored inside |
159
|
|
|
|
|
|
|
# the structure, not only in leaves. |
160
|
153
|
100
|
100
|
|
|
1016
|
if (not defined $page->{'last_key_is_just_overflow'} and defined $backleft and defined $backval) { |
|
|
|
100
|
|
|
|
|
161
|
103
|
|
|
|
|
417
|
return ($backkey, $backval); |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
} |
165
|
0
|
|
|
|
|
0
|
return; |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
# Get list of tags in the indexfile (an indexfile may not have any) |
169
|
|
|
|
|
|
|
sub tags { |
170
|
1
|
|
|
1
|
0
|
1
|
my $self = shift; |
171
|
1
|
50
|
|
|
|
6
|
@{$self->{'tags'}} if defined $self->{'tags'}; |
|
1
|
|
|
|
|
8
|
|
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
# Method allowing to refetch the active values (key, val) without |
175
|
|
|
|
|
|
|
# rolling forward |
176
|
|
|
|
|
|
|
sub fetch_current { |
177
|
20
|
|
|
20
|
0
|
23
|
my $self = shift; |
178
|
20
|
|
|
|
|
22
|
my $level = $self->{'level'}; |
179
|
20
|
|
|
|
|
25
|
my $page = $self->{'pages'}[$level]; |
180
|
20
|
|
|
|
|
23
|
my $row = $self->{'rows'}[$level]; |
181
|
20
|
|
|
|
|
33
|
my ($key, $val, $left) = $page->get_key_val_left($row); |
182
|
20
|
|
|
|
|
45
|
return ($key, $val); |
183
|
|
|
|
|
|
|
} |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
# Rewind the index to start |
186
|
|
|
|
|
|
|
# the easiest way to do this is to cancel the 'level' -- this way we |
187
|
|
|
|
|
|
|
# do not know where we are and we have to start anew |
188
|
|
|
|
|
|
|
sub prepare_select { |
189
|
77
|
|
|
77
|
0
|
523
|
my $self = shift; |
190
|
77
|
|
|
|
|
562
|
delete $self->{'level'}; |
191
|
77
|
|
|
|
|
204
|
delete $self->{'pages'}; |
192
|
77
|
|
|
|
|
133
|
delete $self->{'rows'}; |
193
|
77
|
|
|
|
|
156
|
1; |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# Position index to a value (or behind it, if nothing found), so that |
197
|
|
|
|
|
|
|
# next fetch fetches the correct value |
198
|
|
|
|
|
|
|
sub prepare_select_eq { |
199
|
59
|
|
|
59
|
0
|
114
|
my ($self, $eq, $recno) = @_; |
200
|
59
|
|
|
|
|
147
|
$self->prepare_select(); # start from scratch |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
### { local $^W = 0; print STDERR "Will look for $eq $recno\n"; } |
203
|
|
|
|
|
|
|
|
204
|
59
|
|
|
|
|
122
|
my $left = $self->{'start_page'}; |
205
|
59
|
|
|
|
|
77
|
my $level = 0; |
206
|
59
|
|
|
|
|
80
|
my $parent = undef; |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
# we'll need to know if we want numeric or string compares |
209
|
59
|
100
|
|
|
|
164
|
my $numdate = ($self->{'key_type'} ? 1 : 0); |
210
|
|
|
|
|
|
|
|
211
|
59
|
|
|
|
|
70
|
while (1) { |
212
|
95
|
|
|
|
|
213
|
my $page = $self->get_record($left); # get page |
213
|
95
|
50
|
|
|
|
229
|
if (not defined $page) { |
214
|
0
|
|
|
|
|
0
|
$self->Error("Index corrupt: $self: no page $left for level $level\n"); |
215
|
0
|
|
|
|
|
0
|
return; |
216
|
|
|
|
|
|
|
} |
217
|
95
|
|
|
|
|
223
|
my $row = 0; |
218
|
95
|
|
|
|
|
104
|
my ($key, $val); |
219
|
95
|
|
|
|
|
190
|
my $empty = 1; |
220
|
95
|
|
|
|
|
255
|
while (($key, $val, my $newleft) = $page->get_key_val_left($row)) { |
221
|
|
|
|
|
|
|
### { local $^W = 0; print "Got: $key, $val, $newleft ($numdate)\n"; } |
222
|
|
|
|
|
|
|
|
223
|
584
|
|
|
|
|
845
|
$empty = 0; # There is at least 1 key |
224
|
584
|
|
|
|
|
669
|
$left = $newleft; |
225
|
|
|
|
|
|
|
# Joe Campbell says: |
226
|
|
|
|
|
|
|
# Compound char keys have two parts preceded by white space |
227
|
|
|
|
|
|
|
# get rid of the white space so that I can do a matching.... |
228
|
|
|
|
|
|
|
# and suggests |
229
|
|
|
|
|
|
|
# $key =~ s/^\s*//g; |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# finish if we are at the end of the page or |
233
|
|
|
|
|
|
|
# behind the correct value |
234
|
584
|
50
|
|
|
|
1162
|
if (not defined $key) |
235
|
0
|
|
|
|
|
0
|
{ last; } |
236
|
584
|
100
|
|
|
|
1726
|
if ($numdate == 1 ? $key >= $eq : $key ge $eq) |
|
|
100
|
|
|
|
|
|
237
|
85
|
|
|
|
|
112
|
{ last; } |
238
|
499
|
|
|
|
|
2825
|
$row++; |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
# we know where we are positioned on the page now |
242
|
95
|
|
|
|
|
305
|
$self->{'pages'}[$level] = $page; |
243
|
95
|
|
|
|
|
190
|
$self->{'rows'}[$level] = $row; |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
# if there is no lower level |
246
|
95
|
100
|
66
|
|
|
424
|
if ($empty or not defined $left) { |
247
|
59
|
100
|
|
|
|
190
|
$self->{'rows'}[$level] = ( $row ? $row - 1: undef); |
248
|
59
|
|
|
|
|
96
|
$self->{'level'} = $level; |
249
|
59
|
|
|
|
|
102
|
last; |
250
|
|
|
|
|
|
|
} |
251
|
36
|
100
|
|
|
|
97
|
$page->{'parent'} = $parent->{'num'} if defined $parent; |
252
|
36
|
|
|
|
|
44
|
$parent = $page; |
253
|
36
|
|
|
|
|
49
|
$level++; |
254
|
|
|
|
|
|
|
} |
255
|
59
|
100
|
|
|
|
131
|
if (defined $recno) { # exact match requested |
256
|
|
|
|
|
|
|
# get current values |
257
|
10
|
|
|
|
|
31
|
my ($key, $val) = $self->fetch_current; |
258
|
10
|
|
|
|
|
20
|
while (defined $val) { |
259
|
20
|
50
|
|
|
|
47
|
last if ($numdate ? $key > $eq : $key gt $eq); |
|
|
100
|
|
|
|
|
|
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
# if we're here, we still have exact match |
262
|
10
|
50
|
|
|
|
19
|
last if $val == $recno; |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
# move forward |
265
|
10
|
|
|
|
|
45
|
($key, $val) = $self->fetch; |
266
|
|
|
|
|
|
|
} |
267
|
|
|
|
|
|
|
} |
268
|
59
|
|
|
|
|
170
|
1; |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
# Get (key, dbf record number, lower page index) from the index page |
272
|
|
|
|
|
|
|
sub get_key_val_left { |
273
|
2301
|
|
|
2301
|
0
|
2903
|
my ($self, $num) = @_; |
274
|
|
|
|
|
|
|
{ |
275
|
2301
|
|
|
|
|
2261
|
local $^W = 0; |
|
2301
|
|
|
|
|
5071
|
|
276
|
2301
|
|
|
|
|
3824
|
my $printkey = $self->{'keys'}[$num]; |
277
|
2301
|
|
|
|
|
6231
|
$printkey =~ s/\s+$//; |
278
|
2301
|
|
|
|
|
4094
|
$printkey =~ s/\000/\\0/g; |
279
|
2301
|
50
|
|
|
|
4593
|
print "Getkeyval: Page $self->{'num'}, row $num: $printkey, $self->{'values'}[$num], $self->{'lefts'}[$num]\n" |
280
|
|
|
|
|
|
|
if $DEBUG > 5; |
281
|
2301
|
|
|
|
|
13188
|
return ($self->{'keys'}[$num], $self->{'values'}[$num], $self->{'lefts'}[$num]) |
282
|
2301
|
100
|
|
|
|
2351
|
if $num <= $#{$self->{'keys'}}; |
283
|
|
|
|
|
|
|
} |
284
|
222
|
|
|
|
|
470
|
return; |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
sub num_keys { |
288
|
0
|
|
|
0
|
0
|
0
|
$#{shift->{'keys'}}; |
|
0
|
|
|
|
|
0
|
|
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
sub delete { |
292
|
5
|
|
|
5
|
0
|
13
|
my ($self, $key, $value) = @_; |
293
|
5
|
50
|
|
|
|
13
|
print "XBase::Index::delete($key, $value) called ($self->{'tag'} -> $self->{'key_string'}/$self->{'for_string'})\n" if $XBase::Index::VERBOSE; |
294
|
5
|
50
|
|
|
|
13
|
$self->prepare_select_eq($key, $value) or return; |
295
|
5
|
|
|
|
|
12
|
my ($foundkey, $foundvalue) = $self->fetch_current; |
296
|
|
|
|
|
|
|
|
297
|
5
|
50
|
33
|
|
|
28
|
if (defined $foundvalue |
|
|
|
33
|
|
|
|
|
298
|
|
|
|
|
|
|
and $foundkey eq $key and $foundvalue == $value) { |
299
|
0
|
|
|
|
|
0
|
$self->delete_current; |
300
|
0
|
|
|
|
|
0
|
return 1; |
301
|
|
|
|
|
|
|
} |
302
|
5
|
50
|
|
|
|
22
|
print "$key/$value is not in the index (wanted to delete)\n" if $XBase::Index::VERBOSE; |
303
|
5
|
|
|
|
|
15
|
undef; |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
sub insert { |
306
|
5
|
|
|
5
|
0
|
10
|
my ($self, $key, $value) = @_; |
307
|
5
|
50
|
|
|
|
14
|
print "XBase::Index::insert($key, $value) called\n" if $XBase::Index::VERBOSE; |
308
|
|
|
|
|
|
|
|
309
|
5
|
50
|
|
|
|
10
|
$self->prepare_select_eq($key, $value) or return; |
310
|
5
|
|
|
|
|
9
|
my ($foundkey, $foundvalue) = $self->fetch_current; |
311
|
|
|
|
|
|
|
|
312
|
5
|
50
|
33
|
|
|
28
|
if (defined $foundvalue |
|
|
|
33
|
|
|
|
|
313
|
|
|
|
|
|
|
and $foundkey eq $key and $foundvalue == $value) { |
314
|
0
|
|
|
|
|
0
|
print STDERR "Already found, strange.\n"; |
315
|
0
|
|
|
|
|
0
|
return; |
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
|
318
|
5
|
|
|
|
|
25
|
$self->insert_before_current($key, $value); |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
sub delete_current { |
322
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
323
|
0
|
0
|
|
|
|
0
|
print "Delete_current called\n" if $XBase::Index::VERBOSE; |
324
|
0
|
|
|
|
|
0
|
my $level = $self->{'level'}; |
325
|
0
|
|
|
|
|
0
|
my $page = $self->{'pages'}[$level]; |
326
|
0
|
|
|
|
|
0
|
my $row = $self->{'rows'}[$level]; |
327
|
|
|
|
|
|
|
|
328
|
0
|
|
|
|
|
0
|
splice @{$page->{'values'}}, $row, 1; |
|
0
|
|
|
|
|
0
|
|
329
|
0
|
|
|
|
|
0
|
splice @{$page->{'keys'}}, $row, 1; |
|
0
|
|
|
|
|
0
|
|
330
|
0
|
|
|
|
|
0
|
splice @{$page->{'lefts'}}, $row, 1; |
|
0
|
|
|
|
|
0
|
|
331
|
|
|
|
|
|
|
|
332
|
0
|
|
|
|
|
0
|
$self->{'rows'}[$level]--; |
333
|
0
|
0
|
|
|
|
0
|
if ($self->{'rows'}[$level] < 0) { |
334
|
0
|
|
|
|
|
0
|
$self->{'rows'}[$level] = undef; |
335
|
|
|
|
|
|
|
} |
336
|
|
|
|
|
|
|
|
337
|
0
|
|
|
|
|
0
|
$page->write_with_context; |
338
|
|
|
|
|
|
|
|
339
|
0
|
|
|
|
|
0
|
delete $self->{'pages_cache'}; |
340
|
|
|
|
|
|
|
|
341
|
0
|
0
|
|
|
|
0
|
print STDERR "Delete_current returning\n" if $DEBUG; |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
sub insert_before_current { |
345
|
5
|
|
|
5
|
0
|
6
|
my ($self, $key, $value) = @_; |
346
|
5
|
50
|
|
|
|
16
|
print "Insert_current called ($key $value)\n" if $XBase::Index::VERBOSE; |
347
|
5
|
|
|
|
|
8
|
my $level = $self->{'level'}; |
348
|
5
|
|
|
|
|
7
|
my $page = $self->{'pages'}[$level]; |
349
|
5
|
|
|
|
|
8
|
my $row = $self->{'rows'}[$level]; |
350
|
5
|
50
|
|
|
|
12
|
$row = 0 unless defined $row; |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
# update keys and values and then call save |
353
|
5
|
|
|
|
|
6
|
splice @{$page->{'keys'}}, $row, 0, $key; |
|
5
|
|
|
|
|
13
|
|
354
|
5
|
|
|
|
|
11
|
splice @{$page->{'values'}}, $row, 0, $value; |
|
5
|
|
|
|
|
9
|
|
355
|
5
|
50
|
|
|
|
11
|
splice @{$page->{'lefts'}}, $row, 0, undef if defined $page->{'lefts'}; |
|
5
|
|
|
|
|
11
|
|
356
|
|
|
|
|
|
|
|
357
|
5
|
|
|
|
|
11
|
$page->write_with_context; |
358
|
|
|
|
|
|
|
|
359
|
5
|
|
|
|
|
45
|
delete $self->{'pages_cache'}; |
360
|
|
|
|
|
|
|
|
361
|
5
|
50
|
|
|
|
32
|
print STDERR "Insert_current returning\n" if $DEBUG; |
362
|
|
|
|
|
|
|
} |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
# ############# |
365
|
|
|
|
|
|
|
# dBase III NDX |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
package XBase::ndx; |
368
|
6
|
|
|
6
|
|
37
|
use strict; |
|
6
|
|
|
|
|
12
|
|
|
6
|
|
|
|
|
194
|
|
369
|
6
|
|
|
6
|
|
27
|
use vars qw( @ISA $DEBUG ); |
|
6
|
|
|
|
|
8
|
|
|
6
|
|
|
|
|
1626
|
|
370
|
|
|
|
|
|
|
@ISA = qw( XBase::Base XBase::Index ); |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
*DEBUG = \$XBase::Index::DEBUG; |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
sub read_header { |
375
|
3
|
|
|
3
|
|
6
|
my $self = shift; |
376
|
3
|
|
|
|
|
7
|
my %opts = @_; |
377
|
3
|
|
|
|
|
7
|
my $header; |
378
|
3
|
|
|
|
|
8
|
$self->{'dbf'} = $opts{'dbf'}; |
379
|
|
|
|
|
|
|
$self->{'fh'}->read($header, 512) == 512 or do |
380
|
3
|
50
|
|
|
|
12
|
{ __PACKAGE__->Error("Error reading header of $self->{'filename'}: $!\n"); return; }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
381
|
3
|
|
|
|
|
111
|
@{$self}{ qw( start_page total_pages key_length keys_per_page |
|
3
|
|
|
|
|
32
|
|
382
|
|
|
|
|
|
|
key_type key_record_length unique key_string ) } |
383
|
|
|
|
|
|
|
= unpack 'VV @12vvvv @23c a*', $header; |
384
|
|
|
|
|
|
|
|
385
|
3
|
|
|
|
|
21
|
$self->{'key_string'} =~ s/[\000 ].*$//s; |
386
|
3
|
|
|
|
|
7
|
$self->{'record_len'} = 512; |
387
|
3
|
|
|
|
|
4
|
$self->{'header_len'} = 0; |
388
|
|
|
|
|
|
|
|
389
|
3
|
|
|
|
|
22
|
$self; |
390
|
|
|
|
|
|
|
} |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
sub last_record { |
393
|
30
|
|
|
30
|
|
142
|
shift->{'total_pages'}; |
394
|
|
|
|
|
|
|
} |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
package XBase::ndx::Page; |
397
|
6
|
|
|
6
|
|
38
|
use strict; |
|
6
|
|
|
|
|
21
|
|
|
6
|
|
|
|
|
192
|
|
398
|
6
|
|
|
6
|
|
29
|
use vars qw( @ISA $DEBUG ); |
|
6
|
|
|
|
|
10
|
|
|
6
|
|
|
|
|
2942
|
|
399
|
|
|
|
|
|
|
@ISA = qw( XBase::ndx ); |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
*DEBUG = \$XBase::Index::DEBUG; |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
# Constructor for the ndx page |
404
|
|
|
|
|
|
|
sub new { |
405
|
15
|
|
|
15
|
|
27
|
my ($indexfile, $num) = @_; |
406
|
15
|
|
|
|
|
18
|
my $parent; |
407
|
|
|
|
|
|
|
# we can be called from parent page |
408
|
15
|
50
|
|
|
|
42
|
if ((ref $indexfile) =~ /::Page$/) { |
409
|
0
|
|
|
|
|
0
|
$parent = $indexfile; |
410
|
0
|
|
|
|
|
0
|
$indexfile = $parent->{'indexfile'}; |
411
|
|
|
|
|
|
|
} |
412
|
|
|
|
|
|
|
|
413
|
15
|
50
|
|
|
|
50
|
my $data = $indexfile->read_record($num) or return; # get 512 bytes |
414
|
15
|
|
|
|
|
34
|
my $noentries = unpack 'V', $data; # num of entries |
415
|
|
|
|
|
|
|
|
416
|
15
|
|
|
|
|
23
|
my $keylength = $indexfile->{'key_length'}; |
417
|
15
|
|
|
|
|
21
|
my $keyreclength = $indexfile->{'key_record_length'}; # length |
418
|
|
|
|
|
|
|
|
419
|
15
|
50
|
|
|
|
32
|
print "page $num, noentries $noentries, keylength $keylength\n" if $DEBUG; |
420
|
15
|
|
|
|
|
22
|
my $numdate = $indexfile->{'key_type'}; # numeric or string? |
421
|
|
|
|
|
|
|
|
422
|
15
|
|
|
|
|
17
|
my $offset = 4; |
423
|
15
|
|
|
|
|
27
|
my $i = 0; |
424
|
15
|
|
|
|
|
34
|
my ($keys, $values, $lefts) = ([], [], []); # three arrays |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
# walk the page |
427
|
15
|
|
|
|
|
39
|
while ($i < $noentries) { |
428
|
|
|
|
|
|
|
# get the values for entry |
429
|
147
|
|
|
|
|
575
|
my ($left, $recno, $key) |
430
|
|
|
|
|
|
|
= unpack 'VVa*', substr($data, $offset, $keylength + 8); |
431
|
147
|
100
|
|
|
|
367
|
if ($numdate) { # some decoding for numbers |
432
|
110
|
50
|
|
|
|
217
|
$key = reverse $key if $XBase::Index::BIGEND; |
433
|
110
|
|
|
|
|
181
|
$key = unpack 'd', $key; |
434
|
|
|
|
|
|
|
} |
435
|
147
|
50
|
|
|
|
387
|
print "$i: \@$offset VVa$keylength -> ($left, $recno, $key)\n" if $DEBUG > 1; |
436
|
147
|
|
|
|
|
310
|
push @$keys, $key; |
437
|
147
|
100
|
|
|
|
296
|
push @$values, ($recno ? $recno : undef); |
438
|
147
|
100
|
|
|
|
628
|
$left = ($left ? $left : undef); |
439
|
147
|
|
|
|
|
197
|
push @$lefts, $left; |
440
|
|
|
|
|
|
|
|
441
|
147
|
100
|
100
|
|
|
414
|
if ($i == 0 and defined $left) |
442
|
5
|
|
|
|
|
10
|
{ $noentries++; } # fixup for nonleaf page |
443
|
|
|
|
|
|
|
### shouldn't this be for last page only? |
444
|
|
|
|
|
|
|
} |
445
|
|
|
|
|
|
|
continue { |
446
|
147
|
|
|
|
|
157
|
$i++; |
447
|
147
|
|
|
|
|
394
|
$offset += $keyreclength; |
448
|
|
|
|
|
|
|
} |
449
|
|
|
|
|
|
|
|
450
|
15
|
|
|
|
|
102
|
my $self = bless { 'keys' => $keys, 'values' => $values, |
451
|
|
|
|
|
|
|
'num' => $num, 'keylength' => $keylength, |
452
|
|
|
|
|
|
|
'lefts' => $lefts, 'indexfile' => $indexfile }, __PACKAGE__; |
453
|
|
|
|
|
|
|
|
454
|
15
|
100
|
33
|
|
|
89
|
if ($num == $indexfile->{'start_page'} |
|
0
|
|
66
|
|
|
0
|
|
455
|
|
|
|
|
|
|
or (defined |
456
|
|
|
|
|
|
|
$parent->{'last_key_is_just_overflow'} and |
457
|
|
|
|
|
|
|
$parent->{'lefts'}[$#{$parent->{'lefts'}}] == $num)) { |
458
|
3
|
|
|
|
|
18
|
$self->{'last_key_is_just_overflow'} = 1; |
459
|
|
|
|
|
|
|
} |
460
|
|
|
|
|
|
|
|
461
|
15
|
|
|
|
|
40
|
$self; |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
# ########### |
465
|
|
|
|
|
|
|
# Clipper NTX |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
package XBase::ntx; |
468
|
6
|
|
|
6
|
|
38
|
use strict; |
|
6
|
|
|
|
|
15
|
|
|
6
|
|
|
|
|
219
|
|
469
|
6
|
|
|
6
|
|
30
|
use vars qw( @ISA $DEBUG ); |
|
6
|
|
|
|
|
10
|
|
|
6
|
|
|
|
|
2949
|
|
470
|
|
|
|
|
|
|
@ISA = qw( XBase::Base XBase::Index ); |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
sub read_header { |
473
|
1
|
|
|
1
|
|
2
|
my $self = shift; |
474
|
1
|
|
|
|
|
2
|
my %opts = @_; |
475
|
1
|
|
|
|
|
2
|
my $header; |
476
|
1
|
|
|
|
|
2
|
$self->{'dbf'} = $opts{'dbf'}; |
477
|
|
|
|
|
|
|
$self->{'fh'}->read($header, 1024) == 1024 or do |
478
|
1
|
50
|
|
|
|
5
|
{ __PACKAGE__->Error("Error reading header of $self->{'filename'}: $!\n"); return; }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
479
|
|
|
|
|
|
|
|
480
|
1
|
|
|
|
|
41
|
@{$self}{ qw( signature compiler_version start_offset first_unused |
|
1
|
|
|
|
|
18
|
|
481
|
|
|
|
|
|
|
key_record_length key_length decimals max_item |
482
|
|
|
|
|
|
|
half_page key_string unique ) } |
483
|
|
|
|
|
|
|
= unpack 'vvVVvvvvvA256c', $header; |
484
|
|
|
|
|
|
|
|
485
|
1
|
|
|
|
|
4
|
my $key_string = uc $self->{'key_string'}; |
486
|
1
|
|
|
|
|
5
|
$key_string =~ s/^.*?->//; |
487
|
1
|
|
|
|
|
2
|
$self->{'key_string'} = $key_string; |
488
|
|
|
|
|
|
|
|
489
|
1
|
50
|
33
|
|
|
8
|
if ($self->{'signature'} != 3 and $self->{'signature'} != 6) { |
490
|
0
|
|
|
|
|
0
|
__PACKAGE__->Error("$self: bad signature value `$self->{'signature'}' found\n"); |
491
|
0
|
|
|
|
|
0
|
return; |
492
|
|
|
|
|
|
|
} |
493
|
1
|
|
|
|
|
4
|
$self->{'key_string'} =~ s/[\000 ].*$//s; |
494
|
1
|
|
|
|
|
3
|
$self->{'record_len'} = 1024; |
495
|
1
|
|
|
|
|
3
|
$self->{'header_len'} = 0; |
496
|
|
|
|
|
|
|
|
497
|
1
|
|
|
|
|
5
|
$self->{'start_page'} = int($self->{'start_offset'} / $self->{'record_len'}); |
498
|
1
|
|
|
|
|
2
|
my $field_type; |
499
|
1
|
50
|
|
|
|
5
|
if (defined $opts{'type'}) { |
|
|
50
|
|
|
|
|
|
500
|
0
|
|
|
|
|
0
|
$field_type = $opts{'type'}; |
501
|
|
|
|
|
|
|
} elsif (defined $self->{'dbf'}) { |
502
|
1
|
|
|
|
|
6
|
$field_type = $self->{'dbf'}->field_type($key_string); |
503
|
1
|
50
|
|
|
|
4
|
if (not defined $field_type) { |
504
|
0
|
|
|
|
|
0
|
__PACKAGE__->Error("Couldn't find key string `$key_string' in dbf file, can't determine field type\n"); |
505
|
0
|
|
|
|
|
0
|
return; |
506
|
|
|
|
|
|
|
} |
507
|
|
|
|
|
|
|
} else { |
508
|
0
|
|
|
|
|
0
|
__PACKAGE__->Error("Index type (char/numeric) unknown for $self\n"); |
509
|
0
|
|
|
|
|
0
|
return; |
510
|
|
|
|
|
|
|
} |
511
|
1
|
50
|
|
|
|
3
|
$self->{'key_type'} = ($field_type =~ /^[NDIF]$/ ? 1 : 0); |
512
|
|
|
|
|
|
|
|
513
|
1
|
|
|
|
|
7
|
$self; |
514
|
|
|
|
|
|
|
} |
515
|
|
|
|
|
|
|
sub last_record { |
516
|
9
|
|
|
9
|
|
30
|
-1; |
517
|
|
|
|
|
|
|
} |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
package XBase::ntx::Page; |
521
|
6
|
|
|
6
|
|
40
|
use strict; |
|
6
|
|
|
|
|
12
|
|
|
6
|
|
|
|
|
257
|
|
522
|
6
|
|
|
6
|
|
33
|
use vars qw( @ISA $DEBUG ); |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
2828
|
|
523
|
|
|
|
|
|
|
@ISA = qw( XBase::ntx ); |
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
*DEBUG = \$XBase::Index::DEBUG; |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
# Constructor for the ntx page |
528
|
|
|
|
|
|
|
sub new { |
529
|
9
|
|
|
9
|
|
12
|
my ($indexfile, $num) = @_; |
530
|
9
|
|
|
|
|
8
|
my $parent; |
531
|
|
|
|
|
|
|
# we could be called from parent page |
532
|
9
|
50
|
|
|
|
21
|
if ((ref $indexfile) =~ /::Page$/) { |
533
|
0
|
|
|
|
|
0
|
$parent = $indexfile; |
534
|
0
|
|
|
|
|
0
|
$indexfile = $parent->{'indexfile'}; |
535
|
|
|
|
|
|
|
} |
536
|
9
|
50
|
|
|
|
31
|
my $data = $indexfile->read_record($num) or return; # get data |
537
|
9
|
|
|
|
|
19
|
my $maxnumitem = $indexfile->{'max_item'} + 1; # limit from header |
538
|
9
|
|
|
|
|
11
|
my $keylength = $indexfile->{'key_length'}; |
539
|
9
|
|
|
|
|
11
|
my $record_len = $indexfile->{'record_len'}; # length |
540
|
|
|
|
|
|
|
|
541
|
9
|
|
|
|
|
13
|
my $numdate = $indexfile->{'key_type'}; # numeric or string? |
542
|
|
|
|
|
|
|
|
543
|
9
|
|
|
|
|
53
|
my ($noentries, @pointers) = unpack "vv$maxnumitem", $data; |
544
|
|
|
|
|
|
|
# get pointers where the entries are |
545
|
|
|
|
|
|
|
|
546
|
9
|
50
|
|
|
|
20
|
print "page $num, noentries $noentries, keylength $keylength; pointers @pointers\n" if $DEBUG; |
547
|
|
|
|
|
|
|
|
548
|
9
|
|
|
|
|
19
|
my ($keys, $values, $lefts) = ([], [], []); |
549
|
|
|
|
|
|
|
# walk the pointers |
550
|
9
|
|
|
|
|
23
|
for (my $i = 0; $i < $noentries; $i++) { |
551
|
69
|
|
|
|
|
74
|
my $offset = $pointers[$i]; |
552
|
69
|
|
|
|
|
209
|
my ($left, $recno, $key) |
553
|
|
|
|
|
|
|
= unpack 'VVa*', substr($data, $offset, $keylength + 8); |
554
|
|
|
|
|
|
|
|
555
|
69
|
50
|
|
|
|
125
|
if ($numdate) { |
556
|
|
|
|
|
|
|
### if looks like with ntx the numbers are |
557
|
|
|
|
|
|
|
### stored as ASCII strings or something |
558
|
|
|
|
|
|
|
### To Be Done |
559
|
0
|
0
|
|
|
|
0
|
if ($key =~ tr!,+*)('&%$#"!0123456789!) { $key = '-' . $key; } |
|
0
|
|
|
|
|
0
|
|
560
|
0
|
|
|
|
|
0
|
$key += 0; |
561
|
|
|
|
|
|
|
} |
562
|
|
|
|
|
|
|
|
563
|
69
|
50
|
|
|
|
121
|
print "$i: \@$offset VVa$keylength -> ($left, $recno, $key)\n" if $DEBUG > 1; |
564
|
69
|
|
|
|
|
100
|
push @$keys, $key; |
565
|
69
|
100
|
|
|
|
128
|
push @$values, ($recno ? $recno : undef); |
566
|
69
|
100
|
|
|
|
115
|
$left = ($left ? ($left / $record_len) : undef); |
567
|
69
|
|
|
|
|
82
|
push @$lefts, $left; |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
### if ($i == 0 and defined $left and (not defined $parent or $num == $parent->{'lefts'}[-1])) |
570
|
69
|
100
|
100
|
|
|
248
|
if ($i == 0 and defined $left) |
571
|
1
|
|
|
|
|
80
|
{ $noentries++; } |
572
|
|
|
|
|
|
|
### shouldn't this be for last page only? |
573
|
|
|
|
|
|
|
} |
574
|
|
|
|
|
|
|
|
575
|
9
|
|
|
|
|
61
|
my $self = bless { 'num' => $num, 'indexfile' => $indexfile, |
576
|
|
|
|
|
|
|
'keys' => $keys, 'values' => $values, 'lefts' => $lefts, }, |
577
|
|
|
|
|
|
|
__PACKAGE__; |
578
|
9
|
|
|
|
|
25
|
$self; |
579
|
|
|
|
|
|
|
} |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
# ########### |
582
|
|
|
|
|
|
|
# FoxBase IDX |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
package XBase::idx; |
585
|
6
|
|
|
6
|
|
40
|
use strict; |
|
6
|
|
|
|
|
18
|
|
|
6
|
|
|
|
|
225
|
|
586
|
6
|
|
|
6
|
|
32
|
use vars qw( @ISA $DEBUG ); |
|
6
|
|
|
|
|
15
|
|
|
6
|
|
|
|
|
7068
|
|
587
|
|
|
|
|
|
|
@ISA = qw( XBase::Base XBase::Index ); |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
*DEBUG = \$XBase::Index::DEBUG; |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
sub read_header { |
592
|
2
|
|
|
2
|
|
4
|
my $self = shift; |
593
|
2
|
|
|
|
|
6
|
my %opts = @_; |
594
|
2
|
|
|
|
|
3
|
my $header; |
595
|
2
|
|
|
|
|
7
|
$self->{'dbf'} = $opts{'dbf'}; |
596
|
|
|
|
|
|
|
$self->{'fh'}->read($header, 512) == 512 or do |
597
|
2
|
50
|
|
|
|
15
|
{ __PACKAGE__->Error("Error reading header of $self->{'filename'}: $!\n"); return; }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
598
|
2
|
|
|
|
|
65
|
@{$self}{ qw( start_page start_free_list total_pages |
|
2
|
|
|
|
|
16
|
|
599
|
|
|
|
|
|
|
key_length index_options index_signature |
600
|
|
|
|
|
|
|
key_string for_expression |
601
|
|
|
|
|
|
|
) } |
602
|
|
|
|
|
|
|
= unpack 'VVVv CC a220 a276', $header; |
603
|
|
|
|
|
|
|
|
604
|
2
|
|
|
|
|
7
|
$self->{'key_record_length'} = $self->{'key_length'} + 4; |
605
|
2
|
|
|
|
|
11
|
$self->{'key_string'} =~ s/[\000 ].*$//s; |
606
|
2
|
|
|
|
|
4
|
$self->{'record_len'} = 512; |
607
|
2
|
|
|
|
|
5
|
$self->{'start_page'} /= $self->{'record_len'}; |
608
|
2
|
|
|
|
|
4
|
$self->{'start_free_list'} /= $self->{'record_len'}; |
609
|
2
|
|
|
|
|
6
|
$self->{'header_len'} = 0; |
610
|
|
|
|
|
|
|
|
611
|
2
|
100
|
|
|
|
8
|
if ($opts{'type'} eq 'N') { |
612
|
1
|
|
|
|
|
2
|
$self->{'key_type'} = 1; |
613
|
|
|
|
|
|
|
} |
614
|
|
|
|
|
|
|
|
615
|
2
|
|
|
|
|
14
|
$self; |
616
|
|
|
|
|
|
|
} |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
sub last_record { |
619
|
8
|
|
|
8
|
|
38
|
shift->{'total_pages'}; |
620
|
|
|
|
|
|
|
} |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
sub create { |
623
|
2
|
|
|
2
|
|
227
|
my ($class, $table, $filename, $column) = @_; |
624
|
2
|
|
|
|
|
9
|
my $type = $table->field_type($column); |
625
|
2
|
50
|
|
|
|
9
|
if (not defined $type) { |
626
|
0
|
|
|
|
|
0
|
die "XBase::idx: could determine index type for `$column'\n"; |
627
|
|
|
|
|
|
|
} |
628
|
2
|
|
|
|
|
2
|
my $numdate = 0; |
629
|
2
|
100
|
66
|
|
|
17
|
$numdate = 1 if $type eq 'N' or $type eq 'D'; |
630
|
|
|
|
|
|
|
|
631
|
2
|
|
|
|
|
6
|
my $self = bless {}, $class; |
632
|
2
|
50
|
|
|
|
13
|
$self->create_file($filename) or die "Error creating `$filename'\n"; |
633
|
2
|
|
|
|
|
15
|
$self->write_to(0, "\000" x 512); |
634
|
2
|
|
|
|
|
10
|
my $key_length = $table->field_length($column); |
635
|
2
|
100
|
|
|
|
6
|
$key_length = 8 if $numdate; |
636
|
|
|
|
|
|
|
|
637
|
2
|
|
|
|
|
7
|
my $count = int((512 - 12) / ($key_length + 4)); |
638
|
|
|
|
|
|
|
### warn "Key length $key_length, per page $count.\n"; |
639
|
|
|
|
|
|
|
|
640
|
2
|
|
|
|
|
3
|
my $encode_function; |
641
|
2
|
100
|
|
|
|
6
|
if ($numdate) { |
642
|
|
|
|
|
|
|
$encode_function = sub { |
643
|
8
|
|
|
8
|
|
17
|
my $key = pack 'd', shift; |
644
|
8
|
50
|
|
|
|
19
|
$key = reverse $key unless $XBase::Index::BIGEND; |
645
|
8
|
100
|
|
|
|
23
|
if ((substr($key, 0, 1) & "\200") eq "\200") { |
646
|
2
|
|
|
|
|
3
|
$key ^= "\377\377\377\377\377\377\377\377"; |
647
|
|
|
|
|
|
|
} else { |
648
|
6
|
|
|
|
|
10
|
$key ^= "\200"; |
649
|
|
|
|
|
|
|
} |
650
|
8
|
|
|
|
|
37
|
return $key; |
651
|
1
|
|
|
|
|
11
|
}; |
652
|
|
|
|
|
|
|
} else { |
653
|
|
|
|
|
|
|
$encode_function = sub { |
654
|
8
|
|
|
8
|
|
54
|
return sprintf "%-${key_length}s", shift; |
655
|
1
|
|
|
|
|
7
|
}; |
656
|
|
|
|
|
|
|
} |
657
|
|
|
|
|
|
|
|
658
|
2
|
|
|
|
|
5
|
my @data; |
659
|
2
|
|
|
|
|
6
|
my $last_record = $table->last_record; |
660
|
2
|
|
|
|
|
11
|
for (my $i = 0; $i <= $last_record; $i++) { |
661
|
16
|
|
|
|
|
49
|
my ($deleted, $data) = $table->get_record($i, $column); |
662
|
16
|
|
|
|
|
35
|
push @data, [ $encode_function->($data), $i + 1 ]; |
663
|
|
|
|
|
|
|
} |
664
|
2
|
|
|
|
|
12
|
@data = sort { $a->[0] cmp $b->[0] } @data; |
|
28
|
|
|
|
|
46
|
|
665
|
|
|
|
|
|
|
|
666
|
2
|
|
|
|
|
5
|
$self->{'header_len'} = 0; # it is 512 really, but we |
667
|
|
|
|
|
|
|
# count from 1, not from 0 |
668
|
2
|
|
|
|
|
3
|
$self->{'record_len'} = 512; |
669
|
|
|
|
|
|
|
|
670
|
2
|
|
|
|
|
3
|
my $pageno = 1; |
671
|
2
|
|
|
|
|
3
|
my $level = 1; |
672
|
2
|
|
|
|
|
3
|
my @newdata; |
673
|
2
|
|
100
|
|
|
14
|
while ($level == 1 or @data > 1) { |
674
|
3
|
50
|
|
|
|
13
|
last if $pageno > 5; |
675
|
3
|
|
|
|
|
4
|
my $attributes = 0; |
676
|
3
|
100
|
|
|
|
7
|
$attributes = 2 if $level == 1; |
677
|
3
|
100
|
|
|
|
9
|
if (scalar(@data) < $count) { |
678
|
|
|
|
|
|
|
# we have less than one page, so it's root. |
679
|
2
|
|
|
|
|
2
|
$attributes++; |
680
|
|
|
|
|
|
|
} |
681
|
|
|
|
|
|
|
|
682
|
3
|
|
|
|
|
4
|
my $left_page = 0xFFFFFFFF; |
683
|
3
|
|
|
|
|
3
|
my $current_count = 0; |
684
|
3
|
|
|
|
|
4
|
my $out = ''; |
685
|
3
|
|
|
|
|
5
|
@newdata = (); |
686
|
3
|
|
|
|
|
9
|
for (my $i = 0; $i < @data; $i++) { |
687
|
18
|
|
|
|
|
27
|
my $key = $data[$i][0]; |
688
|
|
|
|
|
|
|
### print STDERR "Page $pageno: $i: @{$data[$i]}\n"; |
689
|
18
|
|
|
|
|
66
|
$out .= pack "a$key_length N", $key, $data[$i][1]; |
690
|
18
|
|
|
|
|
25
|
$current_count++; |
691
|
|
|
|
|
|
|
|
692
|
18
|
100
|
100
|
|
|
93
|
if ($current_count == $count or $i == $#data) { |
693
|
|
|
|
|
|
|
### print STDERR "Dumping $pageno.\n"; |
694
|
|
|
|
|
|
|
# time to close this page and move on |
695
|
4
|
|
|
|
|
5
|
my $right_page = 0xFFFFFFFF; |
696
|
4
|
100
|
|
|
|
11
|
if ($i < $#data) { |
697
|
1
|
|
|
|
|
2
|
$right_page = $pageno + 1; |
698
|
|
|
|
|
|
|
} |
699
|
4
|
|
|
|
|
30
|
$self->write_record($pageno, |
700
|
|
|
|
|
|
|
pack 'a512', |
701
|
|
|
|
|
|
|
pack('vvVV', $attributes, $current_count, |
702
|
|
|
|
|
|
|
$left_page, $right_page) |
703
|
|
|
|
|
|
|
. $out); |
704
|
4
|
|
|
|
|
14
|
push @newdata, [$data[$i][0], $pageno * 512]; |
705
|
4
|
|
|
|
|
5
|
$left_page = $pageno; |
706
|
4
|
|
|
|
|
4
|
$current_count = 0; |
707
|
4
|
|
|
|
|
5
|
$pageno++; |
708
|
4
|
|
|
|
|
13
|
$out = ''; |
709
|
|
|
|
|
|
|
} |
710
|
|
|
|
|
|
|
} |
711
|
|
|
|
|
|
|
|
712
|
3
|
|
|
|
|
11
|
@data = @newdata; |
713
|
3
|
|
|
|
|
16
|
$level++; |
714
|
|
|
|
|
|
|
} |
715
|
|
|
|
|
|
|
|
716
|
2
|
|
|
|
|
11
|
my $header = pack 'VVVv CC a220 a276', |
717
|
|
|
|
|
|
|
($pageno - 1) * 512, 0xFFFFFFFF, $pageno * 512, |
718
|
|
|
|
|
|
|
$key_length, 0, 0, $column, ''; |
719
|
2
|
|
|
|
|
8
|
$self->write_to(0, $header); |
720
|
2
|
|
|
|
|
12
|
$self->close; |
721
|
|
|
|
|
|
|
|
722
|
2
|
|
|
|
|
13
|
return new XBase::Index($filename, 'type' => $type); |
723
|
|
|
|
|
|
|
} |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
package XBase::idx::Page; |
726
|
6
|
|
|
6
|
|
55
|
use strict; |
|
6
|
|
|
|
|
16
|
|
|
6
|
|
|
|
|
213
|
|
727
|
6
|
|
|
6
|
|
40
|
use vars qw( @ISA $DEBUG ); |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
3599
|
|
728
|
|
|
|
|
|
|
@ISA = qw( XBase::idx ); |
729
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
*DEBUG = \$XBase::Index::DEBUG; |
731
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
### $DEBUG = 1; |
733
|
|
|
|
|
|
|
# Constructor for the idx page |
734
|
|
|
|
|
|
|
sub new { |
735
|
4
|
|
|
4
|
|
13
|
local $^W = 0; |
736
|
4
|
|
|
|
|
6
|
my ($indexfile, $num) = @_; |
737
|
4
|
|
|
|
|
5
|
my $parent; |
738
|
|
|
|
|
|
|
# we can be called from parent page |
739
|
4
|
50
|
|
|
|
13
|
if ((ref $indexfile) =~ /::Page$/) { |
740
|
0
|
|
|
|
|
0
|
$parent = $indexfile; |
741
|
0
|
|
|
|
|
0
|
$indexfile = $parent->{'indexfile'}; |
742
|
|
|
|
|
|
|
} |
743
|
4
|
50
|
|
|
|
16
|
my $data = $indexfile->read_record($num) or return; # get 512 bytes |
744
|
4
|
|
|
|
|
12
|
my ($attributes, $noentries, $left_brother, $right_brother) |
745
|
|
|
|
|
|
|
= unpack 'vvVV', $data; # parse header of the page |
746
|
4
|
|
|
|
|
8
|
my $keylength = $indexfile->{'key_length'}; |
747
|
4
|
|
|
|
|
7
|
my $keyreclength = $indexfile->{'key_record_length'}; # length |
748
|
|
|
|
|
|
|
|
749
|
4
|
50
|
|
|
|
10
|
print "page $num, noentries $noentries, keylength $keylength\n" if $DEBUG; |
750
|
4
|
|
|
|
|
5
|
my $numdate = $indexfile->{'key_type'}; # numeric or string? |
751
|
|
|
|
|
|
|
|
752
|
4
|
|
|
|
|
5
|
my $offset = 12; |
753
|
4
|
|
|
|
|
4
|
my $i = 0; |
754
|
4
|
|
|
|
|
9
|
my ($keys, $values, $lefts) = ([], [], []); # three arrays |
755
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
# walk the page |
757
|
4
|
|
|
|
|
11
|
while ($i < $noentries) { |
758
|
|
|
|
|
|
|
# get the values for entry |
759
|
19
|
|
|
|
|
64
|
my ($key, $recno) = unpack "\@$offset a$keylength N", $data; |
760
|
19
|
|
|
|
|
22
|
my $left; |
761
|
19
|
100
|
|
|
|
42
|
unless ($attributes & 2) { |
762
|
3
|
|
|
|
|
5
|
$left = $recno / 512; |
763
|
3
|
|
|
|
|
4
|
$recno = undef; |
764
|
|
|
|
|
|
|
} |
765
|
19
|
50
|
|
|
|
36
|
print "$i: \@$offset a$keylength N -> ($left, $recno, $key)\n" if $DEBUG > 1; |
766
|
|
|
|
|
|
|
### use Data::Dumper; print Dumper $indexfile; |
767
|
|
|
|
|
|
|
# some decoding for numbers |
768
|
19
|
100
|
|
|
|
33
|
if ($numdate) { |
769
|
8
|
100
|
|
|
|
21
|
if ((substr($key, 0, 1) & "\200") ne "\200") { |
770
|
2
|
|
|
|
|
3
|
$key ^= "\377\377\377\377\377\377\377\377"; |
771
|
|
|
|
|
|
|
} else { |
772
|
6
|
|
|
|
|
8
|
$key ^= "\200"; |
773
|
|
|
|
|
|
|
} |
774
|
8
|
50
|
|
|
|
15
|
if (not $XBase::Index::BIGEND) { $key = reverse $key; } |
|
8
|
|
|
|
|
13
|
|
775
|
8
|
|
|
|
|
13
|
$key = unpack 'd', $key; |
776
|
|
|
|
|
|
|
} |
777
|
19
|
50
|
|
|
|
39
|
print "$i: \@$offset a$keylength N -> ($left, $recno, $key)\n" if $DEBUG > 1; |
778
|
19
|
|
|
|
|
31
|
push @$keys, $key; |
779
|
19
|
100
|
|
|
|
37
|
push @$values, ($recno ? $recno : undef); |
780
|
19
|
100
|
|
|
|
27
|
$left = ($left ? $left : undef); |
781
|
19
|
|
|
|
|
25
|
push @$lefts, $left; |
782
|
|
|
|
|
|
|
|
783
|
19
|
100
|
100
|
|
|
61
|
if ($i == 0 and defined $left) |
784
|
1
|
|
|
|
|
2
|
{ $noentries++; } # fixup for nonleaf page |
785
|
|
|
|
|
|
|
### shouldn't this be for last page only? |
786
|
|
|
|
|
|
|
} |
787
|
|
|
|
|
|
|
continue { |
788
|
19
|
|
|
|
|
19
|
$i++; |
789
|
19
|
|
|
|
|
39
|
$offset += $keyreclength; |
790
|
|
|
|
|
|
|
} |
791
|
|
|
|
|
|
|
|
792
|
4
|
|
|
|
|
39
|
my $self = bless { 'keys' => $keys, 'values' => $values, |
793
|
|
|
|
|
|
|
'num' => $num, 'keylength' => $keylength, |
794
|
|
|
|
|
|
|
'lefts' => $lefts, 'indexfile' => $indexfile, |
795
|
|
|
|
|
|
|
'attributes' => $attributes, |
796
|
|
|
|
|
|
|
'left_brother' => $left_brother, |
797
|
|
|
|
|
|
|
'right_brother' => $right_brother }, __PACKAGE__; |
798
|
4
|
|
|
|
|
12
|
$self; |
799
|
|
|
|
|
|
|
} |
800
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
# ############ |
802
|
|
|
|
|
|
|
# dBase IV MDX |
803
|
|
|
|
|
|
|
|
804
|
|
|
|
|
|
|
package XBase::mdx; |
805
|
6
|
|
|
6
|
|
34
|
use strict; |
|
6
|
|
|
|
|
12
|
|
|
6
|
|
|
|
|
205
|
|
806
|
6
|
|
|
6
|
|
29
|
use vars qw( @ISA $DEBUG ); |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
4558
|
|
807
|
|
|
|
|
|
|
@ISA = qw( XBase::Base XBase::Index ); |
808
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
sub read_header { |
810
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
811
|
0
|
|
|
|
|
0
|
my %opts = @_; |
812
|
0
|
|
|
|
|
0
|
my $expr_name = $opts{'tag'}; |
813
|
|
|
|
|
|
|
|
814
|
0
|
|
|
|
|
0
|
my $header; |
815
|
0
|
|
|
|
|
0
|
$self->{'dbf'} = $opts{'dbf'}; |
816
|
|
|
|
|
|
|
$self->{'fh'}->read($header, 544) == 544 or do |
817
|
0
|
0
|
|
|
|
0
|
{ __PACKAGE__->Error("Error reading header of $self->{'filename'}: $!\n"); return; }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
818
|
|
|
|
|
|
|
|
819
|
0
|
|
|
|
|
0
|
@{$self}{ qw( version created dbf_filename block_size |
|
0
|
|
|
|
|
0
|
|
820
|
|
|
|
|
|
|
block_size_adder production noentries tag_length res |
821
|
|
|
|
|
|
|
tags_used res nopages first_free noavail last_update ) } |
822
|
|
|
|
|
|
|
= unpack 'Ca3A16vvccccvvVVVa3', $header; |
823
|
|
|
|
|
|
|
|
824
|
0
|
|
|
|
|
0
|
$self->{'record_len'} = 512; |
825
|
0
|
|
|
|
|
0
|
$self->{'header_len'} = 0; |
826
|
|
|
|
|
|
|
|
827
|
0
|
|
|
|
|
0
|
for my $i (1 .. $self->{'tags_used'}) { |
828
|
0
|
|
|
|
|
0
|
my $len = $self->{'tag_length'}; |
829
|
|
|
|
|
|
|
|
830
|
|
|
|
|
|
|
$self->seek_to(544 + ($i - 1) * $len) or do |
831
|
0
|
0
|
|
|
|
0
|
{ __PACKAGE__->Error($self->errstr); return; }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
832
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
$self->{'fh'}->read($header, $len) == $len or do |
834
|
0
|
0
|
|
|
|
0
|
{ __PACKAGE__->Error("Error reading tag header $i in $self->{'filename'}: $!\n"); return; }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
835
|
|
|
|
|
|
|
|
836
|
0
|
|
|
|
|
0
|
my $tag; |
837
|
0
|
|
|
|
|
0
|
@{$tag}{ qw( header_page tag_name key_format fwd_low |
|
0
|
|
|
|
|
0
|
|
838
|
|
|
|
|
|
|
fwd_high backward res key_type ) } |
839
|
|
|
|
|
|
|
= unpack 'VA11ccccca1', $header; |
840
|
|
|
|
|
|
|
|
841
|
0
|
|
|
|
|
0
|
$self->{'tags'}{$tag->{'tag_name'}} = $tag; |
842
|
0
|
|
0
|
|
|
0
|
$expr_name ||= $tag->{'tag_name'}; # Default to first tag |
843
|
|
|
|
|
|
|
|
844
|
|
|
|
|
|
|
$self->seek_to($tag->{'header_page'} * 512) or do |
845
|
0
|
0
|
|
|
|
0
|
{ __PACKAGE__->Error($self->errstr); return; }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
846
|
|
|
|
|
|
|
|
847
|
|
|
|
|
|
|
$self->{'fh'}->read($header, 24) == 24 or do |
848
|
0
|
0
|
|
|
|
0
|
{ __PACKAGE__->Error("Error reading tag definition in $self->{'filename'}: $!\n"); return; }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
849
|
|
|
|
|
|
|
|
850
|
0
|
|
|
|
|
0
|
@{$tag}{ qw( start_page file_size key_format_1 |
|
0
|
|
|
|
|
0
|
|
851
|
|
|
|
|
|
|
key_type_1 res key_length max_no_keys_per_page |
852
|
|
|
|
|
|
|
second_key_type key_record_length res unique) } |
853
|
|
|
|
|
|
|
= unpack 'VVca1vvvvva3c', $header; |
854
|
|
|
|
|
|
|
} |
855
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
### use Data::Dumper; print Dumper $self; |
857
|
|
|
|
|
|
|
|
858
|
0
|
0
|
|
|
|
0
|
if (defined $expr_name) { |
859
|
0
|
0
|
|
|
|
0
|
if (defined $self->{'tags'}{$expr_name}) { |
860
|
0
|
|
|
|
|
0
|
$self->{'active'} = $self->{'tags'}{$expr_name}; |
861
|
0
|
|
|
|
|
0
|
$self->{'start_page'} = $self->{'active'}{'start_page'}; |
862
|
|
|
|
|
|
|
} else { |
863
|
0
|
|
|
|
|
0
|
__PACKAGE__->Error("No tag $expr_name found in index file $self->{'filename'}.\n"); return; |
|
0
|
|
|
|
|
0
|
|
864
|
|
|
|
|
|
|
} |
865
|
|
|
|
|
|
|
} |
866
|
|
|
|
|
|
|
|
867
|
0
|
|
|
|
|
0
|
$self; |
868
|
|
|
|
|
|
|
} |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
sub last_record { |
871
|
0
|
|
|
0
|
|
0
|
-1; |
872
|
|
|
|
|
|
|
} |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
sub tags { |
875
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
876
|
0
|
0
|
|
|
|
0
|
return sort keys %{$self->{'tags'}} if defined $self->{'tags'}; |
|
0
|
|
|
|
|
0
|
|
877
|
|
|
|
|
|
|
} |
878
|
|
|
|
|
|
|
|
879
|
|
|
|
|
|
|
package XBase::mdx::Page; |
880
|
6
|
|
|
6
|
|
36
|
use strict; |
|
6
|
|
|
|
|
10
|
|
|
6
|
|
|
|
|
194
|
|
881
|
6
|
|
|
6
|
|
38
|
use vars qw( @ISA $DEBUG ); |
|
6
|
|
|
|
|
16
|
|
|
6
|
|
|
|
|
2930
|
|
882
|
|
|
|
|
|
|
@ISA = qw( XBase::mdx ); |
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
*DEBUG = \$XBase::Index::DEBUG; |
885
|
|
|
|
|
|
|
|
886
|
|
|
|
|
|
|
# Constructor for the mdx page |
887
|
|
|
|
|
|
|
sub new { |
888
|
0
|
|
|
0
|
|
0
|
my ($indexfile, $num) = @_; |
889
|
|
|
|
|
|
|
|
890
|
0
|
|
|
|
|
0
|
my $parent; |
891
|
|
|
|
|
|
|
### parent page |
892
|
0
|
0
|
|
|
|
0
|
if ((ref $indexfile) =~ /::Page$/) { |
893
|
0
|
|
|
|
|
0
|
$parent = $indexfile; |
894
|
0
|
|
|
|
|
0
|
$indexfile = $parent->{'indexfile'}; |
895
|
|
|
|
|
|
|
} |
896
|
0
|
0
|
|
|
|
0
|
$indexfile->seek_to_record($num) or return; |
897
|
0
|
|
|
|
|
0
|
my $data; |
898
|
0
|
0
|
|
|
|
0
|
$indexfile->{'fh'}->read($data, 1024) == 1024 or return; |
899
|
|
|
|
|
|
|
|
900
|
0
|
|
|
|
|
0
|
my $keylength = $indexfile->{'active'}{'key_length'}; |
901
|
0
|
|
|
|
|
0
|
my $keyreclength = $indexfile->{'active'}{'key_record_length'}; |
902
|
0
|
|
|
|
|
0
|
my $offset = 8; |
903
|
|
|
|
|
|
|
|
904
|
0
|
|
|
|
|
0
|
my ($noentries, $noleaf) = unpack 'VV', $data; |
905
|
|
|
|
|
|
|
|
906
|
0
|
0
|
|
|
|
0
|
print "page $num, noentries $noentries, keylength $keylength; noleaf: $noleaf\n" if $DEBUG; |
907
|
|
|
|
|
|
|
|
908
|
0
|
|
|
|
|
0
|
my ($keys, $values, $lefts, $refs) = ([], [], [], []); |
909
|
|
|
|
|
|
|
|
910
|
0
|
|
|
|
|
0
|
for (my $i = 0; $i < $noentries; $i++) { |
911
|
0
|
|
|
|
|
0
|
my ($left, $key) |
912
|
|
|
|
|
|
|
= unpack "\@${offset}Va${keylength}", $data; |
913
|
|
|
|
|
|
|
|
914
|
0
|
|
|
|
|
0
|
push @$keys, $key; |
915
|
|
|
|
|
|
|
|
916
|
0
|
|
|
|
|
0
|
push @$refs, $left; |
917
|
|
|
|
|
|
|
|
918
|
0
|
|
|
|
|
0
|
$offset += $keyreclength; |
919
|
|
|
|
|
|
|
} |
920
|
|
|
|
|
|
|
|
921
|
0
|
|
|
|
|
0
|
my $right; |
922
|
|
|
|
|
|
|
|
923
|
0
|
0
|
|
|
|
0
|
$right = unpack "\@${offset}V", $data if $offset <= (1024-4); |
924
|
|
|
|
|
|
|
|
925
|
0
|
0
|
|
|
|
0
|
if ($right) { |
926
|
|
|
|
|
|
|
# It's a branch page and the next ref is for values > last key |
927
|
0
|
|
|
|
|
0
|
push @$keys, ""; |
928
|
0
|
|
|
|
|
0
|
push @$refs, $right; |
929
|
0
|
|
|
|
|
0
|
$lefts = $refs; |
930
|
|
|
|
|
|
|
} else { |
931
|
|
|
|
|
|
|
# It's a leaf page |
932
|
0
|
|
|
|
|
0
|
$values = $refs; |
933
|
|
|
|
|
|
|
} |
934
|
|
|
|
|
|
|
|
935
|
0
|
|
|
|
|
0
|
my $self = bless { 'num' => $num, 'indexfile' => $indexfile, |
936
|
|
|
|
|
|
|
'keys' => $keys, 'values' => $values, 'lefts' => $lefts, }, |
937
|
|
|
|
|
|
|
__PACKAGE__; |
938
|
0
|
|
|
|
|
0
|
$self; |
939
|
|
|
|
|
|
|
} |
940
|
|
|
|
|
|
|
|
941
|
|
|
|
|
|
|
# ########### |
942
|
|
|
|
|
|
|
# FoxBase CDX |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
package XBase::cdx; |
945
|
6
|
|
|
6
|
|
32
|
use strict; |
|
6
|
|
|
|
|
10
|
|
|
6
|
|
|
|
|
184
|
|
946
|
6
|
|
|
6
|
|
25
|
use vars qw( @ISA $DEBUG ); |
|
6
|
|
|
|
|
14
|
|
|
6
|
|
|
|
|
5826
|
|
947
|
|
|
|
|
|
|
@ISA = qw( XBase::Base XBase::Index ); |
948
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
*DEBUG = \$XBase::Index::DEBUG; |
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
sub prepare_write_header { |
952
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
953
|
0
|
|
|
|
|
0
|
my $data = pack 'VVNv CC @502 vvv @510 v @512 a512', |
954
|
|
|
|
|
|
|
$self->{'start_page'} * 512, |
955
|
|
|
|
|
|
|
$self->{'start_free_list'} * 512, |
956
|
0
|
|
|
|
|
0
|
@{$self}{ qw( total_pages |
957
|
|
|
|
|
|
|
key_length index_options index_signature |
958
|
|
|
|
|
|
|
sort_order total_expr_length for_expression_length |
959
|
|
|
|
|
|
|
key_expression_length |
960
|
|
|
|
|
|
|
key_string |
961
|
|
|
|
|
|
|
) }; |
962
|
0
|
|
|
|
|
0
|
$data; |
963
|
|
|
|
|
|
|
} |
964
|
|
|
|
|
|
|
sub write_header { |
965
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
966
|
0
|
|
|
|
|
0
|
my $data = $self->prepare_write_header; |
967
|
0
|
|
0
|
|
|
0
|
$self->{'fh'}->seek($self->{'adjusted_offset'} || 0, 0); |
968
|
0
|
|
|
|
|
0
|
$self->{'fh'}->print($data); |
969
|
|
|
|
|
|
|
} |
970
|
|
|
|
|
|
|
sub read_header { |
971
|
18
|
|
|
18
|
|
44
|
my ($self, %opts) = @_; |
972
|
18
|
100
|
|
|
|
61
|
$self->{'dbf'} = $opts{'dbf'} if not exists $self->{'dbf'}; |
973
|
|
|
|
|
|
|
|
974
|
18
|
|
|
|
|
23
|
my $header; |
975
|
|
|
|
|
|
|
$self->{'fh'}->read($header, 1024) == 1024 or do |
976
|
18
|
50
|
|
|
|
64
|
{ __PACKAGE__->Error("Error reading header of $self->{'filename'}: $!\n"); return; }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
977
|
|
|
|
|
|
|
|
978
|
18
|
|
|
|
|
485
|
@{$self}{ qw( start_page start_free_list total_pages |
|
18
|
|
|
|
|
131
|
|
979
|
|
|
|
|
|
|
key_length index_options index_signature |
980
|
|
|
|
|
|
|
sort_order total_expr_length for_expression_length |
981
|
|
|
|
|
|
|
key_expression_length |
982
|
|
|
|
|
|
|
key_string |
983
|
|
|
|
|
|
|
) } |
984
|
|
|
|
|
|
|
= unpack 'VVNv CC @502 vvv @510 v @512 a512', $header; |
985
|
|
|
|
|
|
|
|
986
|
18
|
|
|
|
|
41
|
$self->{'total_pages'} = -1; ### the total_pages value 11 |
987
|
|
|
|
|
|
|
### that found in rooms.cdx is not correct, so we invalidate it |
988
|
|
|
|
|
|
|
|
989
|
18
|
|
|
|
|
201
|
($self->{'key_string'}, $self->{'for_string'}) = |
990
|
|
|
|
|
|
|
($self->{'key_string'} =~ /^([^\000]*)\000([^\000]*)/); |
991
|
|
|
|
|
|
|
|
992
|
18
|
|
|
|
|
49
|
$self->{'key_record_length'} = $self->{'key_length'} + 4; |
993
|
18
|
|
|
|
|
29
|
$self->{'record_len'} = 512; |
994
|
18
|
|
|
|
|
67
|
$self->{'start_page'} /= $self->{'record_len'}; |
995
|
18
|
|
|
|
|
29
|
$self->{'start_free_list'} /= $self->{'record_len'}; |
996
|
18
|
|
|
|
|
25
|
$self->{'header_len'} = 0; |
997
|
18
|
|
|
|
|
27
|
$self->{'key_type'} = 0; |
998
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
## my $out = $self->prepare_write_header; |
1000
|
|
|
|
|
|
|
## if ($out ne $header) { |
1001
|
|
|
|
|
|
|
## print STDERR "I won't be able to write the header back\n", |
1002
|
|
|
|
|
|
|
## unpack("H*", $out), "\n ++\n", |
1003
|
|
|
|
|
|
|
## unpack("H*", $header), "\n"; |
1004
|
|
|
|
|
|
|
## } |
1005
|
|
|
|
|
|
|
|
1006
|
18
|
100
|
|
|
|
47
|
if (not defined $self->{'tag'}) { # top level |
1007
|
9
|
|
|
|
|
37
|
$self->prepare_select; |
1008
|
9
|
|
|
|
|
40
|
while (my ($tag) = $self->fetch) { |
1009
|
45
|
|
|
|
|
49
|
push @{$self->{'tags'}}, $tag; |
|
45
|
|
|
|
|
94
|
|
1010
|
45
|
|
66
|
|
|
171
|
$opts{'tag'} ||= $tag; # Default to first tag |
1011
|
|
|
|
|
|
|
} |
1012
|
|
|
|
|
|
|
} |
1013
|
|
|
|
|
|
|
### use Data::Dumper; print Dumper \%opts; |
1014
|
|
|
|
|
|
|
|
1015
|
18
|
100
|
|
|
|
44
|
if (defined $opts{'tag'}) { |
1016
|
9
|
|
|
|
|
42
|
$self->prepare_select_eq($opts{'tag'}); |
1017
|
9
|
|
|
|
|
21
|
my ($foundkey, $value) = $self->fetch; |
1018
|
|
|
|
|
|
|
|
1019
|
9
|
50
|
33
|
|
|
60
|
if (not defined $foundkey or $opts{'tag'} ne $foundkey) { |
1020
|
0
|
|
|
|
|
0
|
__PACKAGE__->Error("No tag $opts{'tag'} found in index file $self->{'filename'}.\n"); return; }; |
|
0
|
|
|
|
|
0
|
|
1021
|
|
|
|
|
|
|
|
1022
|
9
|
|
|
|
|
213
|
my $subidx = bless { %$self }, ref $self; |
1023
|
9
|
50
|
|
|
|
41
|
print "Adjusting start_page value by $value for $opts{'tag'}\n" if $DEBUG; |
1024
|
9
|
|
|
|
|
43
|
$subidx->{'fh'}->seek($value, 0); |
1025
|
9
|
|
|
|
|
121
|
$subidx->{'adjusted_offset'} = $value; |
1026
|
9
|
|
|
|
|
30
|
$subidx->{'tag'} = $opts{'tag'}; |
1027
|
9
|
|
|
|
|
53
|
$subidx->read_header; |
1028
|
|
|
|
|
|
|
|
1029
|
9
|
|
|
|
|
18
|
my $key_string = $subidx->{'key_string'}; |
1030
|
9
|
|
|
|
|
12
|
my $field_type; |
1031
|
9
|
50
|
|
|
|
34
|
if (defined $opts{'type'}) { |
|
|
50
|
|
|
|
|
|
1032
|
0
|
|
|
|
|
0
|
$field_type = $opts{'type'}; |
1033
|
|
|
|
|
|
|
} |
1034
|
|
|
|
|
|
|
elsif (defined $subidx->{'dbf'}) { |
1035
|
9
|
|
|
|
|
44
|
$field_type = $subidx->{'dbf'}->field_type($key_string); |
1036
|
9
|
50
|
|
|
|
29
|
if (not defined $field_type) { |
1037
|
0
|
|
|
|
|
0
|
__PACKAGE__->Error("Couldn't find key string `$key_string' in dbf file, can't determine field type\n"); |
1038
|
0
|
|
|
|
|
0
|
return; |
1039
|
|
|
|
|
|
|
} |
1040
|
|
|
|
|
|
|
} |
1041
|
|
|
|
|
|
|
else { |
1042
|
0
|
|
|
|
|
0
|
__PACKAGE__->Error("Index type (char/numeric) unknown for $subidx\n"); |
1043
|
0
|
|
|
|
|
0
|
return; |
1044
|
|
|
|
|
|
|
} |
1045
|
9
|
50
|
|
|
|
34
|
$subidx->{'key_type'} = ($field_type =~ /^[NDIF]$/ ? 1 : 0); |
1046
|
9
|
50
|
|
|
|
32
|
if ($field_type eq 'D') { |
1047
|
0
|
|
|
|
|
0
|
$subidx->{'key_type'} = 2; |
1048
|
0
|
|
|
|
|
0
|
require Time::JulianDay; |
1049
|
|
|
|
|
|
|
} |
1050
|
|
|
|
|
|
|
|
1051
|
9
|
|
|
|
|
57
|
for (keys %$self) { delete $self->{$_} } |
|
234
|
|
|
|
|
317
|
|
1052
|
9
|
|
|
|
|
63
|
for (keys %$subidx) { $self->{$_} = $subidx->{$_} } |
|
252
|
|
|
|
|
375
|
|
1053
|
9
|
|
|
|
|
31
|
$self = $subidx; |
1054
|
|
|
|
|
|
|
### use Data::Dumper; print Dumper $self; |
1055
|
|
|
|
|
|
|
} |
1056
|
18
|
|
|
|
|
95
|
$self; |
1057
|
|
|
|
|
|
|
} |
1058
|
|
|
|
|
|
|
|
1059
|
|
|
|
|
|
|
sub last_record { |
1060
|
17
|
|
|
17
|
|
85
|
shift->{'total_pages'}; |
1061
|
|
|
|
|
|
|
} |
1062
|
|
|
|
|
|
|
|
1063
|
|
|
|
|
|
|
package XBase::cdx::Page; |
1064
|
6
|
|
|
6
|
|
35
|
use strict; |
|
6
|
|
|
|
|
12
|
|
|
6
|
|
|
|
|
248
|
|
1065
|
6
|
|
|
6
|
|
31
|
use vars qw( @ISA $DEBUG ); |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
27827
|
|
1066
|
|
|
|
|
|
|
@ISA = qw( XBase::cdx ); |
1067
|
|
|
|
|
|
|
|
1068
|
|
|
|
|
|
|
*DEBUG = \$XBase::Index::DEBUG; |
1069
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
# Constructor for the cdx page |
1071
|
|
|
|
|
|
|
sub new { |
1072
|
17
|
|
|
17
|
|
39
|
my ($indexfile, $num) = @_; |
1073
|
|
|
|
|
|
|
my $data = $indexfile->read_record($num) |
1074
|
17
|
50
|
|
|
|
66
|
or do { print $indexfile->errstr; return; }; # get 512 bytes |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
1075
|
|
|
|
|
|
|
|
1076
|
17
|
|
|
|
|
31
|
my $origdata = $data; |
1077
|
|
|
|
|
|
|
|
1078
|
17
|
|
|
|
|
52
|
my ($attributes, $noentries, $left_brother, $right_brother) |
1079
|
|
|
|
|
|
|
= unpack 'vvVV', $data; # parse header of the page |
1080
|
17
|
|
|
|
|
53
|
my $keylength = $indexfile->{'key_length'}; |
1081
|
17
|
|
|
|
|
27
|
my $keyreclength = $indexfile->{'key_record_length'}; # length |
1082
|
|
|
|
|
|
|
|
1083
|
17
|
50
|
|
|
|
61
|
print "page $num, attr $attributes, noentries $noentries, keylength $keylength (bro $left_brother, $right_brother)\n" if $DEBUG; |
1084
|
17
|
|
|
|
|
93
|
my $numdate = $indexfile->{'key_type'}; # numeric or string? |
1085
|
|
|
|
|
|
|
|
1086
|
17
|
|
|
|
|
64
|
my ($keys, $values, $lefts) = ([], [], undef); |
1087
|
|
|
|
|
|
|
|
1088
|
17
|
|
|
|
|
34
|
my %opts = (); |
1089
|
|
|
|
|
|
|
|
1090
|
17
|
50
|
|
|
|
41
|
if ($attributes & 2) { |
1091
|
17
|
50
|
|
|
|
37
|
print "leaf page, compressed\n" if $DEBUG; |
1092
|
17
|
|
|
|
|
62
|
my ($free_space, $recno_mask, $duplicate_count_mask, |
1093
|
|
|
|
|
|
|
$trailing_count_mask, $recno_count, $duplicate_count, |
1094
|
|
|
|
|
|
|
$trailing_count, $holding_recno) = unpack '@12 vVCCCCCC', $data; |
1095
|
17
|
50
|
|
|
|
55
|
print '$free_space, $recno_mask, $duplicate_count_mask, $trailing_count_mask, $recno_count, $duplicate_count, $trailing_count, $holding_recno) = ', |
1096
|
|
|
|
|
|
|
"$free_space, $recno_mask, $duplicate_count_mask, $trailing_count_mask, $recno_count, $duplicate_count, $trailing_count, $holding_recno)\n" if $DEBUG > 2; |
1097
|
|
|
|
|
|
|
|
1098
|
17
|
|
|
|
|
162
|
@opts{ qw! recno_count duplicate_count trailing_count |
1099
|
|
|
|
|
|
|
holding_recno ! } = |
1100
|
|
|
|
|
|
|
( $recno_count, $duplicate_count, $trailing_count, |
1101
|
|
|
|
|
|
|
$holding_recno); |
1102
|
|
|
|
|
|
|
|
1103
|
17
|
|
|
|
|
25
|
my $prevkeyval = ''; |
1104
|
17
|
|
|
|
|
46
|
for (my $i = 0; $i < $noentries; $i++) { |
1105
|
295
|
|
|
|
|
594
|
my $one_item = substr($data, 24 + $i * $holding_recno, $holding_recno) . "\0" x 4; |
1106
|
295
|
|
|
|
|
490
|
my $numeric_one_item = unpack 'V', $one_item; |
1107
|
|
|
|
|
|
|
|
1108
|
295
|
50
|
|
|
|
10125
|
print "one_item: 0x", unpack('H*', $one_item), " ($numeric_one_item)\n" if $DEBUG > 3; |
1109
|
|
|
|
|
|
|
|
1110
|
295
|
|
|
|
|
620
|
my $recno = $numeric_one_item & $recno_mask; |
1111
|
295
|
|
|
|
|
408
|
my $bytes_of_recno = int($recno_count / 8); |
1112
|
295
|
|
|
|
|
382
|
$one_item = substr($one_item, $bytes_of_recno); |
1113
|
|
|
|
|
|
|
|
1114
|
295
|
|
|
|
|
704
|
$numeric_one_item = unpack 'V', $one_item; |
1115
|
295
|
|
|
|
|
525
|
$numeric_one_item >>= $recno_count - (8 * $bytes_of_recno); |
1116
|
|
|
|
|
|
|
|
1117
|
295
|
|
|
|
|
302
|
my $dupl = $numeric_one_item & $duplicate_count_mask; |
1118
|
295
|
|
|
|
|
288
|
$numeric_one_item >>= $duplicate_count; |
1119
|
295
|
|
|
|
|
293
|
my $trail = $numeric_one_item & $trailing_count_mask; |
1120
|
|
|
|
|
|
|
### $numeric_one_item >>= $trailing_count; |
1121
|
|
|
|
|
|
|
|
1122
|
295
|
50
|
|
|
|
537
|
print "Item $i: trail $trail, dupl $dupl, recno $recno\n" if $DEBUG > 6; |
1123
|
|
|
|
|
|
|
|
1124
|
295
|
|
|
|
|
416
|
my $getlength = $keylength - $trail - $dupl; |
1125
|
295
|
|
|
|
|
596
|
my $key = substr($prevkeyval, 0, $dupl); |
1126
|
295
|
100
|
|
|
|
609
|
$key .= substr($data, -$getlength) if $getlength; |
1127
|
295
|
|
|
|
|
396
|
$key .= "\000" x $trail; |
1128
|
295
|
100
|
|
|
|
610
|
substr($data, -$getlength) = '' if $getlength; |
1129
|
295
|
|
|
|
|
328
|
$prevkeyval = $key; |
1130
|
|
|
|
|
|
|
|
1131
|
|
|
|
|
|
|
### print "Numdate $numdate\n"; |
1132
|
295
|
50
|
|
|
|
437
|
if ($numdate) { # some decoding for numbers |
1133
|
|
|
|
|
|
|
### print " *** In: ", unpack("H*", $key), "\n"; |
1134
|
0
|
0
|
|
|
|
0
|
if (0x80 & unpack('C', $key)) { |
1135
|
0
|
|
|
|
|
0
|
substr($key, 0, 1) &= "\177"; |
1136
|
|
|
|
|
|
|
} |
1137
|
0
|
|
|
|
|
0
|
else { $key = ~$key; } |
1138
|
0
|
0
|
|
|
|
0
|
if ($keylength == 8) { |
1139
|
0
|
0
|
|
|
|
0
|
$key = reverse $key unless $XBase::Index::BIGEND; |
1140
|
0
|
|
|
|
|
0
|
$key = unpack 'd', $key; |
1141
|
|
|
|
|
|
|
} else { |
1142
|
0
|
|
|
|
|
0
|
$key = unpack 'N', $key; |
1143
|
|
|
|
|
|
|
} |
1144
|
0
|
0
|
0
|
|
|
0
|
if ($numdate == 2 and $key) { # date |
1145
|
0
|
|
|
|
|
0
|
$key = sprintf "%04d%02d%02d", |
1146
|
|
|
|
|
|
|
Time::JulianDay::inverse_julian_day($key); |
1147
|
|
|
|
|
|
|
} |
1148
|
|
|
|
|
|
|
} else { |
1149
|
295
|
100
|
|
|
|
632
|
substr($key, -$trail) = '' if $trail; |
1150
|
|
|
|
|
|
|
} |
1151
|
|
|
|
|
|
|
|
1152
|
295
|
50
|
|
|
|
526
|
print "$key -> $recno\n" if $DEBUG > 4; |
1153
|
295
|
|
|
|
|
570
|
push @$keys, $key; |
1154
|
295
|
|
|
|
|
870
|
push @$values, $recno; |
1155
|
|
|
|
|
|
|
} |
1156
|
|
|
|
|
|
|
} else { |
1157
|
0
|
|
|
|
|
0
|
for (my $i = 0; $i < $noentries; $i++) { |
1158
|
0
|
|
|
|
|
0
|
my $offset = 12 + $i * ($keylength + 8); |
1159
|
0
|
|
|
|
|
0
|
my ($key, $recno, $page) |
1160
|
|
|
|
|
|
|
= unpack "\@$offset a$keylength NN", $data; |
1161
|
|
|
|
|
|
|
# some decoding for numbers |
1162
|
0
|
0
|
|
|
|
0
|
if ($numdate) { |
1163
|
0
|
0
|
|
|
|
0
|
if (0x80 & unpack('C', $key)) { |
1164
|
|
|
|
|
|
|
### if ("\200" & substr($key, 0, 1)) { |
1165
|
|
|
|
|
|
|
### print STDERR "Declean\n"; |
1166
|
|
|
|
|
|
|
### print STDERR unpack("H*", $key), ' -> '; |
1167
|
0
|
|
|
|
|
0
|
substr($key, 0, 1) &= "\177"; |
1168
|
|
|
|
|
|
|
### print STDERR unpack("H*", $key), "\n"; |
1169
|
|
|
|
|
|
|
} |
1170
|
0
|
|
|
|
|
0
|
else { $key = ~$key; } |
1171
|
0
|
0
|
|
|
|
0
|
if ($keylength == 8) { |
1172
|
0
|
0
|
|
|
|
0
|
$key = reverse $key unless $XBase::Index::BIGEND; |
1173
|
0
|
|
|
|
|
0
|
$key = unpack 'd', $key; |
1174
|
|
|
|
|
|
|
} else { |
1175
|
0
|
|
|
|
|
0
|
$key = unpack 'N', $key; |
1176
|
|
|
|
|
|
|
} |
1177
|
0
|
0
|
0
|
|
|
0
|
if ($numdate == 2 and $key) { # date |
1178
|
0
|
|
|
|
|
0
|
$key = sprintf "%04d%02d%02d", |
1179
|
|
|
|
|
|
|
Time::JulianDay::inverse_julian_day($key); |
1180
|
|
|
|
|
|
|
} |
1181
|
|
|
|
|
|
|
} else { |
1182
|
0
|
|
|
|
|
0
|
$key =~ s/\000+$//; |
1183
|
|
|
|
|
|
|
} |
1184
|
0
|
0
|
|
|
|
0
|
print "item: $key -> $recno via $page\n" if $DEBUG > 4; |
1185
|
0
|
|
|
|
|
0
|
push @$keys, $key; |
1186
|
0
|
|
|
|
|
0
|
push @$values, $recno; |
1187
|
0
|
0
|
|
|
|
0
|
$lefts = [] unless defined $lefts; |
1188
|
0
|
|
|
|
|
0
|
push @$lefts, $page / 512; |
1189
|
|
|
|
|
|
|
} |
1190
|
0
|
|
|
|
|
0
|
$opts{'last_key_is_just_overflow'} = 1; |
1191
|
|
|
|
|
|
|
} |
1192
|
|
|
|
|
|
|
|
1193
|
17
|
|
|
|
|
261
|
my $self = bless { 'keys' => $keys, 'values' => $values, |
1194
|
|
|
|
|
|
|
'num' => $num, 'keylength' => $keylength, |
1195
|
|
|
|
|
|
|
'lefts' => $lefts, 'indexfile' => $indexfile, |
1196
|
|
|
|
|
|
|
'attributes' => $attributes, |
1197
|
|
|
|
|
|
|
'left_brother' => $left_brother, |
1198
|
|
|
|
|
|
|
'right_brother' => $right_brother, %opts, |
1199
|
|
|
|
|
|
|
}, __PACKAGE__; |
1200
|
|
|
|
|
|
|
|
1201
|
17
|
|
|
|
|
67
|
my $outdata = $self->prepare_scalar_for_write; |
1202
|
17
|
|
|
|
|
32
|
if (0 and $outdata ne $origdata) { |
1203
|
|
|
|
|
|
|
print "I won't be able to write this page back.\n", |
1204
|
|
|
|
|
|
|
unpack("H*", $outdata), "\n ++\n", |
1205
|
|
|
|
|
|
|
unpack("H*", $origdata), "\n"; |
1206
|
|
|
|
|
|
|
} else { |
1207
|
|
|
|
|
|
|
### print STDERR " ** Bingo: I will be able to write this page back ($num).\n"; |
1208
|
|
|
|
|
|
|
} |
1209
|
|
|
|
|
|
|
|
1210
|
17
|
|
|
|
|
64
|
$self; |
1211
|
|
|
|
|
|
|
} |
1212
|
|
|
|
|
|
|
|
1213
|
|
|
|
|
|
|
# Create "new" page -- allocates memory in the file and returns |
1214
|
|
|
|
|
|
|
# structure that can reasonably used as XBase::cdx::Page |
1215
|
|
|
|
|
|
|
sub create { |
1216
|
0
|
|
|
0
|
|
0
|
my ($class, $indexfile) = @_; |
1217
|
0
|
0
|
0
|
|
|
0
|
if (not defined $indexfile and ref $class) { |
1218
|
0
|
|
|
|
|
0
|
$indexfile = $class->{'indexfile'}; |
1219
|
|
|
|
|
|
|
} |
1220
|
0
|
|
|
|
|
0
|
my $fh = $indexfile->{'fh'}; |
1221
|
0
|
|
|
|
|
0
|
$fh->seek(0, 2); # seek to the end; |
1222
|
0
|
|
|
|
|
0
|
my $position = $fh->tell; # get the length of the file |
1223
|
0
|
0
|
|
|
|
0
|
if ($position % 512) { |
1224
|
0
|
|
|
|
|
0
|
$fh->print("\000" x (512 - ($position % 512))); |
1225
|
|
|
|
|
|
|
# pad the file to multiply of 512 |
1226
|
0
|
|
|
|
|
0
|
$position = $fh->tell; # get the length of the file |
1227
|
|
|
|
|
|
|
} |
1228
|
0
|
|
|
|
|
0
|
$fh->print("\000" x 512); |
1229
|
0
|
|
|
|
|
0
|
return bless { 'num' => $position / 512, |
1230
|
|
|
|
|
|
|
'keylength' => $indexfile->{'key_length'}, |
1231
|
|
|
|
|
|
|
'indexfile' => $indexfile }, $class; |
1232
|
|
|
|
|
|
|
} |
1233
|
|
|
|
|
|
|
|
1234
|
|
|
|
|
|
|
sub prepare_scalar_for_write { |
1235
|
22
|
|
|
22
|
|
30
|
my $self = shift; |
1236
|
|
|
|
|
|
|
|
1237
|
22
|
|
|
|
|
67
|
my ($attributes, $noentries, $left_brother, $right_brother) |
1238
|
22
|
|
|
|
|
62
|
= ($self->{'attributes'}, scalar(@{$self->{'keys'}}), |
1239
|
|
|
|
|
|
|
$self->{'left_brother'}, $self->{'right_brother'}); |
1240
|
|
|
|
|
|
|
|
1241
|
22
|
|
|
|
|
80
|
my $data = pack 'vvVV', $attributes, $noentries, $left_brother, |
1242
|
|
|
|
|
|
|
$right_brother; |
1243
|
|
|
|
|
|
|
|
1244
|
22
|
|
|
|
|
93
|
my $indexfile = $self->{'indexfile'}; |
1245
|
22
|
|
|
|
|
34
|
my $numdate = $indexfile->{'key_type'}; # numeric or string? |
1246
|
22
|
|
|
|
|
32
|
my $record_len = $indexfile->{'record_len'}; |
1247
|
22
|
|
|
|
|
32
|
my $keylength = $self->{'keylength'}; |
1248
|
|
|
|
|
|
|
|
1249
|
22
|
50
|
|
|
|
46
|
if ($attributes & 2) { |
1250
|
|
|
|
|
|
|
|
1251
|
22
|
|
|
|
|
39
|
my ($recno_count, $duplicate_count, $trailing_count, |
1252
|
|
|
|
|
|
|
$holding_recno) = (16, 4, 4, 3); |
1253
|
22
|
50
|
|
|
|
54
|
if (defined $self->{'recno_count'}) { |
1254
|
22
|
|
|
|
|
59
|
($recno_count, $duplicate_count, $trailing_count, |
1255
|
|
|
|
|
|
|
$holding_recno) = |
1256
|
22
|
|
|
|
|
28
|
@{$self}{ qw! recno_count duplicate_count trailing_count |
1257
|
|
|
|
|
|
|
holding_recno ! }; |
1258
|
|
|
|
|
|
|
} |
1259
|
|
|
|
|
|
|
|
1260
|
|
|
|
|
|
|
### print STDERR "Hmmm. We are setting hardcoded values for bitmasks, not good. Write to adelton.\n"; |
1261
|
22
|
|
|
|
|
75
|
my ($recno_mask, $duplicate_mask, $trailing_mask) |
1262
|
|
|
|
|
|
|
= ( 2**$recno_count - 1, 2**$duplicate_count - 1, |
1263
|
|
|
|
|
|
|
2**$trailing_count - 1); |
1264
|
|
|
|
|
|
|
|
1265
|
|
|
|
|
|
|
|
1266
|
22
|
|
|
|
|
30
|
my $recno_data = ''; |
1267
|
|
|
|
|
|
|
|
1268
|
22
|
|
|
|
|
36
|
my $keys_string = ''; |
1269
|
22
|
|
|
|
|
24
|
my $prevkey = ''; |
1270
|
|
|
|
|
|
|
|
1271
|
22
|
|
|
|
|
24
|
my $row = 0; |
1272
|
22
|
|
|
|
|
25
|
for my $key (@{$self->{'keys'}}) { |
|
22
|
|
|
|
|
52
|
|
1273
|
424
|
|
|
|
|
474
|
my $dupl = 0; |
1274
|
|
|
|
|
|
|
|
1275
|
424
|
|
|
|
|
543
|
my $out = $key; |
1276
|
|
|
|
|
|
|
# some encoding for numbers |
1277
|
424
|
50
|
|
|
|
750
|
if ($numdate) { |
1278
|
0
|
0
|
|
|
|
0
|
if ($keylength == 8) { |
1279
|
0
|
|
|
|
|
0
|
$out = pack 'd', $out; |
1280
|
0
|
0
|
|
|
|
0
|
$out = reverse $out unless $XBase::Index::BIGEND; |
1281
|
|
|
|
|
|
|
} else { |
1282
|
0
|
|
|
|
|
0
|
$out = pack 'N', $out; |
1283
|
|
|
|
|
|
|
} |
1284
|
|
|
|
|
|
|
|
1285
|
|
|
|
|
|
|
|
1286
|
0
|
0
|
|
|
|
0
|
unless (0x80 & unpack('C', $out)) { |
1287
|
0
|
|
|
|
|
0
|
substr($out, 0, 1) |= "\200"; |
1288
|
|
|
|
|
|
|
} |
1289
|
0
|
|
|
|
|
0
|
else { $out = ~$out; } |
1290
|
|
|
|
|
|
|
} |
1291
|
|
|
|
|
|
|
|
1292
|
424
|
|
|
|
|
652
|
for my $i (0 .. length($out) - 1) { |
1293
|
1522
|
100
|
|
|
|
2922
|
unless (substr($out, $i, 1) eq substr($prevkey, $i, 1)) { |
1294
|
265
|
|
|
|
|
315
|
last; |
1295
|
|
|
|
|
|
|
} |
1296
|
1257
|
|
|
|
|
2091
|
$dupl++; |
1297
|
|
|
|
|
|
|
} |
1298
|
|
|
|
|
|
|
|
1299
|
424
|
|
|
|
|
579
|
my $trail = $keylength - length $out; |
1300
|
424
|
|
|
|
|
937
|
while (substr($out, -1) eq "\000") { |
1301
|
0
|
|
|
|
|
0
|
$out = substr($out, 0, length($out) - 1); |
1302
|
0
|
|
|
|
|
0
|
$trail++; |
1303
|
|
|
|
|
|
|
} |
1304
|
424
|
|
|
|
|
1107
|
$keys_string = substr($out, $dupl) . $keys_string; |
1305
|
|
|
|
|
|
|
|
1306
|
|
|
|
|
|
|
|
1307
|
424
|
|
|
|
|
974
|
my $numdata = |
1308
|
|
|
|
|
|
|
(((($trail & $trailing_mask) << $duplicate_count) |
1309
|
|
|
|
|
|
|
| ($dupl & $duplicate_mask)) << $recno_count) |
1310
|
|
|
|
|
|
|
| ($self->{'values'}[$row] & $recno_mask); |
1311
|
|
|
|
|
|
|
|
1312
|
424
|
|
|
|
|
748
|
$recno_data .= substr(pack('V', $numdata), 0, $holding_recno); |
1313
|
|
|
|
|
|
|
|
1314
|
|
|
|
|
|
|
### print unpack("H*", substr($out, $dupl)), ": trail $trail, dupl $dupl\n"; |
1315
|
|
|
|
|
|
|
|
1316
|
424
|
|
|
|
|
428
|
$prevkey = $out; |
1317
|
424
|
|
|
|
|
568
|
$row++; |
1318
|
|
|
|
|
|
|
} |
1319
|
|
|
|
|
|
|
### print $keys_string, "\n"; |
1320
|
|
|
|
|
|
|
|
1321
|
|
|
|
|
|
|
### print STDERR "Hmmm. The \$numdata is really just a hack -- the shifts have to be made 64 bit clean.\n"; |
1322
|
22
|
|
|
|
|
155
|
$data .= pack 'vVCCCCCC', |
1323
|
|
|
|
|
|
|
($record_len - length($recno_data) - length($keys_string) |
1324
|
|
|
|
|
|
|
- 24), $recno_mask, $duplicate_mask, |
1325
|
|
|
|
|
|
|
$trailing_mask, $recno_count, $duplicate_count, |
1326
|
|
|
|
|
|
|
$trailing_count, $holding_recno; |
1327
|
|
|
|
|
|
|
|
1328
|
22
|
|
|
|
|
31
|
$data .= $recno_data; |
1329
|
22
|
|
|
|
|
58
|
$data .= "\000" x ($record_len - length($data) - length($keys_string)); |
1330
|
22
|
|
|
|
|
39
|
$data .= $keys_string; |
1331
|
|
|
|
|
|
|
} else { |
1332
|
0
|
|
|
|
|
0
|
my $row = 0; |
1333
|
0
|
|
|
|
|
0
|
for my $key (@{$self->{'keys'}}) { |
|
0
|
|
|
|
|
0
|
|
1334
|
0
|
|
|
|
|
0
|
my $out = $key; |
1335
|
|
|
|
|
|
|
# some encoding for numbers |
1336
|
0
|
0
|
|
|
|
0
|
if ($numdate) { |
1337
|
0
|
0
|
|
|
|
0
|
if ($keylength == 8) { |
1338
|
0
|
|
|
|
|
0
|
$out = pack 'd', $out; |
1339
|
0
|
0
|
|
|
|
0
|
$out = reverse $out unless $XBase::Index::BIGEND; |
1340
|
|
|
|
|
|
|
} else { |
1341
|
0
|
|
|
|
|
0
|
$out = pack 'N', $out; |
1342
|
|
|
|
|
|
|
} |
1343
|
|
|
|
|
|
|
|
1344
|
|
|
|
|
|
|
|
1345
|
0
|
0
|
|
|
|
0
|
unless (0x80 & unpack('C', $out)) { |
1346
|
0
|
|
|
|
|
0
|
substr($out, 0, 1) |= "\200"; |
1347
|
|
|
|
|
|
|
} |
1348
|
0
|
|
|
|
|
0
|
else { $out = ~$out; } |
1349
|
|
|
|
|
|
|
### print " *** Out2: ", unpack("H*", $out), "\n"; |
1350
|
|
|
|
|
|
|
} |
1351
|
0
|
|
|
|
|
0
|
$data .= pack "a$keylength NN", $out, |
1352
|
|
|
|
|
|
|
$self->{'values'}[$row], |
1353
|
|
|
|
|
|
|
$self->{'lefts'}[$row] * 512; |
1354
|
0
|
|
|
|
|
0
|
$row++; |
1355
|
|
|
|
|
|
|
} |
1356
|
0
|
|
|
|
|
0
|
$data .= "\000" x ($record_len - length($data)); |
1357
|
|
|
|
|
|
|
} |
1358
|
22
|
|
|
|
|
89
|
$data; |
1359
|
|
|
|
|
|
|
} |
1360
|
|
|
|
|
|
|
|
1361
|
|
|
|
|
|
|
sub write_page { |
1362
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1363
|
0
|
|
|
|
|
0
|
my $indexfile = $self->{'indexfile'}; |
1364
|
|
|
|
|
|
|
|
1365
|
0
|
|
|
|
|
0
|
my $data = $self->prepare_scalar_for_write; |
1366
|
0
|
0
|
|
|
|
0
|
die "Data is too long in cdx::write_page for $self->{'num'}\n" |
1367
|
|
|
|
|
|
|
if length $data > 512; |
1368
|
0
|
|
|
|
|
0
|
$indexfile->write_record($self->{'num'}, $data); |
1369
|
|
|
|
|
|
|
} |
1370
|
|
|
|
|
|
|
|
1371
|
|
|
|
|
|
|
# Saves current page, taking into account all neighbour and parent |
1372
|
|
|
|
|
|
|
# pages. We can safely assume that this method is called for pages |
1373
|
|
|
|
|
|
|
# that have been loaded using prepare_select_eq and fetch, so they |
1374
|
|
|
|
|
|
|
# have the parent pointers set correctly. |
1375
|
|
|
|
|
|
|
sub write_with_context { |
1376
|
5
|
|
|
5
|
|
7
|
my $self = shift; # page to save |
1377
|
5
|
50
|
|
|
|
9
|
print STDERR "XBase::cdx::Page::write_with_context called ($self->{'num'})\n" if $DEBUG; |
1378
|
|
|
|
|
|
|
|
1379
|
5
|
|
|
|
|
7
|
my $indexfile = $self->{'indexfile'}; |
1380
|
|
|
|
|
|
|
|
1381
|
5
|
|
|
|
|
7
|
my $self_num = $self->{'num'}; |
1382
|
|
|
|
|
|
|
|
1383
|
|
|
|
|
|
|
# get the current page as data to be written |
1384
|
5
|
|
|
|
|
7
|
my $data = $self->prepare_scalar_for_write; |
1385
|
|
|
|
|
|
|
|
1386
|
5
|
50
|
|
|
|
7
|
if (not @{$self->{'keys'}}) { |
|
5
|
|
|
|
|
15
|
|
1387
|
0
|
|
|
|
|
0
|
$indexfile->write_record($self_num, $data); |
1388
|
|
|
|
|
|
|
|
1389
|
|
|
|
|
|
|
# empty root page means no more work, just save |
1390
|
0
|
0
|
|
|
|
0
|
return if $self_num == $indexfile->{'start_page'}; |
1391
|
|
|
|
|
|
|
|
1392
|
0
|
|
|
|
|
0
|
print STDERR "The page $self_num is empty, releasing from the chain\n"; |
1393
|
|
|
|
|
|
|
|
1394
|
|
|
|
|
|
|
# first we update the brothers |
1395
|
0
|
|
|
|
|
0
|
my $right_brother_num = $self->{'right_brother'}; |
1396
|
0
|
|
|
|
|
0
|
my $left_brother_num = $self->{'left_brother'}; |
1397
|
0
|
0
|
|
|
|
0
|
if ($right_brother_num != 0xFFFFFFFF) { |
1398
|
0
|
|
|
|
|
0
|
my $fix_brother = $indexfile->get_record($right_brother_num / 512); |
1399
|
0
|
|
|
|
|
0
|
$fix_brother->{'left_brother'} = $left_brother_num; |
1400
|
0
|
|
|
|
|
0
|
$fix_brother->write_page; |
1401
|
|
|
|
|
|
|
} |
1402
|
0
|
0
|
|
|
|
0
|
if ($left_brother_num != 0xFFFFFFFF) { |
1403
|
0
|
|
|
|
|
0
|
my $fix_brother = $indexfile->get_record($left_brother_num / 512); |
1404
|
0
|
|
|
|
|
0
|
$fix_brother->{'right_brother'} = $right_brother_num; |
1405
|
0
|
|
|
|
|
0
|
$fix_brother->write_page; |
1406
|
|
|
|
|
|
|
} |
1407
|
|
|
|
|
|
|
|
1408
|
|
|
|
|
|
|
# now we need to release ourselves from parent as well |
1409
|
0
|
0
|
|
|
|
0
|
my $parent = $self->get_parent_page or die "Index corrupt: no parent for page $self ($self_num)\n"; |
1410
|
|
|
|
|
|
|
|
1411
|
0
|
|
|
|
|
0
|
my $maxindex = $#{$parent->{'lefts'}}; |
|
0
|
|
|
|
|
0
|
|
1412
|
0
|
|
|
|
|
0
|
my $i; |
1413
|
0
|
|
|
|
|
0
|
for ($i = 0; $i <= $maxindex; $i++) { |
1414
|
0
|
0
|
|
|
|
0
|
if ($parent->{'lefts'}[$i] == $self_num) { |
1415
|
0
|
|
|
|
|
0
|
splice @{$parent->{'keys'}}, $i, 1; |
|
0
|
|
|
|
|
0
|
|
1416
|
0
|
|
|
|
|
0
|
splice @{$parent->{'values'}}, $i, 1; |
|
0
|
|
|
|
|
0
|
|
1417
|
0
|
|
|
|
|
0
|
splice @{$parent->{'lefts'}}, $i, 1; |
|
0
|
|
|
|
|
0
|
|
1418
|
0
|
|
|
|
|
0
|
last; |
1419
|
|
|
|
|
|
|
} |
1420
|
|
|
|
|
|
|
} |
1421
|
0
|
0
|
|
|
|
0
|
if ($i > $maxindex) { |
1422
|
0
|
|
|
|
|
0
|
die "Index corrupt: parent doesn't point to us in write_with_context $self ($self_num)\n"; |
1423
|
|
|
|
|
|
|
} |
1424
|
0
|
|
|
|
|
0
|
$parent->write_with_context; |
1425
|
0
|
|
|
|
|
0
|
return; |
1426
|
|
|
|
|
|
|
} |
1427
|
|
|
|
|
|
|
|
1428
|
|
|
|
|
|
|
|
1429
|
5
|
50
|
|
|
|
17
|
if (length $data > 512) { # we need to split the page |
|
|
50
|
|
|
|
|
|
1430
|
0
|
|
|
|
|
0
|
print STDERR "Splitting full page $self ($self_num)\n"; |
1431
|
|
|
|
|
|
|
|
1432
|
|
|
|
|
|
|
# create will give us brand new empty page |
1433
|
|
|
|
|
|
|
|
1434
|
0
|
|
|
|
|
0
|
my $new_page = __PACKAGE__->create($indexfile); |
1435
|
0
|
|
|
|
|
0
|
$self->{'attributes'} &= 0xfffe; |
1436
|
0
|
|
|
|
|
0
|
$new_page->{'attributes'} = $self->{'attributes'}; |
1437
|
|
|
|
|
|
|
|
1438
|
0
|
|
|
|
|
0
|
my $total_rows = scalar(@{$self->{'keys'}}); |
|
0
|
|
|
|
|
0
|
|
1439
|
0
|
|
|
|
|
0
|
my $half_rows = int($total_rows / 2); |
1440
|
|
|
|
|
|
|
|
1441
|
|
|
|
|
|
|
# primary split |
1442
|
0
|
0
|
|
|
|
0
|
if ($half_rows == 0) { $half_rows++; } |
|
0
|
|
|
|
|
0
|
|
1443
|
0
|
0
|
|
|
|
0
|
if ($half_rows == $total_rows) { |
1444
|
0
|
|
|
|
|
0
|
die "Fatal trouble: page $self ($self_num) is full but I'm not able to split it\n"; |
1445
|
|
|
|
|
|
|
} |
1446
|
|
|
|
|
|
|
|
1447
|
|
|
|
|
|
|
# new page is right brother (will get bigger values) |
1448
|
0
|
|
|
|
|
0
|
$new_page->{'right_brother'} = $self->{'right_brother'}; |
1449
|
0
|
|
|
|
|
0
|
$new_page->{'left_brother'} = $self_num * 512; |
1450
|
0
|
|
|
|
|
0
|
$self->{'right_brother'} = $new_page->{'num'} * 512; |
1451
|
|
|
|
|
|
|
|
1452
|
0
|
0
|
|
|
|
0
|
if ($new_page->{'right_brother'} != 0xFFFFFFFF) { |
1453
|
0
|
|
|
|
|
0
|
my $fix_brother = $indexfile->get_record($new_page->{'right_brother'} / 512); |
1454
|
0
|
|
|
|
|
0
|
$fix_brother->{'left_brother'} = $new_page->{'num'} * 512; |
1455
|
0
|
|
|
|
|
0
|
$fix_brother->write_page; |
1456
|
|
|
|
|
|
|
} |
1457
|
|
|
|
|
|
|
|
1458
|
|
|
|
|
|
|
# we'll split keys and values |
1459
|
0
|
|
|
|
|
0
|
$new_page->{'keys'} = [ @{$self->{'keys'}}[$half_rows .. $total_rows - 1] ]; |
|
0
|
|
|
|
|
0
|
|
1460
|
0
|
|
|
|
|
0
|
splice @{$self->{'keys'}}, $half_rows, $total_rows - $half_rows; |
|
0
|
|
|
|
|
0
|
|
1461
|
0
|
|
|
|
|
0
|
$new_page->{'values'} = [ @{$self->{'values'}}[$half_rows .. $total_rows - 1] ]; |
|
0
|
|
|
|
|
0
|
|
1462
|
0
|
|
|
|
|
0
|
splice @{$self->{'values'}}, $half_rows, $total_rows - $half_rows; |
|
0
|
|
|
|
|
0
|
|
1463
|
|
|
|
|
|
|
|
1464
|
|
|
|
|
|
|
# and we'll split pointers to lower levels, if there are any |
1465
|
0
|
0
|
|
|
|
0
|
if (defined $self->{'lefts'}) { |
1466
|
0
|
|
|
|
|
0
|
$new_page->{'lefts'} = [ @{$self->{'lefts'}}[$half_rows .. $total_rows - 1] ]; |
|
0
|
|
|
|
|
0
|
|
1467
|
0
|
|
|
|
|
0
|
my $new_page_num = $new_page->{'num'}; |
1468
|
0
|
|
|
|
|
0
|
for my $q (@{$new_page->{'lefts'}}) { |
|
0
|
|
|
|
|
0
|
|
1469
|
0
|
0
|
0
|
|
|
0
|
if (defined $q and defined $indexfile->{'pages_cache'}{$q}) { |
1470
|
0
|
|
|
|
|
0
|
$indexfile->{'pages_cache'}{$q}{'parent'} = $new_page_num; |
1471
|
|
|
|
|
|
|
} |
1472
|
|
|
|
|
|
|
} |
1473
|
0
|
|
|
|
|
0
|
splice @{$self->{'lefts'}}, $half_rows, $total_rows - $half_rows - 1; |
|
0
|
|
|
|
|
0
|
|
1474
|
|
|
|
|
|
|
} |
1475
|
|
|
|
|
|
|
|
1476
|
0
|
|
|
|
|
0
|
my $parent; |
1477
|
0
|
0
|
|
|
|
0
|
if ($self_num == $indexfile->{'start_page'}) { |
1478
|
|
|
|
|
|
|
# we're splitting the root page, so we will |
1479
|
|
|
|
|
|
|
# create new one |
1480
|
0
|
|
|
|
|
0
|
$parent = __PACKAGE__->create($indexfile); |
1481
|
|
|
|
|
|
|
|
1482
|
0
|
|
|
|
|
0
|
$indexfile->{'start_page'} = $parent->{'num'}; |
1483
|
0
|
|
|
|
|
0
|
$indexfile->write_header; |
1484
|
|
|
|
|
|
|
|
1485
|
|
|
|
|
|
|
### xxxxxxxxxxxxxxxxxxx |
1486
|
|
|
|
|
|
|
### And here we should write the header so that |
1487
|
|
|
|
|
|
|
### the new root page is saved to disk. Not |
1488
|
|
|
|
|
|
|
### tested yet. |
1489
|
|
|
|
|
|
|
### xxxxxxxxxxxxxxxxxxx |
1490
|
|
|
|
|
|
|
|
1491
|
0
|
|
|
|
|
0
|
$parent->{'attributes'} = 1; # root page |
1492
|
|
|
|
|
|
|
|
1493
|
0
|
|
|
|
|
0
|
$parent->{'keys'} = [ $self->{'keys'}[-1], |
1494
|
|
|
|
|
|
|
$new_page->{'keys'}[-1] ]; |
1495
|
0
|
|
|
|
|
0
|
$parent->{'values'} = [ $self->{'values'}[-1], |
1496
|
|
|
|
|
|
|
$new_page->{'values'}[-1] ]; |
1497
|
0
|
|
|
|
|
0
|
$parent->{'lefts'} = [ $self_num, $new_page->{'num'} ]; |
1498
|
|
|
|
|
|
|
} else { # update pointers in parent page |
1499
|
0
|
0
|
|
|
|
0
|
$parent = $self->get_parent_page or die "Index corrupt: no parent for page $self ($self_num)\n"; |
1500
|
0
|
|
|
|
|
0
|
my $maxindex = $#{$parent->{'lefts'}}; |
|
0
|
|
|
|
|
0
|
|
1501
|
0
|
|
|
|
|
0
|
my $i = 0; |
1502
|
|
|
|
|
|
|
|
1503
|
|
|
|
|
|
|
# find pointer to ourselves in the parent |
1504
|
0
|
|
|
|
|
0
|
while ($i <= $maxindex) { |
1505
|
0
|
0
|
|
|
|
0
|
last if $parent->{'lefts'}[$i] == $self_num; |
1506
|
0
|
|
|
|
|
0
|
$i++; |
1507
|
|
|
|
|
|
|
} |
1508
|
|
|
|
|
|
|
|
1509
|
0
|
0
|
|
|
|
0
|
if ($i > $maxindex) { |
1510
|
0
|
|
|
|
|
0
|
die "Index corrupt: parent doesn't point to us in write_with_context $self ($self_num)\n"; |
1511
|
|
|
|
|
|
|
} |
1512
|
|
|
|
|
|
|
|
1513
|
|
|
|
|
|
|
# now $i is index in parent of the record pointing to us |
1514
|
|
|
|
|
|
|
|
1515
|
0
|
|
|
|
|
0
|
splice @{$parent->{'keys'}}, $i, 1, |
|
0
|
|
|
|
|
0
|
|
1516
|
|
|
|
|
|
|
$self->{'keys'}[-1], $new_page->{'keys'}[-1]; |
1517
|
0
|
|
|
|
|
0
|
splice @{$parent->{'values'}}, $i, 1, |
|
0
|
|
|
|
|
0
|
|
1518
|
|
|
|
|
|
|
$self->{'values'}[-1], $new_page->{'values'}[-1]; |
1519
|
0
|
|
|
|
|
0
|
splice @{$parent->{'lefts'}}, $i, 1, |
|
0
|
|
|
|
|
0
|
|
1520
|
|
|
|
|
|
|
$self_num, $new_page->{'num'}; |
1521
|
|
|
|
|
|
|
} |
1522
|
|
|
|
|
|
|
|
1523
|
0
|
|
|
|
|
0
|
$self->write_page; |
1524
|
|
|
|
|
|
|
|
1525
|
0
|
|
|
|
|
0
|
$new_page->{'parent'} = $self->{'parent'}; |
1526
|
0
|
|
|
|
|
0
|
$new_page->write_page; |
1527
|
|
|
|
|
|
|
|
1528
|
0
|
|
|
|
|
0
|
$parent->write_with_context; |
1529
|
|
|
|
|
|
|
} |
1530
|
|
|
|
|
|
|
elsif ($self_num != $indexfile->{'start_page'}) { |
1531
|
|
|
|
|
|
|
# the output data is OK, write is out |
1532
|
|
|
|
|
|
|
# but this is not root page, so we need to make sure the |
1533
|
|
|
|
|
|
|
# parent is updated as well |
1534
|
0
|
|
|
|
|
0
|
$indexfile->write_record($self_num, $data); |
1535
|
|
|
|
|
|
|
|
1536
|
|
|
|
|
|
|
# now we need to check if the parent page still points |
1537
|
|
|
|
|
|
|
# correctly to us (the last value might have changed) |
1538
|
0
|
0
|
|
|
|
0
|
my $parent = $self->get_parent_page or die "Index corrupt: no parent for page $self ($self_num)\n"; |
1539
|
|
|
|
|
|
|
|
1540
|
0
|
|
|
|
|
0
|
my $maxindex = $#{$parent->{'lefts'}}; |
|
0
|
|
|
|
|
0
|
|
1541
|
0
|
|
|
|
|
0
|
my $i = 0; |
1542
|
|
|
|
|
|
|
|
1543
|
|
|
|
|
|
|
# find pointer to ourselves in the parent |
1544
|
0
|
|
|
|
|
0
|
while ($i <= $maxindex) { |
1545
|
0
|
0
|
|
|
|
0
|
last if $parent->{'lefts'}[$i] == $self_num; |
1546
|
0
|
|
|
|
|
0
|
$i++; |
1547
|
|
|
|
|
|
|
} |
1548
|
|
|
|
|
|
|
|
1549
|
0
|
0
|
|
|
|
0
|
if ($i > $maxindex) { |
1550
|
0
|
|
|
|
|
0
|
die "Index corrupt: parent doesn't point to us in write_with_context $self ($self_num)\n"; |
1551
|
|
|
|
|
|
|
} |
1552
|
|
|
|
|
|
|
|
1553
|
|
|
|
|
|
|
# now $i is index in parent of the record pointing to us |
1554
|
|
|
|
|
|
|
|
1555
|
0
|
0
|
|
|
|
0
|
if ($parent->{'values'}[$i] != $self->{'values'}[-1]) { |
1556
|
0
|
|
|
|
|
0
|
print STDERR "Will need to update the parent -- last value in myself changed ($self_num)\n"; |
1557
|
0
|
|
|
|
|
0
|
$parent->{'values'}[$i] = $self->{'values'}[-1]; |
1558
|
0
|
|
|
|
|
0
|
$parent->{'keys'}[$i] = $self->{'keys'}[-1]; |
1559
|
0
|
|
|
|
|
0
|
$parent->write_with_context; |
1560
|
|
|
|
|
|
|
} |
1561
|
|
|
|
|
|
|
|
1562
|
|
|
|
|
|
|
} else { # write out root page |
1563
|
5
|
|
|
|
|
24
|
$indexfile->write_record($self_num, $data); |
1564
|
|
|
|
|
|
|
} |
1565
|
|
|
|
|
|
|
|
1566
|
5
|
50
|
|
|
|
14
|
print STDERR "XBase::cdx::Page::write_with_context finished ($self->{'num'})\n" if $DEBUG; |
1567
|
|
|
|
|
|
|
} |
1568
|
|
|
|
|
|
|
|
1569
|
|
|
|
|
|
|
# finds parent page for the object |
1570
|
|
|
|
|
|
|
sub get_parent_page_num { |
1571
|
0
|
|
|
0
|
|
|
my $self = shift; |
1572
|
0
|
0
|
|
|
|
|
return $self->{'parent'} if defined $self->{'parent'}; |
1573
|
|
|
|
|
|
|
|
1574
|
0
|
|
|
|
|
|
my $indexfile = $self->{'indexfile'}; |
1575
|
|
|
|
|
|
|
|
1576
|
0
|
0
|
|
|
|
|
return if $self->{'num'} == $indexfile->{'start_page'}; |
1577
|
|
|
|
|
|
|
|
1578
|
|
|
|
|
|
|
# this should search to this page, effectivelly setting the |
1579
|
|
|
|
|
|
|
# level array in such a way that the parent page is there |
1580
|
0
|
|
|
|
|
|
$indexfile->prepare_select_eq($self->{'keys'}[0], $self->{'values'}[0]); |
1581
|
|
|
|
|
|
|
|
1582
|
|
|
|
|
|
|
### print STDERR "self($self->{'num'}): $self, pages: @{$indexfile->{'pages'}}\n"; |
1583
|
|
|
|
|
|
|
### use Data::Dumper; print Dumper $indexfile; |
1584
|
0
|
|
|
|
|
|
my $pageindex = $#{$indexfile->{'pages'}}; |
|
0
|
|
|
|
|
|
|
1585
|
0
|
|
|
|
|
|
while ($pageindex >= 0) { |
1586
|
0
|
0
|
|
|
|
|
if ("$self" eq "$indexfile->{'pages'}[$pageindex]") { |
1587
|
0
|
|
|
|
|
|
print STDERR "Parent page for $self->{'num'} is $indexfile->{'pages'}[$pageindex - 1]{'num'}.\n"; |
1588
|
0
|
|
|
|
|
|
return $indexfile->{'pages'}[$pageindex - 1]->{'num'}; |
1589
|
|
|
|
|
|
|
} |
1590
|
0
|
|
|
|
|
|
$pageindex--; |
1591
|
|
|
|
|
|
|
} |
1592
|
0
|
|
|
|
|
|
return undef; |
1593
|
|
|
|
|
|
|
} |
1594
|
|
|
|
|
|
|
sub get_parent_page { |
1595
|
0
|
|
|
0
|
|
|
my $self = shift; |
1596
|
0
|
0
|
|
|
|
|
my $parent_num = $self->get_parent_page_num or return; |
1597
|
0
|
|
|
|
|
|
my $indexfile = $self->{'indexfile'}; |
1598
|
0
|
|
|
|
|
|
return $indexfile->get_record($parent_num); |
1599
|
|
|
|
|
|
|
} |
1600
|
|
|
|
|
|
|
|
1601
|
|
|
|
|
|
|
1; |
1602
|
|
|
|
|
|
|
|
1603
|
|
|
|
|
|
|
__END__ |