File Coverage

blib/lib/Gantry/Utils/Model.pm
Criterion Covered Total %
statement 15 198 7.5
branch 0 50 0.0
condition 0 11 0.0
subroutine 5 27 18.5
pod 20 20 100.0
total 40 306 13.0


line stmt bran cond sub pod time code
1             package Gantry::Utils::Model;
2 4     4   26 use strict; use warnings;
  4     4   9  
  4         130  
  4         21  
  4         7  
  4         99  
3              
4 4     4   20 use Carp;
  4         8  
  4         279  
5 4     4   2575 use DBI;
  4         24980  
  4         442  
6              
7             use overload
8 0     0     '""' => sub { shift->stringify_self },
9 4     4   28 fallback => 1; # Shhh. Say nothing.
  4         10  
  4         62  
10              
11             #-----------------------------------------------------------------
12             # dbh managment methods
13             #-----------------------------------------------------------------
14              
15             sub get_db_options {
16 0     0 1   return {};
17             }
18              
19             sub disconnect {
20 0     0 1   my $class = shift;
21 0   0       my $dbh = shift || $class->db_Main();
22              
23 0 0         $dbh->rollback unless ( $dbh->{AutoCommit} );
24              
25 0           $dbh->disconnect;
26             }
27              
28             sub dbi_commit {
29 0     0 1   my $class = shift;
30 0   0       my $dbh = shift || $class->db_Main();
31              
32 0 0         $dbh->commit unless ( $dbh->{AutoCommit} );
33             }
34              
35             #-----------------------------------------------------------------
36             # constructor
37             #-----------------------------------------------------------------
38              
39             sub construct {
40 0     0 1   my $class = shift;
41 0           my $data = shift;
42              
43             # see unless block on line 526 of viperl Class::DBI for cached alternative
44             # that line is in the _init method
45 0           my $obj = bless {}, $class;
46              
47 0           my @cols = keys %{ $data };
  0            
48              
49 0           @{ $obj }{ @cols } = @{ $data }{ @cols };
  0            
  0            
50              
51 0           $obj->{__DIRTY__} = {};
52              
53 0           return $obj;
54             }
55              
56             #-----------------------------------------------------------------
57             # select statements
58             #-----------------------------------------------------------------
59              
60             sub special_sql {
61 0     0 1   my $class = shift;
62 0           my $sql = shift;
63              
64 0           my $dbh = $class->db_Main();
65 0           my $sth = $dbh->prepare( $sql );
66              
67 0           $sth->execute();
68              
69 0           my %row;
70              
71 0           eval {
72 0           $sth->bind_columns( \( @row{ @{ $sth->{NAME_lc} } } ) );
  0            
73             };
74 0 0         if ( $@ ) {
75 0           die "Couldn't execute $sql\n\n$@";
76             }
77              
78 0           my @retvals;
79              
80 0           while ( $sth->fetch() ) {
81             # XXX we are supposed to defer construction to an iterator unless
82             # the user wants an array
83 0           push @retvals, $class->construct( \%row );
84             }
85              
86 0 0         return unless @retvals;
87 0 0         return @retvals if wantarray;
88 0 0         return $retvals[0] if ( $#retvals == 0 );
89              
90             #XXX this is supposed to return an iterator with a next operation
91 0           return \@retvals;
92             }
93              
94             sub retrieve_all {
95 0     0 1   my $class = shift;
96              
97 0           my $attr;
98              
99 0 0         if ( @_ > 1 ) { $attr = { @_ }; }
  0            
100 0           else { $attr = shift; }
101              
102 0           my $order = '';
103 0 0         if ( $attr->{order_by} ) {
104 0           $order = "ORDER BY $attr->{order_by}";
105             }
106              
107 0           my $cols = $class->get_essential_cols();
108 0           my $table = $class->get_table_name();
109            
110 0           my $sql = "SELECT $cols FROM $table $order";
111              
112 0           return $class->special_sql( $sql );
113             }
114              
115             sub retrieve_by_pk {
116 0     0 1   my $class = shift;
117 0           my $id = shift;
118              
119 0 0         return unless $id;
120              
121 0           my $cols = $class->get_essential_cols();
122 0           my $table = $class->get_table_name();
123 0           my $primary = $class->get_primary_col();
124 0           my $sql = "SELECT $cols FROM $table WHERE $primary = $id";
125 0           my @answer = $class->special_sql( $sql );
126              
127 0           return $answer[0];
128             }
129              
130             sub retrieve {
131 0     0 1   my $class = shift;
132              
133 0 0         if ( @_ == 1 ) { return $class->retrieve_by_pk( shift ); }
  0            
134 0           else { return $class->search( @_ ); }
135             }
136              
137             sub search {
138 0     0 1   my $class = shift;
139 0           my %value_for;
140             my $attr;
141              
142 0           my $cols = $class->get_essential_cols();
143 0           my $table = $class->get_table_name();
144              
145             # see if there is an order by clause
146 0 0         if ( ref( $_[-1] ) =~ /HASH/ ) {
147 0           $attr = pop @_;
148             }
149 0           my $order = '';
150 0 0         if ( $attr->{order_by} ) {
151 0           $order = "ORDER BY $attr->{order_by}";
152             }
153              
154             # build where clause
155 0 0         if ( ref( $_[0] ) eq 'HASH' ) { %value_for = %{ $_[0] }; }
  0 0          
  0            
156 0           elsif ( ref( $_[0] ) eq 'ARRAY' ) { %value_for = @{ $_[0] }; }
  0            
157 0           else { %value_for = @_; }
158              
159 0           my @where_frags;
160              
161 0           foreach my $col ( keys %value_for ) {
162              
163 0           push @where_frags,
164             "$col = " . $class->quote_scalar( $col, $value_for{$col} );
165             }
166              
167 0           my $where = join ' AND ', @where_frags;
168              
169 0           my $sql = "SELECT $cols FROM $table WHERE $where $order";
170              
171             # are we paging?
172 0 0         if ( my $rows_per_page = delete $attr->{rows} ) {
173              
174 0   0       my $current_page = delete $attr->{page} || 1;
175              
176             # calculate offset
177 0           my $offset = ( $current_page - 1 ) * $rows_per_page;
178              
179 0           $sql .= " LIMIT $rows_per_page OFFSET $offset";
180             }
181              
182 0           return $class->special_sql( $sql );
183             }
184              
185 0     0 1   sub page { shift->search( @_ ) }
186              
187             sub lazy_fetch {
188 0     0 1   my $self = shift;
189 0           my $col = shift;
190             # my $col_group = shift; # using this would require several changes
191              
192 0           my $primary = $self->get_primary_col();
193              
194 0           my $sql = "SELECT $col FROM " . $self->get_table_name()
195             . " WHERE $primary = " . $self->get_primary_key();
196              
197 0           my $dbh = $self->db_Main();
198 0           my $sth = $dbh->prepare( $sql );
199 0           $sth->execute();
200              
201 0           my $value;
202 0           $sth->bind_columns( \$value );
203              
204 0 0         if ( $sth->fetch() ) {
205 0           my $method = "set_$col";
206 0           $self->$method( $value );
207             }
208             else {
209 0           croak "Error couldn't fetch $col for" . $self->get_primary_key() . "\n";
210             }
211             }
212              
213             #-----------------------------------------------------------------
214             # other CRUD
215             #-----------------------------------------------------------------
216              
217             sub create {
218 0     0 1   my $class = shift;
219 0           my $value_for = shift;
220              
221             # fill in primary_key if needed (it's usually needed)
222 0           my $primary = $class->get_primary_col();
223              
224 0 0 0       if ( not defined $value_for->{ $primary }
225             and
226             $class->can( 'get_sequence_name' )
227             ) {
228 0           $value_for->{ $primary } = $class->_next_primary_key();
229             }
230              
231             # construct object
232 0           my $new_object = $class->construct( $value_for );
233              
234             # make the sql, including quoting
235 0           my @quoted_values;
236              
237 0           foreach my $col ( keys %{ $value_for } ) {
  0            
238 0           push @quoted_values, $new_object->quote_attribute( $col );
239             }
240              
241 0           my $sql = 'INSERT INTO ' . $class->get_table_name . ' ( '
242 0           . join( ', ', keys %{ $value_for } ) . ' ) VALUES ( '
243             . join( ', ', @quoted_values ) . ' );';
244              
245             # execute sql
246 0           my $dbh = $class->db_Main();
247 0 0         unless ( $dbh->do( $sql ) ) {
248 0 0         $dbh->rollback unless ( $dbh->{AutoCommit} );
249              
250 0           croak "Database error with $sql\n$DBI::errstr $!\n";
251             }
252              
253 0           return $new_object;
254             }
255              
256             sub _next_primary_key {
257 0     0     my $class = shift;
258              
259 0           my $seq = $class->get_sequence_name();
260              
261 0           my $sql = "SELECT NEXTVAL ( '$seq' );";
262 0           my $dbh = $class->db_Main();
263 0           my $sth = $dbh->prepare( $sql );
264 0           $sth->execute();
265              
266 0           my $retval;
267 0           $sth->bind_columns( \$retval );
268            
269 0 0         unless ( $sth->fetch() ) {
270 0           croak "Error couldn't fetch next primary_key for $class\n"
271             . "using sequecne $seq\n";
272             }
273              
274 0           return $retval;
275             }
276              
277             sub find_or_create {
278 0     0 1   my $class = shift;
279              
280 0 0         my $data = ( ref $_[0] ) ? shift : { @_ };
281              
282             # see if this data is in some row
283 0           my ( $row ) = $class->search( %{ $data } );
  0            
284              
285 0 0         return ( defined $row ) ? $row : $class->create( $data );
286             }
287              
288             sub delete {
289 0     0 1   my $self = shift;
290              
291 0           my $table = $self->get_table_name();
292 0           my $pk = $self->get_primary_col();
293              
294 0           my $sql = "DELETE FROM $table WHERE $pk = " . $self->get_primary_key();
295              
296 0           my $dbh = $self->db_Main();
297 0           my $sth = $dbh->prepare( $sql );
298 0           $sth->execute();
299              
300 0           undef %$self;
301 0           bless $self, 'Deleted::Object';
302              
303 0           return 1;
304             }
305              
306             sub update {
307 0     0 1   my $self = shift;
308              
309             # build set clause for dirty cols
310 0           my @dirty_cols = keys %{ $self->{__DIRTY__} };
  0            
311              
312 0           my @new_values;
313 0           foreach my $dirty_col ( @dirty_cols ) {
314 0           my $value = $self->quote_attribute( $dirty_col );
315 0           push @new_values, "$dirty_col=$value";
316             }
317 0           my $new_values = join ',', @new_values;
318              
319             # build sql string
320 0           my $primary = $self->get_primary_col();
321              
322 0           my $sql = 'UPDATE ' . $self->get_table_name() . " SET $new_values"
323             . " WHERE $primary = " . $self->get_primary_key() . ';';
324              
325             # execute sql
326 0           my $dbh = $self->db_Main();
327 0 0         unless ( $dbh->do( $sql ) ) {
328 0 0         $dbh->rollback unless ( $dbh->{AutoCommit} );
329              
330 0           croak "Database error with $sql\n$DBI::errstr $!\n";
331             }
332              
333             # reset dirty
334 0           $self->{__DIRTY__} = {};
335             }
336              
337             #-----------------------------------------------------------------
338             # accessors and their helpers
339             #-----------------------------------------------------------------
340              
341             sub get {
342 0     0 1   my $self = shift;
343 0           my @cols = @_;
344              
345 0           my @retvals;
346              
347 0           foreach my $col ( @cols ) {
348 0           my $method = "get_$col";
349 0           push @retvals, $self->$method();
350             }
351              
352 0 0         return ( wantarray ) ? @retvals : $retvals[0];
353             }
354              
355             sub set {
356 0     0 1   my $self = shift;
357 0           my %value_for = @_;
358              
359 0           foreach my $col ( keys %value_for ) {
360 0           my $method = "set_$col";
361 0           $self->$method( $value_for{$col} );
362             }
363             }
364              
365             sub quote_attribute {
366 0     0 1   my $self = shift;
367 0           my $col = shift;
368              
369 0           my $getter = "get_$col";
370 0           my $quoter = "quote_$col";
371              
372 0           return $self->$quoter( $self->$getter );
373             }
374              
375             sub quote_scalar {
376 0     0 1   my $self_or_class = shift;
377 0           my $col = shift;
378 0           my $value = shift;
379              
380 0           my $quoter = "quote_$col";
381              
382 0           return $self_or_class->$quoter( $value );
383             }
384              
385             sub stringify_self {
386 0     0 1   my $self = shift;
387              
388 0           return $self->get_primary_key();
389             }
390              
391             1;
392              
393             =head1 NAME
394              
395             Gantry::Utils::Model - a general purpose Object Relational Model base class
396              
397             =head1 SYNOPSIS
398              
399             use base 'Gantry::Utils::Model';
400              
401             sub get_table_name { return 'your_table'; }
402             sub get_sequence_name { return 'your_table_seq'; }
403             sub get_primary_col { return 'id'; }
404             sub get_essential_cols { return 'id, text_col'; }
405              
406             sub get_primary_key { goto &get_id; }
407              
408             sub set_id { croak "Can't change primary key"; }
409             sub get_id { return $_->[0]{id}; }
410             sub quote_id { return $_[1]; }
411              
412             sub set_text_col {
413             my $self = shift;
414             my $value = shift;
415              
416             $self->{text_col} = $value;
417             $self->{__DIRTY__}{text_col}++;
418             return $value;
419             }
420             sub get_text_col { return $_->[0]{text_col}; }
421             sub quote_text_col {
422             return ( defined $_[1] ) ? "'$_[1]'" : 'NULL';
423             }
424              
425             sub set_other_text_col {
426             my $self = shift;
427             my $value = shift;
428              
429             $self->{other_text_col} = $value;
430             $self->{__DIRTY__}{other_text_col}++;
431              
432             return $value;
433             }
434             sub get_other_text_col {
435             my $self = shift;
436             unless ( defined $self->{other_text_col} ) {
437             $self->lazy_fetch( 'other_text_col' );
438             }
439             return $self->{other_text_col};
440             }
441             sub quote_other_text_col {
442             return ( defined $_[1] ) ? "'$_[1]'" : 'NULL';
443             }
444              
445             =head1 DESCRIPTION
446              
447             This module is a Class::DBI replacement. Its goal is to reduce the mystery
448             in the internals of that module, while still providing most of its
449             functionality. You'll notice that the inheriting class has a lot more code
450             than a Class::DBI subclass would. This is because we use Bigtop to
451             generate the subclasses. Thus, we don't care so much about the volume of
452             code. The result is code which is easy to read, understand, override
453             and/or modify.
454              
455             =head1 RATIONALE
456              
457             Class::DBI and its cousins provide beautiful APIs for client code. By
458             implementing straightforward database row to Perl object correspondence,
459             they save a lot of mental effort when writing most applications.
460              
461             They do have drawbacks. My premise is that most of these drawbacks stem
462             from a single fundamental design descision. Perl's traditional Object
463             Relation Mappers (ORMs) do a lot of work at run time. For instance, they build
464             accessors at run time. When I first started using them, I thought this
465             was gorgeous. Class::DBI::mysql was one of my favorite modules. I
466             bought the promise of a future where all you had to say was something like
467              
468             package MyModel;
469              
470             use Class::DBI::SuperClever
471             'dbi:Pg:dbname=somedb', 'user', 'passwd', 'MyModel';
472              
473             and the whole somedb database would be mapped without another word. Each table
474             would become a class under MyModel with an accessor for each column. Then
475             I could create, retrieve, update, and delete to my heart's content while
476             beholding the power of Perl.
477              
478             The problem is that use statements like the above example require extreme
479             magic (and not a small amount of time). This leads to a lack of transperency
480             which leaves me with three problems: (1) I worry, in the back of my mind I
481             always have the doubt of not knowing what is going on in these complex beasts
482             (2) I get hit by subtle bugs, like name collisions from inheritence and
483             inadvertant overriding (3) worst, I am left with a system that works really
484             well to do the things the author thought of, but not the thing I really need
485             to do in a particular instance (either because the system is inherently
486             limiting or more likely because it is so complex I can't wrap my small mind
487             around it well enough to carry out my task).
488              
489             This leads to the fundamental principle of this module: simplicity. Any
490             programmer with intermediate Perl skills and a passing familiarity with
491             SQL databases should be able to digest this in a morning. There are
492             other goals, but simplicity is at the core.
493              
494             In order to achieve transperency, it is necessary to have more code in the
495             subclasses. This is really why the magical schemes sprang up. But,
496             recently I have been working on generation of code. This amounts to the
497             same thing, but it happens ahead of time. So, instead of code being
498             generated by magic during run time, my code is generated by grammar
499             based parsing before compile time. The generator in question is bigtop
500             which can build a completely funcational web app from a description of
501             its data model and controllers. Then, when a programmer wonders what
502             the model is up to, she has a set of simple modules which explicitly
503             show what is going on. To make change, she may add methods or override
504             the existing generated ones.
505              
506             =head1 METHODS PROVIDED BY THIS MODULE
507              
508             =over 4
509              
510             =item disconnect
511              
512             Class or instance method.
513             You can pass in a handle or this will call db_Main to get the standard
514             one. In either case, it will rollback any current transaction (if
515             you aren't auto-committing) and disconnect the handle.
516              
517             =item dbi_commit
518              
519             Class or instance method.
520             By default the dbh managed by this module has AutoCommit off. Call this
521             to commit your transactions.
522              
523             =item construct
524              
525             Class method.
526             Mainly for internal use. This method takes a hash (usually one bound
527             to a statement handle) and turns it into an object of the subclass through
528             which it was called.
529              
530             =item special_sql
531              
532             Class method.
533             Accepts sql SELECT statements returns a list (not a reference or iterator)
534             of objects in the class through which it was called. Be careful with
535             column names, they need to be the genuine names of columns in the underlying
536             table.
537              
538             =item retrieve_all
539              
540             Class method. Pass a list of key/value pairs or a single hash ref. The
541             only legal key is order_by, its value will be used literally directly after
542             'ORDER BY' (that means, don't include the ORDER BY keywords in your value).
543             Returns a list of objects.
544              
545             =item retrieve_by_pk
546              
547             Class method. Pass a single primary key value. Returns the row with that
548             primary key value as an object or undef if no such row is found.
549              
550             =item retrieve
551              
552             Class method. Similar to retrieve in Class::DBI. If called with one
553             argument, that argument is taken as a primary key and the request is
554             forwarded to retrieve_by_pk. If called with multiple arguments (or no
555             arguments), those arguments are forwarded to search.
556              
557             =item search
558              
559             Class method. Similar to search in Class::DBI. Call with the key/value
560             pairs you want to match in a simple list.
561              
562             Returns a list of objects one each for every row that matched the search
563             criterion.
564              
565             Add a single hash reference as the last parameter if you like. That hash
566             reference may only contain these keys:
567              
568             =over 4
569              
570             =item order_by
571              
572             Asks for an ORDER BY clause, the value is used literally to fill in the
573             blank in 'ORDER BY ___'.
574              
575             =item rows
576              
577             Indicates that you want paging. The value is the number of rows per page.
578             There is no default, since the absence of this key is taken to mean you don't
579             want paging.
580              
581             =item page
582              
583             Ignored unless rows is supplied. Defaults to 1. This is the page number
584             to retrieve.
585              
586             =back
587              
588             =item page
589              
590             A synonymn for search to better match the Class::DBI::Sweet API.
591             Note that you must set the rows key in the hash reference passed as the last
592             argument. You may also set the page key. See above.
593              
594             =item lazy_fetch
595              
596             Instance method. Call with the column name you want to fetch. Returns
597             nothing useful, but sets the column with the value from the corresponding
598             row in the underlying table.
599              
600             =item create
601              
602             Class method. Call with a hash reference whose keys are the column names
603             you want to populate. The value will be quoted for you according to the
604             corresponding quote_* method in the subclass.
605              
606             =item _next_primary_key
607              
608             Class method. Returns the next value of the sequence associated with
609             the underlying table. This is not reproduceable, it actually increments
610             the sequence. It only works if the database is using a sequence for the
611             table and the model implements get_sequence_name.
612              
613             =item find_or_create
614              
615             Class method. Call with a hash reference of search criteria (think of
616             a WHERE clause). First, it calls search, taking a single resulting
617             object. If that works, you get the object. Otherwise, it calls
618             create with your hash reference and returns the new object.
619              
620             =item update
621              
622             Instance method. Issues an UPDATE to SET the dirty values from the
623             invocant. Returns nothing useful, although it could die if the dbh
624             has problems.
625              
626             =item delete
627              
628             Instance method. Deletes the underlying row from its table and
629             renders the invocant reference unusable.
630              
631             =item get
632              
633             Instance method. Call with a list of columns whose values you want.
634             Returns the values in the invocant for the columns you requested.
635             If you requested only one column a scalar is returned. Otherwise,
636             you get a list.
637              
638             =item set
639              
640             Instance method. Call with a list of key/value pairs for columns that
641             you want to change. Returns nothing useful.
642              
643             =item quote_attribute
644              
645             Instance method. Primarily for internal use. Call with a column name.
646             Returns the value in the column quoted so SQL will take it.
647              
648             =item quote_scalar
649              
650             Class or instance method. Call with a column name and a value. Returns
651             the value quoted for SQL as if it were stored in the column of an object.
652             Even if you call this as an instance method, the instance values are not
653             used.
654              
655             =item get_db_options
656              
657             Subclasses are welcome to override this with a meaningful routine.
658             The one here returns an empty hash reference. Yours should provide
659             data given as extra options to DBI during connection.
660              
661             =item stringify_self
662              
663             Returns the id of the row.
664              
665             =back
666              
667             =head1 METHODS SUBCLASSES MUST PROVIDE
668              
669             You can include any useful method you like in your subclass, but these
670             are the ones this module needs.
671              
672             =over 4
673              
674             =item get_table_name
675              
676             Return the name of the table in the database that your class models.
677              
678             =item get_sequence_name
679              
680             Return the name of the sequence associated with your table. This is
681             needed for the create method.
682              
683             =item get_essential_cols
684              
685             Return an array reference containing the columns you want to fetch
686             automatically during retrieve, search, etc.
687              
688             =item get_primary_col
689              
690             We assume that each table has a unique primary key (though we assume
691             nothing about its name). Return the name of that column.
692              
693             =item get_primary_key
694              
695             An instance method. Return the value of the primary key for the invocant.
696              
697             =item set_COL_NAME
698              
699             Provide one of these for each column. Called on an existing object with
700             a new value. It must store the value in the object's hash (whose keys
701             are the column labels) AND set the dirty flag for the column so that eventual
702             updates will be effective. Some callers may expect to receive the
703             new value in return, document whether it returns that value or not.
704             Example:
705              
706             sub set_amount {
707             my $self = shift;
708             my $value = shift;
709              
710             $self->{amount} = $value;
711             $self->{__DIRTY__}{amount}++;
712              
713             return $self->{amount};
714             }
715              
716             =item get_COL_NAME
717              
718             Provide one of these for each column. Return the unquoted value in the
719             column. Example:
720              
721             sub get_amount {
722             my $self = shift;
723              
724             return $self->{amount};
725             }
726              
727             =item quote_COL_NAME
728              
729             Called as a class or instance method with one argument. Take that argument,
730             hold it up to the light, examine in detail. Then return something that has
731             the same value properly quoted for SQL.
732              
733             Note that you should not look in the object, even if one is used as the
734             invocant. Always only work on the other argument.
735              
736             =item COL_NAME (completely optional)
737              
738             Provide one of these for each column only if you like. Dispatch to get_ and
739             set_ methods based on the arguments you receive. These methods are NEVER
740             called internally, but your callers might like them. Example (with
741             apologies to Dr. Conway):
742              
743             sub amount {
744             my $self = shift;
745             my $value = shift;
746              
747             if ( defined $value ) { return $self->set_amount( $value ); }
748             else { return $self->get_amount(); }
749             }
750              
751             =back
752              
753             =head1 OMISSIONS
754              
755             There is no caching. This means two things: (1) no sql statement is
756             prepared with bind parameter place holders and stored for possible
757             reuse (2) objects are always built for each row retrieved, even if
758             there is a live object for that row elsewhere in memory.
759              
760             There are no triggers. If you need these, put them in the accessors
761             as needed. Feel free to override construct.
762              
763             There are no iterators. Class::DBI makes iterators, but they only
764             delay object instantiation, the full query results are pulled from the
765             beginning. Replicating that behavior seems like the pursuit of
766             diminishing returns.
767              
768             =head1 AUTHOR
769              
770             Phil Crow
771              
772             =head1 COPYRIGHT and LICENSE
773              
774             Copyright (c) 2006, Phil Crow.
775              
776             This library is free software; you can redistribute it and/or modify
777             it under the same terms as Perl itself, either Perl version 5.8.6 or,
778             at your option, any later version of Perl 5 you may have available.
779              
780             =cut