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