Tighten up select list processing in ::SQLMaker
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage.pm
CommitLineData
4012acd8 1package DBIx::Class::Storage;
a62cf8d4 2
3use strict;
4use warnings;
5
046ad905 6use base qw/DBIx::Class/;
2ad62d97 7use mro 'c3';
046ad905 8
b78ed1e4 9BEGIN {
10 no warnings 'once';
11 @DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION::ISA
12 = 'DBIx::Class::Exception';
90d7422f 13}
14
15use DBIx::Class::Carp;
9345b14c 16use DBIx::Class::Storage::BlockRunner;
90d7422f 17use Scalar::Util qw/blessed weaken/;
1bc193ac 18use DBIx::Class::Storage::TxnScopeGuard;
e2741c7f 19use DBIx::Class::_Util qw( dbic_internal_try dbic_internal_catch fail_on_internal_call );
fd323bf1 20use namespace::clean;
046ad905 21
90d7422f 22__PACKAGE__->mk_group_accessors(simple => qw/debug schema transaction_depth auto_savepoint savepoints/);
23__PACKAGE__->mk_group_accessors(component_class => 'cursor_class');
e4eb8ee1 24
25__PACKAGE__->cursor_class('DBIx::Class::Cursor');
26
1b822bd3 27sub cursor :DBIC_method_is_indirect_sugar {
e5053694 28 DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
29 shift->cursor_class(@_);
30}
046ad905 31
046ad905 32=head1 NAME
33
34DBIx::Class::Storage - Generic Storage Handler
35
36=head1 DESCRIPTION
37
38A base implementation of common Storage methods. For specific
39information about L<DBI>-based storage, see L<DBIx::Class::Storage::DBI>.
40
41=head1 METHODS
42
43=head2 new
44
45Arguments: $schema
46
47Instantiates the Storage object.
48
49=cut
50
51sub new {
52 my ($self, $schema) = @_;
53
54 $self = ref $self if ref $self;
55
90d7422f 56 my $new = bless( {
90d7422f 57 savepoints => [],
58 }, $self);
046ad905 59
60 $new->set_schema($schema);
4d753fb8 61 $new->debug(1)
62 if $ENV{DBIX_CLASS_STORAGE_DBI_DEBUG} || $ENV{DBIC_TRACE};
046ad905 63
64 $new;
65}
66
67=head2 set_schema
68
69Used to reset the schema class or object which owns this
70storage object, such as during L<DBIx::Class::Schema/clone>.
71
72=cut
73
74sub set_schema {
75 my ($self, $schema) = @_;
76 $self->schema($schema);
6298a324 77 weaken $self->{schema} if ref $self->{schema};
046ad905 78}
79
80=head2 connected
81
82Returns true if we have an open storage connection, false
83if it is not (yet) open.
84
85=cut
86
a62cf8d4 87sub connected { die "Virtual method!" }
046ad905 88
89=head2 disconnect
90
91Closes any open storage connection unconditionally.
92
93=cut
94
95sub disconnect { die "Virtual method!" }
96
97=head2 ensure_connected
98
99Initiate a connection to the storage if one isn't already open.
100
101=cut
102
a62cf8d4 103sub ensure_connected { die "Virtual method!" }
046ad905 104
105=head2 throw_exception
106
107Throws an exception - croaks.
108
109=cut
110
111sub throw_exception {
112 my $self = shift;
113
2a2a7b23 114 if (ref $self and $self->schema) {
1a58752c 115 $self->schema->throw_exception(@_);
116 }
117 else {
118 DBIx::Class::Exception->throw(@_);
119 }
046ad905 120}
a62cf8d4 121
4012acd8 122=head2 txn_do
a62cf8d4 123
4012acd8 124=over 4
a62cf8d4 125
4012acd8 126=item Arguments: C<$coderef>, @coderef_args?
a62cf8d4 127
4012acd8 128=item Return Value: The return value of $coderef
129
130=back
131
132Executes C<$coderef> with (optional) arguments C<@coderef_args> atomically,
133returning its result (if any). If an exception is caught, a rollback is issued
134and the exception is rethrown. If the rollback fails, (i.e. throws an
135exception) an exception is thrown that includes a "Rollback failed" message.
136
137For example,
138
139 my $author_rs = $schema->resultset('Author')->find(1);
140 my @titles = qw/Night Day It/;
141
142 my $coderef = sub {
143 # If any one of these fails, the entire transaction fails
144 $author_rs->create_related('books', {
145 title => $_
146 }) foreach (@titles);
147
148 return $author->books;
149 };
150
151 my $rs;
20674fcd 152 try {
4012acd8 153 $rs = $schema->txn_do($coderef);
e2741c7f 154 } dbic_internal_catch {
6b89ee0b 155 my $error = shift;
20674fcd 156 # Transaction failed
90d7422f 157 die "something terrible has happened!"
6b89ee0b 158 if ($error =~ /Rollback failed/); # Rollback failed
4012acd8 159
160 deal_with_failed_transaction();
20674fcd 161 };
4012acd8 162
163In a nested transaction (calling txn_do() from within a txn_do() coderef) only
164the outermost transaction will issue a L</txn_commit>, and txn_do() can be
165called in void, scalar and list context and it will behave as expected.
166
05075aee 167Please note that all of the code in your coderef, including non-DBIx::Class
168code, is part of a transaction. This transaction may fail out halfway, or
169it may get partially double-executed (in the case that our DB connection
170failed halfway through the transaction, in which case we reconnect and
171restart the txn). Therefore it is best that any side-effects in your coderef
172are idempotent (that is, can be re-executed multiple times and get the
173same result), and that you check up on your side-effects in the case of
174transaction failure.
6500d50f 175
4012acd8 176=cut
177
178sub txn_do {
38ed54cd 179 my $self = shift;
4012acd8 180
9345b14c 181 DBIx::Class::Storage::BlockRunner->new(
182 storage => $self,
9345b14c 183 wrap_txn => 1,
7d534e68 184 retry_handler => sub {
185 $_[0]->failed_attempt_count == 1
186 and
187 ! $_[0]->storage->connected
188 },
189 )->run(@_);
a62cf8d4 190}
191
046ad905 192=head2 txn_begin
193
194Starts a transaction.
195
196See the preferred L</txn_do> method, which allows for
197an entire code block to be executed transactionally.
198
199=cut
200
90d7422f 201sub txn_begin {
202 my $self = shift;
203
204 if($self->transaction_depth == 0) {
205 $self->debugobj->txn_begin()
206 if $self->debug;
207 $self->_exec_txn_begin;
208 }
209 elsif ($self->auto_savepoint) {
210 $self->svp_begin;
211 }
212 $self->{transaction_depth}++;
213
214}
046ad905 215
216=head2 txn_commit
217
218Issues a commit of the current transaction.
219
be01f1be 220It does I<not> perform an actual storage commit unless there's a DBIx::Class
221transaction currently in effect (i.e. you called L</txn_begin>).
222
046ad905 223=cut
224
90d7422f 225sub txn_commit {
226 my $self = shift;
227
228 if ($self->transaction_depth == 1) {
229 $self->debugobj->txn_commit() if $self->debug;
230 $self->_exec_txn_commit;
231 $self->{transaction_depth}--;
398215b1 232 $self->savepoints([]);
90d7422f 233 }
234 elsif($self->transaction_depth > 1) {
235 $self->{transaction_depth}--;
236 $self->svp_release if $self->auto_savepoint;
237 }
238 else {
239 $self->throw_exception( 'Refusing to commit without a started transaction' );
240 }
241}
046ad905 242
243=head2 txn_rollback
244
245Issues a rollback of the current transaction. A nested rollback will
246throw a L<DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION> exception,
247which allows the rollback to propagate to the outermost transaction.
248
249=cut
250
90d7422f 251sub txn_rollback {
252 my $self = shift;
253
254 if ($self->transaction_depth == 1) {
255 $self->debugobj->txn_rollback() if $self->debug;
90d7422f 256 $self->{transaction_depth}--;
729656c5 257
258 # in case things get really hairy - just disconnect
ddcc02d1 259 dbic_internal_try { $self->_exec_txn_rollback; 1 } or do {
729656c5 260 my $rollback_error = $@;
261
262 # whatever happens, too low down the stack to care
263 # FIXME - revisit if stackable exceptions become a thing
ddcc02d1 264 dbic_internal_try { $self->disconnect };
729656c5 265
266 die $rollback_error;
267 };
268
398215b1 269 $self->savepoints([]);
90d7422f 270 }
271 elsif ($self->transaction_depth > 1) {
272 $self->{transaction_depth}--;
273
274 if ($self->auto_savepoint) {
275 $self->svp_rollback;
276 $self->svp_release;
277 }
278 else {
279 DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION->throw(
280 "A txn_rollback in nested transaction is ineffective! (depth $self->{transaction_depth})"
281 );
282 }
283 }
284 else {
285 $self->throw_exception( 'Refusing to roll back without a started transaction' );
286 }
287}
046ad905 288
84efb6d7 289# to be called by several internal stacked transaction handler codepaths
290# not for external consumption
291# *DOES NOT* throw exceptions, instead:
292# - returns false on success
293# - returns the exception on failed rollback
294sub __delicate_rollback {
295 my $self = shift;
296
297 if (
729656c5 298 ( $self->transaction_depth || 0 ) > 1
84efb6d7 299 and
300 # FIXME - the autosvp check here shouldn't be happening, it should be a role-ish thing
301 # The entire concept needs to be rethought with the storage layer... or something
302 ! $self->auto_savepoint
303 and
304 # the handle seems healthy, and there is nothing for us to do with it
305 # just go ahead and bow out, without triggering the txn_rollback() "nested exception"
306 # the unwind will eventually fail somewhere higher up if at all
307 # FIXME: a ::Storage::DBI-specific method, not a generic ::Storage one
308 $self->_seems_connected
309 ) {
310 # all above checks out - there is nothing to do on the $dbh itself
311 # just a plain soft-decrease of depth
312 $self->{transaction_depth}--;
313 return;
314 }
315
ddcc02d1 316 my @args = @_;
84efb6d7 317 my $rbe;
318
ddcc02d1 319 dbic_internal_try {
320 $self->txn_rollback; 1
321 }
e2741c7f 322 dbic_internal_catch {
84efb6d7 323
ddcc02d1 324 $rbe = $_;
84efb6d7 325
326 # we were passed an existing exception to augment (think DESTROY stacks etc)
ddcc02d1 327 if (@args) {
328 my ($exception) = @args;
84efb6d7 329
330 # append our text - THIS IS A TEMPORARY FIXUP!
331 #
332 # If the passed in exception is a reference, or an object we don't know
333 # how to augment - flattening it is just damn rude
334 if (
335 # FIXME - a better way, not liable to destroy an existing exception needs
336 # to be created. For the time being perpetuating the sin below in order
337 # to break the deadlock of which yak is being shaved first
338 0
339 and
340 length ref $$exception
341 and
342 (
343 ! defined blessed $$exception
344 or
345 ! $$exception->isa( 'DBIx::Class::Exception' )
346 )
347 ) {
348
349 ##################
350 ### FIXME - TODO
351 ##################
352
353 }
354 else {
355
356 # SUCH HIDEOUS, MUCH AUGH! (and double WOW on the s/// at the end below)
357 $rbe =~ s/ at .+? line \d+$//;
358
359 (
360 (
361 defined blessed $$exception
362 and
363 $$exception->isa( 'DBIx::Class::Exception' )
364 )
365 ? (
366 $$exception->{msg} =
367 "Transaction aborted: $$exception->{msg}. Rollback failed: $rbe"
368 )
369 : (
370 $$exception =
371 "Transaction aborted: $$exception. Rollback failed: $rbe"
372 )
373 ) =~ s/Transaction aborted: (?=Transaction aborted:)//;
374 }
375 }
ddcc02d1 376 };
84efb6d7 377
378 return $rbe;
379}
380
adb3554a 381=head2 svp_begin
382
360dc8a5 383Arguments: $savepoint_name?
adb3554a 384
360dc8a5 385Created a new savepoint using the name provided as argument. If no name
386is provided, a random name will be used.
adb3554a 387
388=cut
389
90d7422f 390sub svp_begin {
391 my ($self, $name) = @_;
392
393 $self->throw_exception ("You can't use savepoints outside a transaction")
394 unless $self->transaction_depth;
395
396 my $exec = $self->can('_exec_svp_begin')
397 or $self->throw_exception ("Your Storage implementation doesn't support savepoints");
398
399 $name = $self->_svp_generate_name
400 unless defined $name;
401
402 push @{ $self->{savepoints} }, $name;
403
404 $self->debugobj->svp_begin($name) if $self->debug;
405
406 $exec->($self, $name);
407}
408
409sub _svp_generate_name {
410 my ($self) = @_;
411 return 'savepoint_'.scalar(@{ $self->{'savepoints'} });
412}
413
adb3554a 414
415=head2 svp_release
416
360dc8a5 417Arguments: $savepoint_name?
adb3554a 418
360dc8a5 419Release the savepoint provided as argument. If none is provided,
420release the savepoint created most recently. This will implicitly
421release all savepoints created after the one explicitly released as well.
adb3554a 422
423=cut
424
90d7422f 425sub svp_release {
426 my ($self, $name) = @_;
427
428 $self->throw_exception ("You can't use savepoints outside a transaction")
429 unless $self->transaction_depth;
430
431 my $exec = $self->can('_exec_svp_release')
432 or $self->throw_exception ("Your Storage implementation doesn't support savepoints");
433
434 if (defined $name) {
435 my @stack = @{ $self->savepoints };
f5f0cb1d 436 my $svp = '';
90d7422f 437
f5f0cb1d 438 while( $svp ne $name ) {
90d7422f 439
f5f0cb1d 440 $self->throw_exception ("Savepoint '$name' does not exist")
441 unless @stack;
442
443 $svp = pop @stack;
444 }
90d7422f 445
446 $self->savepoints(\@stack); # put back what's left
447 }
448 else {
449 $name = pop @{ $self->savepoints }
450 or $self->throw_exception('No savepoints to release');;
451 }
452
453 $self->debugobj->svp_release($name) if $self->debug;
454
455 $exec->($self, $name);
456}
adb3554a 457
458=head2 svp_rollback
459
360dc8a5 460Arguments: $savepoint_name?
adb3554a 461
360dc8a5 462Rollback to the savepoint provided as argument. If none is provided,
463rollback to the savepoint created most recently. This will implicitly
464release all savepoints created after the savepoint we rollback to.
adb3554a 465
466=cut
467
90d7422f 468sub svp_rollback {
469 my ($self, $name) = @_;
470
471 $self->throw_exception ("You can't use savepoints outside a transaction")
472 unless $self->transaction_depth;
473
474 my $exec = $self->can('_exec_svp_rollback')
475 or $self->throw_exception ("Your Storage implementation doesn't support savepoints");
476
477 if (defined $name) {
478 my @stack = @{ $self->savepoints };
479 my $svp;
480
481 # a rollback doesn't remove the named savepoint,
482 # only everything after it
483 while (@stack and $stack[-1] ne $name) {
484 pop @stack
485 };
486
487 $self->throw_exception ("Savepoint '$name' does not exist")
488 unless @stack;
489
490 $self->savepoints(\@stack); # put back what's left
491 }
492 else {
493 $name = $self->savepoints->[-1]
494 or $self->throw_exception('No savepoints to rollback');;
495 }
496
497 $self->debugobj->svp_rollback($name) if $self->debug;
498
499 $exec->($self, $name);
500}
adb3554a 501
11544e1d 502=head2 txn_scope_guard
3b7f3eac 503
11544e1d 504An alternative way of transaction handling based on
505L<DBIx::Class::Storage::TxnScopeGuard>:
1bc193ac 506
11544e1d 507 my $txn_guard = $storage->txn_scope_guard;
89028f42 508
47d7b769 509 $result->col1("val1");
510 $result->update;
89028f42 511
11544e1d 512 $txn_guard->commit;
89028f42 513
11544e1d 514If an exception occurs, or the guard object otherwise leaves the scope
515before C<< $txn_guard->commit >> is called, the transaction will be rolled
516back by an explicit L</txn_rollback> call. In essence this is akin to
517using a L</txn_begin>/L</txn_commit> pair, without having to worry
518about calling L</txn_rollback> at the right places. Note that since there
519is no defined code closure, there will be no retries and other magic upon
520database disconnection. If you need such functionality see L</txn_do>.
1bc193ac 521
522=cut
523
524sub txn_scope_guard {
525 return DBIx::Class::Storage::TxnScopeGuard->new($_[0]);
526}
527
046ad905 528=head2 sql_maker
529
530Returns a C<sql_maker> object - normally an object of class
d5dedbd6 531C<DBIx::Class::SQLMaker>.
046ad905 532
533=cut
534
535sub sql_maker { die "Virtual method!" }
536
537=head2 debug
538
f92a9d79 539Causes trace information to be emitted on the L</debugobj> object.
540(or C<STDERR> if L</debugobj> has not specifically been set).
046ad905 541
542This is the equivalent to setting L</DBIC_TRACE> in your
543shell environment.
544
545=head2 debugfh
546
4d93345c 547An opportunistic proxy to L<< ->debugobj->debugfh(@_)
548|DBIx::Class::Storage::Statistics/debugfh >>
549If the currently set L</debugobj> does not have a L</debugfh> method, caling
550this is a no-op.
046ad905 551
552=cut
553
554sub debugfh {
555 my $self = shift;
556
557 if ($self->debugobj->can('debugfh')) {
558 return $self->debugobj->debugfh(@_);
559 }
560}
561
562=head2 debugobj
563
564Sets or retrieves the object used for metric collection. Defaults to an instance
565of L<DBIx::Class::Storage::Statistics> that is compatible with the original
566method of using a coderef as a callback. See the aforementioned Statistics
567class for more information.
568
4d753fb8 569=cut
570
571sub debugobj {
572 my $self = shift;
573
574 if (@_) {
575 return $self->{debugobj} = $_[0];
576 }
577
578 $self->{debugobj} ||= do {
579 if (my $profile = $ENV{DBIC_TRACE_PROFILE}) {
580 require DBIx::Class::Storage::Debug::PrettyPrint;
6e102c8f 581 my @pp_args;
582
4d753fb8 583 if ($profile =~ /^\.?\//) {
0dd1b736 584
18a2903b 585 require DBIx::Class::Optional::Dependencies;
0dd1b736 586 if ( my $missing = DBIx::Class::Optional::Dependencies->req_missing_for ('config_file_reader') ) {
587 $self->throw_exception("Unable to parse TRACE_PROFILE config file '$profile' without $missing");
588 }
4d753fb8 589
ddcc02d1 590 my $cfg = dbic_internal_try {
4d753fb8 591 Config::Any->load_files({ files => [$profile], use_ext => 1 });
e2741c7f 592 } dbic_internal_catch {
4d753fb8 593 # sanitize the error message a bit
594 $_ =~ s/at \s+ .+ Storage\.pm \s line \s \d+ $//x;
595 $self->throw_exception("Failure processing \$ENV{DBIC_TRACE_PROFILE}: $_");
596 };
597
6e102c8f 598 @pp_args = values %{$cfg->[0]};
4d753fb8 599 }
600 else {
6e102c8f 601 @pp_args = { profile => $profile };
602 }
603
604 # FIXME - FRAGILE
605 # Hash::Merge is a sorry piece of shit and tramples all over $@
606 # *without* throwing an exception
607 # This is a rather serious problem in the debug codepath
608 # Insulate the condition here with a try{} until a review of
609 # DBIx::Class::Storage::Debug::PrettyPrint takes place
610 # we do rethrow the error unconditionally, the only reason
611 # to try{} is to preserve the precise state of $@ (down
612 # to the scalar (if there is one) address level)
613 #
614 # Yes I am aware this is fragile and TxnScopeGuard needs
615 # a better fix. This is another yak to shave... :(
ddcc02d1 616 dbic_internal_try {
6e102c8f 617 DBIx::Class::Storage::Debug::PrettyPrint->new(@pp_args);
e2741c7f 618 } dbic_internal_catch {
6e102c8f 619 $self->throw_exception($_);
4d753fb8 620 }
621 }
622 else {
623 require DBIx::Class::Storage::Statistics;
624 DBIx::Class::Storage::Statistics->new
625 }
626 };
627}
628
046ad905 629=head2 debugcb
630
631Sets a callback to be executed each time a statement is run; takes a sub
632reference. Callback is executed as $sub->($op, $info) where $op is
633SELECT/INSERT/UPDATE/DELETE and $info is what would normally be printed.
634
f92a9d79 635See L</debugobj> for a better way.
046ad905 636
637=cut
638
639sub debugcb {
640 my $self = shift;
641
642 if ($self->debugobj->can('callback')) {
643 return $self->debugobj->callback(@_);
644 }
645}
646
e4eb8ee1 647=head2 cursor_class
046ad905 648
649The cursor class for this Storage object.
650
651=cut
652
046ad905 653=head2 deploy
654
655Deploy the tables to storage (CREATE TABLE and friends in a SQL-based
656Storage class). This would normally be called through
657L<DBIx::Class::Schema/deploy>.
658
659=cut
660
661sub deploy { die "Virtual method!" }
662
a3eaff0e 663=head2 connect_info
664
665The arguments of C<connect_info> are always a single array reference,
666and are Storage-handler specific.
667
668This is normally accessed via L<DBIx::Class::Schema/connection>, which
669encapsulates its argument list in an arrayref before calling
670C<connect_info> here.
671
672=cut
673
046ad905 674sub connect_info { die "Virtual method!" }
a3eaff0e 675
676=head2 select
677
678Handle a select statement.
679
680=cut
681
682sub select { die "Virtual method!" }
683
684=head2 insert
685
686Handle an insert statement.
687
688=cut
689
046ad905 690sub insert { die "Virtual method!" }
a3eaff0e 691
692=head2 update
693
694Handle an update statement.
695
696=cut
697
046ad905 698sub update { die "Virtual method!" }
a3eaff0e 699
700=head2 delete
701
702Handle a delete statement.
703
704=cut
705
046ad905 706sub delete { die "Virtual method!" }
a3eaff0e 707
708=head2 select_single
709
710Performs a select, fetch and return of data - handles a single row
711only.
712
713=cut
714
046ad905 715sub select_single { die "Virtual method!" }
a3eaff0e 716
717=head2 columns_info_for
718
c22c7625 719Returns metadata for the given source's columns. This
720is *deprecated*, and will be removed before 1.0. You should
721be specifying the metadata yourself if you need it.
a3eaff0e 722
723=cut
724
046ad905 725sub columns_info_for { die "Virtual method!" }
726
727=head1 ENVIRONMENT VARIABLES
728
729=head2 DBIC_TRACE
730
731If C<DBIC_TRACE> is set then trace information
f92a9d79 732is produced (as when the L</debug> method is set).
046ad905 733
734If the value is of the form C<1=/path/name> then the trace output is
735written to the file C</path/name>.
736
737This environment variable is checked when the storage object is first
fd323bf1 738created (when you call connect on your schema). So, run-time changes
739to this environment variable will not take effect unless you also
046ad905 740re-connect on your schema.
741
b6cd6478 742=head2 DBIC_TRACE_PROFILE
743
2514a73f 744If C<DBIC_TRACE_PROFILE> is set, L<DBIx::Class::Storage::Debug::PrettyPrint>
b6cd6478 745will be used to format the output from C<DBIC_TRACE>. The value it
746is set to is the C<profile> that it will be used. If the value is a
747filename the file is read with L<Config::Any> and the results are
748used as the configuration for tracing. See L<SQL::Abstract::Tree/new>
749for what that structure should look like.
750
046ad905 751=head2 DBIX_CLASS_STORAGE_DBI_DEBUG
752
753Old name for DBIC_TRACE
754
ace385bd 755=head1 SEE ALSO
756
2f0790c4 757L<DBIx::Class::Storage::DBI> - reference storage implementation using
758SQL::Abstract and DBI.
ace385bd 759
a2bd3796 760=head1 FURTHER QUESTIONS?
046ad905 761
a2bd3796 762Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
046ad905 763
a2bd3796 764=head1 COPYRIGHT AND LICENSE
046ad905 765
a2bd3796 766This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
767by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
768redistribute it and/or modify it under the same terms as the
769L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.
046ad905 770
771=cut
772
a62cf8d4 7731;