line
stmt
bran
cond
sub
pod
time
code
1
=head1 NAME
2
3
TableMap - Maps relational tables into hashes
4
5
=head1 SYNOPSIS
6
7
use DBI;
8
$dbh=DBI->connect(...);
9
10
use TableMap;
11
12
$database=new TableMap::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 TableMap::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 TableMap ( 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
# New record
82
$id=$company->new(
83
{ name=>"abc",
84
phone1=>"30/123567",
85
mobile=>"20/1234" } );
86
if ($id) { print "Id: $id\n"; } else { print "Insert failed: "; };
87
88
# Delete record
89
delete $company->{13};
90
%{ $company->{13} }=();
91
92
=head1 DESCRIPTION
93
94
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.
95
96
=head2 Basic Usage
97
98
You can create TableMap objects for tables. You must specify a parameter hash to the constructor, which has the following keys:
99
100
=over 4
101
102
=item db
103
104
This is a reference to a TableMap::DB module. Normally you create a new TableMap object by the method of the "new_table" of a TableMap::DB instance, then you may not specify this.
105
106
=item table
107
108
Specifies the table name
109
110
=item key
111
112
Specifies the primary key. This must be specified, so if you don't have primary keys in your table, you cannot use the TableMap (for the whole table. You can use it for a subset of rows specified by the "constraint" param).
113
114
=item seq
115
116
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.
117
118
=item ref
119
120
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:
121
122
ref => {
123
field1 => [ $table1, "function1" ],
124
field2 => [ $table2, "function2" ],
125
};
126
127
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}.
128
129
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.
130
131
=item where
132
133
Specifies a "where" condition for selecting id-s, so you can select a subset of keys with this. Also available with the "search" function:
134
135
@user_ids= keys %{ $table->search("age > 25") };
136
137
or
138
139
$table=new TableMap (table => "table", where => "age > 25");
140
@user_ids=keys %$table;
141
142
=item constraint
143
144
This is similar to "select", but it can be used only for equality test. The main advantage is that it can be used for deletion and insertion. If you insert something into a table, which has constraint parameter, all the values in the constraint hash is set in the new record. This constraint is used internally, when somebody creates a back reference by a back-reference function.
145
146
=back 4
147
148
=head2 References
149
150
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).
151
152
=over 4
153
154
=item ref
155
156
$user->company_id gives a TableMap::Row record, which is a ROW in the company table. Each hash keys are field names.
157
158
=item back_ref
159
160
$company->users gives a TableMap 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).
161
162
=back 4
163
164
=head2 Caching
165
166
All the sql queries are cached in this module. This must be rethink, because sometimes it is not the best solution.
167
I want some extra parameter for caching in the newer versions. Now all the query results are cached for 10 seconds.
168
169
The Cache object is $TableMap::cache, and it can be invalidated by the $TableMap::cache->invalidate_cache call.
170
171
The cache is hierarchical (it is stored in tree structure).
172
173
For more information on the cache you can see the source code.
174
175
=head2 Performance
176
177
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.
178
179
The module uses two kind of sql queries:
180
181
=over 4
182
183
=item select key from table
184
185
This is used for querying all the keys from a table. This can be affected by the "constraint" and the "where" parameter.
186
187
=item select * from table where id=1234
188
189
This is used for querying all the fields of one row. This can be affected by the "constraint" parameter, but not thw "where".
190
191
=back 4
192
193
Sometimes querying the whole table is more effective, (when you have enough memory), but currently it is only a planned parameter.
194
195
=head2 TODO
196
197
=over 2
198
199
=item *
200
201
This module is now usable for one purpose. I have profiled it, and I've found that the "read_data" function is the most time-consuming. This must be handled by re-organizing the cache.
202
203
=item *
204
205
I want to add a parameter for setting for the caching mode. Sometimes no caching is allowed, but sometimes te full table must be cached for good performance.
206
207
=item *
208
209
Cahce reorganization is needed, because reaching a cached data is quite slow.
210
211
=back 2
212
213
=head1 COPYRIGHT
214
215
Copyrigh (c) 2000 Balázs Szabó (dLux)
216
All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
217
218
=head1 AUTHOR
219
220
dLux
221
222
=cut
223
224
package TableMap::DB;
225
1
1
7544
use strict;
1
2
1
49
226
require Storable;
227
1
1
1692
use DBI;
0
0
228
use Carp qw(confess cluck);
229
use vars qw($DEBUG);
230
231
sub new { my ($o,%param)=@_;
232
my $s=\%param;
233
bless ($s,$o);
234
if (exists $s->{connect}) {
235
$s->{dbh}=DBI->connect(@{ $s->{connect} });
236
};
237
confess "Not enough parameter" if !$s->{dbh};
238
$s->set_seq_mode($s->{seq_mode});
239
return $s;
240
};
241
242
sub sql { my ($s,$sql,@array)=@_;
243
my $sth;
244
eval {
245
if (ref($sql) eq 'ARRAY') {
246
$sth=$s->{prepare_cached} ?
247
$s->{dbh}->prepare_cached($sql->[0]) :
248
$s->{dbh}->prepare($sql->[0]);
249
} else {
250
$sth=$s->{dbh}->prepare($sql);
251
};
252
cluck $s->{dbh}->errstr if $s->{dbh}->err or $DEBUG;
253
$sth->execute(@array);
254
};
255
return $sth if $s->{quiet};
256
if ($s->{dbh}->err || $@ || $DEBUG) {
257
$sql="@$sql" if ref($sql) eq 'ARRAY';
258
cluck "$@ ".$s->{dbh}->errstr."\nQUERY: \"$sql\"".
259
(@array ? " Parameters: ".join(",",@array) : "")."\n";
260
};
261
return $sth;
262
};
263
264
sub set_seq_mode { my ($s,$mode)=@_;
265
$s->{seq_query}=
266
$mode eq 'ora' ? "select %s.currval from dual;" :
267
$mode eq 'pg' ? "select %s.last_value" :
268
undef;
269
};
270
271
sub select_currval { my ($s,$sequence)=@_;
272
cluck "No sequence handling installed!" if !$s->{seq_query};
273
return $s->sql([sprintf($s->{seq_query},$sequence)]);
274
};
275
276
sub new_table { my ($s,@params)=@_;
277
return new TableMap( db=> $s, @params );
278
};
279
280
package TableMap;
281
use strict;
282
use Carp qw(confess cluck);
283
use vars qw($DEBUG $VERSION $cache);
284
285
$VERSION='1.0';
286
287
sub cache { $TableMap::cache; };
288
289
sub new { my ($o,%param)=@_;
290
my $s={};
291
bless($s,$o);
292
confess "Not enough Parameter"
293
if !exists $param{table} || !exists $param{key} || !exists $param{db};
294
tie (%$s,"TableMap::TIE",$s,%param);
295
(tied %$s)->make_back_refs;
296
return $s;
297
};
298
299
sub write { my ($ss)=@_; my $s=tied %$ss;
300
my @r=values %{ $s->{dirty_rows} };
301
foreach my $r (@r) { $r->write; };
302
};
303
304
sub write_cascade { my ($ss)=@_; my $s=tied %$ss;
305
$ss->write;
306
foreach my $v (values %{ $s->{param}->{back_ref} }) {
307
$v->[0]->write_cascade;
308
};
309
};
310
311
sub select { my ($ss,$sql,@par)=@_; my $s=tied %$ss;
312
my $p=$s->clone_param();
313
$p->{"where"}= exists $p->{"where"} ?
314
"(".$p->{"where"}.") and ($sql)" : $sql;
315
push @{ $p->{"query_param"} },@par;
316
return new TableMap(%$p);
317
};
318
319
sub constraint { my ($ss,%cons)=@_; my $s=tied %$ss;
320
my $p=$s->clone_param();
321
foreach my $i (keys %cons) {
322
$p->{"constraint"}->{$i}=$cons{$i};
323
};
324
return new TableMap(%$p);
325
};
326
327
sub insert { my ($ss,$data)=@_; my $s=tied %$ss;
328
my $key_field=$s->{param}->{key};
329
confess "Specify \"seq\" if you want to insert"
330
if !exists $s->{param}->{seq} && !exists $data->{$key_field};
331
$ss->insert_row($data);
332
};
333
334
sub insert_row { my ($ss,$data)=@_; my $s=tied %$ss;
335
my $key_field=$s->{param}->{key};
336
my $constraint=$s->{param}->{"constraint"};
337
my $db=$s->{param}->{db};
338
if ($constraint) {
339
foreach my $k (keys %$constraint) {
340
$data->{$k}=$constraint->{$k};
341
};
342
};
343
my (@sql1,@sql2,@data);
344
foreach my $k (keys %$data) {
345
push @sql1,$k;
346
push @sql2,"?";
347
push @data,$data->{$k};
348
};
349
$db->sql(["insert into ".$s->{param}->{table}." (".
350
join(",",@sql1).") values (".join(",",@sql2).")"],@data);
351
cache->invalidate_cache([$s->{param}->{table}]);
352
return undef if $db->{dbh}->err;
353
if (!exists $data->{$key_field}) {
354
my $sth=$db->select_currval($s->{param}->{seq});
355
my ($seq)=$sth->fetchrow;
356
$sth->finish;
357
return $seq;
358
} else {
359
return $data->{$key_field};
360
};
361
};
362
363
sub delete { my ($ss,$key)=@_; my $s=tied %$ss;
364
my $constraint=$s->{param}->{"constraint"};
365
my $db=$s->{param}->{db};
366
my $cons; my @cons;
367
if ($constraint) {
368
foreach my $k (keys %$constraint) {
369
$cons.=" and $k=?";
370
push @cons,$constraint->{$k};
371
};
372
};
373
$db->sql(["delete from ".$s->{param}->{table}." where ".
374
$s->{param}->{key}."=?".$cons],$key,@cons);
375
cache->invalidate_cache([$s->{param}->{table}]);
376
foreach my $v (values %{ $s->{param}->{back_ref} }) {
377
# Invalidate every back-referenced table
378
cache->invalidate_cache([ $v->[0]->{param}->{table} ]);
379
};
380
};
381
382
#####################################
383
package TableMap::TIE;
384
use strict;
385
use Carp qw(confess cluck);
386
use vars qw($DEBUG);
387
388
sub cache { $TableMap::cache; };
389
sub TIEHASH { my ($o,$main_obj,%param)=@_;
390
my $s={
391
param =>\%param,
392
main_obj =>$main_obj,
393
dirty_row=>{},
394
};
395
$s->{param}->{back_ref} ||= {};
396
bless ($s,$o);
397
return $s;
398
};
399
400
sub make_back_refs { my ($s)=@_;
401
my $main_obj=$s->{main_obj};
402
if (exists $s->{param}->{"ref"}) {
403
foreach my $k (keys %{ $s->{param}->{"ref"} }) {
404
my ($ref_table,$function)=@{ $s->{param}->{"ref"}->{$k} };
405
(tied %$ref_table)->{param}->{back_ref}->{$function}||=[$main_obj,$k];
406
# write only when no data is already written to it
407
};
408
};
409
};
410
411
sub FIRSTKEY { my ($s)=@_;
412
my (@where,@qp,@path);
413
push @path,$s->{param}->{table},$s->{param}->{key},"__all_keys__";
414
my $constraint=$s->{param}->{constraint};
415
if ($constraint) {
416
foreach my $k (sort keys %$constraint) {
417
push @where,"$k=?";
418
push @path,$k,$constraint->{$k};
419
push @qp,$constraint->{$k};
420
};
421
};
422
if (exists $s->{param}->{"where"}) {
423
push @where, $s->{param}->{"where"};
424
push @path,"where ".$s->{param}->{where},@{ $s->{param}->{"query_param"} };
425
push @qp, @{ $s->{param}->{"query_param"} };
426
};
427
my $array=cache->get_array($s->{param}->{db},[@path],
428
"select ".$s->{param}->{key}." from ".$s->{param}->{table}.
429
(@where ? " where ".join(" and ",@where) : ""), [@qp]
430
);
431
$s->{keys}=$array;
432
return $s->{keys}->[0]->[0];
433
};
434
435
sub NEXTKEY { my ($s,$lastkey)=@_;
436
my $key= ++$s->{keycount};
437
return undef if !exists $s->{"keys"};
438
if ($key >= @{ $s->{"keys"} }) {
439
delete $s->{keycount};
440
delete $s->{"keys"};
441
return undef;
442
};
443
return $s->{"keys"}->[$key]->[0];
444
};
445
446
sub STORE { my ($s,$key,$val)=@_;
447
# unimplemented
448
cluck "STORE unimplemented";
449
};
450
451
# delete $hash->{key};
452
sub DELETE { my ($s,$key)=@_;
453
my $main_obj=$s->{main_obj};
454
$main_obj->delete($key);
455
};
456
457
# CLEAR: %{ $hash->{key} }=();
458
sub CLEAR { &DELETE; };
459
460
sub FETCH { my ($s,$key)=@_;
461
return new TableMap::Row( $s, $key);
462
};
463
464
sub EXISTS { &FETCH; };
465
466
sub clone_param { my ($s)=@_;
467
my $p=$s->{param};
468
my $r; %$r=%$p;
469
foreach my $k (qw(constraint query_param)) {
470
$r->{$k}=Storable::dclone($p->{$k}) if exists $p->{$k};
471
};
472
return $r;
473
};
474
475
#####################################
476
package TableMap::Row;
477
use strict;
478
use Carp qw(confess cluck);
479
use vars qw($DEBUG $AUTOLOAD);
480
481
sub cache { $TableMap::cache; };
482
483
sub new { my ($o,$table,$key)=@_;
484
my $s={};
485
bless $s,$o;
486
return tie(%$s,"TableMap::Row::TIE",$table,$key) ? $s : undef;
487
};
488
489
sub write { my ($ss)=@_; my $s=tied %$ss;
490
my $param=$s->{table}->{param};
491
my $db=$param->{db};
492
my $key_field=$param->{key};
493
my $key_value=$s->{data}->{$key_field};
494
my $table=$param->{table};
495
my $sql="update $table set ";
496
my @sql; my @data;
497
foreach my $k (keys %{ $s->{newdata} }) {
498
push @sql, "$k=?";
499
push @data,$s->{newdata}->{$k};
500
};
501
my $constraint=$param->{"constraint"};
502
my @where="$key_field=?";
503
push @data,$key_value;
504
if ($constraint) {
505
foreach my $k (keys %$constraint) {
506
push @where,"$k=?";
507
push @data,$constraint->{$k};
508
};
509
};
510
if (@sql) {
511
$db->sql(["update $table set ".join(",",@sql)." where ".
512
join(" and ",@where)],@data);
513
cache->invalidate_cache([ $table ]);
514
};
515
$s->{newdata}={};
516
delete $s->{table}->{dirty_rows}->{$key_value};
517
};
518
519
sub AUTOLOAD { my ($ss)=@_; my $s=tied %$ss;
520
my ($sub) = $AUTOLOAD =~ /.*::(.*)/o;
521
my $param=$s->{table}->{param};
522
my $back_ref=$param->{back_ref};
523
my $ref=$param->{'ref'};
524
if (exists $back_ref->{$sub}) {
525
my $param=(tied %{ $back_ref->{$sub}->[0] })->clone_param();
526
$param->{"constraint"}->{ $back_ref->{$sub}->[1] }= $s->{key};
527
return new TableMap (%$param);
528
} elsif (exists $ref->{$sub}) {
529
return undef if !exists $s->{data}->{$sub};
530
return new TableMap::Row( tied %{ $ref->{$sub}->[0] },
531
$s->{data}->{ $sub });
532
};
533
};
534
535
sub DESTROY {}; # Don't bother AUTOLOAD for it...
536
537
#####################################
538
package TableMap::Row::TIE;
539
use strict;
540
use Carp qw(confess cluck);
541
use vars qw($DEBUG);
542
543
sub cache { $TableMap::cache; };
544
545
sub TIEHASH { my ($o,$table,$key)=@_;
546
my $s={
547
table=>$table,
548
key=>$key,
549
data=>undef,
550
newdata=>undef,
551
};
552
bless $s,$o;
553
$s->read_data;
554
return $s->{data} ? $s : undef;
555
};
556
557
sub read_data { my ($s)=@_;
558
my ($where,$path,$val);
559
my $param=$s->{table}->{param};
560
if (! ($path=$s->{cache_path})) {
561
my $key=$s->{key};
562
push @$path,$param->{table},$param->{key},$key;
563
my $constraint=$param->{"constraint"};
564
if ($constraint) {
565
foreach my $k (sort keys %$constraint) {
566
push @$path,$k,$constraint->{$k};
567
};
568
};
569
$s->{cache_path}=$path;
570
};
571
$s->{data}=cache->cache_hit($path);
572
if (!$s->{data}) {
573
if (!$s->{query}) {
574
my $key=$s->{key};
575
my $constraint=$param->{"constraint"};
576
push @$where,$param->{key}."=?";
577
push @$val,$key;
578
if ($constraint) {
579
foreach my $k (sort keys %$constraint) {
580
push @$where,"$k=?";
581
push @$val,$constraint->{$k};
582
};
583
};
584
$s->{query}=[$where,$val];
585
} else {
586
($where,$val)=@{ $s->{query} };
587
};
588
$s->{data}=cache->get_hash_directly($param->{db},
589
$path, "select * from ".$param->{table}." where ".
590
join(" and ",@$where), [@$val]
591
);
592
};
593
};
594
595
sub FETCH { my ($s,$key)=@_;
596
if (exists $s->{newdata}->{$key}) {
597
return $s->{newdata}->{$key}
598
} else {
599
$s->read_data;
600
cluck "Invalid TableMap Key!" if !exists $s->{data}->{$key};
601
return $s->{data}->{$key};
602
};
603
};
604
605
sub STORE { my ($s,$key,$value)=@_;
606
my $key_field=$s->{table}->{param}->{key};
607
confess "Cannot modify a key value" if $key eq $key_field;
608
$s->{table}->{dirty_rows}->{ $s->{data}->{$key_field} }=$s;
609
$s->{newdata}->{$key}=$value;
610
};
611
612
sub DELETE { my ($s,$key)=@_;
613
return undef;
614
};
615
616
sub CLEAR { my ($s)=@_;
617
return undef;
618
};
619
620
sub EXISTS { my ($s,$key)=@_;
621
return exists $s->{data}->{$key};
622
};
623
624
sub FIRSTKEY { my ($s)=@_;
625
my $a= scalar keys %{$s->{data}};
626
each %{$s->{data}};
627
};
628
629
sub NEXTKEY { my ($s,$lastkey)=@_;
630
each %{$s->{data}};
631
};
632
633
#####################################
634
package TableMap::Cache;
635
use strict;
636
use Carp qw(confess cluck);
637
use vars qw($DEBUG $CACHE_SECS $CACHE_EXPIRE_PERIOD);
638
$CACHE_SECS=10; # How much time a data is valid in the cache
639
$CACHE_EXPIRE_PERIOD=300;# How often visit through the cache for expired entries
640
641
sub new { my ($o)=@_;
642
my $s={};
643
bless ($s,$o);
644
return $s;
645
};
646
647
sub get_array{ my ($s,$db,$cache_path,$sql,$params)=@_;
648
$s->expire_cache;
649
my $a=$s->cache_hit($cache_path);
650
return $a if $a;
651
$a=[];
652
my $sth=$db->sql([$sql],@$params);
653
while (my @row=$sth->fetchrow) {
654
push @$a,[@row];
655
};
656
$sth->finish;
657
$s->cache_write($a,$cache_path);
658
return $a;
659
};
660
661
sub get_hash { my ($s,$db,$cache_path,$sql,$params)=@_;
662
$s->expire_cache;
663
my $h=$s->cache_hit($cache_path);
664
return $h if $h;
665
&get_hash_directly;
666
};
667
668
sub get_hash_directly { my ($s,$db,$cache_path,$sql,$params)=@_;
669
$s->expire_cache;
670
my $h;
671
my $sth=$db->sql([$sql],@$params);
672
$h=$sth->fetchrow_hashref;
673
$sth->finish;
674
$s->cache_write($h,$cache_path);
675
return $h;
676
};
677
678
sub cache_hit{ my ($s,$path)=@_;
679
return undef if !exists $s->{cache};
680
my $walk=$s->{cache};
681
for (my $i=0; $i<@$path; $i++) {
682
my $key=$path->[$i];
683
return undef if !exists $walk->[0]->{$key};
684
$walk=$walk->[0]->{$key};
685
};
686
return undef if $walk->[1]
687
return $walk->[2];
688
};
689
690
sub invalidate_cache { my ($s,$path)=@_;
691
if (! @$path ) { $s->{cache}=[{},0,undef]; return; };
692
my $walk=$s->{cache};
693
for (my $i=0; $i<@$path-1; $i++) {
694
my $k=$path->[$i];
695
return if !exists $walk->[0]->{$k};
696
$walk=$walk->[0]->{$k};
697
};
698
$walk->[0]->{ $path->[-1] }=[{},0,undef];
699
};
700
701
sub expire_cache { my ($s)=@_;
702
return if $s->{expire_time}>time;
703
$s->expire_cache_what($s->{cache});
704
$s->{expire_time}=time+$CACHE_EXPIRE_PERIOD;
705
};
706
707
sub expire_cache_what { my ($s,$what)=@_;
708
my $keep_me=1;
709
if ($what->[1]
710
$keep_me=0;
711
$what->[2]=undef;
712
};
713
my @k=keys %{ $what->[0] };
714
foreach my $k (@k) {
715
my $keep_it=$s->expire_cache_what($what->[0]->{$k});
716
delete $what->[0]->{$k} if !$keep_it;
717
$keep_me ||= $keep_it;
718
};
719
return $keep_me;
720
};
721
722
sub cache_write { my ($s,$data,$path)=@_;
723
$s->{cache}=[{},0,undef] if !exists $s->{cache};
724
my $walk=$s->{cache};
725
for (my $i=0; $i<@$path; $i++) {
726
my $k=$path->[$i];
727
$walk->[0]->{$k}=[{},0,undef] if !exists $walk->[0]->{$k};
728
$walk=$walk->[0]->{$k};
729
};
730
$walk->[1]=time;
731
$walk->[2]=$data;
732
};
733
734
$TableMap::cache=new TableMap::Cache;
735
736
1;