]> Dogcows Code - chaz/p5-DBIx-Class-ResultSet-RecursiveUpdate/blob - lib/DBIx/Class/ResultSet/RecursiveUpdate.pm
allow undef for has_many rels in addition to empty arrayref + tests
[chaz/p5-DBIx-Class-ResultSet-RecursiveUpdate] / lib / DBIx / Class / ResultSet / RecursiveUpdate.pm
1 use strict;
2 use warnings;
3
4 package DBIx::Class::ResultSet::RecursiveUpdate;
5
6 our $VERSION = '0.013';
7
8 use base qw(DBIx::Class::ResultSet);
9
10 sub recursive_update {
11 my ( $self, $updates, $fixed_fields ) = @_;
12 return
13 DBIx::Class::ResultSet::RecursiveUpdate::Functions::recursive_update(
14 resultset => $self,
15 updates => $updates,
16 fixed_fields => $fixed_fields
17 );
18 }
19
20 package DBIx::Class::ResultSet::RecursiveUpdate::Functions;
21 use Carp;
22 use Scalar::Util qw( blessed );
23 use List::MoreUtils qw/ any /;
24
25 sub recursive_update {
26 my %params = @_;
27 my ( $self, $updates, $fixed_fields, $object, $resolved,
28 $if_not_submitted )
29 = @params{
30 qw/resultset updates fixed_fields object resolved if_not_submitted/};
31 $resolved ||= {};
32
33 # warn 'entering: ' . $self->result_source->from();
34 carp 'fixed fields needs to be an array ref'
35 if $fixed_fields && ref($fixed_fields) ne 'ARRAY';
36 my %fixed_fields;
37 %fixed_fields = map { $_ => 1 } @$fixed_fields if $fixed_fields;
38 if ( blessed($updates) && $updates->isa('DBIx::Class::Row') ) {
39 return $updates;
40 }
41 if ( $updates->{id} ) {
42 $object = $self->find( $updates->{id}, { key => 'primary' } );
43 }
44 my @missing =
45 grep { !exists $updates->{$_} && !exists $fixed_fields{$_} }
46 $self->result_source->primary_columns;
47 if ( !$object && !scalar @missing ) {
48
49 # warn 'finding by: ' . Dumper( $updates ); use Data::Dumper;
50 $object = $self->find( $updates, { key => 'primary' } );
51 }
52 $updates = { %$updates, %$resolved };
53 @missing =
54 grep { !exists $resolved->{$_} } @missing;
55 if ( !$object && !scalar @missing ) {
56
57 # warn 'finding by +resolved: ' . Dumper( $updates ); use Data::Dumper;
58 $object = $self->find( $updates, { key => 'primary' } );
59 }
60 $object ||= $self->new( {} );
61
62 # warn Dumper( $updates ); use Data::Dumper;
63 # direct column accessors
64 my %columns;
65
66 # relations that that should be done before the row is inserted into the
67 # database like belongs_to
68 my %pre_updates;
69
70 # relations that that should be done after the row is inserted into the
71 # database like has_many, might_have and has_one
72 my %post_updates;
73 my %other_methods;
74 my %columns_by_accessor = _get_columns_by_accessor($self);
75
76 # warn 'resolved: ' . Dumper( $resolved );
77 # warn 'updates: ' . Dumper( $updates ); use Data::Dumper;
78 # warn 'columns: ' . Dumper( \%columns_by_accessor );
79 for my $name ( keys %$updates ) {
80 my $source = $self->result_source;
81
82 # columns
83 if ( exists $columns_by_accessor{$name}
84 && !( $source->has_relationship($name)
85 && ref( $updates->{$name} ) ) )
86 {
87
88 #warn "$name is a column\n";
89 $columns{$name} = $updates->{$name};
90 next;
91 }
92
93 # relationships
94 if ( $source->has_relationship($name) ) {
95 if ( _master_relation_cond( $self, $name ) ) {
96
97 #warn "$name is a pre-update rel\n";
98 $pre_updates{$name} = $updates->{$name};
99 next;
100 }
101 else {
102
103 #warn "$name is a post-update rel\n";
104 $post_updates{$name} = $updates->{$name};
105 next;
106 }
107 }
108
109 # many-to-many helper accessors
110 if ( is_m2m( $self, $name ) ) {
111
112 #warn "$name is a many-to-many helper accessor\n";
113 $other_methods{$name} = $updates->{$name};
114 next;
115 }
116
117 # accessors
118 if ( $object->can($name) && not $source->has_relationship($name) ) {
119
120 #warn "$name is an accessor";
121 $other_methods{$name} = $updates->{$name};
122 next;
123 }
124
125 # unknown
126 # TODO: don't throw a warning instead of an exception to give users
127 # time to adapt to the new API
128 $self->throw_exception(
129 "No such column, relationship, many-to-many helper accessor or generic accessor '$name'"
130 );
131 }
132
133 # warn 'other: ' . Dumper( \%other_methods ); use Data::Dumper;
134
135 # first update columns and other accessors
136 # so that later related records can be found
137 for my $name ( keys %columns ) {
138
139 #warn "update col $name\n";
140 $object->$name( $columns{$name} );
141 }
142 for my $name ( keys %other_methods ) {
143
144 #warn "update other $name\n";
145 $object->$name( $updates->{$name} );
146 }
147 for my $name ( keys %pre_updates ) {
148
149 #warn "pre_update $name\n";
150 _update_relation( $self, $name, $pre_updates{$name}, $object,
151 $if_not_submitted );
152 }
153
154 # $self->_delete_empty_auto_increment($object);
155 # don't allow insert to recurse to related objects
156 # do the recursion ourselves
157 # $object->{_rel_in_storage} = 1;
158 #warn "CHANGED: " . $object->is_changed . "\n":
159 #warn "IN STOR: " . $object->in_storage . "\n";
160 $object->update_or_insert if $object->is_changed;
161 $object->discard_changes;
162
163 # updating many_to_many
164 for my $name ( keys %$updates ) {
165 next if exists $columns{$name};
166 my $value = $updates->{$name};
167
168 if ( is_m2m( $self, $name ) ) {
169
170 #warn "update m2m $name\n";
171 my ($pk) = _get_pk_for_related( $self, $name );
172 my @rows;
173 my $result_source = $object->$name->result_source;
174 my @updates;
175 if ( !defined $value ) {
176 next;
177 }
178 elsif ( ref $value ) {
179 @updates = @{$value};
180 }
181 else {
182 @updates = ($value);
183 }
184 for my $elem (@updates) {
185 if ( ref $elem ) {
186 push @rows,
187 recursive_update(
188 resultset => $result_source->resultset,
189 updates => $elem
190 );
191 }
192 else {
193 push @rows,
194 $result_source->resultset->find( { $pk => $elem } );
195 }
196 }
197 my $set_meth = 'set_' . $name;
198 $object->$set_meth( \@rows );
199 }
200 }
201 for my $name ( keys %post_updates ) {
202
203 #warn "post_update $name\n";
204 _update_relation( $self, $name, $post_updates{$name}, $object,
205 $if_not_submitted );
206 }
207 return $object;
208 }
209
210 # returns DBIx::Class::ResultSource::column_info as a hash indexed by column accessor || name
211 sub _get_columns_by_accessor {
212 my $self = shift;
213 my $source = $self->result_source;
214 my %columns;
215 for my $name ( $source->columns ) {
216 my $info = $source->column_info($name);
217 $info->{name} = $name;
218 $columns{ $info->{accessor} || $name } = $info;
219 }
220 return %columns;
221 }
222
223 # Arguments: $rs, $name, $updates, $row, $if_not_submitted
224 sub _update_relation {
225 my ( $self, $name, $updates, $object, $if_not_submitted ) = @_;
226
227 # this should never happen because we're checking the paramters passed to
228 # recursive_update, but just to be sure...
229 $object->throw_exception("No such relationship '$name'")
230 unless $object->has_relationship($name);
231
232 #warn "_update_relation $name: OBJ: " . ref($object) . "\n";
233
234 my $info = $object->result_source->relationship_info($name);
235
236 # get a related resultset without a condition
237 my $related_resultset =
238 $self->related_resultset($name)->result_source->resultset;
239 my $resolved;
240 if ( $self->result_source->can('_resolve_condition') ) {
241 $resolved =
242 $self->result_source->_resolve_condition( $info->{cond}, $name,
243 $object );
244 }
245 else {
246 $self->throw_exception(
247 "result_source must support _resolve_condition");
248 }
249
250 # warn "$name resolved: " . Dumper( $resolved ); use Data::Dumper;
251 $resolved = {}
252 if defined $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION
253 && $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION
254 == $resolved;
255
256 my @rel_cols = keys %{ $info->{cond} };
257 map {s/^foreign\.//} @rel_cols;
258
259 #warn "REL_COLS: " . Dumper(@rel_cols); use Data::Dumper;
260 my $rel_col_cnt = scalar @rel_cols;
261
262 # find out if all related columns are nullable
263 my $all_fks_nullable = 1;
264 for my $rel_col (@rel_cols) {
265 $all_fks_nullable = 0
266 unless $related_resultset->result_source->column_info($rel_col)
267 ->{is_nullable};
268 }
269
270 $if_not_submitted = $all_fks_nullable ? 'nullify' : 'delete'
271 unless defined $if_not_submitted;
272
273 #warn "\tNULLABLE: $all_fks_nullable ACTION: $if_not_submitted\n";
274
275 #warn "RELINFO for $name: " . Dumper($info); use Data::Dumper;
276
277 # the only valid datatype for a has_many rels is an arrayref
278 if ( $info->{attrs}{accessor} eq 'multi' ) {
279
280 # handle undef like empty arrayref
281 $updates = []
282 unless defined $updates;
283 $self->throw_exception(
284 "data for has_many relationship '$name' must be an arrayref")
285 unless ref $updates eq 'ARRAY';
286
287 my @updated_objs;
288
289 #warn "\tupdating has_many rel '$name' ($rel_col_cnt columns cols)\n";
290 for my $sub_updates ( @{$updates} ) {
291 my $sub_object = recursive_update(
292 resultset => $related_resultset,
293 updates => $sub_updates,
294 resolved => $resolved
295 );
296
297 push @updated_objs, $sub_object;
298 }
299
300 #warn "\tcreated and updated related rows\n";
301
302 my @cond;
303 my @related_pks = $related_resultset->result_source->primary_columns;
304
305 my $rs_rel_delist = $object->$name;
306
307 # foreign table has a single pk column
308 if ( scalar @related_pks == 1 ) {
309 $rs_rel_delist = $rs_rel_delist->search_rs(
310 { $related_pks[0] =>
311 { -not_in => [ map ( $_->id, @updated_objs ) ] }
312 }
313 );
314 }
315
316 # foreign table has multiple pk columns
317 else {
318 for my $obj (@updated_objs) {
319 my %cond_for_obj;
320 for my $col (@related_pks) {
321 $cond_for_obj{$col} = $obj->get_column($col);
322 }
323 push @cond, \%cond_for_obj;
324 }
325 $rs_rel_delist = $rs_rel_delist->search_rs( { -not => [@cond] } );
326 }
327
328 #warn "\tCOND: " . Dumper(\%cond);
329 #my $rel_delist_cnt = $rs_rel_delist->count;
330 if ( $if_not_submitted eq 'delete' ) {
331
332 #warn "\tdeleting related rows: $rel_delist_cnt\n";
333 $rs_rel_delist->delete;
334 }
335 elsif ( $if_not_submitted eq 'set_to_null' ) {
336
337 #warn "\tnullifying related rows: $rel_delist_cnt\n";
338 my %update = map { $_ => undef } @rel_cols;
339 $rs_rel_delist->update( \%update );
340 }
341 }
342 elsif ($info->{attrs}{accessor} eq 'single'
343 || $info->{attrs}{accessor} eq 'filter' )
344 {
345
346 #warn "\tupdating rel '$name': $if_not_submitted\n";
347 my $sub_object;
348 if ( ref $updates ) {
349
350 # for might_have relationship
351 if ( $info->{attrs}{accessor} eq 'single'
352 && defined $object->$name )
353 {
354 $sub_object = recursive_update(
355 resultset => $related_resultset,
356 updates => $updates,
357 object => $object->$name
358 );
359 }
360 else {
361 $sub_object = recursive_update(
362 resultset => $related_resultset,
363 updates => $updates,
364 resolved => $resolved
365 );
366 }
367 }
368 else {
369 $sub_object = $related_resultset->find($updates)
370 unless (
371 !$updates
372 && ( exists $info->{attrs}{join_type}
373 && $info->{attrs}{join_type} eq 'LEFT' )
374 );
375 }
376 $object->set_from_related( $name, $sub_object )
377 unless (
378 !$sub_object
379 && !$updates
380 && ( exists $info->{attrs}{join_type}
381 && $info->{attrs}{join_type} eq 'LEFT' )
382 );
383 }
384 else {
385 $self->throw_exception(
386 "recursive_update doesn't now how to handle relationship '$name' with accessor "
387 . $info->{attrs}{accessor} );
388 }
389 }
390
391 sub is_m2m {
392 my ( $self, $relation ) = @_;
393 my $rclass = $self->result_class;
394
395 # DBIx::Class::IntrospectableM2M
396 if ( $rclass->can('_m2m_metadata') ) {
397 return $rclass->_m2m_metadata->{$relation};
398 }
399 my $object = $self->new( {} );
400 if ( $object->can($relation)
401 and !$self->result_source->has_relationship($relation)
402 and $object->can( 'set_' . $relation ) )
403 {
404 return 1;
405 }
406 return;
407 }
408
409 sub get_m2m_source {
410 my ( $self, $relation ) = @_;
411 my $rclass = $self->result_class;
412
413 # DBIx::Class::IntrospectableM2M
414 if ( $rclass->can('_m2m_metadata') ) {
415 return $self->result_source->related_source(
416 $rclass->_m2m_metadata->{$relation}{relation} )
417 ->related_source(
418 $rclass->_m2m_metadata->{$relation}{foreign_relation} );
419 }
420 my $object = $self->new( {} );
421 my $r = $object->$relation;
422 return $r->result_source;
423 }
424
425 sub _delete_empty_auto_increment {
426 my ( $self, $object ) = @_;
427 for my $col ( keys %{ $object->{_column_data} } ) {
428 if ($object->result_source->column_info($col)->{is_auto_increment}
429 and ( !defined $object->{_column_data}{$col}
430 or $object->{_column_data}{$col} eq '' )
431 )
432 {
433 delete $object->{_column_data}{$col};
434 }
435 }
436 }
437
438 sub _get_pk_for_related {
439 my ( $self, $relation ) = @_;
440 my $result_source;
441 if ( $self->result_source->has_relationship($relation) ) {
442 $result_source = $self->result_source->related_source($relation);
443 }
444
445 # many to many case
446 if ( is_m2m( $self, $relation ) ) {
447 $result_source = get_m2m_source( $self, $relation );
448 }
449 return $result_source->primary_columns;
450 }
451
452 # This function determines wheter a relationship should be done before or
453 # after the row is inserted into the database
454 # relationships before: belongs_to
455 # relationships after: has_many, might_have and has_one
456 # true means before, false after
457 sub _master_relation_cond {
458 my ( $self, $name ) = @_;
459
460 my $source = $self->result_source;
461 my $info = $source->relationship_info($name);
462
463 #warn "INFO: " . Dumper($info) . "\n";
464
465 # has_many rels are always after
466 return 0
467 if $info->{attrs}->{accessor} eq 'multi';
468
469 my @foreign_ids = _get_pk_for_related( $self, $name );
470
471 #warn "IDS: " . join(', ', @foreign_ids) . "\n";
472
473 my $cond = $info->{cond};
474
475 sub _inner {
476 my ( $source, $cond, @foreign_ids ) = @_;
477
478 while ( my ( $f_key, $col ) = each %{$cond} ) {
479
480 # might_have is not master
481 $col =~ s/^self\.//;
482 $f_key =~ s/^foreign\.//;
483 if ( $source->column_info($col)->{is_auto_increment} ) {
484 return 0;
485 }
486 if ( any { $_ eq $f_key } @foreign_ids ) {
487 return 1;
488 }
489 }
490 return 0;
491 }
492
493 if ( ref $cond eq 'HASH' ) {
494 return _inner( $source, $cond, @foreign_ids );
495 }
496
497 # arrayref of hashrefs
498 elsif ( ref $cond eq 'ARRAY' ) {
499 for my $new_cond (@$cond) {
500 return _inner( $source, $new_cond, @foreign_ids );
501 }
502 }
503 else {
504 $source->throw_exception(
505 "unhandled relation condition " . ref($cond) );
506 }
507 return;
508 }
509
510 1; # Magic true value required at end of module
511 __END__
512
513 =head1 NAME
514
515 DBIx::Class::ResultSet::RecursiveUpdate - like update_or_create - but recursive
516
517 =head1 SYNOPSIS
518
519 The functional interface:
520
521 my $new_item = DBIx::Class::ResultSet::RecursiveUpdate::Functions::recursive_update({
522 resultset => $schema->resultset( 'Dvd' ),
523 updates => {
524 id => 1,
525 owned_dvds => [
526 {
527 title => 'One Flew Over the Cuckoo's Nest'
528 }
529 ]
530 }
531 });
532
533
534 As ResultSet subclass:
535
536 __PACKAGE__->load_namespaces( default_resultset_class => '+DBIx::Class::ResultSet::RecursiveUpdate' );
537
538 in the Schema file (see t/lib/DBSchema.pm). Or appriopriate 'use base' in the ResultSet classes.
539
540 Then:
541
542 my $user = $user_rs->recursive_update( {
543 id => 1,
544 owned_dvds => [
545 {
546 title => 'One Flew Over the Cuckoo's Nest'
547 }
548 ]
549 }
550 );
551
552
553 =head1 DESCRIPTION
554
555 This is still experimental. I've added a functional interface so that it can be used
556 in Form Processors and not require modification of the model.
557
558 You can feed the ->create method with a recursive datastructure and have the related records
559 created. Unfortunately you cannot do a similar thing with update_or_create - this module
560 tries to fill that void.
561
562 It is a base class for ResultSets providing just one method: recursive_update
563 which works just like update_or_create but can recursively update or create
564 data objects composed of multiple rows. All rows need to be identified by primary keys
565 - so you need to provide them in the update structure (unless they can be deduced from
566 the parent row - for example when you have a belongs_to relationship).
567 If not all colums comprising the primary key are specified - then a new row will be created,
568 with the expectation that the missing columns will be filled by it (as in the case of auto_increment
569 primary keys).
570
571
572 If the resultset itself stores an assignement for the primary key,
573 like in the case of:
574
575 my $restricted_rs = $user_rs->search( { id => 1 } );
576
577 then you need to inform recursive_update about additional predicate with a second argument:
578
579 my $user = $restricted_rs->recursive_update( {
580 owned_dvds => [
581 {
582 title => 'One Flew Over the Cuckoo's Nest'
583 }
584 ]
585 },
586 [ 'id' ]
587 );
588
589 This will work with a new DBIC release.
590
591 For a many_to_many (pseudo) relation you can supply a list of primary keys
592 from the other table - and it will link the record at hand to those and
593 only those records identified by them. This is convenient for handling web
594 forms with check boxes (or a SELECT box with multiple choice) that let you
595 update such (pseudo) relations.
596
597 For a description how to set up base classes for ResultSets see load_namespaces
598 in DBIx::Class::Schema.
599
600 =head1 DESIGN CHOICES
601
602 Columns and relationships which are excluded from the updates hashref aren't
603 touched at all.
604
605 =head2 Treatment of belongs_to relations
606
607 In case the relationship is included but undefined in the updates hashref,
608 all columns forming the relationship will be set to null.
609 If not all of them are nullable, DBIx::Class will throw an error.
610
611 Updating the relationship:
612
613 my $dvd = $dvd_rs->recursive_update( {
614 id => 1,
615 owner => $user->id,
616 });
617
618 Clearing the relationship (only works if cols are nullable!):
619
620 my $dvd = $dvd_rs->recursive_update( {
621 id => 1,
622 owner => undef,
623 });
624
625 =head2 Treatment of might_have relationships
626
627 In case the relationship is included but undefined in the updates hashref,
628 all columns forming the relationship will be set to null.
629
630 Updating the relationship:
631
632 my $user = $user_rs->recursive_update( {
633 id => 1,
634 address => {
635 street => "101 Main Street",
636 city => "Podunk",
637 state => "New York",
638 }
639 });
640
641 Clearing the relationship:
642
643 my $user = $user_rs->recursive_update( {
644 id => 1,
645 address => undef,
646 });
647
648 =head2 Treatment of has_many relations
649
650 If a relationship key is included in the data structure with a value of undef
651 or an empty array, all existing related rows will be deleted, or their foreign
652 key columns will be set to null.
653
654 The exact behaviour depends on the nullability of the foreign key columns and
655 the value of the "if_not_submitted" parameter. The parameter defaults to
656 undefined which neither nullifies nor deletes.
657
658 When the array contains elements they are updated if they exist, created when
659 not and deleted if not included.
660
661 =head3 All foreign table columns are nullable
662
663 In this case recursive_update defaults to nullifying the foreign columns.
664
665 =head3 Not all foreign table columns are nullable
666
667 In this case recursive_update deletes the foreign rows.
668
669 Updating the relationship:
670
671 Passing ids:
672
673 my $dvd = $dvd_rs->recursive_update( {
674 id => 1,
675 tags => [1, 2],
676 });
677
678 Passing hashrefs:
679
680 my $dvd = $dvd_rs->recursive_update( {
681 id => 1,
682 tags => [
683 {
684 id => 1,
685 file => 'file0'
686 },
687 {
688 id => 2,
689 file => 'file1',
690 },
691 ],
692 });
693
694 Passing objects:
695
696 TODO
697
698 You can even mix them:
699
700 my $dvd = $dvd_rs->recursive_update( {
701 id => 1,
702 tags => [ '2', { id => '3' } ],
703 });
704
705 Clearing the relationship:
706
707 my $dvd = $dvd_rs->recursive_update( {
708 id => 1,
709 tags => undef,
710 });
711
712 This is the same as passing an empty array:
713
714 my $dvd = $dvd_rs->recursive_update( {
715 id => 1,
716 tags => [],
717 });
718
719 =head2 Treatment of many-to-many pseudo relations
720
721 The function gets the information about m2m relations from DBIx::Class::IntrospectableM2M.
722 If it isn't loaded in the ResultSource classes the code relies on the fact that:
723
724 if($object->can($name) and
725 !$object->result_source->has_relationship($name) and
726 $object->can( 'set_' . $name )
727 )
728
729 Then $name must be a many to many pseudo relation.
730 And that in a similarly ugly was I find out what is the ResultSource of
731 objects from that many to many pseudo relation.
732
733
734 =head1 INTERFACE
735
736 =head1 METHODS
737
738 =head2 recursive_update
739
740 The method that does the work here.
741
742 =head2 is_m2m
743
744 $self->is_m2m( 'name ' ) - answers the question if 'name' is a many to many
745 (pseudo) relation on $self.
746
747 =head2 get_m2m_source
748
749 $self->get_m2m_source( 'name' ) - returns the ResultSource linked to by the many
750 to many (pseudo) relation 'name' from $self.
751
752
753 =head1 DIAGNOSTICS
754
755
756 =head1 CONFIGURATION AND ENVIRONMENT
757
758 DBIx::Class::RecursiveUpdate requires no configuration files or environment variables.
759
760 =head1 DEPENDENCIES
761
762 DBIx::Class
763
764 =head1 INCOMPATIBILITIES
765
766 None reported.
767
768
769 =head1 BUGS AND LIMITATIONS
770
771 No bugs have been reported.
772
773 Please report any bugs or feature requests to
774 C<bug-dbix-class-recursiveput@rt.cpan.org>, or through the web interface at
775 L<http://rt.cpan.org>.
776
777
778 =head1 AUTHOR
779
780 Zbigniew Lukasiak C<< <zby@cpan.org> >>
781 Influenced by code by Pedro Melo.
782
783 =head1 LICENCE AND COPYRIGHT
784
785 Copyright (c) 2008, Zbigniew Lukasiak C<< <zby@cpan.org> >>. All rights reserved.
786
787 This module is free software; you can redistribute it and/or
788 modify it under the same terms as Perl itself. See L<perlartistic>.
789
790
791 =head1 DISCLAIMER OF WARRANTY
792
793 BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
794 FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
795 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
796 PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
797 EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
798 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
799 ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
800 YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
801 NECESSARY SERVICING, REPAIR, OR CORRECTION.
802
803 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
804 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
805 REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
806 LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
807 OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
808 THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
809 RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
810 FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
811 SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
812 SUCH DAMAGES.
This page took 0.081237 seconds and 4 git commands to generate.