File Coverage

lib/DB/Object/Fields.pm
Criterion Covered Total %
statement 31 88 35.2
branch 0 28 0.0
condition 0 27 0.0
subroutine 11 18 61.1
pod 5 5 100.0
total 47 166 28.3


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Database Object Interface - ~/lib/DB/Object/Fields.pm
3             ## Version v1.1.1
4             ## Copyright(c) 2022 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2020/01/01
7             ## Modified 2023/03/24
8             ## All rights reserved
9             ##
10             ## This program is free software; you can redistribute it and/or modify it
11             ## under the same terms as Perl itself.
12             ##----------------------------------------------------------------------------
13             package DB::Object::Fields;
14             BEGIN
15             {
16 2     2   1280 use strict;
  2         4  
  2         69  
17 2     2   11 use warnings;
  2         4  
  2         59  
18 2     2   14 use common::sense;
  2         5  
  2         30  
19 2     2   113 use parent qw( Module::Generic );
  2         2  
  2         10  
20 2     2   136 use vars qw( $VERSION );
  2         16  
  2         85  
21 2     2   465 use DB::Object::Fields::Field;
  2         4  
  2         15  
22 2     2   662 use Devel::Confess;
  2         4  
  2         10  
23 2     2   183 our( $VERSION ) = 'v1.1.1';
24             };
25              
26 2     2   10 use strict;
  2         5  
  2         36  
27 2     2   10 use warnings;
  2         5  
  2         1276  
28              
29             sub init
30             {
31 0     0 1   my $self = shift( @_ );
32 0           $self->{prefixed} = 0;
33 0           $self->{query_object} = '';
34 0           $self->{table_object} = '';
35             # $self->{fatal} = 1;
36 0           $self->{_init_strict_use_sub} = 1;
37 0           $self->{_init_params_order} = [qw( table_object query_object prefixed )];
38 0           $self->SUPER::init( @_ );
39 0 0         return( $self->error( "No table object was provided" ) ) if( !$self->{table_object} );
40 0           return( $self );
41             }
42              
43 0     0 1   sub database_object { return( shift->table_object->database_object ); }
44              
45             sub prefixed
46             {
47 0     0 1   my $self = shift( @_ );
48 0 0         if( @_ )
49             {
50 0 0         $self->{prefixed} = ( $_[0] =~ /^\d+$/ ? $_[0] : ( $_[0] ? 1 : 0 ) );
    0          
51             }
52             else
53             {
54 0           $self->{prefixed} = 1;
55             }
56 0           my $fields = $self->table_object->fields;
57 0           foreach my $f ( keys( %$fields ) )
58             {
59 0 0         next if( !CORE::length( $self->{ $f } ) );
60 0 0         next if( !$self->_is_object( $self->{ $f } ) );
61 0           my $o = $self->{ $f };
62 0           $o->prefixed( $self->{prefixed} );
63             }
64 0           return( $self );
65             }
66              
67             # sub query_object { return( shift->_set_get_object_without_init( 'query_object', 'DB::Object::Query', @_ ) ); }
68 0     0 1   sub query_object { return( shift->table_object->query_object ); }
69              
70 0     0 1   sub table_object { return( shift->_set_get_object_without_init( 'table_object', 'DB::Object::Tables', @_ ) ); }
71              
72             sub _initiate_field_object
73             {
74 0     0     my $self = shift( @_ );
75 0   0       my $field = shift( @_ ) || return( $self->error( "No field was provided to get its object." ) );
76 0   0       my $class = ref( $self ) || $self;
77 0           my $fields = $self->table_object->fields;
78 0 0         return( $self->error( "Table ", $self->table_object->name, " has no such field \"$field\"." ) ) if( !CORE::exists( $fields->{ $field } ) );
79             # eval( "package ${class}; sub ${field} { return( shift->_set_get_object( '$field', 'DB::Object::Fields::Field', \@_ ) ); }" );
80 0           my $def = $self->table_object->default;
81 0           my $types = $self->table_object->types;
82 0           my $const = $self->table_object->types_const;
83 0   0       $const->{ $field }->{constant} //= q{''};
84 0   0       $const->{ $field }->{name} //= '';
85 0   0       $const->{ $field }->{type} //= '';
86             my $hash =
87             {
88             debug => ( $self->debug || 0 ),
89             name => $field,
90             type => ( $types->{ $field } // '' ),
91             default => ( $def->{ $field } // '' ),
92             pos => ( $fields->{ $field } // '' ),
93             const => $const->{ $field },
94             prefixed => $self->{prefixed},
95 0   0       query_object => $self->query_object,
      0        
      0        
      0        
96             table_object => $self->table_object,
97             };
98 0           my $perl = <<EOT;
99             package ${class};
100             sub ${field}
101             {
102             my \$self = shift( \@_ );
103             unless( \$self->{$field} )
104             {
105             \$self->{$field} = DB::Object::Fields::Field->new(
106             debug => ( \$self->debug || 0 ),
107             name => '$field',
108             type => '$hash->{type}',
109             default => '$hash->{default}',
110             pos => $hash->{pos},
111             constant => { constant => $hash->{const}->{constant}, name => '$hash->{const}->{name}', type => '$hash->{const}->{type}' },
112             prefixed => \$self->{prefixed},
113             query_object => \$self->query_object,
114             table_object => \$self->table_object,
115             );
116             }
117             return( \$self->{$field} );
118             }
119             EOT
120 0           eval( $perl );
121 0 0         die( $@ ) if( $@ );
122             # my $o = DB::Object::Fields::Field->new( $hash );
123 0           my $o = $self->$field;
124             # $self->$field( $o ) || return( $self->error( "Unable to set field '$field' object to '$o': ", $self->error ) );
125 0           return( $o );
126             }
127              
128             AUTOLOAD
129             {
130 0     0     my( $method ) = our $AUTOLOAD =~ /([^:]+)$/;
131             # my( $class, $method ) = our $AUTOLOAD =~ /^(.*?)::([^\:]+)$/;
132 2     2   16 no overloading;
  2         3  
  2         665  
133 0           my $self = shift( @_ );
134 0           my $fields = $self->table_object->fields;
135             # $self->debug(3);
136 0 0         if( my $code = $self->can( $method ) )
    0          
137             {
138 0           return( $code->( $self, @_ ) );
139             }
140             elsif( exists( $fields->{ $method } ) )
141             {
142 0           return( $self->_initiate_field_object( $method ) );
143             }
144             else
145             {
146             # This is an unrecoverable error. We have no choice, but to die.
147 0           my $error = "Table " . $self->table_object->name . " has no such field \"$method\"";
148 0 0         $self->_load_class( 'Module::Generic::Exception' ) || die( $self->error );
149 0           my $exception = Module::Generic::Exception->new( $error );
150 0           my $on_unknown_field = $self->table_object->database_object->unknown_field;
151 0 0 0       if( ref( $on_unknown_field ) eq 'CODE' )
    0 0        
152             {
153 0           return( $on_unknown_field->({
154             table => $self->table_object,
155             field => $method,
156             error => $exception,
157             }) );
158             }
159             elsif( defined( $on_unknown_field ) && ( $on_unknown_field eq 'die' || $on_unknown_field eq 'fatal' ) )
160             {
161 0           die( $exception );
162             }
163             else
164             {
165 0 0         $self->_load_class( 'DB::Object::Fields::Unknown' ) ||
166             die( "${error}, and I could not load the module DB::Object::Fields::Unknown: ", $self->error );
167 0   0       my $unknown = DB::Object::Fields::Unknown->new(
168             table => $self->table_object->name,
169             error => $exception,
170             field => $method,
171             ) || die( "${error}, and I could not instantiate a new instance of the module DB::Object::Fields::Unknown: ", DB::Object::Fields::Unknown->error );
172 0           warn( "Table ", $self->table_object->name, " has no such field \"$method\".\n" );
173             # return( $self->error( "Table ", $self->table_object->name, " has no such field \"$method\"." ) );
174             #die( "Table ", $self->table_object->name, " has no such field \"$method\".\n" );
175 0           return( $unknown );
176             }
177             }
178             };
179              
180             1;
181             # NOTE: POD
182             __END__
183              
184             =encoding utf8
185              
186             =head1 NAME
187              
188             DB::Object::Fields - Tables Fields Object Accessor
189              
190             =head1 SYNOPSIS
191              
192             my $dbh = DB::Object->connect({
193             driver => 'Pg',
194             conf_file => $conf,
195             database => 'my_shop',
196             host => 'localhost',
197             login => 'super_admin',
198             schema => 'auth',
199             unknown_field => 'fatal',
200             # debug => 3,
201             }) || bailout( "Unable to connect to sql server on host localhost: ", DB::Object->error );
202            
203             my $tbl = $dbh->some_table || die( "No table \"some_table\" could be found: ", $dbh->error, "\n" );
204             my $fo = $tbl->fields_object || die( $tbl->error );
205             my $expr = $fo->id == 2;
206             print "Expression is: $expr\n"; # Expression is: id = 2
207              
208             my $tbl_object = $dbh->customers || die( "Unable to get the customers table object: ", $dbh->error, "\n" );
209             my $fields = $tbl_object->fields;
210             print( "Fields for table \"", $tbl_object->name, "\": ", Dumper( $fields ), "\n" );
211             my $c = $tbl_object->fo->currency;
212             print( "Got field object for currency: \"", ref( $c ), "\": '$c'\n" );
213             printf( "Name: %s\n", $c->name );
214             printf( "Type: %s\n", $c->type );
215             printf( "Default: %s\n", $c->default );
216             printf( "Position: %s\n", $c->pos );
217             printf( "Table: %s\n", $c->table );
218             printf( "Database: %s\n", $c->database );
219             printf( "Schema: %s\n", $c->schema );
220             printf( "Next field: %s (%s)\n", $c->next, ref( $c->next ) );
221             print( "Showing name fully qualified: ", $c->prefixed( 3 )->name, "\n" );
222             ## would print: my_shop.public.customers.currency
223             print( "Trying again (should keep prefix): ", $c->name, "\n" );
224             ## would print again: my_shop.public.customers.currency
225             print( "Now cancel prefixing at the table fields level.\n" );
226             $tbl_object->fo->prefixed( 0 );
227             print( "Showing name fully qualified again (should not be prefixed): ", $c->name, "\n" );
228             ## would print currency
229             print( "First element is: ", $c->first, "\n" );
230             print( "Last element is: ", $c->last, "\n" );
231             # Works also with the operators +, -, *, /, %, <, <=, >, >=, !=, <<, >>, &, |, ^, ==
232             my $table = $dbh->dummy;
233             $table->select( $c + 10 ); # SELECT currency + 10 FROM dummy;
234             $c == 'NULL' # currency IS NULL
235              
236             # if DB::Object unknown_field option is set to fatal, this will die. By default, it will simply be ignored
237             my $unknown_field = $tbl->unknown;
238              
239             =head1 VERSION
240              
241             v1.1.1
242              
243             =head1 DESCRIPTION
244              
245             The purpose of this module is to enable access to the table fields as L<DB::Object::Fields::Field> objects.
246              
247             The way this works is by having L<DB::Object::Tables/fields_object> or L<DB::Object::Tables/fo> for short, dynamically create a class based on the database name and table name. For example if the database driver were C<PostgreSQL>, the database were C<my_shop> and the table C<customers>, the dynamically created package would become C<DB::Object::Postgres::Tables::MyShop::Customers>. This class would inherit from this package L<DB::Object::Fields>.
248              
249             Field objects can than be dynamically instantiated by accessing them, such as (assuming the table object C<$tbl_object> here represent the table C<customers>) C<$tbl_object->fo->last_name>. This will return a L<DB::Object::Fields::Field> object.
250              
251             A note on the design: there had to be a separate this separate package L<DB::Object::Fields>, because access to table fields is done through the C<AUTOLOAD> and the methods within the package L<DB::Object::Tables> and its inheriting packages would clash with the tables fields. This package has very few methods, so the risk of a sql table field clashing with a method name is very limited. In any case, if you have in your table a field with the same name as one of those methods here (see below for the list), then you can instantiate a field object with:
252              
253             $tbl_object->_initiate_field_object( 'last_name' );
254              
255             If you call an unknown field, its behaviour will change depending on the option value C<unknown_field> of L<DB::Object> upon instantiation:
256              
257             =over 4
258              
259             =item * C<ignore> (default)
260              
261             The unknown field will be ignored and a warning will be emitted that this field does not exist in the given database table.
262              
263             =item * C<fatal> or C<die>
264              
265             This will trigger a L</die> using a L<Module::Generic::Exception> object. So you could catch it like this:
266              
267             use Nice::Try;
268            
269             try
270             {
271             # $opts contains the property 'unknown_field' set to 'die'
272             my $dbh = DB::Object::Postgres->connect( $opts ) || die( "Unable to connect" );
273             my $tbl = $dbh->some_table || die( "Unable to get the database table \"some_table\": ", $dbh->error );
274             $tbl->where( $dbh->AND(
275             $tbl->fo->faulty_field == '?',
276             $tbl->fo->status == 'live',
277             ) );
278             my $ref = $tbl->select->fetchrow_hashref;
279             }
280             catch( $e isa( 'Module::Generic::Exception' ) )
281             {
282             die( "Caught error preparing SQL: $e" );
283             }
284             else
285             {
286             die( "Caught some other error." );
287             }
288              
289             =item * C<code reference>
290              
291             When the option C<unknown_field> is set to a code reference, this will be executed and passed an hash reference that will contain 3 properties:
292              
293             =over 8
294              
295             =item 1. C<table>
296              
297             The L<table object|DB::Object::Tables>
298              
299             =item 2. C<field>
300              
301             A regular string containing the unknown field name
302              
303             =item 3. C<error>
304              
305             The L<error object|Module::Generic::Exception>, which includes the error string and a stack trace
306              
307             =back
308              
309             =back
310              
311             By default, the unknown field will be ignored.
312              
313             =head1 CONSTRUCTOR
314              
315             =head2 new
316              
317             Creates a new L<DB::Object::Fields> objects. It may also take an hash like arguments, that also are method of the same name.
318              
319             =over 4
320              
321             =item I<debug>
322              
323             Toggles debug mode on/off
324              
325             =back
326              
327             =head1 METHODS
328              
329             =head2 database_object
330              
331             The database object, which is a L<DB::Object> object or one of its descendant.
332              
333             =head2 prefixed
334              
335             This si the prefix level, from 0 to 2.
336              
337             2 or higher including the database, higher than 1 includes the schema name and above 0 includes the table name. 0 includes nothing.
338              
339             When this value is changed, it is propagated to all the fields objects.
340              
341             =head2 query_object
342              
343             The query object, which is a L<DB::Object::Query> object or one of its descendant.
344              
345             =head2 table_object
346              
347             The query object, which is a L<DB::Object::Tables> object or one of its descendant.
348              
349             =head2 _initiate_field_object
350              
351             This method is called from C<AUTOLOAD>
352              
353             Provided with a table column name and this will create a new L<DB::Object::Fields::Field> object and add dynamically the associated method for this column in the current package so that next time, it returns the cached object without using C<AUTOLOAD>
354              
355             =head1 AUTOLOAD
356              
357             Called with a column name and this will check if the given column name actually exists in this table. If it does, it will call L</_initiate_field_object> to instantiate a new field object and returns it.
358              
359             If the column does not exist, it returns an error.
360              
361             =head1 SEE ALSO
362              
363             L<perl>
364              
365             =head1 AUTHOR
366              
367             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
368              
369             =head1 COPYRIGHT & LICENSE
370              
371             Copyright (c) 2020-2021 DEGUEST Pte. Ltd.
372              
373             You can use, copy, modify and redistribute this package and associated
374             files under the same terms as Perl itself.
375              
376             =cut