File Coverage

blib/lib/Tie/Table.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


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;