PGObject-Util-PseudoCSV-2/0000755000175000017500000000000013104627477014413 5ustar chrischrisPGObject-Util-PseudoCSV-2/META.yml0000664000175000017500000000123413104627477015666 0ustar chrischris--- abstract: 'Tuple/Array parsing and serialization for PGObject' author: - 'Chris Travers ' build_requires: Test::More: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150005' license: bsd meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: PGObject-Util-PseudoCSV no_index: directory: - t - inc requires: PGObject: '1.400001' resources: repository: https://github.com/ledgersmb/PGObject-Util-PseudoCSV.git version: 2 x_serialization_backend: 'CPAN::Meta::YAML version 0.018' PGObject-Util-PseudoCSV-2/Changes0000644000175000017500000000075313104626305015700 0ustar chrischrisRevision history for PGObject-Util-PseudoCSV 2.0.0 TBD Removed dependency on PGObject Removed automatic deserialization Added tests for legacy and current invocation 1.1.1 2014-09-18 Corrected inconsistency between license texts 1.1.0 2014-08-06 Meta now lists repo Direct to hash api Code cleanup PseudoCSV creation API 1.0.3 2014-08-25 Minor documentation fixes 1.0.0 2014-03-03 First version, released on an unsuspecting world. PGObject-Util-PseudoCSV-2/META.json0000664000175000017500000000225113104627477016036 0ustar chrischris{ "abstract" : "Tuple/Array parsing and serialization for PGObject", "author" : [ "Chris Travers " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150005", "license" : [ "bsd" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "PGObject-Util-PseudoCSV", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "Test::More" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "PGObject" : "1.400001" } } }, "release_status" : "stable", "resources" : { "repository" : { "type" : "git", "url" : "https://github.com/ledgersmb/PGObject-Util-PseudoCSV.git", "web" : "https://github.com/ledgersmb/PGObject-Util-PseudoCSV" } }, "version" : 2, "x_serialization_backend" : "JSON::PP version 2.27400" } PGObject-Util-PseudoCSV-2/Makefile.PL0000644000175000017500000000202613104622753016355 0ustar chrischrisuse 5.006; use strict; use warnings; use ExtUtils::MakeMaker; WriteMakefile( NAME => 'PGObject::Util::PseudoCSV', AUTHOR => q{Chris Travers }, VERSION_FROM => 'lib/PGObject/Util/PseudoCSV.pm', ABSTRACT_FROM => 'lib/PGObject/Util/PseudoCSV.pm', ($ExtUtils::MakeMaker::VERSION >= 6.3002 ? ('LICENSE'=> 'bsd') : ()), PL_FILES => {}, PREREQ_PM => { 'PGObject' => 1.400001, }, BUILD_REQUIRES => { 'Test::More' => 0, }, dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, clean => { FILES => 'PGObject-Util-PseudoCSV-*' }, META_MERGE => { 'meta-spec' => { version => 2 }, resources => { repository => { type => 'git', url => 'https://github.com/ledgersmb/PGObject-Util-PseudoCSV.git', web => 'https://github.com/ledgersmb/PGObject-Util-PseudoCSV', }, }, }, ); PGObject-Util-PseudoCSV-2/t/0000755000175000017500000000000013104627477014656 5ustar chrischrisPGObject-Util-PseudoCSV-2/t/pod-coverage.t0000644000175000017500000000104713104622753017410 0ustar chrischrisuse strict; use warnings; use Test::More; # Ensure a recent version of Test::Pod::Coverage my $min_tpc = 1.08; eval "use Test::Pod::Coverage $min_tpc"; plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage" if $@; # Test::Pod::Coverage doesn't require a minimum Pod::Coverage version, # but older versions don't recognize some common documentation styles my $min_pc = 0.18; eval "use Pod::Coverage $min_pc"; plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage" if $@; all_pod_coverage_ok(); PGObject-Util-PseudoCSV-2/t/manifest.t0000644000175000017500000000042013104622753016635 0ustar chrischris#!perl -T use strict; use warnings; use Test::More; unless ( $ENV{RELEASE_TESTING} ) { plan( skip_all => "Author tests not required for installation" ); } eval "use Test::CheckManifest 0.9"; plan skip_all => "Test::CheckManifest 0.9 required" if $@; ok_manifest(); PGObject-Util-PseudoCSV-2/t/boilerplate.t0000644000175000017500000000236713104622753017345 0ustar chrischris#!perl -T use 5.006; use strict; use warnings; use Test::More tests => 3; sub not_in_file_ok { my ($filename, %regex) = @_; open( my $fh, '<', $filename ) or die "couldn't open $filename for reading: $!"; my %violated; while (my $line = <$fh>) { while (my ($desc, $regex) = each %regex) { if ($line =~ $regex) { push @{$violated{$desc}||=[]}, $.; } } } if (%violated) { fail("$filename contains boilerplate text"); diag "$_ appears on lines @{$violated{$_}}" for keys %violated; } else { pass("$filename contains no boilerplate text"); } } sub module_boilerplate_ok { my ($module) = @_; not_in_file_ok($module => 'the great new $MODULENAME' => qr/ - The great new /, 'boilerplate description' => qr/Quick summary of what the module/, 'stub function definition' => qr/function[12]/, ); } not_in_file_ok(README => "The README is used..." => qr/The README is used/, "'version information here'" => qr/to provide version information/, ); not_in_file_ok(Changes => "placeholder date/time" => qr(Date/time) ); module_boilerplate_ok('lib/PGObject/Util/PseudoCSV.pm'); PGObject-Util-PseudoCSV-2/t/01-parsing.t0000644000175000017500000000510413104626144016712 0ustar chrischrisuse Test::More tests => 22; use PGObject::Util::PseudoCSV; # plain forms my $simpletuple = '(a,b,",")'; my $simplearray = '{a,b,","}'; # nulls my $nulltuple = '(a,b,",",NULL)'; my $nullarray = '{a,b,",",NULL}'; # nested tests my $nestedtuple = '(a,b,",","(1,a)")'; my $nestedarray = '{{a,b},{1,a}}'; my $tuplewitharray = '(a,b,",","{1,a}")'; my $arrayoftuples = '{"(a,b)","(1,a)"}'; # Newline tests my $newlinetuple = qq|(a,b,",\n")|; my $newlinearray = qq|{a,b,",\n"}|; my $valarray; my $hashref; # Simple tuple tests to array ok ($valarray = pseudocsv_parse($simpletuple, 'test'), 'Parse success, simple tuple, legacy format'); is_deeply($valarray, ['a', 'b', ','], 'Parse correct, simple tuple'); ok ($valarray = pseudocsv_parse($simpletuple), 'parse success, current format'); is_deeply($valarray, ['a', 'b', ','], 'Parse correct, simple tuple'); # Simple array parse ok ($valarray = pseudocsv_parse($simplearray, 'test'), 'Parse success, simple array'); is_deeply($valarray, ['a', 'b', ','], 'Parse correct, simple array'); # Null tuple ok ($valarray = pseudocsv_parse($nulltuple, 'test'), 'Parse success, simple tuple'); is_deeply($valarray, ['a', 'b', ',', undef], 'Parse correct, simple tuple'); ok($hashref = pcsv2hash($nulltuple, 'a', 'b', 'c', 'd'), 'parsed null tuple to hashref'); is_deeply($hashref, {a => 'a', b => 'b', c => ',', d => undef }, 'hashref correct from null tuple'); # Null array ok ($valarray = pseudocsv_parse($nullarray, 'test'), 'Parse success, simple array'); is_deeply($valarray, ['a', 'b', ',', undef], 'Parse correct, simple array'); # Nested tuple ok ($valarray = pseudocsv_parse($nestedtuple, 'test'), 'Parse success, nested tuple'); is_deeply($valarray, ['a', 'b', ',', '(1,a)'], 'Parse correct, simple array'); # Tuple with array ok ($valarray = pseudocsv_parse($tuplewitharray, 'test'), 'Parse success, tuple with array member'); is_deeply($valarray, ['a', 'b', ',', [1, 'a']], 'Parse correct, tuple with array'); # Array of tuples ok ($valarray = pseudocsv_parse($arrayoftuples, 'test'), 'Parse success, tuple with array of tuples'); is_deeply($valarray, ['(a,b)','(1,a)'], 'Parse correct, array of tuples'); # New line tuple ok ($valarray = pseudocsv_parse($newlinetuple, 'test'), 'Parse success, tuple with array of tuples'); is_deeply($valarray, ['a', 'b', ",\n"], 'Parse correct, array of tuples'); # New line array ok ($valarray = pseudocsv_parse($newlinearray, 'test'), 'Parse success, tuple with array of tuples'); is_deeply($valarray, ['a', 'b', ",\n"], 'Parse correct, array of tuples'); PGObject-Util-PseudoCSV-2/t/02-serialization.t0000644000175000017500000000151113104622753020125 0ustar chrischrisuse Test::More tests => 8; use PGObject::Util::PseudoCSV; my $proplist = ["test", '1', '3', undef, '44']; my $nestedprops = ["test", '1', '3', ['1', '3', '4'], '44']; my $nullstring = ["test", "null"]; my $testval; ok ($testval = to_pseudocsv($proplist, 0), 'serialized successfully'); is $testval, '{test,1,3,NULL,44}', 'correct value for array serialization'; ok ($testval = to_pseudocsv($nestedprops, 1), 'serialized successfully'); is $testval, '(test,1,3,"{1,3,4}",44)', 'correct value for array serialization'; ok ($testval = to_pseudocsv($nullstring, 1), 'serialized successfully nulltest'); is $testval, '(test,"null")', 'correct value for array serialization'; ok ($testval = hash2pcsv({a => 1, b => 2, c => undef}, 'b', 'a', 'c'), 'Serialized hashref'); is ($testval, '(2,1,NULL)', 'Correctly serialized hashref'); PGObject-Util-PseudoCSV-2/t/pod.t0000644000175000017500000000035013104622753015613 0ustar chrischris#!perl -T use strict; use warnings; use Test::More; # Ensure a recent version of Test::Pod my $min_tp = 1.22; eval "use Test::Pod $min_tp"; plan skip_all => "Test::Pod $min_tp required for testing POD" if $@; all_pod_files_ok(); PGObject-Util-PseudoCSV-2/t/00-load.t0000644000175000017500000000032313104622753016165 0ustar chrischris#!perl -T use Test::More tests => 1; BEGIN { use_ok( 'PGObject::Util::PseudoCSV' ) || print "Bail out!\n"; } diag( "Testing PGObject::Util::PseudoCSV $PGObject::Util::PseudoCSV::VERSION, Perl $], $^X" ); PGObject-Util-PseudoCSV-2/lib/0000755000175000017500000000000013104627477015161 5ustar chrischrisPGObject-Util-PseudoCSV-2/lib/PGObject/0000755000175000017500000000000013104627477016616 5ustar chrischrisPGObject-Util-PseudoCSV-2/lib/PGObject/Util/0000755000175000017500000000000013104627477017533 5ustar chrischrisPGObject-Util-PseudoCSV-2/lib/PGObject/Util/PseudoCSV.pm0000644000175000017500000001757313104627173021712 0ustar chrischrispackage PGObject::Util::PseudoCSV; use 5.008; use strict; use warnings; use Carp; =head1 NAME PGObject::Util::PseudoCSV - Tuple/Array parsing and serialization for PGObject =head1 VERSION Version 2 =cut our $VERSION = 2.000000; =head1 SYNOPSIS This is a parser and constructor for PostgreSQL text representations of tuples and arrays. To parse: For a tuple, we'd typically: my @list = pseudocsv_parse($text_representation); We can then arrange the hash as: my $hashref = pseudocsv_to_hash(\@list, \@col_list); Which we can combine as: my $hashref = pseudocsv_to_hash( pseudocsv_parse($text_representation), \@col_list ); =head1 DESCRIPTION PostgreSQL can represent tuples and arrays in a text format that is almost like CSV. Unfortunately this format has a number of gotchas which render existing CSV-parsers useless. This module provides basic parsing functions to other programs for db interface purposes. With this module you can both parse pseudocsv representations of tuples and arrays and you can create them from a list. The API's here assume you send one (and only one) pseudo-csv record to the API at once. These may be nested, so a single tuple can contain arrays of tuples which can contain arrays of tuples ad infinitum but the parsing only goes one layer deep tuple-wise so that handling classes have an opportunity to re-parse with appropriate type information. Naturally this has performance implications, so depth in SQL structures passed should be reasonably limited. As of 2.0, we no longer automatically call deserialization functions from the parser itself. At his point the calling classes MUST call the deserializer themselves but this is far easier since this has been moved to a separate service in PGObject 2.0. This avoids an unecessary dependency on PGObject and ensures that the module is more geneally useful. =head1 EXPORT =over =item pseudocsv_to_hash =item pseudocsv_parse =item to_pseudocsv =back =cut use parent 'Exporter'; our @EXPORT = qw(pseudocsv_to_hash pseudocsv_parse to_pseudocsv hash2pcsv pcsv2hash); =head1 SUBROUTINES/METHODS =head2 pseudocsv_parse This does a one-level deep parse of the pseudo-csv, with additional levels in arrays. When a tuple is encountered it is instantiated as its type but a subarray is parsed for more entities. Only one pseudocsv record can be passed in at once, but embedded newlines are properly handled. =cut sub pseudocsv_parse { my ($csv, $type, $registry) = @_; if ($csv =~ /^\(/ ) { # tuple $csv =~ s/^\((.*)\)$/$1/s; } elsif ($csv =~ /^\{/ ) { # array $csv =~ s/^\{(.*)\}$/$1/s; } $registry ||= 'default'; my @returnlist = (); while (length($csv)) { my $val = _parse(\$csv); my $in_type = $type; $in_type = shift @$type if ref $type eq ref []; $val =~ s/""/"/g if defined $val; push @returnlist, $val; } return @returnlist if wantarray; return \@returnlist; } =head2 pcsv2hash($literal_string, @cols); Returns a hash from a tuple literal or array literal. =cut sub pcsv2hash { my $string = shift; $string = shift if $string eq __PACKAGE__; my @colnames = @_; my @colvals = pseudocsv_parse($string, undef, undef); my $hash = { map{ $_ => shift @colvals } @colnames }; return %$hash if wantarray; return $hash; } =head2 hash2pcsv($hashref, @cols) Takes an ordered list of columns and a hashref and returns a tuple literal =cut sub hash2pcsv { my ($hashref, @cols) = @_; return to_pseudocsv([map { $hashref->{$_} } @cols], 1) } # _parse is the private method which does the hard work of parsing. sub _parse { my ($csvref) = @_; my $retval; if ($$csvref =~ /^"/){ # quoted string $$csvref =~ s/^"(([^"]|"")*)",?//s; $retval = $1; $retval =~ s/""/"/g; } else { $$csvref =~ s/^([^,]*)(,|$)//s; $retval = $1; $retval = undef if $retval =~ /^null$/i; } if (defined $retval and $retval =~ s/^\{(.*)\}$/$1/){ my $listref = []; push @$listref, _parse(\$retval) while $retval; $retval = $listref; } return $retval; } =head2 pseudocsv_tohash($coldata, $colnames) DEPRECATED Takes an arrayref of column data and an arrayref of column names and returns a hash. This is mostly a helper function designed to help with tuple types. This interface is deprecated and will go away in 2.0. Use pcsv2hash instead. =cut sub pseudocsv_tohash { my ($cols, $colnames) = @_; my $hash = { map{ $_ => shift @$cols } @$colnames }; return %$hash if wantarray; return $hash; } =head2 to_pseudocsv($datalist, $is_tuple) Takes a list of data and an is_tuple argument and creates a pseudocsv. Note: this does not check for array sanity. If you are not careful you can get arrays returned which are not valid SQL arrays. Remember that SQL arrays have every item being the same size, and all SQL arrays are are regular in size (so all 1 and 2d arrays follow the same form as mathematical matrices). =cut sub _val { my ($val, $is_tuple) = @_; return 'NULL' unless defined $val; $val = $val->to_db if eval { $val->can('to_db') }; $val = to_pseudocsv($_, 0) if ref $_ eq ref []; return $val if ref $_ eq ref [] and !$is_tuple; $val =~ s/"/""/; $val = qq("$val") if $val =~ /(^null$|[",{}])/i; return $val; } sub to_pseudocsv { my ($list, $is_tuple) = @_; Carp::croak 'First arg must be an arrayref' unless ref $list; my $csv = join(',', map { _val($_, $is_tuple) } @$list); return qq|($csv)| if $is_tuple; return qq|{$csv}|; } =head1 AUTHOR Chris Travers, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc PGObject::Util::PseudoCSV You can also look for information at: =over 4 =item * RT: CPAN's request tracker (report bugs here) L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 ACKNOWLEDGEMENTS =head1 LICENSE AND COPYRIGHT Copyright 2014-2017 Chris Travers. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =cut 1; # End of PGObject::Util::PseudoCSV PGObject-Util-PseudoCSV-2/README0000644000175000017500000000431613104622753015267 0ustar chrischrisPGObject-Util-PseudoCSV This module provides a parser and serializer for complex data types in PostgreSQL. For example, hashrefs can be serialized as tuples, and arrayrefs as arrays, and this serialization is reversible too (given the same catalog entries). INSTALLATION To install this module, run the following commands: perl Makefile.PL make make test make install SUPPORT AND DOCUMENTATION After installing, you can find documentation for this module with the perldoc command. perldoc PGObject::Util::PseudoCSV You can also look for information at: RT, CPAN's request tracker (report bugs here) http://rt.cpan.org/NoAuth/Bugs.html?Dist=PGObject-Util-PseudoCSV AnnoCPAN, Annotated CPAN documentation http://annocpan.org/dist/PGObject-Util-PseudoCSV CPAN Ratings http://cpanratings.perl.org/d/PGObject-Util-PseudoCSV Search CPAN http://search.cpan.org/dist/PGObject-Util-PseudoCSV/ LICENSE AND COPYRIGHT Copyright (C) 2014 Chris Travers Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PGObject-Util-PseudoCSV-2/ignore.txt0000644000175000017500000000020513104622753016424 0ustar chrischrisblib* Makefile Makefile.old Build Build.bat _build* pm_to_blib* *.tar.gz .lwpcookies cover_db pod2htm*.tmp PGObject-Util-PseudoCSV-* PGObject-Util-PseudoCSV-2/MANIFEST0000644000175000017500000000057113104627477015547 0ustar chrischrisChanges ignore.txt lib/PGObject/Util/PseudoCSV.pm LICENSE Makefile.PL MANIFEST This list of files README t/00-load.t t/01-parsing.t t/02-serialization.t t/boilerplate.t t/manifest.t t/pod-coverage.t t/pod.t META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) PGObject-Util-PseudoCSV-2/LICENSE0000644000175000017500000000242013104622753015406 0ustar chrischrisCopyright (c) 2014, Chris Travers All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.