1=head1 NAME
2
3DBIx::SQLEngine::Record::Cache - Avoid Repeated Selects
4
5=head1 SYNOPSIS
6
7B<Setup:> Several ways to create a class.
8
9  my $sqldb = DBIx::SQLEngine->new( ... );
10
11  $class_name = $sqldb->record_class( $table_name, undef, 'Cache' );
12
13  $sqldb->record_class( $table_name, 'My::Record', 'Cache' );
14
15  package My::Record;
16  use DBIx::SQLEngine::Record::Class '-isasubclass', 'Cache';
17  My::Record->table( $sqldb->table($table_name) );
18
19B<Cache:> Uses Cache::Cache interface.
20
21  $class_name->use_cache_style('simple');
22
23  # requires Cache::FastMemoryCache
24  $class_name->use_cache_style('active');
25
26  use Cache::Cache;
27  $class_name->cache_cache( $my_cache_cache_object );
28
29B<Basics:> Layered over superclass.
30
31  # Fetches from cache if it's been seen before
32  $record = $class_name->fetch_record( $primary_key );
33
34  # Fetches from cache if we've run this query before
35  @records = $class_name->fetch_select(%clauses)->records;
36
37  # Clears cache so it's seen by next select query
38  $record->insert_record();
39
40  # Clears cache so it's seen by next select query
41  $record->update_record();
42
43  # Clears cache so it's seen by next select query
44  $record->delete_record();
45
46
47=head1 DESCRIPTION
48
49This package provides a caching layer for DBIx::SQLEngine::Record objects.
50
51Don't use this module directly; instead, pass its name as a trait when you create a new record class. This package provides a multiply-composable collection of functionality for Record classes. It is combined with the base class and other traits by DBIx::SQLEngine::Record::Class.
52
53=cut
54
55########################################################################
56
57package DBIx::SQLEngine::Record::Cache;
58
59use strict;
60use Carp;
61
62use Storable 'freeze';
63
64########################################################################
65
66########################################################################
67
68=head1 CACHE INTERFACE
69
70=cut
71
72########################################################################
73
74=head2 Cache Configuration
75
76=over 4
77
78=item cache_cache()
79
80  $record_class->cache_cache() : $cache_cache
81  $record_class->cache_cache( $cache_cache )
82
83Gets or sets the cache object associated with this record class.
84
85If no cache has been set for a given class, no caching is performed.
86
87=back
88
89B<Cache Object Requirements:> This package in intended to work with cache object that use the Cache::Cache interface. However, any package which support the limited cache interface used by this package should be sufficient.
90
91Two small classes are included that support this interface; see L<DBIx::SQLEngine::Cache::TrivialCache> and  L<DBIx::SQLEngine::Cache::BasicCache>.
92
93The following methods are used
94
95=over 4
96
97Constructor.
98
99=item get_namespace()
100
101Used to differentiate one cache object from another.
102
103=item get()
104
105Fetch a value from the cache, if it is present.
106
107=item set()
108
109Set a value in the cache.
110
111=item clear()
112
113Clear some or all values in the cache.
114
115=back
116
117=cut
118
119use Class::MakeMethods (
120  'Template::ClassInherit:scalar' => 'cache_cache',
121);
122
123########################################################################
124
125=head2 Cache Operations
126
127=over 4
128
129=item cache_key()
130
131  $record_class->cache_key( $key ) : $string_value
132  $record_class->cache_key( \@key ) : $string_value
133  $record_class->cache_key( \%key ) : $string_value
134
135Returns the string value to be used as a cache key. The argument may be an existing string, a reference to a shallow array whose elements will be joined with "\0/\0", or any other reference value which will be stringified by Storable.
136
137=item cache_get()
138
139  $record_class->cache_get( $key ) : $value
140  $record_class->cache_get( $key ) : ( $value, $updater_code_ref )
141
142Returns the cached value associated with this key, if any. If called in a list context, also returns a reference to a subroutine which will save a new value for that key.
143
144=item cache_set()
145
146  $record_class->cache_set( $key, $value )
147
148Caches this value under the provided key.
149
150=item cache_get_set()
151
152  $record_class->cache_get_set( $key, $code_ref, @args ) : $value
153
154Returns the curent value provided by a cache_get on the provided key, or if it is undefined, invokes the subroutine reference with any additional arguments provided, and saves the subroutine's return value as the cached value.
155
156=item cache_clear()
157
158  $record_class->cache_clear()
159  $record_class->cache_clear( $key )
160
161Clear all values from the cache, or just those associated with the given key.
162
163=back
164
165=cut
166
167sub cache_key {
168  my ( $self, $key ) = @_;
169  my $type = ref($key);
170  if ( ! $type ) {
171    $key
172  } elsif ( $type eq 'ARRAY' ) {
173    join("\0/\0", @$key)
174  } else {
175    local $Storable::canonical = 1;
176    freeze($key)
177  }
178}
179
180# $value = $self->cache_get( $key );
181# ( $value, $update ) = $self->cache_get( $key );
182sub cache_get {
183  my ( $self, $key ) = @_;
184
185  my $cache = $self->cache_cache() or return;
186
187  $key = $self->cache_key($key) if ( ref $key );
188  my $current = $cache->get( $key );
189
190  if ( ! defined $current ) {
191    $self->cache_log_operation( $cache, 'miss', $key );
192  } else {
193    $self->cache_log_operation( $cache, 'hit', $key );
194  }
195
196  ! wantarray ? $current : ( $current, sub {
197    $self->cache_log_operation( $cache, 'update', $key );
198    $cache->set( $key, @_ );
199  } );
200}
201
202# $self->cache_set( $key, $value );
203sub cache_set {
204  my ( $self, $key, @value ) = @_;
205
206  my $cache = $self->cache_cache() or return;
207
208  $key = $self->cache_key($key) if ( ref $key );
209
210  $self->cache_log_operation( $cache, 'write', $key );
211  $cache->set( $key, @value );
212}
213
214# $value = $self->cache_get_set( $key, \&sub, @args );
215sub cache_get_set {
216  my ( $self, $key, $sub, @args ) = @_;
217
218  my ($current, $update) = $self->cache_get($key);
219
220  if ( ! defined $current ) {
221    $current = &$sub( @args );
222    &$update( defined($current) ? $current : '' );
223  }
224
225  $current;
226}
227
228# $self->cache_clear();
229# $self->cache_clear( $key );
230sub cache_clear {
231  my ( $self, $key ) = @_;
232
233  my $cache = $self->cache_cache() or return;
234
235  if ( ! $key ) {
236    $self->cache_log_operation( $cache, 'clear' );
237    $cache->clear();
238  } else {
239    $self->cache_log_operation( $cache, 'clear', $key );
240    $cache->set($key, undef);
241  }
242}
243
244########################################################################
245
246=head2 Cache Logging
247
248=over 4
249
250=item CacheLogging()
251
252  $record_class->CacheLogging() : $level
253  $record_class->CacheLogging( $level )
254
255Sets the logging level associated with a given class.
256
257=item cache_log_operation()
258
259  $record_class->cache_log_operation( $cache, $operation, $key )
260
261Does nothing unless a CacheLogging level is set for this class.
262
263Uses warn() to print a message to the error log, including the key string used, and the operation, which will be one of "hit", "miss", "write", and "clear".
264
265If the level is greater than one, the message will also include a history of prior operations on this key.
266
267=back
268
269=cut
270
271use Class::MakeMethods (
272  'Template::ClassInherit:scalar' => 'CacheLogging',
273);
274
275use vars qw( %CachingHistory );
276
277sub cache_log_operation {
278  my ( $self, $cache, $oper, $key ) = @_;
279  my $level = $self->CacheLogging() or return;
280  my $namespace = $cache->get_namespace;
281  if ( $level > 1 ) {
282    my $history = ( $CachingHistory{ $key } ||= [] );
283    $oper .= " (" . join(' ', @$history ) . ")";
284    push @$history, $oper;
285  }
286  warn "Cache $namespace: $oper " . DBIx::SQLEngine::printable($key) . "\n";
287}
288
289########################################################################
290
291=head2 Cache Styles
292
293=over 4
294
295=item define_cache_styles()
296
297  DBIx::SQLEngine->define_cache_styles( $name, $code_ref )
298  DBIx::SQLEngine->define_cache_styles( %names_and_code_refs )
299
300Define a named caching style. The code ref supplied for each name should create and return an object from the Cache::Cache hierarchy, or another caching class which supports the interface described in the "Cache Object Requirements" section above.
301
302=item cache_styles()
303
304  DBIx::SQLEngine->cache_styles() : %names_and_info
305  DBIx::SQLEngine->cache_styles( $name ) : $info
306  DBIx::SQLEngine->cache_styles( \@names ) : @info
307  DBIx::SQLEngine->cache_styles( $name, $info, ... )
308  DBIx::SQLEngine->cache_styles( \%names_and_info )
309
310Accessor for global hash mapping cache names to initialization subroutines.
311
312=item use_cache_style()
313
314  $class_name->use_cache_style( $cache_style_name )
315  $class_name->use_cache_style( $cache_style_name, @options )
316
317Uses the named caching definition to create a new cache object, and associates it with the given class.
318
319Use one of the predefined caching styles described in the "Default Caching Styles" section below, or define your own cache styles with define_cache_styles.
320
321=back
322
323=cut
324
325use Class::MakeMethods (
326  'Standard::Global:hash' => 'cache_styles',
327);
328
329sub define_cache_styles {
330  my $self = shift;
331  $self->cache_styles( @_ );
332}
333
334sub use_cache_style {
335  my ( $class, $style, %options ) = @_;
336  my $sub = $class->cache_styles( $style );
337  my $cache = $sub->( $class, %options );
338  $class->cache_cache( $cache );
339}
340
341########################################################################
342
343=pod
344
345B<Default Caching Styles:> The following cache styles are predefined. Except for 'simple', using any of these styles will require installation of the Cache::Cache distribution.
346
347=over 4
348
349=item 'simple'
350
351Uses DBIx::SQLEngine::Cache::TrivialCache.
352
353=item 'live'
354
355Uses Cache::FastMemoryCache with a default expiration time of 1 seconds.
356
357=item 'active'
358
359Uses Cache::FastMemoryCache with a default expiration time of 5 seconds.
360
361=item 'stable'
362
363Uses Cache::FastMemoryCache with a default expiration time of 30 seconds.
364
365=item 'file'
366
367Uses Cache::FileCache with a default expiration time of 30 seconds.
368
369=back
370
371B<Examples:>
372
373=over 2
374
375=item *
376
377  # requires DBIx::SQLEngine::Cache::TrivialCache
378  $class_name->use_cache_style('simple');
379
380=item *
381
382  # requires Cache::FastMemoryCache from CPAN
383  $class_name->use_cache_style('active');
384
385=back
386
387=cut
388
389__PACKAGE__->define_cache_styles(
390  'simple' => sub {
391    require DBIx::SQLEngine::Cache::TrivialCache;
392    DBIx::SQLEngine::Cache::TrivialCache->new();
393  },
394  'live' => sub {
395    require Cache::FastMemoryCache;
396    Cache::FastMemoryCache->new( {
397      'namespace' => 'RecordCache:' . (shift),
398      'default_expires_in'  => 1,
399      'auto_purge_interval' => 10,
400      @_
401    } )
402  },
403  'active' => sub {
404    require Cache::FastMemoryCache;
405    Cache::FastMemoryCache->new( {
406      'namespace' => 'RecordCache:' . (shift),
407      'default_expires_in'  => 5,
408      'auto_purge_interval' => 60,
409      @_
410    } )
411  },
412  'stable' => sub {
413    require Cache::FastMemoryCache;
414    Cache::FastMemoryCache->new( {
415      'namespace' => 'RecordCache:' . (shift),
416      'default_expires_in'  => 30,
417      'auto_purge_interval' => 60,
418      @_
419    } )
420  },
421  'file' => sub {
422    require Cache::FileCache;
423    Cache::FileCache->new( {
424      'namespace' => 'RecordCache:' . (shift),
425      'default_expires_in'  => 30,
426      'auto_purge_interval' => 60,
427      @_
428    } )
429  },
430);
431
432########################################################################
433
434########################################################################
435
436=head1 FETCHING DATA (SQL DQL)
437
438Each of these methods provides a cached version of the superclass method.
439The results of queries are cached based on the SQL statement and parameters used.
440
441=over 4
442
443=item fetch_select()
444
445  $class_name->fetch_select( %select_clauses ) : $record_set
446
447Retrives records from the table using the provided SQL select clauses.
448
449=item fetch_one_record()
450
451  $sqldb->fetch_one_record( %select_clauses ) : $record_hash
452
453Retrives one record from the table using the provided SQL select clauses.
454
455=item select_record()
456
457  $class_name->select_record ( $primary_key_value ) : $record_obj
458  $class_name->select_record ( \@compound_primary_key ) : $record_obj
459  $class_name->select_record ( \%hash_with_primary_key_value ) : $record_obj
460
461Fetches a single record by primary key.
462
463=item select_records()
464
465  $class_name->select_records ( @primary_key_values_or_hashrefs ) : $record_set
466
467Fetches a set of one or more records by primary key.
468
469=item visit_select()
470
471  $class_name->visit_select ( $sub_ref, %select_clauses ) : @results
472  $class_name->visit_select ( %select_clauses, $sub_ref ) : @results
473
474Calls the provided subroutine on each matching record as it is retrieved. Returns the accumulated results of each subroutine call (in list context).
475
476To Do: This could perform caching of the matched records, but currently does not.
477
478=back
479
480The conversion of select clauses to a SQL statement is performed by the sql_select method:
481
482=over 4
483
484=item sql_select()
485
486  $class_name->sql_select ( %sql_clauses ) : $sql_stmt, @params
487
488Uses the table to call the sql_select method on the current SQLEngine driver.
489
490=back
491
492=cut
493
494# $records = $record_class->fetch_select( %select_clauses );
495sub fetch_select {
496  my $self = shift;
497  my %clauses = @_;
498
499  my @sql = $self->sql_select( %clauses );
500
501  my ($records, $update) = $self->cache_get( \@sql );
502
503  if ( ! defined $records ) {
504    $records = $self->NEXT('fetch_select', sql => \@sql );
505    $update->( $records ) if ( $update and $records );
506  }
507
508  return $records;
509}
510
511sub fetch_one_record {
512  local $SIG{__DIE__} = \&Carp::confess;
513  (shift)->fetch_select( @_, 'limit' => 1 )->record( 0 )
514}
515
516# @results = $self->visit_select( %select_clauses, $sub );
517sub visit_select {
518  my $self = shift;
519  my $sub = ( ref($_[0]) ? shift : pop );
520  my %clauses = @_;
521
522  my @sql = $self->sql_select( %clauses );
523
524  my ($records, $update) = $self->cache_get( \@sql );
525
526  if ( $records ) {
527    return map &$sub( $_ ), @$records;
528  }
529  $self->sqlengine_do('visit_select', @_, $sub )
530}
531
532########################################################################
533
534sub sql_select {
535  (shift)->get_table->sqlengine_do( 'sql_select', @_ );
536}
537
538########################################################################
539
540=head2 Vivifying Records From The Database
541
542These methods are called internally by the various select methods and do not need to be called directly.
543
544=over 4
545
546=item record_from_db_data()
547
548  $class_name->record_from_db_data( $hash_ref )
549
550Calls SUPER method, then cache_records().
551
552=item record_set_from_db_data()
553
554  $class_name->record_set_from_db_data( $hash_array_ref )
555
556Calls SUPER method, then cache_records().
557
558=item cache_records()
559
560  $class_name->cache_records( @records )
561
562Adds records to the cache.
563
564=back
565
566=cut
567
568# $record_class->record_from_db_data( $hash_ref );
569sub record_from_db_data {
570  my $self = shift;
571  my $record = $self->NEXT('record_from_db_data', @_ );
572  $self->cache_records( $record );
573  $record;
574}
575
576# $record_class->record_set_from_db_data( $hash_array_ref );
577sub record_set_from_db_data {
578  my $self = shift;
579  my $recordset = $self->NEXT('record_set_from_db_data', @_ );
580  $self->cache_records( @$recordset );
581  $recordset;
582}
583
584sub cache_records {
585  my $self = shift;
586  my $id_col = $self->column_primary_name();
587  foreach my $record ( @_ ) {
588    my $tablename = $self->table->name;
589    my $criteria = { $id_col => $record->{ $id_col } };
590    my %index = ( where => { $id_col => $record->{ $id_col } }, limit => 1, table => $self->table->name );
591    $self->cache_set( \%index, DBIx::SQLEngine::Record::Set->new($record) );
592  }
593}
594
595########################################################################
596
597########################################################################
598
599=head1 EDITING DATA (SQL DML)
600
601=head2 Insert to Add Records
602
603After constructing a record with one of the new_*() methods, you may save any changes by calling insert_record.
604
605=over 4
606
607=item insert_record
608
609  $record_obj->insert_record() : $flag
610
611Attempt to insert the record into the database. Calls SUPER method, so implemented using MIXIN.
612
613Clears the cache.
614
615=back
616
617=cut
618
619# $record->insert_record()
620sub insert_record {
621  my $self = shift;
622  $self->cache_clear();
623  $self->NEXT('insert_record', @_ );
624}
625
626########################################################################
627
628=head2 Update to Change Records
629
630After retrieving a record with one of the fetch methods, you may save any changes by calling update_record.
631
632=over 4
633
634=item update_record
635
636  $record_obj->update_record() : $record_count
637
638Attempts to update the record using its primary key as a unique identifier.
639Calls SUPER method, so implemented using MIXIN.
640
641Clears the cache.
642
643=back
644
645=cut
646
647# $record->update_record()
648sub update_record {
649  my $self = shift;
650  $self->cache_clear();
651  $self->NEXT('update_record', @_ );
652}
653
654########################################################################
655
656=head2 Delete to Remove Records
657
658=over 4
659
660=item delete_record()
661
662  $record_obj->delete_record() : $record_count
663
664Delete this existing record based on its primary key.
665Calls SUPER method, so implemented using MIXIN.
666
667Clears the cache.
668
669=back
670
671=cut
672
673# $record->delete_record()
674sub delete_record {
675  my $self = shift;
676  $self->cache_clear();
677  $self->NEXT('delete_record', @_ );
678}
679
680########################################################################
681
682########################################################################
683
684=head1 SEE ALSO
685
686For more about the Record classes, see L<DBIx::SQLEngine::Record::Class>.
687
688See L<DBIx::SQLEngine> for the overall interface and developer documentation.
689
690See L<DBIx::SQLEngine::Docs::ReadMe> for general information about
691this distribution, including installation and license information.
692
693=cut
694
695########################################################################
696
6971;
698
699__END__
700
701### DBO::Row::CachedRow
702
703### Change History
704  # 2000-12-29 Added table_or_die() for better debugging output.
705  # 2000-05-24 Adjusted fall-back behavior in fetch_sql.
706  # 2000-04-12 Check whether being called on instance or class before blessing.
707  # 2000-04-11 Fixed really anoying fetch_id problem.
708  # 2000-04-05 Completed expiration and pruning methods.
709  # 2000-04-04 Check for empty-string criteria, ordering in cache_key_for_fetch
710  # 2000-03-29 Fixed cache expiration for multi-row fetch.
711  # 2000-03-06 Touchups.
712  # 2000-01-13 Overhauled. -Simon
713
714########################################################################
715
716
717
718
719
720########################################################################
721
722# $rows = RowClass->fetch( $criteria, $order )
723sub fetch {
724  my $self = shift;
725
726  return $self->query_cache->cache_get_set(
727    $self->cache_key_for_fetch( @_ ),
728    \&___cache_fetch, $self, @_
729  );
730}
731
732# $rows = RowClass->fetch_sql( $sql )
733sub fetch_sql {
734  my $self = shift;
735
736  return $self->query_cache->cache_get_set(
737    join('__', @_),
738    \&___cache_fetch_sql, $self, @_
739  );
740}
741
742# $row = RowClass->fetch_id( $id )
743sub fetch_id {
744  my $self = shift;
745
746  return $self->row_cache->cache_get_set(
747    join('__', @_),
748    \&___cache_fetch_id, $self, @_
749  );
750}
751
752########################################################################
753
754sub insert_row {
755  my $row = shift;
756
757  $row->query_cache->clear_all() if ( $row->query_cache );
758
759  my $id_col = $row->table_or_die()->id_column();
760  my $row_cache = $row->row_cache;
761  if ( $id_col and $row_cache ) {
762    $row_cache->replace( $row->{$id_col}, $row );
763  }
764
765  return $row->NEXT('insert_row', @_);
766}
767
768sub update_row {
769  my $row = shift;
770  $row->query_cache->clear_all() if ( $row->query_cache );
771  return $row->NEXT('update_row', @_);
772}
773
774sub delete_row {
775  my $row = shift;
776
777  my $id_col = $row->table_or_die()->id_column();
778  my $row_cache = $row->row_cache;
779  if ( $id_col and $row_cache ) {
780    $row_cache->clear( $row->{$id_col} );
781  }
782
783  $row->query_cache->clear_all() if ( $row->query_cache );
784  return $row->NEXT('delete_row, @_);
785}
786
7871;
788