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   70555 use strict;
  6         23  
  6         193  
29 6     6   31 use warnings;
  6         13  
  6         289  
30              
31              
32 6     6   551 use Carp::Clan qr/^PGObject\b/;
  6         4014  
  6         67  
33 6     6   4021 use List::MoreUtils qw(pairwise);
  6         85857  
  6         51  
34 6     6   6946 use Log::Any qw($log);
  6         8610  
  6         45  
35 6     6   3357 use Scalar::Util qw(reftype);
  6         12  
  6         2127  
36              
37              
38             our $VERSION = '2.3.1';
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 495 my ( $self, $name ) = @_;
66 13 100       64 if ( not exists $registry{$name} ) {
67 10         6439 $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 2082 my ( $self, %args ) = @_;
96 17         45 my %defaults = ( registry => 'default' );
97             carp $log->warn( 'Using default registry' )
98 17 100       79 unless $args{registry};
99             croak $log->error( 'Missing dbtype arg' )
100 17 50       811 unless $args{dbtype};
101             croak $log->error( 'Missing apptype arg' )
102 17 50       43 unless $args{apptype};
103 17 100       44 delete $args{registry} unless defined $args{registry};
104 17         82 %args = ( %defaults, %args );
105             croak $log->error( 'Registry does not exist yet' )
106 17 100       59 unless exists $registry{ $args{registry} };
107             croak $log->error( 'Type registered with different target' )
108             if exists $registry{ $args{registry} }->{ $args{dbtype} }
109 15 100 100     88 and $registry{ $args{registry} }->{ $args{dbtype} } ne $args{apptype};
110 12         29 $args{apptype} =~ /^(.*)::(\w*)$/;
111 12         58 my ( $parent, $final ) = ( $1, $2 );
112 12   50     62 $parent ||= '';
113 12   33     46 $final ||= $args{apptype};
114             {
115 6     6   51 no strict 'refs';
  6         13  
  6         6690  
  12         32  
116 12 50       26 $parent = "${parent}::" if $parent;
117             croak "apptype not yet loaded ($args{apptype})"
118 12 100       18 unless exists ${"::${parent}"}{"${final}::"};
  12         62  
119             croak 'apptype does not have from_db function'
120 10 50       89 unless $args{apptype}->can('from_db');
121             }
122 10         45 %args = ( %defaults, %args );
123 10         56 $registry{ $args{registry} }->{ $args{dbtype} } = $args{apptype};
124             }
125              
126             =head1 UNREGISTERING A TYPE
127              
128             To unregister a type, you provide the dbtype and registry information, both
129             of which are required. Note that at this time this is rarely needed.
130              
131             =head2 unregister_type
132              
133             =cut
134              
135             sub unregister_type {
136 3     3 1 9 my ( $self, %args ) = @_;
137             croak $log->error( 'Missing registry' )
138 3 50       9 unless $args{registry};
139             croak $log->error( 'Missing dbtype arg' )
140 3 50       8 unless $args{dbtype};
141             croak $log->error( 'Registry does not exist yet' )
142 3 100       12 unless exists $registry{ $args{registry} };
143             carp $log->warn( 'Type not registered' )
144 2 100       10 unless $registry{ $args{registry} }->{ $args{dbtype} };
145 2         189 delete $registry{ $args{registry} }->{ $args{dbtype} };
146             }
147              
148             =head1 DESERIALIZING A VALUE
149              
150             =head2 deserialize
151              
152             This function deserializes a data from a db string.
153              
154             Mandatory args are dbtype and dbstring
155             The registry arg should be provided but if not, a warning will be issued and
156             'default' will be used.
157              
158             This function returns the output of the from_db method.
159              
160             =cut
161              
162             sub deserialize {
163 2     2 1 318 my ( $self, %args ) = @_;
164              
165             croak $log->error( "Missing dbstring arg" )
166 2 50       7 unless exists $args{dbstring};
167 2         9 return $self->deserializer( %args )->( $args{dbstring} );
168             }
169              
170             =head2 deserializer
171              
172             This returns a coderef to deserialize data from a db string. The coderef
173             should be called with a single argument: the argument that would be passed
174             as 'dbstring' into C. E.g.:
175              
176             my $deserializer = PGObject::Type::Registry->deserializer(dbtype => $type);
177             my $value = $deserializer->($dbvalue);
178              
179             Mandatory argument is dbtype.
180             The registry arg should be provided but if not, a warning will be issued and
181             'default' will be used.
182              
183             This function returns the output of the C method of the registered
184             class.
185              
186             =cut
187              
188             sub deserializer {
189 4     4 1 361 my ( $self, %args ) = @_;
190 4         11 my %defaults = ( registry => 'default' );
191             carp $log->info( 'No registry specified, using default' )
192 4 50       12 unless exists $args{registry};
193             croak $log->error( "Missing dbtype arg" )
194 4 50       10 unless $args{dbtype};
195 4         17 %args = ( %defaults, %args );
196 4         8 my $arraytype = 0;
197 4 50       14 if ( $args{dbtype} =~ /^_/ ) {
198 0         0 $args{dbtype} =~ s/^_//;
199 0         0 $arraytype = 1;
200             }
201              
202 2     2   14 return $args{_unmapped_undef} ? undef : sub { shift }
203 4 50       36 unless $registry{ $args{registry} }->{ $args{dbtype} };
    100          
204              
205 2 50       6 if ($arraytype) {
206 0         0 my $deserializer = $self->deserializer( %args );
207 0     0   0 return sub { [ map { $deserializer->( $_ ) } @{ (shift) } ] };
  0         0  
  0         0  
  0         0  
208             }
209              
210 2         5 my $clazz = $registry{ $args{registry} }->{ $args{dbtype} };
211 2         11 my $from_db = $clazz->can('from_db');
212 2         4 my $dbtype = $args{dbtype};
213 2     2   520 return sub { $from_db->($clazz, (shift), $dbtype); }
214 2         14 }
215              
216             =head2 rowhash_deserializer
217              
218             This returns a coderef to deserialize data from a call to e.g.
219             C. The coderef should be called with a single argument:
220             the hash that holds the row values with the keys being the column names.
221              
222             Mandatory argument is C, which is either an arrayref or hashref.
223             In case of a hashref, the keys are the names of the columns to be expected
224             in the data hashrefs. The values are the types (same as the C
225             parameter of the C method). In case of an arrayref, an additional
226             argument C is required, containing the names of the columns in the
227             same order as C.
228              
229             The registry arg should be provided but if not, a warning will be issued and
230             'default' will be used.
231              
232             This function returns the output of the C method of the registered
233             class.
234              
235             =cut
236              
237             sub rowhash_deserializer {
238 0     0 1 0 my ( $self, %args ) = @_;
239 0         0 my %defaults = ( registry => 'default' );
240             carp $log->warn( 'No registry specified, using default' )
241 0 0       0 unless exists $args{registry};
242             croak $log->error( 'No types specied' )
243 0 0       0 unless exists $args{types};
244              
245 0         0 %args = ( %defaults, %args );
246 0         0 my $types = $args{types};
247              
248 0 0       0 if (reftype $types eq 'ARRAY') {
249             croak $log->error( 'No columns specified' )
250 0 0       0 unless exists $args{columns};
251              
252 0     0   0 $types = { pairwise { $a => $b } @{$args{columns}}, @$types };
  0         0  
  0         0  
253             }
254              
255             my %column_deserializers =
256 0         0 map { $_ => $self->deserializer(dbtype => $types->{$_},
257             registry => $args{registry},
258 0         0 _unmapped_undef => 1) } keys %$types;
259 0         0 for (keys %column_deserializers) {
260 0 0       0 if (not defined $column_deserializers{$_}) {
261 0         0 delete $column_deserializers{$_}
262             }
263             }
264             return sub {
265 0     0   0 my $row = shift;
266              
267 0         0 for my $col (keys %column_deserializers) {
268             $row->{$col} =
269 0         0 $column_deserializers{$col}->( $row->{$col} );
270             }
271 0         0 return $row;
272             }
273 0         0 }
274              
275             =head1 INSPECTING A REGISTRY
276              
277             Sometimes we need to see what types are registered. To do this, we can
278             request a copy of the registry.
279              
280             =head2 inspect($name)
281              
282             $name is required. If it does not exist an exception is thrown.
283              
284             =cut
285              
286             sub inspect {
287 3     3 1 685 my ( $self, $name ) = @_;
288 3 50       12 croak $log->error( 'Must specify a name' )
289             unless $name;
290             croak $log->error( 'Registry does not exist' )
291 3 50       10 unless exists $registry{$name};
292 3         7 return { %{ $registry{$name} } };
  3         20  
293             }
294              
295             =head2 list()
296              
297             Returns a list of existing registries.
298              
299             =cut
300              
301             sub list {
302 1     1 1 538 return keys %registry;
303             }
304              
305             =head1 COPYRIGHT AND LICENSE
306              
307             COPYRIGHT (C) 2017-2021 The LedgerSMB Core Team
308              
309             Redistribution and use in source and compiled forms with or without
310             modification, are permitted provided that the following conditions are met:
311              
312             =over
313              
314             =item
315              
316             Redistributions of source code must retain the above
317             copyright notice, this list of conditions and the following disclaimer as the
318             first lines of this file unmodified.
319              
320             =item
321              
322             Redistributions in compiled form must reproduce the above copyright
323             notice, this list of conditions and the following disclaimer in the
324             source code, documentation, and/or other materials provided with the
325             distribution.
326              
327             =back
328              
329             THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) "AS IS" AND
330             ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
331             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
332             DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR
333             ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
334             (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
335             LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
336             ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
337             (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
338             SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
339              
340              
341             =cut
342              
343             1;