File Coverage

blib/lib/Class/ReluctantORM/SQL/Expression/Literal.pm
Criterion Covered Total %
statement 21 76 27.6
branch 0 28 0.0
condition 0 5 0.0
subroutine 7 20 35.0
pod 11 11 100.0
total 39 140 27.8


line stmt bran cond sub pod time code
1             package Class::ReluctantORM::SQL::Expression::Literal;
2              
3             =head1 NAME
4              
5             Class::ReluctantORM::SQL::Where::Literal - Represent literals in WHEREs
6              
7             =head1 SYNOPSIS
8              
9             # Save yourself some typing
10             use Class::ReluctantORM::SQL::Aliases;
11              
12             # Make a literal for some reason.
13             my $lit_num = Literal->new(1);
14             my $lit_str = Literal->new('foo');
15             my $lit_empty_str = Literal->new('');
16             my $lit_null = Literal->new(undef);
17             my $clone = $other_lit->clone();
18              
19             # These are shortcut constructors
20             $null = Literal->NULL();
21             $true = Literal->TRUE();
22             $false = Literal->FALSE();
23             $blip = Literal->EMPTY_STRING();
24              
25             # This throws an exception - to force proper use of NULL semantics
26             eval { Literal->new(); };
27              
28             # Criterion provides auto-boxing
29             my $crit = Criterion->new('=', 1, 1);
30              
31             # Same thing
32             my $crit = Criterion->new('=', Literal->new(1), Literal->new(1));
33              
34             =head1 DESCRIPTION
35              
36             It's not likely you'll need to interact directly with this module. It is used to
37             simply provide a consistent interface for literal arguments in WHERE clauses.
38              
39             =head1 DATA TYPE SUPPORT
40              
41             There is very rudimentary support for data types. Since perl is very loosely typed, and so is the DBI placeholder system, there's not much sense in building a strongly typed SQL object model.
42              
43             Its current purpose is to simply distinguish Boolean values from string or numeric values.
44              
45             Data types are represented by an all-caps string. Literal will use BOOLEAN, NULL, STRING, and NUMBER by default, but if you pass in other values, it won't complain.
46              
47             =cut
48              
49 1     1   5 use strict;
  1         2  
  1         36  
50 1     1   5 use warnings;
  1         1  
  1         24  
51              
52 1     1   31 use Data::Dumper;
  1         3  
  1         55  
53 1     1   8 use Class::ReluctantORM::Exception;
  1         1  
  1         24  
54 1     1   5 use Class::ReluctantORM::Utilities qw(install_method);
  1         2  
  1         57  
55 1     1   5 use Scalar::Util qw(looks_like_number);
  1         3  
  1         42  
56              
57 1     1   5 use base 'Class::ReluctantORM::SQL::Expression';
  1         2  
  1         1008  
58             our $DEBUG = 0;
59              
60             # heh
61             our $ONE_TRUE_TRUE = 'TRUE';
62             our $ONE_TRUE_FALSE = 'FALSE';
63             our @FALSINESS = (
64             qr/^FALSE$/i,
65             qr/^F$/i,
66             qr/^-1$/,
67             qr/^#F$/i,
68             );
69              
70              
71             =head1 PREFAB CONSTRUCTORS
72              
73             These constructors represent Literals that are common or awkward to specify. Their value should be obvious.
74              
75             =over
76              
77             =item $lit = Literal->FALSE()
78              
79             =item $lit = Literal->TRUE()
80              
81             =item $lit = Literal->NULL()
82              
83             =item $lit = Literal->EMPTY_STRING()
84              
85             =back
86              
87             =cut
88              
89 0     0 1   sub FALSE { return __PACKAGE__->new(0, 'BOOLEAN'); }
90 0     0 1   sub TRUE { return __PACKAGE__->new(1, 'BOOLEAN'); }
91 0     0 1   sub NULL { return __PACKAGE__->new(undef, 'NULL'); }
92 0     0 1   sub EMPTY_STRING { return __PACKAGE__->new('', 'STRING'); }
93              
94             =head1 GENERIC CONSTRUCTOR
95              
96             =cut
97              
98             =head2 my $lit = Literal->new($value);
99              
100             =head2 my $lit = Literal->new($value, $data_type);
101              
102             Creates a new Literal with the given value. $value is required. Pass a literal undef
103             to get a Literal that represents NULL.
104              
105             The optional second parameter is an all-caps string representing the data type. You may send any value here. If not provided, it will be guessed as one of NULL, STRING, or NUMERIC (using Scalar::Util::looks_like_number()).
106              
107             =cut
108              
109             sub new {
110 0     0 1   my $class = shift;
111 0 0         unless (@_) { Class::ReluctantORM::Exception::Param::Missing->croak(param => 'value'); }
  0            
112              
113 0           my $val = shift;
114 0           my $data_type = shift;
115 0 0         unless ($data_type) {
116 0 0         if (!defined($val)) {
    0          
117 0           $data_type = 'NULL';
118             } elsif (looks_like_number($val)) {
119 0           $data_type = 'NUMBER';
120             } else {
121 0           $data_type = 'STRING';
122             }
123             }
124 0           my $self = bless { value => $val, data_type => uc($data_type) }, $class;
125              
126 0 0         if ($self->data_type eq 'BOOLEAN') {
127 0           $self->__normalize_boolean_value();
128             }
129 0           return $self;
130             }
131              
132             sub __normalize_boolean_value {
133 0     0     my $self = shift;
134 0           my $v = $self->value();
135 0 0         if ($v) {
136 0 0         if (grep { $v =~ $_ } @FALSINESS) {
  0            
137 0           $self->value($ONE_TRUE_FALSE);
138             } else {
139 0           $self->value($ONE_TRUE_TRUE);
140             }
141             } else {
142             # '', 0
143 0           $self->value($ONE_TRUE_FALSE);
144             }
145             }
146              
147             =head1 ACCESSORS AND MUTATORS
148              
149             =cut
150              
151              
152             =head2 @empty = $crit->child_expressions();
153              
154             Always returns an empty list. Required by the Argument interface.
155              
156             =cut
157              
158 0     0 1   sub child_expressions { return (); }
159              
160             =head2 $bool = $arg->is_literal();
161              
162             All objects of this class return true. The class add this method to Expression, making all other subclasses of it return false.
163              
164             =cut
165              
166 0     0     install_method('Class::ReluctantORM::SQL::Expression', 'is_literal', sub { return 0; });
167 0     0 1   sub is_literal { return 1; }
168              
169              
170             =head2 $bool = $crit->is_leaf_expression();
171              
172             Always returns true for this class. Required by the Argument interface.
173              
174             =cut
175              
176 0     0 1   sub is_leaf_expression { return 1; }
177              
178              
179             =head2 $str = $col->pretty_print();
180              
181             Renders a human-readable representation of the Literal.
182              
183             =cut
184              
185             sub pretty_print {
186 0     0 1   my $self = shift;
187 0           my %args = @_;
188 0 0         if ($args{one_line}) {
189 0           my $val = $self->value();
190 0           my $dt = $self->data_type();
191 0 0         if ($dt eq 'NULL') {
    0          
192 0           $val = 'NULL';
193             } elsif ($dt eq 'STRING') {
194 0           $val = "'" . $val . "'";
195             }
196 0           return $val . ':' . $dt;
197             } else {
198 0   0       return ($args{prefix} || '' ) . 'LITERAL ' . $self->pretty_print(one_line => 1) . "\n";
199             }
200             }
201              
202             =head2 $clone = $lit->clone();
203              
204             Makes a new Literal with the same boxed value as the original.
205              
206             =cut
207              
208             sub clone {
209 0     0 1   my $self = shift;
210 0           my $class = ref $self;
211 0           return $class->new($self->value(), $self->data_type());
212             }
213              
214              
215             =head2 $val = $lit->value();
216              
217             Returns the enclosed value. Keep in mind that undef represents NULL.
218              
219             You may need to check the data type to confirm that you have the right thing. For example, a Literal->FALSE->value() will return 0.
220              
221             =cut
222              
223             __PACKAGE__->mk_accessors('value');
224              
225             =head2 $str = $lit->data_type();
226              
227             Returns an all-caps string representing the datatype.
228              
229             =cut
230              
231             __PACKAGE__->mk_accessors('data_type');
232              
233             =head2 $bool = $lit->is_equivalent($expr);
234              
235             Returns true if $expr is a Literal, with matching data_type and value.
236              
237             =cut
238              
239             sub is_equivalent {
240 0     0 1   my $left = shift;
241 0           my $right = shift;
242 0 0         unless ($right->is_literal()) { return 0; }
  0            
243 0 0         unless ($left->data_type() eq $right->data_type()) { return 0; }
  0            
244              
245 0           my $dt = $left->data_type();
246 0 0         if (0) { # formatting
    0          
247 0           } elsif ($dt eq 'NULL') {
248 0   0       return ((!defined($left->value())) && (!defined($right->value())));
249             } elsif ($dt eq 'NUMBER') {
250 0           return ($left->value() == $right->value());
251             } else {
252             # May have some nasty string coercions....
253 0           return ($left->value() eq $right->value());
254             }
255              
256             }
257              
258              
259             =head1 AUTHOR
260              
261             Clinton Wolfe January 2009
262              
263             =cut
264              
265             1;