File Coverage

blib/lib/PGObject/Type/Registry.pm
Criterion Covered Total %
statement 74 102 72.5
branch 32 56 57.1
condition 5 8 62.5
subroutine 16 20 80.0
pod 8 8 100.0
total 135 194 69.5


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             PGObject::Type::Registry - Registration of types for handing db types
5              
6             =head1 SYNOPSIS
7              
8             PGObject::Type::Registry->add_registry('myapp'); # required
9              
10             PGObject::Type::Registry->register_type(
11             registry => 'myapp', dbtype => 'int4',
12             apptype => 'PGObject::Type::BigFloat'
13             );
14              
15             # to get back a type:
16             my $number = PGObject::Type::Registry->deserialize(
17             registry => 'myapp', dbtype => 'int4',
18             dbstring => '1023'
19             );
20              
21             # To get registry data:
22             my %registry = PGObject::Type::Registry->inspect(registry => 'myapp');
23              
24             =cut
25              
26             package PGObject::Type::Registry;
27              
28 6     6   71562 use strict;
  6         23  
  6         195  
29 6     6   39 use warnings;
  6         12  
  6         220  
30              
31              
32 6     6   34 use Carp;
  6         11  
  6         349  
33 6     6   3513 use List::MoreUtils qw(pairwise);
  6         78672  
  6         37  
34 6     6   6416 use Scalar::Util qw(reftype);
  6         15  
  6         319  
35 6     6   3195 use Try::Tiny;
  6         12939  
  6         1731  
36              
37              
38             our $VERSION = '2.2.0';
39              
40             my %registry = ( default => {} );
41              
42             =head1 DESCRIPTION
43              
44             The PGObject type registry stores data for serialization and deserialization
45             relating to the database.
46              
47             =head1 USE
48              
49             Generally we like to separate applications into their own registries so that
50             different libraries can be used in a more harmonious way.
51              
52             =head1 CREATING A REGISTRY
53              
54             You must create a registry before using it. This is there to ensure that we
55             make sure that subtle problems are avoided and strings returned when serialized
56             types expected. This is idempotent and repeat calls are safe. There is no
57             abiltiy to remove an existing registry though you can loop through and remove
58             the existing registrations.
59              
60             =head2 new_registry(name)
61              
62             =cut
63              
64             sub new_registry {
65 13     13 1 1054 my ( $self, $name ) = @_;
66 13 100       99 if ( not exists $registry{$name} ) {
67 10         6237 $registry{$name} = {};
68             }
69             }
70              
71             =head1 REGISTERING A TYPE
72              
73             =head2 register_type
74              
75             Args:
76              
77             registry => 'default', #warning thrown if not specified
78             dbtype => [required], #exception thrown if not specified
79             apptype => [required], #exception thrown if not specified
80              
81             Use:
82              
83             This registers a type for use by PGObject. PGObject calls with the same
84             registry key will serialize to this type, using the from_db method provided.
85              
86             from_db will be provided two arguments. The first is the string from the
87             database and the second is the type provided. The second argument is optional
88             and passed along for the db interface class's use.
89              
90             A warning is thrown if no
91              
92             =cut
93              
94             sub register_type {
95 17     17 1 2702 my ( $self, %args ) = @_;
96 17         46 my %defaults = ( registry => 'default' );
97 17 100       96 carp 'Using default registry' unless $args{registry};
98 17 50       1881 croak 'Must provide dbtype arg' unless $args{dbtype};
99 17 50       39 croak 'Must provide apptype arg' unless $args{apptype};
100 17 100       46 delete $args{registry} unless defined $args{registry};
101 17         74 %args = ( %defaults, %args );
102             croak 'Registry does not exist yet'
103 17 100       70 unless exists $registry{ $args{registry} };
104             croak 'Type registered with different target'
105             if exists $registry{ $args{registry} }->{ $args{dbtype} }
106 15 100 100     109 and $registry{ $args{registry} }->{ $args{dbtype} } ne $args{apptype};
107 12         44 $args{apptype} =~ /^(.*)::(\w*)$/;
108 12         45 my ( $parent, $final ) = ( $1, $2 );
109 12   50     64 $parent ||= '';
110 12   33     49 $final ||= $args{apptype};
111             {
112 6     6   54 no strict 'refs';
  6         28  
  6         6231  
  12         32  
113 12 50       25 $parent = "${parent}::" if $parent;
114             croak "apptype not yet loaded ($args{apptype})"
115 12 100       17 unless exists ${"::${parent}"}{"${final}::"};
  12         75  
116             croak 'apptype does not have from_db function'
117 10 50       86 unless $args{apptype}->can('from_db');
118             }
119 10         41 %args = ( %defaults, %args );
120 10         55 $registry{ $args{registry} }->{ $args{dbtype} } = $args{apptype};
121             }
122              
123             =head1 UNREGISTERING A TYPE
124              
125             To unregister a type, you provide the dbtype and registry information, both
126             of which are required. Note that at that this is rarely needed.
127              
128             =head2 unregister_type
129              
130             =cut
131              
132             sub unregister_type {
133 3     3 1 10 my ( $self, %args ) = @_;
134 3 50       11 croak 'Must provide registry' unless $args{registry};
135 3 50       7 croak 'Must provide dbtype arg' unless $args{dbtype};
136             croak 'Registry does not exist yet'
137 3 100       18 unless exists $registry{ $args{registry} };
138             croak 'Type not registered'
139 2 100       15 unless $registry{ $args{registry} }->{ $args{dbtype} };
140 1         8 delete $registry{ $args{registry} }->{ $args{dbtype} };
141             }
142              
143             =head1 DESERIALIZING A VALUE
144              
145             =head2 deserialize
146              
147             This function deserializes a data from a db string.
148              
149             Mandatory args are dbtype and dbstring
150             The registry arg should be provided but if not, a warning will be issued and
151             'default' will be used.
152              
153             This function returns the output of the from_db method.
154              
155             =cut
156              
157             sub deserialize {
158 2     2 1 286 my ( $self, %args ) = @_;
159              
160 2 50       8 croak "Must specify dbstring arg" unless exists $args{dbstring};
161 2         8 return $self->deserializer( %args )->( $args{dbstring} );
162             }
163              
164             =head2 deserializer
165              
166             This returns a coderef to deserialize data from a db string. The coderef
167             should be called with a single argument: the argument that would be passed
168             as 'dbstring' into C. E.g.:
169              
170             my $deserializer = PGObject::Type::Registry->deserializer(dbtype => $type);
171             my $value = $deserializer->($dbvalue);
172              
173             Mandatory argument is dbtype.
174             The registry arg should be provided but if not, a warning will be issued and
175             'default' will be used.
176              
177             This function returns the output of the C method of the registered
178             class.
179              
180             =cut
181              
182             sub deserializer {
183 4     4 1 372 my ( $self, %args ) = @_;
184 4         10 my %defaults = ( registry => 'default' );
185 4 50       12 carp 'No registry specified, using default' unless exists $args{registry};
186 4 50       9 croak "Must specify dbtype arg" unless $args{dbtype};
187 4         15 %args = ( %defaults, %args );
188 4         7 my $arraytype = 0;
189 4 50       13 if ( $args{dbtype} =~ /^_/ ) {
190 0         0 $args{dbtype} =~ s/^_//;
191 0         0 $arraytype = 1;
192             }
193              
194 2     2   11 return $args{_unmapped_undef} ? undef : sub { shift }
195 4 50       39 unless $registry{ $args{registry} }->{ $args{dbtype} };
    100          
196              
197 2 50       6 if ($arraytype) {
198 0         0 my $deserializer = $self->deserializer( %args );
199 0     0   0 return sub { [ map { $deserializer->( $_ ) } @{ (shift) } ] };
  0         0  
  0         0  
  0         0  
200             }
201              
202 2         5 my $clazz = $registry{ $args{registry} }->{ $args{dbtype} };
203 2         10 my $from_db = $clazz->can('from_db');
204 2         4 my $dbtype = $args{dbtype};
205 2     2   542 return sub { $from_db->($clazz, (shift), $dbtype); }
206 2         13 }
207              
208             =head2 rowhash_deserializer
209              
210             This returns a coderef to deserialize data from a call to e.g.
211             C. The coderef should be called with a single argument:
212             the hash that holds the row values with the keys being the column names.
213              
214             Mandatory argument is C, which is either an arrayref or hashref.
215             In case of a hashref, the keys are the names of the columns to be expected
216             in the data hashrefs. The values are the types (same as the C
217             parameter of the C method). In case of an arrayref, an additional
218             argument C is required, containing the names of the columns in the
219             same order as C.
220              
221             The registry arg should be provided but if not, a warning will be issued and
222             'default' will be used.
223              
224             This function returns the output of the C method of the registered
225             class.
226              
227             =cut
228              
229             sub rowhash_deserializer {
230 0     0 1 0 my ( $self, %args ) = @_;
231 0         0 my %defaults = ( registry => 'default' );
232 0 0       0 carp 'No registry specified, using default' unless exists $args{registry};
233 0 0       0 croak 'No types specied' unless exists $args{types};
234              
235 0         0 %args = ( %defaults, %args );
236 0         0 my $types = $args{types};
237              
238 0 0       0 if (reftype $types eq 'ARRAY') {
239 0 0       0 croak 'No columns specified' unless exists $args{columns};
240              
241 0     0   0 $types = { pairwise { $a => $b } @{$args{columns}}, @$types };
  0         0  
  0         0  
242             }
243              
244             my %column_deserializers =
245 0         0 map { $_ => $self->deserializer(dbtype => $types->{$_},
246             registry => $args{registry},
247 0         0 _unmapped_undef => 1) } keys %$types;
248 0         0 for (keys %column_deserializers) {
249 0 0       0 if (not defined $column_deserializers{$_}) {
250 0         0 delete $column_deserializers{$_}
251             }
252             }
253             return sub {
254 0     0   0 my $row = shift;
255              
256 0         0 for my $col (keys %column_deserializers) {
257             $row->{$col} =
258 0         0 $column_deserializers{$col}->( $row->{$col} );
259             }
260 0         0 return $row;
261             }
262 0         0 }
263              
264             =head1 INSPECTING A REGISTRY
265              
266             Sometimes we need to see what types are registered. To do this, we can
267             request a copy of the registry.
268              
269             =head2 inspect($name)
270              
271             $name is required. If it does not exist an exception is thrown.
272              
273             =cut
274              
275             sub inspect {
276 3     3 1 679 my ( $self, $name ) = @_;
277 3 50       13 croak 'Must specify a name' unless $name;
278 3 50       11 croak 'Registry does not exist' unless exists $registry{$name};
279 3         6 return { %{ $registry{$name} } };
  3         22  
280             }
281              
282             =head2 list()
283              
284             Returns a list of existing registries.
285              
286             =cut
287              
288             sub list {
289 1     1 1 519 return keys %registry;
290             }
291              
292             =head1 COPYRIGHT AND LICENSE
293              
294             COPYRIGHT (C) 2017-2020 The LedgerSMB Core Team
295              
296             Redistribution and use in source and compiled forms with or without
297             modification, are permitted provided that the following conditions are met:
298              
299             =over
300              
301             =item
302              
303             Redistributions of source code must retain the above
304             copyright notice, this list of conditions and the following disclaimer as the
305             first lines of this file unmodified.
306              
307             =item
308              
309             Redistributions in compiled form must reproduce the above copyright
310             notice, this list of conditions and the following disclaimer in the
311             source code, documentation, and/or other materials provided with the
312             distribution.
313              
314             =back
315              
316             THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) "AS IS" AND
317             ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
318             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
319             DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR
320             ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
321             (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
322             LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
323             ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
324             (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
325             SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
326              
327              
328             =cut
329              
330             1;