::DBI:Replicated - merge connect_info from master to replicants
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Replicated.pm
CommitLineData
2156bbdd 1package DBIx::Class::Storage::DBI::Replicated;
f5d3a5de 2
ecb65397 3BEGIN {
4 use Carp::Clan qw/^DBIx::Class/;
5
6 ## Modules required for Replication support not required for general DBIC
7 ## use, so we explicitly test for these.
8
9 my %replication_required = (
9901aad7 10 Moose => '0.77',
ecb65397 11 MooseX::AttributeHelpers => '0.12',
9901aad7 12 MooseX::Types => '0.10',
13 namespace::clean => '0.11',
ecb65397 14 );
15
16 my @didnt_load;
17
18 for my $module (keys %replication_required) {
19 eval "use $module $replication_required{$module}";
20 push @didnt_load, "$module $replication_required{$module}"
21 if $@;
22 }
23
24 croak("@{[ join ', ', @didnt_load ]} are missing and are required for Replication")
25 if @didnt_load;
26}
27
b2e4d522 28use Moose;
26ab719a 29use DBIx::Class::Storage::DBI;
2bf79155 30use DBIx::Class::Storage::DBI::Replicated::Pool;
26ab719a 31use DBIx::Class::Storage::DBI::Replicated::Balancer;
9901aad7 32use DBIx::Class::Storage::DBI::Replicated::Types 'BalancerClassNamePart';
41916570 33use MooseX::Types::Moose qw/ClassName HashRef Object/;
b2e4d522 34use Scalar::Util 'reftype';
35use Carp::Clan qw/^DBIx::Class/;
9901aad7 36
37use namespace::clean -except => 'meta';
2bf79155 38
39=head1 NAME
40
ecb65397 41DBIx::Class::Storage::DBI::Replicated - BETA Replicated database support
2bf79155 42
43=head1 SYNOPSIS
44
45The Following example shows how to change an existing $schema to a replicated
46storage type, add some replicated (readonly) databases, and perform reporting
955a6df6 47tasks.
2bf79155 48
64cdad22 49 ## Change storage_type in your schema class
50 $schema->storage_type( ['::DBI::Replicated', {balancer=>'::Random'}] );
51
52 ## Add some slaves. Basically this is an array of arrayrefs, where each
53 ## arrayref is database connect information
54
55 $schema->storage->connect_replicants(
56 [$dsn1, $user, $pass, \%opts],
57 [$dsn2, $user, $pass, \%opts],
58 [$dsn3, $user, $pass, \%opts],
59 );
60
7e38d850 61 ## Now, just use the $schema as normal
62 $schema->resultset('Source')->search({name=>'etc'});
63
64 ## You can force a given query to use a particular storage using the search
65 ### attribute 'force_pool'. For example:
66
67 my $RS = $schema->resultset('Source')->search(undef, {force_pool=>'master'});
68
69 ## Now $RS will force everything (both reads and writes) to use whatever was
70 ## setup as the master storage. 'master' is hardcoded to always point to the
71 ## Master, but you can also use any Replicant name. Please see:
72 ## L<DBIx::Class::Storage::Replicated::Pool> and the replicants attribute for
73 ## More. Also see transactions and L</execute_reliably> for alternative ways
74 ## to force read traffic to the master.
75
2bf79155 76=head1 DESCRIPTION
77
7e38d850 78Warning: This class is marked BETA. This has been running a production
ccb3b897 79website using MySQL native replication as its backend and we have some decent
7e38d850 80test coverage but the code hasn't yet been stressed by a variety of databases.
81Individual DB's may have quirks we are not aware of. Please use this in first
82development and pass along your experiences/bug fixes.
2bf79155 83
84This class implements replicated data store for DBI. Currently you can define
85one master and numerous slave database connections. All write-type queries
86(INSERT, UPDATE, DELETE and even LAST_INSERT_ID) are routed to master
87database, all read-type queries (SELECTs) go to the slave database.
88
89Basically, any method request that L<DBIx::Class::Storage::DBI> would normally
bca099a3 90handle gets delegated to one of the two attributes: L</read_handler> or to
91L</write_handler>. Additionally, some methods need to be distributed
2bf79155 92to all existing storages. This way our storage class is a drop in replacement
93for L<DBIx::Class::Storage::DBI>.
94
95Read traffic is spread across the replicants (slaves) occuring to a user
96selected algorithm. The default algorithm is random weighted.
97
bca099a3 98=head1 NOTES
99
100The consistancy betweeen master and replicants is database specific. The Pool
101gives you a method to validate it's replicants, removing and replacing them
7e38d850 102when they fail/pass predefined criteria. Please make careful use of the ways
ecb65397 103to force a query to run against Master when needed.
104
105=head1 REQUIREMENTS
106
107Replicated Storage has additional requirements not currently part of L<DBIx::Class>
108
9901aad7 109 Moose => 0.77
ecb65397 110 MooseX::AttributeHelpers => 0.12
9901aad7 111 MooseX::Types => 0.10
112 namespace::clean => 0.11
ecb65397 113
114You will need to install these modules manually via CPAN or make them part of the
115Makefile for your distribution.
2bf79155 116
117=head1 ATTRIBUTES
118
119This class defines the following attributes.
120
2ce6e9a6 121=head2 schema
122
123The underlying L<DBIx::Class::Schema> object this storage is attaching
124
125=cut
126
127has 'schema' => (
128 is=>'rw',
129 isa=>'DBIx::Class::Schema',
130 weak_ref=>1,
131 required=>1,
132);
133
26ab719a 134=head2 pool_type
2bf79155 135
26ab719a 136Contains the classname which will instantiate the L</pool> object. Defaults
137to: L<DBIx::Class::Storage::DBI::Replicated::Pool>.
2bf79155 138
139=cut
140
26ab719a 141has 'pool_type' => (
64cdad22 142 is=>'ro',
41916570 143 isa=>ClassName,
2ce6e9a6 144 required=>1,
145 default=>'DBIx::Class::Storage::DBI::Replicated::Pool',
64cdad22 146 handles=>{
147 'create_pool' => 'new',
148 },
2bf79155 149);
150
f068a139 151=head2 pool_args
152
153Contains a hashref of initialized information to pass to the Balancer object.
154See L<DBIx::Class::Storage::Replicated::Pool> for available arguments.
155
156=cut
157
158has 'pool_args' => (
64cdad22 159 is=>'ro',
41916570 160 isa=>HashRef,
64cdad22 161 lazy=>1,
162 required=>1,
163 default=>sub { {} },
f068a139 164);
165
166
26ab719a 167=head2 balancer_type
2bf79155 168
169The replication pool requires a balance class to provider the methods for
170choose how to spread the query load across each replicant in the pool.
171
172=cut
173
26ab719a 174has 'balancer_type' => (
64cdad22 175 is=>'ro',
9901aad7 176 isa=>BalancerClassNamePart,
2ce6e9a6 177 coerce=>1,
178 required=>1,
179 default=> 'DBIx::Class::Storage::DBI::Replicated::Balancer::First',
64cdad22 180 handles=>{
181 'create_balancer' => 'new',
182 },
2bf79155 183);
184
17b05c13 185=head2 balancer_args
186
187Contains a hashref of initialized information to pass to the Balancer object.
f068a139 188See L<DBIx::Class::Storage::Replicated::Balancer> for available arguments.
17b05c13 189
190=cut
191
192has 'balancer_args' => (
64cdad22 193 is=>'ro',
41916570 194 isa=>HashRef,
64cdad22 195 lazy=>1,
196 required=>1,
197 default=>sub { {} },
17b05c13 198);
199
26ab719a 200=head2 pool
2bf79155 201
26ab719a 202Is a <DBIx::Class::Storage::DBI::Replicated::Pool> or derived class. This is a
203container class for one or more replicated databases.
2bf79155 204
205=cut
206
26ab719a 207has 'pool' => (
64cdad22 208 is=>'ro',
209 isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
210 lazy_build=>1,
211 handles=>[qw/
212 connect_replicants
213 replicants
214 has_replicants
215 /],
2bf79155 216);
217
26ab719a 218=head2 balancer
2bf79155 219
26ab719a 220Is a <DBIx::Class::Storage::DBI::Replicated::Balancer> or derived class. This
221is a class that takes a pool (<DBIx::Class::Storage::DBI::Replicated::Pool>)
2bf79155 222
26ab719a 223=cut
2bf79155 224
26ab719a 225has 'balancer' => (
64cdad22 226 is=>'ro',
227 isa=>'DBIx::Class::Storage::DBI::Replicated::Balancer',
228 lazy_build=>1,
229 handles=>[qw/auto_validate_every/],
26ab719a 230);
2bf79155 231
cb6ec758 232=head2 master
233
234The master defines the canonical state for a pool of connected databases. All
235the replicants are expected to match this databases state. Thus, in a classic
236Master / Slaves distributed system, all the slaves are expected to replicate
237the Master's state as quick as possible. This is the only database in the
238pool of databases that is allowed to handle write traffic.
239
240=cut
241
242has 'master' => (
64cdad22 243 is=> 'ro',
244 isa=>'DBIx::Class::Storage::DBI',
245 lazy_build=>1,
cb6ec758 246);
247
cb6ec758 248=head1 ATTRIBUTES IMPLEMENTING THE DBIx::Storage::DBI INTERFACE
249
250The following methods are delegated all the methods required for the
251L<DBIx::Class::Storage::DBI> interface.
252
253=head2 read_handler
254
255Defines an object that implements the read side of L<BIx::Class::Storage::DBI>.
256
257=cut
258
259has 'read_handler' => (
64cdad22 260 is=>'rw',
41916570 261 isa=>Object,
64cdad22 262 lazy_build=>1,
263 handles=>[qw/
264 select
265 select_single
266 columns_info_for
267 /],
cb6ec758 268);
269
cb6ec758 270=head2 write_handler
271
272Defines an object that implements the write side of L<BIx::Class::Storage::DBI>.
273
274=cut
275
276has 'write_handler' => (
64cdad22 277 is=>'ro',
41916570 278 isa=>Object,
64cdad22 279 lazy_build=>1,
280 lazy_build=>1,
281 handles=>[qw/
282 on_connect_do
283 on_disconnect_do
284 connect_info
285 throw_exception
286 sql_maker
287 sqlt_type
288 create_ddl_dir
289 deployment_statements
290 datetime_parser
291 datetime_parser_type
292 last_insert_id
293 insert
294 insert_bulk
295 update
296 delete
297 dbh
2ce6e9a6 298 txn_begin
64cdad22 299 txn_do
300 txn_commit
301 txn_rollback
2ce6e9a6 302 txn_scope_guard
64cdad22 303 sth
304 deploy
0180bef9 305 with_deferred_fk_checks
2ce6e9a6 306
64cdad22 307 reload_row
2ce6e9a6 308 _prep_for_execute
2ce6e9a6 309
64cdad22 310 /],
cb6ec758 311);
312
b2e4d522 313has _master_connect_info_opts =>
314 (is => 'rw', isa => HashRef, default => sub { {} });
315
316=head2 around: connect_info
317
318Preserve master's C<connect_info> options (for merging with replicants.)
319
320=cut
321
322around connect_info => sub {
323 my ($next, $self, $info, @extra) = @_;
324
325 my %opts;
326 for my $arg (@$info) {
327 next unless (reftype($arg)||'') eq 'HASH';
328 %opts = (%opts, %$arg);
329 }
330
331 delete $opts{dsn};
332
333 $self->_master_connect_info_opts(\%opts);
334
335 $self->$next($info, @extra);
336};
337
26ab719a 338=head1 METHODS
2bf79155 339
26ab719a 340This class defines the following methods.
2bf79155 341
c354902c 342=head2 BUILDARGS
2bf79155 343
cb6ec758 344L<DBIx::Class::Schema> when instantiating it's storage passed itself as the
2ce6e9a6 345first argument. So we need to massage the arguments a bit so that all the
346bits get put into the correct places.
2bf79155 347
348=cut
349
c354902c 350sub BUILDARGS {
351 my ($class, $schema, $storage_type_args, @args) = @_;
352
353 return {
354 schema=>$schema,
355 %$storage_type_args,
356 @args
357 }
358}
2bf79155 359
cb6ec758 360=head2 _build_master
2bf79155 361
cb6ec758 362Lazy builder for the L</master> attribute.
2bf79155 363
364=cut
365
cb6ec758 366sub _build_master {
2ce6e9a6 367 my $self = shift @_;
368 DBIx::Class::Storage::DBI->new($self->schema);
106d5f3b 369}
370
26ab719a 371=head2 _build_pool
2bf79155 372
26ab719a 373Lazy builder for the L</pool> attribute.
2bf79155 374
375=cut
376
26ab719a 377sub _build_pool {
64cdad22 378 my $self = shift @_;
379 $self->create_pool(%{$self->pool_args});
2bf79155 380}
381
26ab719a 382=head2 _build_balancer
2bf79155 383
cb6ec758 384Lazy builder for the L</balancer> attribute. This takes a Pool object so that
385the balancer knows which pool it's balancing.
2bf79155 386
387=cut
388
26ab719a 389sub _build_balancer {
64cdad22 390 my $self = shift @_;
391 $self->create_balancer(
392 pool=>$self->pool,
393 master=>$self->master,
394 %{$self->balancer_args},
395 );
2bf79155 396}
397
cb6ec758 398=head2 _build_write_handler
2bf79155 399
cb6ec758 400Lazy builder for the L</write_handler> attribute. The default is to set this to
401the L</master>.
50336325 402
403=cut
404
cb6ec758 405sub _build_write_handler {
64cdad22 406 return shift->master;
cb6ec758 407}
50336325 408
cb6ec758 409=head2 _build_read_handler
2bf79155 410
cb6ec758 411Lazy builder for the L</read_handler> attribute. The default is to set this to
412the L</balancer>.
2bf79155 413
414=cut
415
cb6ec758 416sub _build_read_handler {
64cdad22 417 return shift->balancer;
cb6ec758 418}
50336325 419
cb6ec758 420=head2 around: connect_replicants
2bf79155 421
cb6ec758 422All calls to connect_replicants needs to have an existing $schema tacked onto
b2e4d522 423top of the args, since L<DBIx::Storage::DBI> needs it, and any C<connect_info>
424options merged with the master, with replicant opts having higher priority.
955a6df6 425
cb6ec758 426=cut
955a6df6 427
b2e4d522 428around connect_replicants => sub {
429 my ($next, $self, @args) = @_;
430
431 for my $r (@args) {
432 $r = [ $r ] unless reftype $r eq 'ARRAY';
433
434 croak "coderef replicant connect_info not supported"
435 if ref $r->[0] && reftype $r->[0] eq 'CODE';
436
437# any connect_info options?
438 my $i = 0;
439 $i++ while $i < @$r && (reftype($r->[$i])||'') ne 'HASH';
440
441# make one if none
442 $r->[$i] = {} unless $r->[$i];
443
444# merge if two hashes
445 my %opts = map %$_, @$r[$i .. $#{$r}];
446 splice @$r, $i+1, ($#{$r} - $i), ();
447
448# merge with master
449 %opts = (%{ $self->_master_connect_info_opts }, %opts);
450
451# update
452 $r->[$i] = \%opts;
453 }
454
455 $self->$next($self->schema, @args);
955a6df6 456};
2bf79155 457
2bf79155 458=head2 all_storages
459
460Returns an array of of all the connected storage backends. The first element
461in the returned array is the master, and the remainings are each of the
462replicants.
463
464=cut
465
466sub all_storages {
64cdad22 467 my $self = shift @_;
468 return grep {defined $_ && blessed $_} (
469 $self->master,
6412a592 470 values %{ $self->replicants },
64cdad22 471 );
2bf79155 472}
473
c4d3fae2 474=head2 execute_reliably ($coderef, ?@args)
475
476Given a coderef, saves the current state of the L</read_handler>, forces it to
477use reliable storage (ie sets it to the master), executes a coderef and then
478restores the original state.
479
480Example:
481
64cdad22 482 my $reliably = sub {
483 my $name = shift @_;
484 $schema->resultset('User')->create({name=>$name});
485 my $user_rs = $schema->resultset('User')->find({name=>$name});
486 return $user_rs;
487 };
c4d3fae2 488
64cdad22 489 my $user_rs = $schema->storage->execute_reliably($reliably, 'John');
c4d3fae2 490
491Use this when you must be certain of your database state, such as when you just
492inserted something and need to get a resultset including it, etc.
493
494=cut
495
496sub execute_reliably {
64cdad22 497 my ($self, $coderef, @args) = @_;
498
499 unless( ref $coderef eq 'CODE') {
500 $self->throw_exception('Second argument must be a coderef');
501 }
502
503 ##Get copy of master storage
504 my $master = $self->master;
505
506 ##Get whatever the current read hander is
507 my $current = $self->read_handler;
508
509 ##Set the read handler to master
510 $self->read_handler($master);
511
512 ## do whatever the caller needs
513 my @result;
514 my $want_array = wantarray;
515
516 eval {
517 if($want_array) {
518 @result = $coderef->(@args);
519 } elsif(defined $want_array) {
520 ($result[0]) = ($coderef->(@args));
ed213e85 521 } else {
64cdad22 522 $coderef->(@args);
523 }
524 };
525
526 ##Reset to the original state
527 $self->read_handler($current);
528
529 ##Exception testing has to come last, otherwise you might leave the
530 ##read_handler set to master.
531
532 if($@) {
533 $self->throw_exception("coderef returned an error: $@");
534 } else {
535 return $want_array ? @result : $result[0];
536 }
c4d3fae2 537}
538
cb6ec758 539=head2 set_reliable_storage
540
541Sets the current $schema to be 'reliable', that is all queries, both read and
542write are sent to the master
64cdad22 543
cb6ec758 544=cut
545
546sub set_reliable_storage {
64cdad22 547 my $self = shift @_;
548 my $schema = $self->schema;
549 my $write_handler = $self->schema->storage->write_handler;
550
551 $schema->storage->read_handler($write_handler);
cb6ec758 552}
553
554=head2 set_balanced_storage
555
556Sets the current $schema to be use the </balancer> for all reads, while all
557writea are sent to the master only
64cdad22 558
cb6ec758 559=cut
560
561sub set_balanced_storage {
64cdad22 562 my $self = shift @_;
563 my $schema = $self->schema;
564 my $write_handler = $self->schema->storage->balancer;
565
566 $schema->storage->read_handler($write_handler);
cb6ec758 567}
2bf79155 568
6834cc1d 569=head2 around: txn_do ($coderef)
c4d3fae2 570
571Overload to the txn_do method, which is delegated to whatever the
572L<write_handler> is set to. We overload this in order to wrap in inside a
573L</execute_reliably> method.
574
575=cut
576
6834cc1d 577around 'txn_do' => sub {
64cdad22 578 my($txn_do, $self, $coderef, @args) = @_;
579 $self->execute_reliably(sub {$self->$txn_do($coderef, @args)});
6834cc1d 580};
c4d3fae2 581
2bf79155 582=head2 connected
583
584Check that the master and at least one of the replicants is connected.
585
586=cut
587
588sub connected {
64cdad22 589 my $self = shift @_;
590 return
591 $self->master->connected &&
592 $self->pool->connected_replicants;
2bf79155 593}
594
2bf79155 595=head2 ensure_connected
596
597Make sure all the storages are connected.
598
599=cut
600
601sub ensure_connected {
64cdad22 602 my $self = shift @_;
603 foreach my $source ($self->all_storages) {
604 $source->ensure_connected(@_);
605 }
2bf79155 606}
607
2bf79155 608=head2 limit_dialect
609
610Set the limit_dialect for all existing storages
611
612=cut
613
614sub limit_dialect {
64cdad22 615 my $self = shift @_;
616 foreach my $source ($self->all_storages) {
617 $source->limit_dialect(@_);
618 }
3fbe08e3 619 return $self->master->quote_char;
2bf79155 620}
621
2bf79155 622=head2 quote_char
623
624Set the quote_char for all existing storages
625
626=cut
627
628sub quote_char {
64cdad22 629 my $self = shift @_;
630 foreach my $source ($self->all_storages) {
631 $source->quote_char(@_);
632 }
3fbe08e3 633 return $self->master->quote_char;
2bf79155 634}
635
2bf79155 636=head2 name_sep
637
638Set the name_sep for all existing storages
639
640=cut
641
642sub name_sep {
64cdad22 643 my $self = shift @_;
644 foreach my $source ($self->all_storages) {
645 $source->name_sep(@_);
646 }
3fbe08e3 647 return $self->master->name_sep;
2bf79155 648}
649
2bf79155 650=head2 set_schema
651
652Set the schema object for all existing storages
653
654=cut
655
656sub set_schema {
64cdad22 657 my $self = shift @_;
658 foreach my $source ($self->all_storages) {
659 $source->set_schema(@_);
660 }
2bf79155 661}
662
2bf79155 663=head2 debug
664
665set a debug flag across all storages
666
667=cut
668
669sub debug {
64cdad22 670 my $self = shift @_;
3fbe08e3 671 if(@_) {
672 foreach my $source ($self->all_storages) {
673 $source->debug(@_);
674 }
64cdad22 675 }
3fbe08e3 676 return $self->master->debug;
2bf79155 677}
678
2bf79155 679=head2 debugobj
680
681set a debug object across all storages
682
683=cut
684
685sub debugobj {
64cdad22 686 my $self = shift @_;
3fbe08e3 687 if(@_) {
688 foreach my $source ($self->all_storages) {
689 $source->debugobj(@_);
690 }
64cdad22 691 }
3fbe08e3 692 return $self->master->debugobj;
2bf79155 693}
694
2bf79155 695=head2 debugfh
696
697set a debugfh object across all storages
698
699=cut
700
701sub debugfh {
64cdad22 702 my $self = shift @_;
3fbe08e3 703 if(@_) {
704 foreach my $source ($self->all_storages) {
705 $source->debugfh(@_);
706 }
64cdad22 707 }
3fbe08e3 708 return $self->master->debugfh;
2bf79155 709}
710
2bf79155 711=head2 debugcb
712
713set a debug callback across all storages
714
715=cut
716
717sub debugcb {
64cdad22 718 my $self = shift @_;
3fbe08e3 719 if(@_) {
720 foreach my $source ($self->all_storages) {
721 $source->debugcb(@_);
722 }
64cdad22 723 }
3fbe08e3 724 return $self->master->debugcb;
2bf79155 725}
726
2bf79155 727=head2 disconnect
728
729disconnect everything
730
731=cut
732
733sub disconnect {
64cdad22 734 my $self = shift @_;
735 foreach my $source ($self->all_storages) {
736 $source->disconnect(@_);
737 }
2bf79155 738}
739
b2e4d522 740=head2 cursor_class
741
742set cursor class on all storages, or return master's
743
744=cut
745
746sub cursor_class {
747 my ($self, $cursor_class) = @_;
748
749 if ($cursor_class) {
750 $_->cursor_class($cursor_class) for $self->all_storages;
751 }
752 $self->master->cursor_class;
753}
754
7e38d850 755=head1 GOTCHAS
756
757Due to the fact that replicants can lag behind a master, you must take care to
758make sure you use one of the methods to force read queries to a master should
759you need realtime data integrity. For example, if you insert a row, and then
760immediately re-read it from the database (say, by doing $row->discard_changes)
761or you insert a row and then immediately build a query that expects that row
762to be an item, you should force the master to handle reads. Otherwise, due to
763the lag, there is no certainty your data will be in the expected state.
764
765For data integrity, all transactions automatically use the master storage for
766all read and write queries. Using a transaction is the preferred and recommended
767method to force the master to handle all read queries.
768
769Otherwise, you can force a single query to use the master with the 'force_pool'
770attribute:
771
772 my $row = $resultset->search(undef, {force_pool=>'master'})->find($pk);
773
774This attribute will safely be ignore by non replicated storages, so you can use
775the same code for both types of systems.
776
777Lastly, you can use the L</execute_reliably> method, which works very much like
778a transaction.
779
780For debugging, you can turn replication on/off with the methods L</set_reliable_storage>
781and L</set_balanced_storage>, however this operates at a global level and is not
782suitable if you have a shared Schema object being used by multiple processes,
783such as on a web application server. You can get around this limitation by
784using the Schema clone method.
785
786 my $new_schema = $schema->clone;
787 $new_schema->set_reliable_storage;
788
789 ## $new_schema will use only the Master storage for all reads/writes while
790 ## the $schema object will use replicated storage.
791
f5d3a5de 792=head1 AUTHOR
793
64cdad22 794 John Napiorkowski <john.napiorkowski@takkle.com>
f5d3a5de 795
c4d3fae2 796Based on code originated by:
f5d3a5de 797
64cdad22 798 Norbert Csongrádi <bert@cpan.org>
799 Peter Siklósi <einon@einon.hu>
2156bbdd 800
f5d3a5de 801=head1 LICENSE
802
803You may distribute this code under the same terms as Perl itself.
804
805=cut
806
c354902c 807__PACKAGE__->meta->make_immutable;
808
f5d3a5de 8091;