Make DateTime objects work for all ops
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / SQLMaker / DateOps.pm
CommitLineData
c173ce76 1package DBIx::Class::SQLMaker::DateOps;
2
5e7a0b00 3use strict;
4use warnings;
5
c173ce76 6use base qw/
7 Class::Accessor::Grouped
8/;
9__PACKAGE__->mk_group_accessors (simple => qw/datetime_parser/);
c173ce76 10use Sub::Name 'subname';
5972c6a7 11use Scalar::Util 'blessed';
c173ce76 12
13sub _where_op_CONVERT_DATETIME {
14 my $self = shift;
15 my ($op, $rhs) = splice @_, -2;
90676d46 16 $self->throw_exception("-$op takes a DateTime only") unless ref $rhs && $rhs->isa('DateTime');
c173ce76 17
18 # in case we are called as a top level special op (no '=')
19 my $lhs = shift;
20
21 $rhs = $self->datetime_parser->format_datetime($rhs);
22
23 my @bind = [
24 ($lhs || $self->{_nested_func_lhs} || undef),
25 $rhs
26 ];
27
28 return $lhs
29 ? (
30 $self->_convert($self->_quote($lhs)) . ' = ' . $self->_convert('?'),
31 @bind
32 )
33 : (
34 $self->_convert('?'),
35 @bind
36 )
37 ;
38}
39
40sub _unsupported_date_extraction {
41 "date part extraction not supported for part \"$_[1]\" with database \"$_[2]\""
42}
43
44sub _unsupported_date_adding {
45 "date part adding not supported for part \"$_[1]\" with database \"$_[2]\""
46}
47
48sub _unsupported_date_diff {
49 "date diff not supported for part \"$_[1]\" with database \"$_[2]\""
50}
51
52sub _datetime_sql { die 'date part extraction not implemented for this database' }
53
54sub _datetime_diff_sql { die 'date diffing not implemented for this database' }
55sub _datetime_add_sql { die 'date adding not implemented for this database' }
56
57sub _where_op_GET_DATETIME {
58 my ($self) = @_;
59
60 my ($k, $op, $vals);
61
62 if (@_ == 3) {
63 $op = $_[1];
64 $vals = $_[2];
65 $k = '';
66 } elsif (@_ == 4) {
67 $k = $_[1];
68 $op = $_[2];
69 $vals = $_[3];
70 }
71
90676d46 72 $self->throw_exception('args to -dt_get must be an arrayref') unless ref $vals eq 'ARRAY';
73 $self->throw_exception('first arg to -dt_get must be a scalar or ARRAY ref') unless !ref $vals->[0] || ref $vals->[0] eq 'ARRAY';
c173ce76 74
75 my $part = $vals->[0];
76 my $val = $vals->[1];
77
5972c6a7 78 my ($sql, @bind) = $self->_dt_arg_transform('', $val);
c173ce76 79
464a9709 80 if (!ref $part) {
81 return $self->_datetime_sql($part, $sql), @bind;
82 } elsif (ref $part eq 'ARRAY' ) {
83 return ( join ', ', map { $self->_datetime_sql($_, $sql) } @$part ), (@bind) x @$part;
84 }
c173ce76 85}
86
fcaf47ee 87for my $part (qw(month year hour minute second)) {
c173ce76 88 no strict 'refs';
89 my $name = '_where_op_GET_DATETIME_' . uc($part);
90 *{$name} = subname "DBIx::Class::SQLMaker::DateOps::$name", sub {
91 my $self = shift;
92 my ($op, $rhs) = splice @_, -2;
93
94 my $lhs = shift;
95
96 return $self->_where_op_GET_DATETIME($op, $lhs, [$part, $rhs])
97 }
98}
99
fcaf47ee 100sub _where_op_GET_DATETIME_DAY {
101 my $self = shift;
102 my ($op, $rhs) = splice @_, -2;
103
104 my $lhs = shift;
105
455edaef 106 # we use day_of_month instead of plain day internally
107 # because some databases also support day_of_week and day_of_year
fcaf47ee 108 return $self->_where_op_GET_DATETIME($op, $lhs, [day_of_month => $rhs])
109}
110
c173ce76 111sub _where_op_DATETIME_NOW {
112 my ($self) = @_;
113
114 my ($k, $op, $vals);
115
116 if (@_ == 3) {
117 $op = $_[1];
118 $vals = $_[2];
119 $k = '';
120 } elsif (@_ == 4) {
121 $k = $_[1];
122 $op = $_[2];
123 $vals = $_[3];
124 }
125
90676d46 126 $self->throw_exception("args to -$op must be an arrayref") unless ref $vals eq 'ARRAY';
c173ce76 127 if (!exists $vals->[0]) {
128 return $self->_datetime_now_sql()
129 } elsif ($vals->[0] eq 'system') {
130 require DateTime;
131 return $self->_where_op_CONVERT_DATETIME('dt', DateTime->now);
132 } else {
90676d46 133 $self->throw_exception("first arg to -$op must be a 'system' or non-existant")
c173ce76 134 }
135}
136
137sub _reorder_add_datetime_vars {
138 my ($self, $amount, $date) = @_;
139
140 return ($amount, $date);
141}
142
90676d46 143sub _dt_arg_transform {
5972c6a7 144 my $self = shift;
145
146 my ($k, $val) = @_;
147
148 if (blessed $val && $val->isa('DateTime')) {
149 return (
150 $self->_convert('?'),
151 [
152 \'timestamp',
153 $self->datetime_parser->format_datetime($val)
154 ]
155 )
156 }
157
158 $self->_dt_arg_non_date_transform(@_);
159}
160
161sub _dt_arg_non_date_transform {
90676d46 162 my ($self, $k, $val) = @_;
5972c6a7 163
90676d46 164 my ($sql, @bind) = $self->_SWITCH_refkind($val, {
165 SCALAR => sub {
166 return ($self->_convert('?'), $self->_bindtype($k, $val) );
167 },
168 SCALARREF => sub {
169 return $$val;
170 },
171 ARRAYREFREF => sub {
172 my ($sql, @bind) = @$$val;
173 $self->_assert_bindval_matches_bindtype(@bind);
174 return ($sql, @bind);
175 },
176 HASHREF => sub {
177 my $method = $self->_METHOD_FOR_refkind("_where_hashpair", $val);
178 $self->$method('', $val);
179 }
180 });
181 return ($sql, @bind);
182}
183
fbf2cd0a 184sub _where_op_ADD_DATETIME_transform_args {
185 if ($_[1] == 0) {
186 $_[0]->_dt_arg_non_date_transform($_[2], $_[3])
187 } else {
188 $_[0]->_dt_arg_transform($_[2], $_[3])
189 }
190}
90676d46 191
c173ce76 192sub _where_op_ADD_DATETIME {
193 my ($self) = @_;
194
195 my ($k, $op, $vals);
196
197 if (@_ == 3) {
198 $op = $_[1];
199 $vals = $_[2];
200 $k = '';
201 } elsif (@_ == 4) {
202 $k = $_[1];
203 $op = $_[2];
204 $vals = $_[3];
205 }
206
90676d46 207 $self->throw_exception("args to -$op must be an arrayref") unless ref $vals eq 'ARRAY';
208 $self->throw_exception("first arg to -$op must be a scalar") unless !ref $vals->[0];
209 $self->throw_exception("-$op must have two more arguments") unless scalar @$vals == 3;
c173ce76 210
211 my ($part, @rest) = @$vals;
212
c173ce76 213 my (@all_sql, @all_bind);
90676d46 214 my $i = 0;
fbf2cd0a 215 foreach my $val ($self->_reorder_add_datetime_vars(map [ $i++, $_ ], @rest)) {
216 my ($sql, @bind) = $self->_where_op_ADD_DATETIME_transform_args($val->[0], $k, $val->[1]);
c173ce76 217 push @all_sql, $sql;
218 push @all_bind, @bind;
219 }
220
221 return $self->_datetime_add_sql($part, $all_sql[0], $all_sql[1]), @all_bind
222}
223
f94672f9 224sub _reorder_diff_datetime_vars {
225 my ($self, $d1, $d2) = @_;
226
227 return ($d1, $d2);
228}
229
c173ce76 230sub _where_op_DIFF_DATETIME {
231 my ($self) = @_;
232
233 my ($k, $op, $vals);
234
235 if (@_ == 3) {
236 $op = $_[1];
237 $vals = $_[2];
238 $k = '';
239 } elsif (@_ == 4) {
240 $k = $_[1];
241 $op = $_[2];
242 $vals = $_[3];
243 }
244
90676d46 245 $self->throw_exception('args to -dt_diff must be an arrayref') unless ref $vals eq 'ARRAY';
246 $self->throw_exception('first arg to -dt_diff must be a scalar') unless !ref $vals->[0];
247 $self->throw_exception('-dt_diff must have two more arguments') unless scalar @$vals == 3;
c173ce76 248
249 my ($part, @val) = @$vals;
250 my $placeholder = $self->_convert('?');
251
f94672f9 252 @val = $self->_reorder_diff_datetime_vars(@val);
c173ce76 253 my (@all_sql, @all_bind);
254 foreach my $val (@val) {
fbf2cd0a 255 my ($sql, @bind) = $self->_dt_arg_transform($k, $val);
c173ce76 256 push @all_sql, $sql;
257 push @all_bind, @bind;
258 }
259
260 return $self->_datetime_diff_sql($part, $all_sql[0], $all_sql[1]), @all_bind
261}
262
69320ddf 263sub _where_op_CIRCA_DATETIME {
264 my ($self) = @_;
265
266 my ($k, $op, $val);
267
268 if (@_ == 3) {
269 $op = $_[1];
270 $val = $_[2];
271 $k = '';
272 } elsif (@_ == 4) {
273 $k = $_[1];
274 $op = $_[2];
275 $val = $_[3];
276 }
277
fbf2cd0a 278 my ($sql, @bind) = $self->_dt_arg_transform($k, $val);
69320ddf 279
280 my ($equal, $before, $after) = $op =~ /dt_(on_or_)?(before)?(after)?/;
281 my $sym = $before
282 ? '<'
283 : '>'
284 ;
285
286 $sym .= $equal
287 ? '='
288 : ''
289 ;
290 return "$k $sym $sql", @bind;
291}
292
c173ce76 2931;