File Coverage

blib/lib/PGObject/Util/PseudoCSV.pm
Criterion Covered Total %
statement 68 73 93.1
branch 27 36 75.0
condition 6 8 75.0
subroutine 11 12 91.6
pod 5 5 100.0
total 117 134 87.3


line stmt bran cond sub pod time code
1             package PGObject::Util::PseudoCSV;
2              
3 3     3   44087 use 5.008;
  3         12  
4 3     3   15 use strict;
  3         6  
  3         55  
5 3     3   15 use warnings;
  3         9  
  3         85  
6 3     3   18 use Carp;
  3         8  
  3         263  
7              
8             =head1 NAME
9              
10             PGObject::Util::PseudoCSV - Tuple/Array parsing and serialization for PGObject
11              
12             =head1 VERSION
13              
14             Version 2
15              
16             =cut
17              
18             our $VERSION = 2.000000;
19              
20              
21             =head1 SYNOPSIS
22              
23             This is a parser and constructor for PostgreSQL text representations of tuples
24             and arrays.
25              
26             To parse:
27              
28             For a tuple, we'd typically:
29              
30             my @list = pseudocsv_parse($text_representation);
31              
32             We can then arrange the hash as:
33              
34             my $hashref = pseudocsv_to_hash(\@list, \@col_list);
35              
36             Which we can combine as:
37              
38             my $hashref = pseudocsv_to_hash(
39             pseudocsv_parse($text_representation),
40             \@col_list
41             );
42              
43             =head1 DESCRIPTION
44              
45             PostgreSQL can represent tuples and arrays in a text format that is almost like
46             CSV. Unfortunately this format has a number of gotchas which render existing
47             CSV-parsers useless. This module provides basic parsing functions to other
48             programs for db interface purposes. With this module you can both parse
49             pseudocsv representations of tuples and arrays and you can create them from a
50             list.
51              
52             The API's here assume you send one (and only one) pseudo-csv record to the API
53             at once. These may be nested, so a single tuple can contain arrays of tuples
54             which can contain arrays of tuples ad infinitum but the parsing only goes one
55             layer deep tuple-wise so that handling classes have an opportunity to re-parse
56             with appropriate type information. Naturally this has performance implications,
57             so depth in SQL structures passed should be reasonably limited.
58              
59             As of 2.0, we no longer automatically call deserialization functions from the
60             parser itself. At his point the calling classes MUST call the deserializer
61             themselves but this is far easier since this has been moved to a separate
62             service in PGObject 2.0. This avoids an unecessary dependency on PGObject
63             and ensures that the module is more geneally useful.
64              
65             =head1 EXPORT
66              
67             =over
68              
69             =item pseudocsv_to_hash
70              
71             =item pseudocsv_parse
72              
73             =item to_pseudocsv
74              
75             =back
76              
77             =cut
78              
79 3     3   1118 use parent 'Exporter';
  3         696  
  3         16  
80              
81             our @EXPORT = qw(pseudocsv_to_hash pseudocsv_parse to_pseudocsv
82             hash2pcsv pcsv2hash);
83              
84             =head1 SUBROUTINES/METHODS
85              
86             =head2 pseudocsv_parse
87              
88             This does a one-level deep parse of the pseudo-csv, with additional levels in
89             arrays. When a tuple is encountered it is instantiated as its type but a
90             subarray is parsed for more entities.
91              
92             Only one pseudocsv record can be passed in at once, but embedded newlines are properly handled.
93              
94             =cut
95              
96             sub pseudocsv_parse {
97 11     11 1 50 my ($csv, $type, $registry) = @_;
98 11 100       52 if ($csv =~ /^\(/ ) { # tuple
    50          
99 7         39 $csv =~ s/^\((.*)\)$/$1/s;
100             } elsif ($csv =~ /^\{/ ) { # array
101 4         21 $csv =~ s/^\{(.*)\}$/$1/s;
102             }
103 11   50     63 $registry ||= 'default';
104 11         24 my @returnlist = ();
105 11         27 while (length($csv)) {
106 37         80 my $val = _parse(\$csv);
107 37         64 my $in_type = $type;
108 37 50       95 $in_type = shift @$type if ref $type eq ref [];
109 37 100       90 $val =~ s/""/"/g if defined $val;
110 37         97 push @returnlist, $val;
111             }
112 11 100       29 return @returnlist if wantarray;
113 10         42 return \@returnlist;
114             }
115              
116             =head2 pcsv2hash($literal_string, @cols);
117              
118             Returns a hash from a tuple literal or array literal.
119              
120             =cut
121              
122             sub pcsv2hash {
123 1     1 1 3 my $string = shift;
124 1 50       4 $string = shift if $string eq __PACKAGE__;
125 1         4 my @colnames = @_;
126              
127 1         4 my @colvals = pseudocsv_parse($string, undef, undef);
128            
129 1         3 my $hash = { map{ $_ => shift @colvals } @colnames };
  4         10  
130 1 50       4 return %$hash if wantarray;
131 1         15 return $hash;
132             }
133              
134             =head2 hash2pcsv($hashref, @cols)
135              
136             Takes an ordered list of columns and a hashref and returns a tuple literal
137              
138             =cut
139              
140             sub hash2pcsv {
141 1     1 1 4 my ($hashref, @cols) = @_;
142 1         3 return to_pseudocsv([map { $hashref->{$_} } @cols], 1)
  3         11  
143             }
144              
145              
146             # _parse is the private method which does the hard work of parsing.
147              
148             sub _parse {
149 39     39   67 my ($csvref) = @_;
150 39         59 my $retval;
151 39 100       94 if ($$csvref =~ /^"/){ # quoted string
152 14         59 $$csvref =~ s/^"(([^"]|"")*)",?//s;
153 14         29 $retval = $1;
154 14         24 $retval =~ s/""/"/g;
155             } else {
156 25         88 $$csvref =~ s/^([^,]*)(,|$)//s;
157 25         57 $retval = $1;
158 25 100       63 $retval = undef if $retval =~ /^null$/i;
159             }
160 39 100 100     150 if (defined $retval and $retval =~ s/^\{(.*)\}$/$1/){
161 1         2 my $listref = [];
162 1         6 push @$listref, _parse(\$retval) while $retval;
163 1         3 $retval = $listref;
164             }
165 39         89 return $retval;
166             }
167              
168             =head2 pseudocsv_tohash($coldata, $colnames) DEPRECATED
169              
170             Takes an arrayref of column data and an arrayref of column names and returns
171             a hash. This is mostly a helper function designed to help with tuple types.
172              
173             This interface is deprecated and will go away in 2.0. Use pcsv2hash instead.
174              
175             =cut
176              
177             sub pseudocsv_tohash {
178 0     0 1 0 my ($cols, $colnames) = @_;
179 0         0 my $hash = { map{ $_ => shift @$cols } @$colnames };
  0         0  
180 0 0       0 return %$hash if wantarray;
181 0         0 return $hash;
182             }
183              
184             =head2 to_pseudocsv($datalist, $is_tuple)
185              
186             Takes a list of data and an is_tuple argument and creates a pseudocsv.
187              
188             Note: this does not check for array sanity. If you are not careful you can
189             get arrays returned which are not valid SQL arrays. Remember that SQL arrays
190             have every item being the same size, and all SQL arrays are are regular in
191             size (so all 1 and 2d arrays follow the same form as mathematical matrices).
192              
193             =cut
194              
195             sub _val {
196 18     18   31 my ($val, $is_tuple) = @_;
197 18 100       40 return 'NULL' unless defined $val;
198              
199 16 50       29 $val = $val->to_db if eval { $val->can('to_db') };
  16         86  
200 16 100       51 $val = to_pseudocsv($_, 0) if ref $_ eq ref [];
201 16 50 66     48 return $val if ref $_ eq ref [] and !$is_tuple;
202              
203 16         33 $val =~ s/"/""/;
204 16 100       56 $val = qq("$val") if $val =~ /(^null$|[",{}])/i;
205 16         40 return $val;
206             }
207              
208             sub to_pseudocsv {
209 5     5 1 30 my ($list, $is_tuple) = @_;
210 5 50       16 Carp::croak 'First arg must be an arrayref' unless ref $list;
211 5         13 my $csv = join(',', map { _val($_, $is_tuple) } @$list);
  18         39  
212 5 100       24 return qq|($csv)| if $is_tuple;
213 2         11 return qq|{$csv}|;
214             }
215              
216             =head1 AUTHOR
217              
218             Chris Travers, C<< >>
219              
220             =head1 BUGS
221              
222             Please report any bugs or feature requests to C, or through
223             the web interface at L. I will be notified, and then you'll
224             automatically be notified of progress on your bug as I make changes.
225              
226              
227              
228              
229             =head1 SUPPORT
230              
231             You can find documentation for this module with the perldoc command.
232              
233             perldoc PGObject::Util::PseudoCSV
234              
235              
236             You can also look for information at:
237              
238             =over 4
239              
240             =item * RT: CPAN's request tracker (report bugs here)
241              
242             L
243              
244             =item * AnnoCPAN: Annotated CPAN documentation
245              
246             L
247              
248             =item * CPAN Ratings
249              
250             L
251              
252             =item * Search CPAN
253              
254             L
255              
256             =back
257              
258              
259             =head1 ACKNOWLEDGEMENTS
260              
261              
262             =head1 LICENSE AND COPYRIGHT
263              
264             Copyright 2014-2017 Chris Travers.
265              
266             Redistribution and use in source and binary forms, with or without modification,
267             are permitted provided that the following conditions are met:
268              
269             * Redistributions of source code must retain the above copyright notice, this
270             list of conditions and the following disclaimer.
271              
272             * Redistributions in binary form must reproduce the above copyright notice, this
273             list of conditions and the following disclaimer in the documentation and/or
274             other materials provided with the distribution.
275              
276             THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
277             ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
278             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
279             DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
280             ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
281             (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
282             LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
283             ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
284             (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
285             SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
286              
287             =cut
288              
289             1; # End of PGObject::Util::PseudoCSV