Commit | Line | Data |
4f0f6fff |
1 | package DBM::Deep::SQL::Array; |
2 | |
3 | use strict; |
4 | use warnings FATAL => 'all'; |
5 | |
6 | BEGIN { |
7 | use base 'DBM::Deep::SQL::Util'; |
8 | |
9 | use Storable 'nfreeze', 'thaw'; |
10 | } |
11 | |
12 | sub _get_self |
13 | { |
14 | eval { local $SIG{'__DIE__'}; tied( @{$_[0]} ) } || $_[0]; |
15 | } |
16 | |
17 | sub _size |
18 | { |
19 | my ($obj) = @_; |
20 | my $sq = $obj->_select( |
21 | 'table' => 'rec_array_item', |
22 | 'fields' => 'max(pos)', |
23 | 'where' => { |
24 | 'array' => $obj->{'id'}, |
25 | }, |
26 | ); |
27 | if (defined $sq->[0]->[0]) |
28 | { |
29 | return $sq->[0]->[0] + 1; |
30 | } |
31 | return 0; |
32 | } |
33 | |
34 | sub _clear |
35 | { |
36 | my ($obj) = @_; |
37 | my $sz = $obj->_size(); |
38 | foreach my $i (1..$sz) |
39 | { |
40 | $obj->_delete($i - 1); |
41 | } |
42 | $obj->{'cache'} = []; |
43 | } |
44 | |
45 | sub _delete |
46 | { |
47 | my ($obj, $i) = @_; |
48 | my $q = $obj->_select( |
49 | 'table' => 'rec_array_item', |
50 | 'fields' => ['value_type', 'value_data', 'id'], |
51 | 'where' => { |
52 | 'array' => $obj->{'id'}, |
53 | 'pos' => $i, |
54 | }, |
55 | ); |
56 | if (scalar @$q) |
57 | { |
58 | my $dt = $q->[0]->[0]; |
59 | if ($dt eq 'text' || $dt eq 'data') |
60 | { |
61 | $obj->_delete_sql('rec_value_'. $dt, {'id' => $q->[0]->[1]}); |
62 | } |
63 | elsif ($dt eq 'hash') |
64 | { |
65 | my $rec = $obj->_tiehash($q->[0]->[1]); |
66 | %$rec = (); |
67 | $obj->_delete_sql('rec_hash', {'id' => $q->[0]->[1]}); |
68 | $obj->_delete_sql('rec_item', {'id' => $q->[0]->[1]}); |
69 | } |
70 | elsif ($dt eq 'array') |
71 | { |
72 | my $rec = $obj->_tiearray($q->[0]->[1]); |
73 | @$rec = (); |
74 | $obj->_delete_sql('rec_array', {'id' => $q->[0]->[1]}); |
75 | $obj->_delete_sql('rec_item', {'id' => $q->[0]->[1]}); |
76 | } |
77 | $obj->_delete_sql('rec_array_item', {'id' => $q->[0]->[2]}); |
78 | } |
79 | delete $obj->{'cache'}->[$i]; |
80 | } |
81 | |
82 | sub _set_cache |
83 | { |
84 | my ($obj, $pos, $val) = @_; |
85 | $obj->{'cache'}->[$pos] = $val; |
86 | } |
87 | |
88 | sub _get_cache |
89 | { |
90 | my ($obj, $pos, $vref) = @_; |
91 | if (exists $obj->{'cache'}->[$pos]) |
92 | { |
93 | $$vref = $obj->{'cache'}->[$pos]; |
94 | return 1; |
95 | } |
96 | return undef; |
97 | } |
98 | |
99 | sub _exists |
100 | { |
101 | my ($obj, $i) = @_; |
102 | if (exists $obj->{'cache'}->[$i]) |
103 | { |
104 | return 1; |
105 | } |
106 | my $c = $obj->_select( |
107 | 'table' => 'rec_array_item', |
108 | 'fields' => 'count(id)', |
109 | 'where' => { |
110 | 'array' => $obj->{'id'}, |
111 | 'pos' => $i, |
112 | }, |
113 | )->[0]->[0]; |
114 | return $c; |
115 | } |
116 | |
117 | sub _data |
118 | { |
119 | my ($obj, $i) = @_; |
120 | my $q = $obj->_select( |
121 | 'table' => 'rec_array_item', |
122 | 'fields' => ['value_type', 'value_data'], |
123 | 'where' => { |
124 | 'array' => $obj->{'id'}, |
125 | 'pos' => $i, |
126 | }, |
127 | ); |
128 | if (scalar @$q) |
129 | { |
130 | my $dt = $q->[0]->[0]; |
131 | my $val = $q->[0]->[1]; |
132 | if ($dt eq 'value') |
133 | { |
134 | return $val; |
135 | } |
136 | elsif ($dt eq 'text') |
137 | { |
138 | my $dq = $obj->_select( |
139 | 'table' => 'rec_value_text', |
140 | 'fields' => 'data', |
141 | 'where' => { |
142 | 'id' => $val, |
143 | }, |
144 | ); |
145 | return $dq->[0]->[0]; |
146 | } |
147 | elsif ($dt eq 'data') |
148 | { |
149 | my $dq = $obj->_select( |
150 | 'table' => 'rec_value_data', |
151 | 'fields' => 'data', |
152 | 'where' => { |
153 | 'id' => $val, |
154 | }, |
155 | ); |
156 | if (scalar @$dq) |
157 | { |
158 | my $rec = thaw($dq->[0]->[0]); |
159 | return $rec; |
160 | } |
161 | return undef; |
162 | } |
163 | elsif ($dt eq 'array') |
164 | { |
165 | my $rec = $obj->_tiearray($val); |
166 | if ($obj->{'prefetch'}) |
167 | { |
168 | (tied(@$rec))->_prefetch(); |
169 | } |
170 | return $rec; |
171 | } |
172 | elsif ($dt eq 'hash') |
173 | { |
174 | my $rec = $obj->_tiehash($val); |
175 | if ($obj->{'prefetch'}) |
176 | { |
177 | (tied(%$rec))->_prefetch(); |
178 | } |
179 | return $rec; |
180 | } |
181 | } |
182 | return undef; |
183 | } |
184 | |
185 | sub _tiearray |
186 | { |
187 | my ($obj, $id) = @_; |
188 | my $rec = undef; |
189 | tie(@$rec, 'DBM::Deep::SQL::Array', ( |
190 | 'dbi' => $obj->{'dbi'}, |
191 | 'id' => $id, |
192 | 'prefetch' => $obj->{'prefetch'}, |
193 | )); |
194 | bless $rec, 'DBM::Deep::SQL::Array'; |
195 | return $rec; |
196 | } |
197 | |
198 | sub _tiehash |
199 | { |
200 | my ($obj, $id) = @_; |
201 | my $rec = undef; |
202 | tie(%$rec, 'DBM::Deep::SQL::Hash', ( |
203 | 'dbi' => $obj->{'dbi'}, |
204 | 'id' => $id, |
205 | 'prefetch' => $obj->{'prefetch'}, |
206 | )); |
207 | bless $rec, 'DBM::Deep::SQL::Hash'; |
208 | return $rec; |
209 | } |
210 | |
211 | sub _prefetch |
212 | { |
213 | my ($obj) = @_; |
214 | my $pd = $obj->_select( |
215 | 'table' => 'rec_array_item', |
216 | 'fields' => ['pos', 'value_type', 'value_data'], |
217 | 'where' => { |
218 | 'array' => $obj->{'id'}, |
219 | }, |
220 | ); |
221 | my @data = (); |
222 | my @datapos = (); |
223 | my @text = (); |
224 | my @textpos = (); |
225 | my @array = (); |
226 | foreach my $r (@$pd) |
227 | { |
228 | my $i = $r->[0]; |
229 | my $vt = $r->[1]; |
230 | my $val = $r->[2]; |
231 | if ($vt eq 'value') |
232 | { |
233 | $array[$i] = $val; |
234 | } |
235 | elsif ($vt eq 'text') |
236 | { |
237 | push @textpos, $i; |
238 | push @text, $val; |
239 | } |
240 | elsif ($vt eq 'data') |
241 | { |
242 | push @datapos, $i; |
243 | push @data, $val; |
244 | } |
245 | elsif ($vt eq 'array') |
246 | { |
247 | my $rec = $obj->_tiearray($val); |
248 | if ($obj->{'prefetch'}) |
249 | { |
250 | (tied(@$rec))->_prefetch(); |
251 | } |
252 | $array[$i] = $rec; |
253 | } |
254 | elsif ($vt eq 'hash') |
255 | { |
256 | my $rec = $obj->_tiehash($val); |
257 | if ($obj->{'prefetch'}) |
258 | { |
259 | (tied(@$rec))->_prefetch(); |
260 | } |
261 | $array[$i] = $rec; |
262 | } |
263 | } |
264 | if (scalar @text) |
265 | { |
266 | my $ids = join(',', @text); |
267 | my $tq = $obj->_select( |
268 | 'table' => 'rec_value_text', |
269 | 'fields' => ['id', 'data'], |
270 | 'where' => "id in ($ids)", |
271 | ); |
272 | my %data = map {$_->[0] => $_->[1]} @$tq; |
273 | foreach my $x (0..$#text) |
274 | { |
275 | my $id = $text[$x]; |
276 | my $i = $textpos[$x]; |
277 | $array[$i] = $data{$id}; |
278 | } |
279 | } |
280 | if (scalar @data) |
281 | { |
282 | my $ids = join(',', @data); |
283 | my $tq = $obj->_select( |
284 | 'table' => 'rec_value_data', |
285 | 'fields' => ['id', 'data'], |
286 | 'where' => "id in ($ids)", |
287 | ); |
288 | my %d = map {$_->[0] => $_->[1]} @$tq; |
289 | foreach my $x (0..$#data) |
290 | { |
291 | my $id = $data[$x]; |
292 | my $i = $datapos[$x]; |
293 | if (defined $d{$id}) |
294 | { |
295 | $array[$i] = thaw($d{$id}); |
296 | } |
297 | } |
298 | } |
299 | return $obj->{'cache'} = \@array; |
300 | } |
301 | |
302 | sub TIEARRAY |
303 | { |
304 | my $class = shift; |
305 | my %prm = @_; |
306 | my $obj = \%prm; |
307 | $obj->{'cache'} = []; |
308 | bless $obj, $class; |
309 | return $obj; |
310 | } |
311 | |
312 | sub FETCH |
313 | { |
314 | my ($tobj, $i) = @_; |
315 | my $obj = $tobj->_get_self(); |
316 | my $val = undef; |
317 | if ($obj->_get_cache($i, \$val)) |
318 | { |
319 | return $val; |
320 | } |
321 | $val = $obj->_data($i); |
322 | if (defined $val) |
323 | { |
324 | $obj->_set_cache($i, $val); |
325 | } |
326 | return $val; |
327 | } |
328 | |
329 | sub STORE |
330 | { |
331 | my ($tobj, $i, $val) = @_; |
332 | my $obj = $tobj->_get_self(); |
333 | my $dval = $val; |
334 | my $vt; |
335 | $val = '' unless (defined $val); |
336 | if (ref $val) |
337 | { |
338 | my $done = 0; |
339 | unless ($obj->{'serialize'}) |
340 | { |
341 | if ($val =~ /HASH/) |
342 | { |
343 | my $id = $obj->_create('hash'); |
344 | my $ta = $obj->_tiehash($id); |
345 | $dval = $ta; |
346 | foreach my $k (keys %$val) |
347 | { |
348 | $ta->{$k} = $val->{$k}; |
349 | } |
350 | $vt = 'hash'; |
351 | $val = $id; |
352 | $done = 1; |
353 | } |
354 | elsif ($val =~ /ARRAY/) |
355 | { |
356 | my $id = $obj->_create('array'); |
357 | my $ta = $obj->_tiearray($id); |
358 | $dval = $ta; |
359 | foreach my $i (0..$#{$val}) |
360 | { |
361 | $ta->[$i] = $val->[$i]; |
362 | } |
363 | $vt = 'array'; |
364 | $val = $id; |
365 | $done = 1; |
366 | } |
367 | } |
368 | unless ($done) |
369 | { |
370 | my $data = nfreeze($val); |
371 | $val = $obj->_create('value_data', { |
372 | 'data' => $data, |
373 | }); |
374 | $vt = 'data'; |
375 | } |
376 | } |
377 | elsif (length($val) > 255) |
378 | { |
379 | $val = $obj->_create('value_text', { |
380 | 'data' => $val, |
381 | }); |
382 | $vt = 'text'; |
383 | } |
384 | else |
385 | { |
386 | $vt = 'value'; |
387 | } |
388 | my $c = $obj->_select( |
389 | 'table' => 'rec_array_item', |
390 | 'fields' => ['value_type', 'id'], |
391 | 'where' => { |
392 | 'array' => $obj->{'id'}, |
393 | 'pos' => $i, |
394 | }, |
395 | ); |
396 | my $create = 1; |
397 | if (scalar @$c) |
398 | { |
399 | if ($c->[0]->[0] eq 'value') |
400 | { |
401 | $create = 0; |
402 | $obj->_update( |
403 | 'table' => 'rec_array_item', |
404 | 'fields' => { |
405 | 'value_type' => $vt, |
406 | 'value_data' => $val, |
407 | }, |
408 | 'where' => { |
409 | 'id' => $c->[0]->[1], |
410 | }, |
411 | ); |
412 | } |
413 | else |
414 | { |
415 | $obj->_delete($i); |
416 | } |
417 | } |
418 | if ($create) |
419 | { |
420 | $obj->_create('array_item', { |
421 | 'array' => $obj->{'id'}, |
422 | 'pos' => $i, |
423 | 'value_data' => $val, |
424 | 'value_type' => $vt, |
425 | }); |
426 | } |
427 | $obj->_set_cache($i, $dval); |
428 | return $dval; |
429 | } |
430 | |
431 | sub FETCHSIZE |
432 | { |
433 | my ($tobj) = @_; |
434 | my $obj = $tobj->_get_self(); |
435 | return $obj->_size(); |
436 | } |
437 | |
438 | sub EXISTS |
439 | { |
440 | my ($tobj, $i) = @_; |
441 | my $obj = $tobj->_get_self(); |
442 | return $obj->_exists($i); |
443 | } |
444 | |
445 | sub DELETE |
446 | { |
447 | my ($tobj, $i) = @_; |
448 | my $obj = $tobj->_get_self(); |
449 | return $obj->_delete($i); |
450 | } |
451 | |
452 | sub CLEAR |
453 | { |
454 | my ($tobj) = @_; |
455 | my $obj = $tobj->_get_self(); |
456 | return $obj->_clear(); |
457 | } |
458 | |
459 | sub PUSH |
460 | { |
461 | my ($tobj, @list) = @_; |
462 | my $obj = $tobj->_get_self(); |
463 | my $last = $obj->_size(); |
464 | foreach my $i (0..$#list) |
465 | { |
466 | $tobj->STORE($last + $i, $list[$i]); |
467 | } |
468 | return $obj->_size(); |
469 | } |
470 | |
471 | sub POP |
472 | { |
473 | my ($tobj) = @_; |
474 | my $obj = $tobj->_get_self(); |
475 | my $top = $obj->_size(); |
476 | unless ($top > 0) |
477 | { |
478 | return undef; |
479 | } |
480 | my $val = $obj->_data($top - 1); |
481 | $obj->_delete($top - 1); |
482 | return $val; |
483 | } |
484 | |
485 | sub SHIFT |
486 | { |
487 | my ($tobj) = @_; |
488 | my $obj = $tobj->_get_self(); |
489 | my $top = $obj->_size(); |
490 | unless ($top > 0) |
491 | { |
492 | return undef; |
493 | } |
494 | my $val = $obj->_data(0); |
495 | $obj->_delete(0); |
496 | my $sql = 'update rec_array_item set pos=pos-1 where array=? order by pos asc'; |
497 | $obj->{'dbi'}->query($sql, $obj->{'id'}); |
498 | return $val; |
499 | } |
500 | |
501 | sub UNSHIFT |
502 | { |
503 | my ($tobj, $val) = @_; |
504 | my $obj = $tobj->_get_self(); |
505 | my $top = $obj->_size(); |
506 | if ($top > 0) |
507 | { |
508 | my $sql = 'update rec_array_item set pos=pos+1 where array=? order by pos desc'; |
509 | $obj->{'dbi'}->query($sql, $obj->{'id'}); |
510 | } |
511 | return $tobj->STORE(0, $val); |
512 | } |
513 | |
514 | sub EXTEND |
515 | { |
516 | # Not needed |
517 | return; |
518 | } |
519 | |
520 | sub SPLICE |
521 | { |
522 | my ($tobj, $offset, $len, @list) = @_; |
523 | my $obj = $tobj->_get_self(); |
524 | my $cache = $obj->{'cache'}; |
525 | $obj->{'cache'} = []; |
526 | unless (defined $offset) |
527 | { |
528 | $offset = 0; |
529 | } |
530 | if (length($offset) < 0) |
531 | { |
532 | die('Splice with negative offset not supported'); # TODO |
533 | } |
534 | unless (defined $len) |
535 | { |
536 | $len = $obj->_size() - $offset; |
537 | } |
538 | if (length($len) < 0) |
539 | { |
540 | die('Splice with negative length not supported'); # TODO |
541 | } |
542 | if ($offset < $#{$cache} || $offset == 0) |
543 | { |
544 | splice(@$cache, $offset, $len, @list); |
545 | } |
546 | else |
547 | { |
548 | $cache = []; |
549 | } |
550 | my $lc = (wantarray) ? 1 : 0; |
551 | my @res = (); |
552 | if ($len > 0) |
553 | { |
554 | foreach my $i (0..($len - 1)) |
555 | { |
556 | my $k = $offset + $i; |
557 | if ($lc || $i == ($len - 1)) |
558 | { |
559 | my $rc = $tobj->FETCH($k); |
560 | my $cl = $obj->_clone_tree($rc); |
561 | push @res, $cl; |
562 | } |
563 | $obj->_delete($k); |
564 | } |
565 | } |
566 | my $elems = scalar @list; |
567 | my $diff = $elems - $len; |
568 | if ($elems > 0 || $diff < 0) |
569 | { |
570 | my $st = $offset + $len - 1; |
571 | my $dir = ($diff > 0) ? 'desc' : 'asc'; |
572 | my $sql = 'update rec_array_item set pos=pos+? where array=? and pos > ? order by pos '. $dir; |
573 | $obj->{'dbi'}->query($sql, $diff, $obj->{'id'}, $st); |
574 | foreach my $i (0..$#list) |
575 | { |
576 | $tobj->STORE($offset + $i, $list[$i]); |
577 | } |
578 | } |
579 | $obj->{'cache'} = $cache; |
580 | if ($lc) |
581 | { |
582 | return @res; |
583 | } |
584 | else |
585 | { |
586 | return $res[0]; |
587 | } |
588 | } |
589 | |
590 | sub id |
591 | { |
592 | my ($tobj) = @_; |
593 | my $obj = $tobj->_get_self(); |
594 | return $obj->{'id'}; |
595 | } |
596 | |
597 | 1; |
598 | |