Committed fix for RT#35140
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep / Array.pm
CommitLineData
6fe26b29 1package DBM::Deep::Array;
2
2120a181 3use 5.006_000;
460b1067 4
6fe26b29 5use strict;
460b1067 6use warnings;
6fe26b29 7
b8370759 8our $VERSION = q(1.0010);
86867f3a 9
8fec41b9 10# This is to allow DBM::Deep::Array to handle negative indices on
11# its own. Otherwise, Perl would intercept the call to negative
12# indices for us. This was causing bugs for negative index handling.
460b1067 13our $NEGATIVE_INDICES = 1;
7910cf68 14
6fe26b29 15use base 'DBM::Deep';
16
e1b265cc 17use Scalar::Util ();
18
596e9574 19sub _get_self {
8db25060 20 eval { local $SIG{'__DIE__'}; tied( @{$_[0]} ) } || $_[0]
596e9574 21}
22
e00d0eb3 23sub _repr { [] }
2120a181 24
6fe26b29 25sub TIEARRAY {
6fe26b29 26 my $class = shift;
0ca7ea98 27 my $args = $class->_get_args( @_ );
504185fb 28
29 $args->{type} = $class->TYPE_ARRAY;
30
31 return $class->_init($args);
6fe26b29 32}
33
7f441181 34sub FETCH {
eea0d863 35 my $self = shift->_get_self;
36 my ($key) = @_;
7f441181 37
504185fb 38 $self->lock( $self->LOCK_SH );
9a187d8c 39
2120a181 40 if ( !defined $key ) {
888453b9 41 $self->unlock;
2120a181 42 DBM::Deep->_throw_error( "Cannot use an undefined array index." );
43 }
44 elsif ( $key =~ /^-?\d+$/ ) {
7f441181 45 if ( $key < 0 ) {
46 $key += $self->FETCHSIZE;
9281d66b 47 unless ( $key >= 0 ) {
48 $self->unlock;
49 return;
50 }
7f441181 51 }
c3aafc14 52 }
2120a181 53 elsif ( $key ne 'length' ) {
54 $self->unlock;
55 DBM::Deep->_throw_error( "Cannot use '$key' as an array index." );
7f441181 56 }
57
2120a181 58 my $rv = $self->SUPER::FETCH( $key );
9281d66b 59
60 $self->unlock;
61
62 return $rv;
7f441181 63}
64
cb79ec85 65sub STORE {
66 my $self = shift->_get_self;
67 my ($key, $value) = @_;
68
9281d66b 69 $self->lock( $self->LOCK_EX );
70
9ab67b8c 71 my $size;
c3aafc14 72 my $idx_is_numeric;
2120a181 73 if ( !defined $key ) {
888453b9 74 $self->unlock;
2120a181 75 DBM::Deep->_throw_error( "Cannot use an undefined array index." );
76 }
77 elsif ( $key =~ /^-?\d+$/ ) {
c3aafc14 78 $idx_is_numeric = 1;
cb79ec85 79 if ( $key < 0 ) {
9ab67b8c 80 $size = $self->FETCHSIZE;
c3aafc14 81 if ( $key + $size < 0 ) {
82 die( "Modification of non-creatable array value attempted, subscript $key" );
baa27ab6 83 }
c3aafc14 84 $key += $size
cb79ec85 85 }
cb79ec85 86 }
2120a181 87 elsif ( $key ne 'length' ) {
88 $self->unlock;
89 DBM::Deep->_throw_error( "Cannot use '$key' as an array index." );
90 }
cb79ec85 91
2120a181 92 my $rv = $self->SUPER::STORE( $key, $value );
cb79ec85 93
c3aafc14 94 if ( $idx_is_numeric ) {
9ab67b8c 95 $size = $self->FETCHSIZE unless defined $size;
c3aafc14 96 if ( $key >= $size ) {
97 $self->STORESIZE( $key + 1 );
9ab67b8c 98 }
cb79ec85 99 }
100
9281d66b 101 $self->unlock;
102
cb79ec85 103 return $rv;
104}
105
baa27ab6 106sub EXISTS {
eea0d863 107 my $self = shift->_get_self;
108 my ($key) = @_;
baa27ab6 109
504185fb 110 $self->lock( $self->LOCK_SH );
9281d66b 111
2120a181 112 if ( !defined $key ) {
888453b9 113 $self->unlock;
2120a181 114 DBM::Deep->_throw_error( "Cannot use an undefined array index." );
115 }
116 elsif ( $key =~ /^-?\d+$/ ) {
baa27ab6 117 if ( $key < 0 ) {
118 $key += $self->FETCHSIZE;
9281d66b 119 unless ( $key >= 0 ) {
120 $self->unlock;
121 return;
122 }
123 }
9281d66b 124 }
2120a181 125 elsif ( $key ne 'length' ) {
126 $self->unlock;
127 DBM::Deep->_throw_error( "Cannot use '$key' as an array index." );
128 }
9281d66b 129
130 my $rv = $self->SUPER::EXISTS( $key );
131
132 $self->unlock;
133
134 return $rv;
135}
136
137sub DELETE {
eea0d863 138 my $self = shift->_get_self;
139 my ($key) = @_;
6e6789b0 140 warn "ARRAY::DELETE($self,$key)\n" if DBM::Deep::DEBUG;
9281d66b 141
9281d66b 142 $self->lock( $self->LOCK_EX );
143
144 my $size = $self->FETCHSIZE;
2120a181 145 if ( !defined $key ) {
888453b9 146 $self->unlock;
2120a181 147 DBM::Deep->_throw_error( "Cannot use an undefined array index." );
148 }
149 elsif ( $key =~ /^-?\d+$/ ) {
9281d66b 150 if ( $key < 0 ) {
151 $key += $size;
152 unless ( $key >= 0 ) {
153 $self->unlock;
154 return;
155 }
baa27ab6 156 }
baa27ab6 157 }
2120a181 158 elsif ( $key ne 'length' ) {
159 $self->unlock;
160 DBM::Deep->_throw_error( "Cannot use '$key' as an array index." );
161 }
baa27ab6 162
c3aafc14 163 my $rv = $self->SUPER::DELETE( $key );
9281d66b 164
c3aafc14 165 if ($rv && $key == $size - 1) {
2120a181 166 $self->STORESIZE( $key );
504185fb 167 }
9281d66b 168
169 $self->unlock;
170
171 return $rv;
baa27ab6 172}
173
2120a181 174# Now that we have a real Reference sector, we should store arrayzize there. However,
175# arraysize needs to be transactionally-aware, so a simple location to store it isn't
176# going to work.
6fe26b29 177sub FETCHSIZE {
9281d66b 178 my $self = shift->_get_self;
179
180 $self->lock( $self->LOCK_SH );
181
83371fe3 182 my $SAVE_FILTER = $self->_storage->{filter_fetch_value};
183 $self->_storage->{filter_fetch_value} = undef;
504185fb 184
2120a181 185 my $size = $self->FETCH('length') || 0;
504185fb 186
83371fe3 187 $self->_storage->{filter_fetch_value} = $SAVE_FILTER;
504185fb 188
9281d66b 189 $self->unlock;
190
2120a181 191 return $size;
6fe26b29 192}
193
194sub STORESIZE {
eea0d863 195 my $self = shift->_get_self;
504185fb 196 my ($new_length) = @_;
197
9281d66b 198 $self->lock( $self->LOCK_EX );
199
83371fe3 200 my $SAVE_FILTER = $self->_storage->{filter_store_value};
201 $self->_storage->{filter_store_value} = undef;
504185fb 202
2120a181 203 my $result = $self->STORE('length', $new_length, 'length');
504185fb 204
83371fe3 205 $self->_storage->{filter_store_value} = $SAVE_FILTER;
504185fb 206
9281d66b 207 $self->unlock;
208
504185fb 209 return $result;
6fe26b29 210}
211
212sub POP {
eea0d863 213 my $self = shift->_get_self;
9281d66b 214
215 $self->lock( $self->LOCK_EX );
216
504185fb 217 my $length = $self->FETCHSIZE();
218
219 if ($length) {
220 my $content = $self->FETCH( $length - 1 );
221 $self->DELETE( $length - 1 );
9281d66b 222
223 $self->unlock;
224
504185fb 225 return $content;
226 }
227 else {
9281d66b 228 $self->unlock;
504185fb 229 return;
230 }
6fe26b29 231}
232
233sub PUSH {
2ac02042 234 my $self = shift->_get_self;
504185fb 235
9281d66b 236 $self->lock( $self->LOCK_EX );
237
504185fb 238 my $length = $self->FETCHSIZE();
9281d66b 239
504185fb 240 while (my $content = shift @_) {
241 $self->STORE( $length, $content );
242 $length++;
243 }
8f6d6ed0 244
9281d66b 245 $self->unlock;
246
8f6d6ed0 247 return $length;
6fe26b29 248}
249
807f63a7 250# XXX This really needs to be something more direct within the file, not a
251# fetch and re-store. -RobK, 2007-09-20
252sub _move_value {
253 my $self = shift;
254 my ($old_key, $new_key) = @_;
255
1cff45d7 256 return $self->_engine->make_reference( $self, $old_key, $new_key );
807f63a7 257}
258
6fe26b29 259sub SHIFT {
eea0d863 260 my $self = shift->_get_self;
6e6789b0 261 warn "SHIFT($self)\n" if DBM::Deep::DEBUG;
9281d66b 262
263 $self->lock( $self->LOCK_EX );
264
504185fb 265 my $length = $self->FETCHSIZE();
266
1cff45d7 267 if ( !$length ) {
9281d66b 268 $self->unlock;
504185fb 269 return;
270 }
1cff45d7 271
272 my $content = $self->FETCH( 0 );
273
274 for (my $i = 0; $i < $length - 1; $i++) {
275 $self->_move_value( $i+1, $i );
276 }
6e6789b0 277
1cff45d7 278 $self->DELETE( $length - 1 );
279
280 $self->unlock;
281
282 return $content;
6fe26b29 283}
284
285sub UNSHIFT {
2ac02042 286 my $self = shift->_get_self;
504185fb 287 my @new_elements = @_;
9281d66b 288
289 $self->lock( $self->LOCK_EX );
290
504185fb 291 my $length = $self->FETCHSIZE();
292 my $new_size = scalar @new_elements;
293
294 if ($length) {
295 for (my $i = $length - 1; $i >= 0; $i--) {
807f63a7 296 $self->_move_value( $i, $i+$new_size );
504185fb 297 }
1cff45d7 298
299 $self->STORESIZE( $length + $new_size );
504185fb 300 }
301
302 for (my $i = 0; $i < $new_size; $i++) {
303 $self->STORE( $i, $new_elements[$i] );
304 }
8f6d6ed0 305
9281d66b 306 $self->unlock;
307
8f6d6ed0 308 return $length + $new_size;
6fe26b29 309}
310
311sub SPLICE {
2ac02042 312 my $self = shift->_get_self;
9281d66b 313
314 $self->lock( $self->LOCK_EX );
315
504185fb 316 my $length = $self->FETCHSIZE();
317
318 ##
319 # Calculate offset and length of splice
320 ##
321 my $offset = shift;
714618f0 322 $offset = 0 unless defined $offset;
504185fb 323 if ($offset < 0) { $offset += $length; }
324
325 my $splice_length;
326 if (scalar @_) { $splice_length = shift; }
327 else { $splice_length = $length - $offset; }
328 if ($splice_length < 0) { $splice_length += ($length - $offset); }
329
330 ##
331 # Setup array with new elements, and copy out old elements for return
332 ##
333 my @new_elements = @_;
334 my $new_size = scalar @new_elements;
335
df3c5701 336 my @old_elements = map {
337 $self->FETCH( $_ )
338 } $offset .. ($offset + $splice_length - 1);
504185fb 339
340 ##
341 # Adjust array length, and shift elements to accomodate new section.
342 ##
6fe26b29 343 if ( $new_size != $splice_length ) {
344 if ($new_size > $splice_length) {
345 for (my $i = $length - 1; $i >= $offset + $splice_length; $i--) {
807f63a7 346 $self->_move_value( $i, $i + ($new_size - $splice_length) );
6fe26b29 347 }
1cff45d7 348 $self->STORESIZE( $length + $new_size - $splice_length );
6fe26b29 349 }
350 else {
351 for (my $i = $offset + $splice_length; $i < $length; $i++) {
807f63a7 352 $self->_move_value( $i, $i + ($new_size - $splice_length) );
6fe26b29 353 }
354 for (my $i = 0; $i < $splice_length - $new_size; $i++) {
355 $self->DELETE( $length - 1 );
356 $length--;
357 }
358 }
504185fb 359 }
360
361 ##
362 # Insert new elements into array
363 ##
364 for (my $i = $offset; $i < $offset + $new_size; $i++) {
365 $self->STORE( $i, shift @new_elements );
366 }
367
9281d66b 368 $self->unlock;
369
504185fb 370 ##
371 # Return deleted section, or last element in scalar context.
372 ##
373 return wantarray ? @old_elements : $old_elements[-1];
6fe26b29 374}
375
2120a181 376# We don't need to populate it, yet.
460b1067 377# It will be useful, though, when we split out HASH and ARRAY
685e40f1 378sub EXTEND {
504185fb 379 ##
380 # Perl will call EXTEND() when the array is likely to grow.
381 # We don't care, but include it because it gets called at times.
382 ##
685e40f1 383}
6fe26b29 384
f9c33187 385sub _copy_node {
898fd1fd 386 my $self = shift;
f9c33187 387 my ($db_temp) = @_;
388
389 my $length = $self->length();
390 for (my $index = 0; $index < $length; $index++) {
391 my $value = $self->get($index);
392 $self->_copy_value( \$db_temp->[$index], $value );
393 }
394
395 return 1;
396}
397
6fe26b29 398##
399# Public method aliases
400##
f9c33187 401sub length { (shift)->FETCHSIZE(@_) }
402sub pop { (shift)->POP(@_) }
403sub push { (shift)->PUSH(@_) }
404sub unshift { (shift)->UNSHIFT(@_) }
405sub splice { (shift)->SPLICE(@_) }
406
407# This must be last otherwise we have to qualify all other calls to shift
408# as calls to CORE::shift
f75b719e 409sub shift { (CORE::shift)->SHIFT(@_) }
6fe26b29 410
4111;
412__END__