line
stmt
bran
cond
sub
pod
time
code
1
=head1 NAME
2
3
Tie::Table - Maps relational tables into hashes
4
5
=head1 SYNOPSIS
6
7
use DBI;
8
$dbh=DBI->connect(...);
9
10
use Tie::Table;
11
12
$database=new Tie::Table::DB(
13
dbh => $dbh,
14
# DBI Database handler
15
seq_mode => "ora",
16
# Sequence handling mode
17
# "ora": "select seqence.currval from dual";
18
# "pg" : "select sequence.last_value";
19
# db systems, which doesn't support sequences currently
20
# doesn't supported (insert won't work)
21
prepare_cached => 0,
22
# Can use "prepare_cached" method of the DBI?
23
# This causes problems for me, and that's why the
24
# default is now 0. This param is not mandatory.
25
);
26
27
# You can use connect hash to specify connect parameters directly.
28
# In this case you doesn't need to specify "dbh" parameter:
29
# $database=new Tie::Table::DB(
30
# connect=> [$data_source, $username, $auth, \%attr],
31
# seq_currval => ...
32
# );
33
34
$company=$database->new_table (
35
table => "company", # Table name, mandatory
36
key => "id", # Primary Key for the table
37
seq => "seq_company",# Sequence name for key field generation.
38
# Mandatory only if "insert" is in use
39
);
40
41
# $database->new_table(...)
42
# is the same as
43
# new Tie::Table ( db => $database, ... )
44
45
$user =$database->new_table (
46
table => "users",
47
key => "id",
48
seq => "seq_users",
49
ref => { company_id => [ $company, "user" ] },
50
# This can be used for connecting tables.
51
# This is similar to the SQL phrase:
52
#
53
# .. company_id int references company (id),
54
#
55
# only the key field can be referenced.
56
);
57
58
%company_14_users= % {$company->{14}->user };
59
60
# All user IDs
61
keys %$user;
62
63
# Sets Company #14 Data:
64
$company_14 = $company->{14};
65
$company_14->{tax_num} = "123456";
66
$company_14->{phone1} = "+42456245546";
67
$company_14->write;
68
69
# Wrong example:
70
# $company->{14}->{tax_num} = "123456"
71
# $company->{14}->write;
72
# This doesn't work, because it always create a new Row object,
73
# and the cache is stored per object.
74
75
# Select a sub-relation
76
$table=$user->select("company_id = ?",$id);
77
78
# Select with constraint
79
$user->constraint( company_id => $id );
80
81
# Inserting a new record
82
$id=$company->insert(
83
{ name=>"abc",
84
phone1=>"30/123567",
85
mobile=>"20/1234" } );
86
if ($id) { print "Id: $id\n"; } else { print "Insert failed: "; };
87
88
# Inserting or replacing a record with a specified id;
89
$company->{1456} = { name => "abc", phone1=>"30/123456" };
90
91
# Delete record
92
delete $company->{13};
93
%{ $company->{13} }=();
94
95
# Select and constraint with returning only one row (the first):
96
97
$row = $user->select1("age > ? and parent_id = ? ",18,175);
98
99
$user_row_by_name = $user->constraint1( name => "Ms. Jackson" );
100
$user_row_by_name = $user->by( name => "Ms. Jackson" ); # by == constraint1
101
102
# Changing key order
103
104
@keys = keys %{ $table->order("group desc") };
105
106
=head1 DESCRIPTION
107
108
This class is designed for mapping a table into a perl hash, which has keys (these are the primary keys of the table), and the values are the rows, represented by a hash.
109
110
=head2 Basic Usage
111
112
You can create Tie::Table objects for tables. You must specify a parameter hash to the constructor, which has the following keys:
113
114
=over 4
115
116
=item db
117
118
This is a reference to a Tie::Table::DB module. Normally you create a new Tie::Table object by the method of the "new_table" of a Tie::Table::DB instance, then you may not specify this.
119
120
=item table
121
122
Specifies the table name
123
124
=item key
125
126
Specifies the primary key. This must be specified, so if you don't have primary keys in your table, you cannot use the Tie::Table (for the whole table. You can use it for a subset of rows specified by the "constraint" param).
127
128
=item seq
129
130
If you want to use "insert" with self-incremental keys, you must specify this. Database servers, which doesn't implement sequences (mySQL) currently not supported.
131
132
=item ref
133
134
Creating a 1:N reference. The value is a hash reference, where the keys are database fields, and the values contains the reference information in an array reference:
135
136
ref => {
137
field1 => [ $table1, "function1" ],
138
field2 => [ $table2, "function2" ],
139
};
140
141
In the example above you can use the reference field (company_id) from the "user" table to query the company in which the user work: $company_name = $user->{17}->company_id->{name}.
142
143
function1 is the name of the function, which can be used for the back-reference, eg. can be used to determine the user-ids in one company: @user_ids= keys %{ $company->{45}->users }. "users" is the function name in this example.
144
145
=item where
146
147
Specifies a "where" condition for selecting id-s, so you can select a subset of keys with this. Also available with the "search" function:
148
149
@user_ids= keys %{ $table->search("age > 25") };
150
151
or
152
153
$table=new Tie::Table (table => "table", where => "age > 25");
154
@user_ids=keys %$table;
155
156
=item constraint
157
158
This is similar to "select", but it can be used only for equality test.
159
The main advantage is that it can be used for deletion and insertion.
160
If you insert something into a table, which has constraint parameter, all
161
the values in the constraint hash is set in the new record.
162
This constraint is used internally, when somebody creates a back reference
163
by a back-reference function.
164
165
=item order
166
167
This parameter describes the key-retrieval order. The value of the parameter
168
is appended to an "order by" parameter to the sql query, which retrieves the
169
keys from the database.
170
171
=back 4
172
173
=head2 Tie::Table methods
174
175
There are predefined methods, which can be called on table objects:
176
177
=over 4
178
179
=item select $sql, @parameters
180
181
Creates a new table object with "select" parameter appended to the existing
182
one, for example:
183
184
$selected_table = $table->select("age > ?", 18);
185
186
The result is also a Tie::Table object.
187
188
=item constraint
189
190
Creates a new table object with "constraint" parameters set. This is similar
191
to the select method,but this only usable for equality relations:
192
193
$selected_table = $table->constraint( age => 18 );
194
195
If you insert into the $selected_table, then the "age" parameter automatically
196
set to "18".
197
198
=item select1, constraint1 and by
199
200
These are variations of "select" and "constraint". The only difference is that
201
you will return only the first row of the result if more than one row matched.
202
203
These syntax are good if you know that at most 1 row is returned by the
204
select, for example when you have more than one unique indices on the table.
205
206
"by" is a short version of "constraint1", only a syntactic sugar:
207
208
$ms_jackson_row = $user->by( name => "Ms. Jackson" );
209
210
=item order $name
211
212
Sets the "order" parameter on the table and returns it as a new object, e.g:
213
214
my $ordered_table = $table->order("group_name desc");
215
216
If you call keys on %$ordered_table, then the key order will appropriate. If
217
the $table already has an order parameter, then it will be overwritten.
218
219
=item key $key
220
221
Sets the "key" parameter on the table and returns it as a new object. Useful
222
for tables, which are used as an N:N relation, e.g., the table is the
223
following:
224
225
create table rel_user_day (
226
id int primary key serial,
227
user_id int not null references users (id),
228
day_id int not null references day (id)
229
);
230
231
The tablemap table-declaration is the following:
232
233
$database->new_table(
234
table => "rel_user_day",
235
key => "id",
236
ref => {
237
user_id => [ $tables->{user}, "days" ],
238
day_id => [ $tables->{day}, "users" ],
239
}
240
);
241
242
Then your key is "id", but you can temporarily change the keys if you want
243
to get the day_id-s for a user by the following command:
244
245
$user_day_hash = $tables->{user}->{$user_id}->days->key("day_id");
246
247
then you will get the day_id-s by keys %$user_day_hash
248
249
=back
250
251
=head2 Tie::Table::Row methods
252
253
=over 4
254
255
=item write
256
257
This method must be called when the user is finished modifying the record,
258
e.g:
259
260
my $record = $table->{$id};
261
262
$record->{name} = "Blah";
263
$record->{street} = "Headway";
264
$record->write;
265
266
=back
267
268
=head2 References
269
270
There is two kind of reference in this module. All two are set up by "ref" parameter in the table. If you use a "ref" parameter, then the "back_ref" is automatically created in the other table (if not already exists).
271
272
=over 4
273
274
=item ref
275
276
$user->company_id gives a Tie::Table::Row record, which is a ROW in the company table. Each hash keys are field names.
277
278
=item back_ref
279
280
$company->users gives a Tie::Table object, which is a COLLECTION of rows (represented by a hash), which gives back the employees of the companies. (you can use "keys ..." expression for the ids).
281
282
=back 4
283
284
=head2 Caching
285
286
All the sql queries are cached in this module. This must be rethought,
287
because sometimes it is not the best solution.
288
I want some extra parameter for caching in the newer versions. Now all the
289
query results are cached for 10 seconds. This value can be tuned by setting
290
the Tie::Table::CACHE_SECS variable.
291
292
The global cache object is $Tie::Table::cache, and it can be invalidated by the
293
$Tie::Table::cache->invalidate_cache call.
294
295
The cache is hierarchical (it is stored in tree structure). If you want to
296
invalidate the whole cache, you can call:
297
298
$Tie::Table::cache->invalidate_cache([])
299
300
If you want to invalidate only one table, you can call:
301
302
$Tie::Table::cache->invalidate_cache(["table_name"])
303
304
No other syntax currently supported.
305
306
=head2 Performance
307
308
This module is NOT the most efficient method for getting data from the database. It is written to avoid endless sql-query-typing with minimal performance loss.
309
310
The module uses two kind of sql queries:
311
312
=over 4
313
314
=item select key from table
315
316
This is used for querying all the keys from a table. This can be affected by the "constraint" and the "where" parameter.
317
318
=item select * from table where id=1234
319
320
This is used for querying all the fields of one row. This can be affected by the "constraint" parameter, but not thw "where".
321
322
=back 4
323
324
Sometimes querying the whole table is more effective, (when you have enough memory), but currently it is only a planned parameter.
325
326
=head2 BUGS AND LIMITATIONS
327
328
=over 4
329
330
=item *
331
332
The current implementation cannot handle tables, which is used to express a
333
relationship between two data. These tables normally have two foreign key
334
fields. If you want to use them with that module, then you need to add a
335
unique identifier for each row. For examply by using postgresql and if your
336
table looks like this:
337
338
You can write the following definition for this table (assumed that users and
339
day tables are already defined):
340
341
=item *
342
343
This module is now usable for one purpose. I have profiled it, and I've found
344
that the "read_data" function is the most time-consuming. This must be
345
handled by re-organizing the cache.
346
347
=item *
348
349
Caching can be set globally right now (by $Tie::Table::CACHE_SECS) but it must
350
be more fine-grained in the future.
351
352
=back
353
354
=head1 COPYRIGHT
355
356
Copyrigh (c) 2000 Balázs Szabó (dLux)
357
All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
358
359
=head1 AUTHOR
360
361
dLux
362
363
=cut
364
365
package Tie::Table::DB;
366
1
1
10601
use strict;
1
2
1
48
367
require Storable;
368
1
1
1421
use DBI;
0
0
369
use Carp qw(confess cluck);
370
use vars qw($DEBUG);
371
372
sub new { my ($o,%param)=@_;
373
my $s=\%param;
374
bless ($s,$o);
375
if (exists $s->{connect}) {
376
$s->{dbh}=DBI->connect(@{ $s->{connect} });
377
};
378
confess "Not enough parameter" if !$s->{dbh};
379
$s->set_seq_mode($s->{seq_mode});
380
return $s;
381
};
382
383
sub sql { my ($s,$sql,@array)=@_;
384
my $sth;
385
eval {
386
if (ref($sql) eq 'ARRAY') {
387
$sth=$s->{prepare_cached} ?
388
$s->{dbh}->prepare_cached($sql->[0]) :
389
$s->{dbh}->prepare($sql->[0]);
390
} else {
391
$sth=$s->{dbh}->prepare($sql);
392
};
393
cluck $s->{dbh}->errstr if $s->{dbh}->err || $DEBUG;
394
$sth->execute(map { ref($_) ? "$_" : $_ } @array); # hack for overloaded objects
395
};
396
return $sth if $s->{quiet};
397
if ($s->{dbh}->err || $@ || $DEBUG) {
398
$sql="@$sql" if ref($sql) eq 'ARRAY';
399
cluck "$@ ".$s->{dbh}->errstr."\nQUERY: \"$sql\"".
400
(@array ? " Parameters: ".join(",",@array) : "")."\n";
401
};
402
return $sth;
403
};
404
405
sub set_seq_mode { my ($s,$mode)=@_;
406
$s->{seq_query}=
407
$mode eq 'ora' ? "select %s.currval from dual;" :
408
$mode eq 'pg' ? "select %s.last_value" :
409
undef;
410
};
411
412
sub select_currval { my ($s,$sequence)=@_;
413
cluck "No sequence handling installed!" if !$s->{seq_query};
414
return $s->sql([sprintf($s->{seq_query},$sequence)]);
415
};
416
417
sub new_table { my ($s,@params)=@_;
418
return new Tie::Table( db=> $s, @params );
419
};
420
421
package Tie::Table;
422
use strict;
423
use Carp qw(confess cluck);
424
use vars qw($DEBUG $VERSION $cache);
425
426
$VERSION='1.1.2';
427
428
sub cache { $Tie::Table::cache; };
429
430
sub new { my ($o,%param)=@_;
431
my $s={};
432
bless($s,$o);
433
confess "Not enough Parameter"
434
if !exists $param{table} || !exists $param{key} || !exists $param{db};
435
tie (%$s,"Tie::Table::TIE",$s,%param);
436
(tied %$s)->make_back_refs;
437
return $s;
438
};
439
440
sub write { my ($ss)=@_; my $s=tied %$ss;
441
my @r=values %{ $s->{dirty_rows} };
442
foreach my $r (@r) { $r->write; };
443
};
444
445
sub write_cascade { my ($ss)=@_; my $s=tied %$ss;
446
$ss->write;
447
foreach my $v (values %{ $s->{param}->{back_ref} }) {
448
$v->[0]->write_cascade;
449
};
450
};
451
452
sub select { my ($ss,$sql,@par)=@_; my $s=tied %$ss;
453
my $p=$s->clone_param();
454
$p->{"where"}= exists $p->{"where"} ?
455
"(".$p->{"where"}.") and ($sql)" : $sql;
456
push @{ $p->{"query_param"} },@par;
457
return new Tie::Table(%$p);
458
};
459
460
sub order { my ($ss,$fieldlist) = @_; my $s = tied %$ss;
461
my $p = $s->clone_param();
462
$p->{"order"} = $fieldlist;
463
return new Tie::Table(%$p);
464
}
465
466
sub key { my ($ss,$new_key) = @_; my $s = tied %$ss;
467
my $p = $s->clone_param();
468
$p->{key} = $new_key;
469
delete $p->{back_ref}; # must not use back_ref, because the key is changed!
470
delete $p->{seq}; # must not use the sequence
471
return new Tie::Table(%$p);
472
}
473
474
sub constraint { my ($ss,%cons)=@_; my $s=tied %$ss;
475
my $p=$s->clone_param();
476
foreach my $i (keys %cons) {
477
$p->{"constraint"}->{$i}=$cons{$i};
478
};
479
return new Tie::Table(%$p);
480
};
481
482
sub _first_val {
483
my $key = (keys %{$_[0]})[0];
484
return defined $key ? $_[0]->{$key} : undef;
485
}
486
sub select1 {
487
_first_val &select;
488
}
489
490
sub constraint1 {
491
_first_val &constraint;
492
}
493
494
*by = *constraint1;
495
496
sub insert { my ($ss,$data)=@_; my $s=tied %$ss;
497
my $key_field=$s->{param}->{key};
498
confess "Specify \"seq\" if you want to insert"
499
if !exists $s->{param}->{seq} &&
500
!exists $data->{$key_field} &&
501
!exists $s->{param}->{constraint}->{$key_field};
502
$ss->insert_row($data);
503
};
504
505
sub insert_row { my ($ss,$data)=@_; my $s=tied %$ss;
506
my $key_field=$s->{param}->{key};
507
my $constraint=$s->{param}->{"constraint"};
508
my $db=$s->{param}->{db};
509
if ($constraint) {
510
foreach my $k (keys %$constraint) {
511
$data->{$k}=$constraint->{$k};
512
};
513
};
514
my (@sql1,@sql2,@data);
515
foreach my $k (keys %$data) {
516
push @sql1,$k;
517
push @sql2,"?";
518
push @data,$data->{$k};
519
};
520
$db->sql(["insert into ".$s->{param}->{table}." (".
521
join(",",@sql1).") values (".join(",",@sql2).")"],@data);
522
cache->invalidate_cache([$s->{param}->{table}]);
523
return undef if $db->{dbh}->err;
524
if (!exists $data->{$key_field}) {
525
my $sth=$db->select_currval($s->{param}->{seq});
526
my ($seq)=$sth->fetchrow;
527
$sth->finish;
528
return $seq;
529
} else {
530
return $data->{$key_field};
531
};
532
};
533
534
sub delete { my ($ss,$key)=@_; my $s=tied %$ss;
535
my $constraint=$s->{param}->{"constraint"};
536
my $db=$s->{param}->{db};
537
my $cons; my @cons;
538
if ($constraint) {
539
foreach my $k (keys %$constraint) {
540
$cons.=" and $k=?";
541
push @cons,$constraint->{$k};
542
};
543
};
544
$db->sql(["delete from ".$s->{param}->{table}." where ".
545
$s->{param}->{key}."=?".$cons],$key,@cons);
546
cache->invalidate_cache([$s->{param}->{table}]);
547
foreach my $v (values %{ $s->{param}->{back_ref} || {} }) {
548
# Invalidate every back-referenced table
549
cache->invalidate_cache([ tied(%{$v->[0]})->{param}->{table} ]);
550
};
551
};
552
553
#####################################
554
package Tie::Table::TIE;
555
use strict;
556
use Carp qw(confess cluck);
557
use vars qw($DEBUG);
558
use UNIVERSAL qw(isa);
559
560
sub cache { $Tie::Table::cache; };
561
sub TIEHASH { my ($o,$main_obj,%param)=@_;
562
my $s={
563
param =>\%param,
564
main_obj =>$main_obj,
565
dirty_row=>{},
566
};
567
$s->{param}->{back_ref} ||= {};
568
bless ($s,$o);
569
return $s;
570
};
571
572
sub make_back_refs { my ($s)=@_;
573
my $main_obj=$s->{main_obj};
574
if (exists $s->{param}->{"ref"}) {
575
foreach my $k (keys %{ $s->{param}->{"ref"} }) {
576
my ($ref_table,$function)=@{ $s->{param}->{"ref"}->{$k} };
577
(tied %$ref_table)->{param}->{back_ref}->{$function} ||= [$main_obj,$k];
578
# write only when no data is already written to it
579
};
580
};
581
};
582
583
sub FIRSTKEY { my ($s)=@_;
584
my (@where,@qp,@path);
585
push @path,$s->{param}->{table},$s->{param}->{key},"__all_keys__";
586
my $constraint=$s->{param}->{constraint};
587
if ($constraint) {
588
foreach my $k (sort keys %$constraint) {
589
push @where,"$k=?";
590
push @path,$k,$constraint->{$k};
591
push @qp,$constraint->{$k};
592
};
593
};
594
if (exists $s->{param}->{"where"}) {
595
push @where, $s->{param}->{"where"};
596
push @path,"where ".$s->{param}->{where},@{ $s->{param}->{"query_param"} };
597
push @qp, @{ $s->{param}->{"query_param"} };
598
};
599
my $order_by = $s->{param}->{"order"};
600
my $array=cache->get_array($s->{param}->{db},[@path],
601
"select ".$s->{param}->{key}." from ".$s->{param}->{table}.
602
(@where ? " where ".join(" and ",@where) : "").
603
($order_by ? " order by $order_by" : ""), [@qp]
604
);
605
$s->{keys}=$array;
606
return $s->{keys}->[0]->[0];
607
};
608
609
sub NEXTKEY { my ($s,$lastkey)=@_;
610
my $key= ++$s->{keycount};
611
return undef if !exists $s->{"keys"};
612
if ($key >= @{ $s->{"keys"} }) {
613
delete $s->{keycount};
614
delete $s->{"keys"};
615
return undef;
616
};
617
return $s->{"keys"}->[$key]->[0];
618
};
619
620
sub STORE { my ($s,$key,$val)=@_;
621
$val = {} if !defined $val;
622
die "Cannot insert non-hashref value to a table" if !isa($val,'HASH');
623
if (my $row = $s->FETCH($key)) { # key already exists
624
$row->{$_} = $val->{$_} foreach keys %$val;
625
$row->write;
626
return $row;
627
} else {
628
my %val = (%$val, $s->{param}->{key} => $key);
629
$s->{main_obj}->insert_row(\%val);
630
return $s->FETCH($key);
631
}
632
};
633
634
# delete $hash->{key};
635
sub DELETE { my ($s,$key)=@_;
636
my $main_obj=$s->{main_obj};
637
$main_obj->delete($key);
638
};
639
640
# CLEAR: %{ $hash->{key} }=();
641
sub CLEAR { &DELETE; };
642
643
sub FETCH { my ($s,$key)=@_;
644
return new Tie::Table::Row( $s, $key);
645
};
646
647
sub EXISTS { &FETCH; };
648
649
sub clone_param { my ($s)=@_;
650
my $p=$s->{param};
651
my $r; %$r=%$p;
652
foreach my $k (qw(constraint query_param)) {
653
$r->{$k}=Storable::dclone($p->{$k}) if exists $p->{$k};
654
};
655
return $r;
656
};
657
658
#####################################
659
package Tie::Table::Row;
660
use strict;
661
use Carp qw(confess cluck);
662
use vars qw($DEBUG $AUTOLOAD);
663
664
sub cache { $Tie::Table::cache; };
665
666
sub new { my ($o,$table,$key)=@_;
667
my $s={};
668
bless $s,$o;
669
return tie(%$s,"Tie::Table::Row::TIE",$table,$key) ? $s : undef;
670
};
671
672
sub write { my ($ss)=@_; my $s=tied %$ss;
673
my $param=$s->{table}->{param};
674
my $db=$param->{db};
675
my $key_field=$param->{key};
676
my $key_value=$s->{data}->{$key_field};
677
my $table=$param->{table};
678
my $sql="update $table set ";
679
my @sql; my @data;
680
foreach my $k (keys %{ $s->{newdata} }) {
681
push @sql, "$k=?";
682
push @data,$s->{newdata}->{$k};
683
};
684
my $constraint=$param->{"constraint"};
685
my @where="$key_field=?";
686
push @data,$key_value;
687
if ($constraint) {
688
foreach my $k (keys %$constraint) {
689
push @where,"$k=?";
690
push @data,$constraint->{$k};
691
};
692
};
693
if (@sql) {
694
$db->sql(["update $table set ".join(",",@sql)." where ".
695
join(" and ",@where)],@data);
696
cache->invalidate_cache([ $table ]);
697
};
698
$s->{newdata}={};
699
delete $s->{table}->{dirty_rows}->{$key_value};
700
};
701
702
sub AUTOLOAD { my ($ss)=@_; my $s=tied %$ss;
703
my ($sub) = $AUTOLOAD =~ /.*::(.*)/o;
704
my $param=$s->{table}->{param};
705
my $back_ref=$param->{back_ref};
706
my $ref=$param->{'ref'};
707
if (exists $back_ref->{$sub}) {
708
my $param=(tied %{ $back_ref->{$sub}->[0] })->clone_param();
709
$param->{"constraint"}->{ $back_ref->{$sub}->[1] }= $s->{key};
710
return new Tie::Table (%$param);
711
} elsif (exists $ref->{$sub}) {
712
return undef if !exists $s->{data}->{$sub};
713
return new Tie::Table::Row( tied %{ $ref->{$sub}->[0] },
714
$s->{data}->{ $sub });
715
};
716
};
717
718
sub DESTROY {}; # Don't bother AUTOLOAD for it...
719
720
#####################################
721
package Tie::Table::Row::TIE;
722
use strict;
723
use Carp qw(confess cluck);
724
use vars qw($DEBUG);
725
726
sub cache { $Tie::Table::cache; };
727
728
sub TIEHASH { my ($o,$table,$key)=@_;
729
my $s={
730
table=>$table,
731
key=>$key,
732
data=>undef,
733
newdata=>undef,
734
};
735
bless $s,$o;
736
$s->read_data;
737
return $s->{data} ? $s : undef;
738
};
739
740
sub read_data { my ($s)=@_;
741
my ($where,$path,$val);
742
my $param=$s->{table}->{param};
743
if (! ($path=$s->{cache_path})) {
744
my $key=$s->{key};
745
push @$path,$param->{table},$param->{key},$key;
746
my $constraint=$param->{"constraint"};
747
if ($constraint) {
748
foreach my $k (sort keys %$constraint) {
749
push @$path,$k,$constraint->{$k};
750
};
751
};
752
push @$path,$param->{"where"},$param->{"query_param"};
753
$s->{cache_path}=$path;
754
};
755
$s->{data}=cache->cache_hit($path);
756
if (!$s->{data}) {
757
if (!$s->{query}) {
758
my $key=$s->{key};
759
my $constraint=$param->{"constraint"};
760
push @$where,$param->{key}."=?";
761
push @$val,$key;
762
if ($constraint) {
763
foreach my $k (sort keys %$constraint) {
764
push @$where,"$k=?";
765
push @$val,$constraint->{$k};
766
};
767
};
768
if (my $wh = $param->{where}) {
769
push @$where, ,"($wh)";
770
push @$val, @{ $param->{"query_param"} }
771
}
772
$s->{query}=[$where,$val];
773
} else {
774
($where,$val)=@{ $s->{query} };
775
};
776
$s->{data}=cache->get_hash_directly($param->{db},
777
$path, "select * from ".$param->{table}." where ".
778
join(" and ",@$where), [@$val]
779
);
780
};
781
};
782
783
sub FETCH { my ($s,$key)=@_;
784
if (exists $s->{newdata}->{$key}) {
785
return $s->{newdata}->{$key}
786
} else {
787
$s->read_data;
788
cluck "Invalid Tie::Table Key!" if !exists $s->{data}->{$key};
789
return $s->{data}->{$key};
790
};
791
};
792
793
sub STORE { my ($s,$key,$value)=@_;
794
my $key_field=$s->{table}->{param}->{key};
795
confess "Cannot modify a key value" if $key eq $key_field;
796
$s->{table}->{dirty_rows}->{ $s->{data}->{$key_field} }=$s;
797
$s->{newdata}->{$key}=$value;
798
};
799
800
sub DELETE { my ($s,$key)=@_;
801
return undef;
802
};
803
804
sub CLEAR { my ($s)=@_;
805
return undef;
806
};
807
808
sub EXISTS { my ($s,$key)=@_;
809
return exists $s->{data}->{$key};
810
};
811
812
sub FIRSTKEY { my ($s)=@_;
813
my $a= scalar keys %{$s->{data}};
814
each %{$s->{data}};
815
};
816
817
sub NEXTKEY { my ($s,$lastkey)=@_;
818
each %{$s->{data}};
819
};
820
821
#####################################
822
package Tie::Table::Cache;
823
use strict;
824
use Carp qw(confess cluck);
825
use vars qw($DEBUG $CACHE_SECS $CACHE_EXPIRE_PERIOD);
826
$CACHE_SECS=10; # How much time a data is valid in the cache
827
$CACHE_EXPIRE_PERIOD=300;# How often visit through the cache for expired entries
828
829
sub new { my ($o)=@_;
830
my $s={};
831
bless ($s,$o);
832
return $s;
833
};
834
835
sub get_array{ my ($s,$db,$cache_path,$sql,$params)=@_;
836
$s->expire_cache;
837
my $a=$s->cache_hit($cache_path);
838
return $a if $a;
839
$a=[];
840
my $sth=$db->sql([$sql],@$params);
841
while (my @row=$sth->fetchrow) {
842
push @$a,[@row];
843
};
844
$sth->finish;
845
$s->cache_write($a,$cache_path);
846
return $a;
847
};
848
849
sub get_hash { my ($s,$db,$cache_path,$sql,$params)=@_;
850
$s->expire_cache;
851
my $h=$s->cache_hit($cache_path);
852
return $h if $h;
853
&get_hash_directly;
854
};
855
856
sub get_hash_directly { my ($s,$db,$cache_path,$sql,$params)=@_;
857
$s->expire_cache;
858
my $h;
859
my $sth=$db->sql([$sql],@$params);
860
$h=$sth->fetchrow_hashref;
861
$sth->finish;
862
$s->cache_write($h,$cache_path);
863
return $h;
864
};
865
866
sub cache_hit{ my ($s,$path)=@_;
867
return undef if !exists $s->{cache};
868
my $walk=$s->{cache};
869
for (my $i=0; $i<@$path; $i++) {
870
my $key=$path->[$i];
871
return undef if !exists $walk->[0]->{$key};
872
$walk=$walk->[0]->{$key};
873
};
874
return undef if $walk->[1]
875
return $walk->[2];
876
};
877
878
sub invalidate_cache { my ($s,$path)=@_;
879
if (! @$path ) { $s->{cache}=[{},0,undef]; return; };
880
my $walk=$s->{cache};
881
for (my $i=0; $i<@$path-1; $i++) {
882
my $k=$path->[$i];
883
return if !exists $walk->[0]->{$k};
884
$walk=$walk->[0]->{$k};
885
};
886
$walk->[0]->{ $path->[-1] }=[{},0,undef];
887
};
888
889
sub expire_cache { my ($s)=@_;
890
return if $s->{expire_time}>time;
891
$s->expire_cache_what($s->{cache});
892
$s->{expire_time}=time+$CACHE_EXPIRE_PERIOD;
893
};
894
895
sub expire_cache_what { my ($s,$what)=@_;
896
my $keep_me=1;
897
if ($what->[1]
898
$keep_me=0;
899
$what->[2]=undef;
900
};
901
my @k=keys %{ $what->[0] };
902
foreach my $k (@k) {
903
my $keep_it=$s->expire_cache_what($what->[0]->{$k});
904
delete $what->[0]->{$k} if !$keep_it;
905
$keep_me ||= $keep_it;
906
};
907
return $keep_me;
908
};
909
910
sub cache_write { my ($s,$data,$path)=@_;
911
$s->{cache}=[{},0,undef] if !exists $s->{cache};
912
my $walk=$s->{cache};
913
for (my $i=0; $i<@$path; $i++) {
914
my $k=$path->[$i];
915
$walk->[0]->{$k}=[{},0,undef] if !exists $walk->[0]->{$k};
916
$walk=$walk->[0]->{$k};
917
};
918
$walk->[1]=time;
919
$walk->[2]=$data;
920
};
921
922
$Tie::Table::cache=new Tie::Table::Cache;
923
924
1;