line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Jifty::DBI::Collection; |
2
|
|
|
|
|
|
|
|
3
|
13
|
|
|
13
|
|
40875
|
use warnings; |
|
13
|
|
|
|
|
20
|
|
|
13
|
|
|
|
|
346
|
|
4
|
13
|
|
|
13
|
|
45
|
use strict; |
|
13
|
|
|
|
|
12
|
|
|
13
|
|
|
|
|
324
|
|
5
|
13
|
|
|
13
|
|
2114
|
use Scalar::Defer qw/lazy/; |
|
13
|
|
|
|
|
78398
|
|
|
13
|
|
|
|
|
78
|
|
6
|
13
|
|
|
13
|
|
872
|
use Scalar::Util qw/weaken/; |
|
13
|
|
|
|
|
16
|
|
|
13
|
|
|
|
|
943
|
|
7
|
|
|
|
|
|
|
use overload ( |
8
|
435
|
|
|
435
|
|
25591
|
'@{}' => \&items_array_ref, |
9
|
|
|
|
|
|
|
'<>' => \&next, |
10
|
|
|
|
|
|
|
bool => sub {shift}, |
11
|
13
|
|
|
|
|
103
|
fallback => 1 |
12
|
13
|
|
|
13
|
|
51
|
); |
|
13
|
|
|
|
|
18
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=head1 NAME |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
Jifty::DBI::Collection - Encapsulate SQL queries and rows in simple |
17
|
|
|
|
|
|
|
perl objects |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 SYNOPSIS |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
use Jifty::DBI::Collection; |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
package My::ThingCollection; |
24
|
|
|
|
|
|
|
use base qw/Jifty::DBI::Collection/; |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
package My::Thing; |
27
|
|
|
|
|
|
|
use Jifty::DBI::Schema; |
28
|
|
|
|
|
|
|
use Jifty::DBI::Record schema { |
29
|
|
|
|
|
|
|
column column_1 => type is 'text'; |
30
|
|
|
|
|
|
|
}; |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
package main; |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
use Jifty::DBI::Handle; |
35
|
|
|
|
|
|
|
my $handle = Jifty::DBI::Handle->new(); |
36
|
|
|
|
|
|
|
$handle->connect( driver => 'SQLite', database => "my_test_db" ); |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
my $collection = My::ThingCollection->new( handle => $handle ); |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
$collection->limit( column => "column_1", value => "matchstring" ); |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
while ( my $record = $collection->next ) { |
43
|
|
|
|
|
|
|
print $record->id; |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
=head1 DESCRIPTION |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
This module provides an object-oriented mechanism for retrieving and |
49
|
|
|
|
|
|
|
updating data in a DBI-accessible database. |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
In order to use this module, you should create a subclass of |
52
|
|
|
|
|
|
|
L and a subclass of L for |
53
|
|
|
|
|
|
|
each table that you wish to access. (See the documentation of |
54
|
|
|
|
|
|
|
L for more information on subclassing it.) |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
Your L subclass must override L, |
57
|
|
|
|
|
|
|
and probably should override at least L also; at the very |
58
|
|
|
|
|
|
|
least, L should probably call L and L to |
59
|
|
|
|
|
|
|
set the database handle (a L object) and table |
60
|
|
|
|
|
|
|
name for the class -- see the L for an example. |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
=cut |
64
|
|
|
|
|
|
|
|
65
|
13
|
|
|
13
|
|
937
|
use vars qw($VERSION); |
|
13
|
|
|
|
|
23
|
|
|
13
|
|
|
|
|
434
|
|
66
|
|
|
|
|
|
|
|
67
|
13
|
|
|
13
|
|
5330
|
use Data::Page; |
|
13
|
|
|
|
|
67564
|
|
|
13
|
|
|
|
|
77
|
|
68
|
13
|
|
|
13
|
|
6677
|
use Clone; |
|
13
|
|
|
|
|
25072
|
|
|
13
|
|
|
|
|
555
|
|
69
|
13
|
|
|
13
|
|
65
|
use Carp qw/croak/; |
|
13
|
|
|
|
|
15
|
|
|
13
|
|
|
|
|
478
|
|
70
|
13
|
|
|
13
|
|
53
|
use base qw/Class::Accessor::Fast/; |
|
13
|
|
|
|
|
14
|
|
|
13
|
|
|
|
|
71210
|
|
71
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw/pager prefetch_related derived _handle _is_limited rows_per_page/); |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=head1 METHODS |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=head2 new |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
Creates a new L object and immediately calls |
78
|
|
|
|
|
|
|
L with the same parameters that were passed to L. If |
79
|
|
|
|
|
|
|
you haven't overridden L<_init> in your subclass, this means that you |
80
|
|
|
|
|
|
|
should pass in a L (or one of its subclasses) like |
81
|
|
|
|
|
|
|
this: |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
my $collection = My::Jifty::DBI::Subclass->new( handle => $handle ); |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
However, if your subclass overrides L you do not need to take |
86
|
|
|
|
|
|
|
a handle argument, as long as your subclass takes care of calling the |
87
|
|
|
|
|
|
|
L method somehow. This is useful if you want all of your |
88
|
|
|
|
|
|
|
L objects to use a shared global handle and don't want to |
89
|
|
|
|
|
|
|
have to explicitly pass it in each time, for example. |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=cut |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
sub new { |
94
|
230
|
|
|
230
|
1
|
53798
|
my $proto = shift; |
95
|
230
|
|
66
|
|
|
696
|
my $class = ref($proto) || $proto; |
96
|
230
|
|
|
|
|
234
|
my $self = {}; |
97
|
230
|
|
|
|
|
400
|
bless( $self, $class ); |
98
|
230
|
100
|
|
|
|
353
|
$self->record_class( $proto->record_class ) if ref $proto; |
99
|
230
|
|
|
|
|
360
|
$self->_init(@_); |
100
|
230
|
|
|
|
|
976
|
return ($self); |
101
|
|
|
|
|
|
|
} |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=head2 _init |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
This method is called by L with whatever arguments were passed to |
106
|
|
|
|
|
|
|
L. By default, it takes a C object as a |
107
|
|
|
|
|
|
|
C argument and calls L with that. |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
=cut |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
sub _init { |
112
|
230
|
|
|
230
|
|
254
|
my $self = shift; |
113
|
230
|
|
|
|
|
528
|
my %args = ( |
114
|
|
|
|
|
|
|
handle => undef, |
115
|
|
|
|
|
|
|
derived => undef, |
116
|
|
|
|
|
|
|
@_ |
117
|
|
|
|
|
|
|
); |
118
|
230
|
50
|
|
|
|
787
|
$self->_handle( $args{'handle'} ) if ( $args{'handle'} ); |
119
|
230
|
100
|
|
|
|
2086
|
$self->derived( $args{'derived'} ) if ( $args{'derived'} ); |
120
|
230
|
|
|
|
|
408
|
$self->table( $self->record_class->table() ); |
121
|
230
|
|
|
|
|
543
|
$self->clean_slate(%args); |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
sub _init_pager { |
125
|
296
|
|
|
296
|
|
243
|
my $self = shift; |
126
|
296
|
|
|
|
|
798
|
return $self->pager( Data::Page->new(0, 10, 1) ); |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=head2 clean_slate |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
This completely erases all the data in the object. It's useful if a |
132
|
|
|
|
|
|
|
subclass is doing funky stuff to keep track of a search and wants to |
133
|
|
|
|
|
|
|
reset the object's data without losing its own data; it's probably |
134
|
|
|
|
|
|
|
cleaner to accomplish that in a different way, though. |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=cut |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
sub clean_slate { |
139
|
296
|
|
|
296
|
1
|
7040
|
my $self = shift; |
140
|
296
|
|
|
|
|
443
|
my %args = (@_); |
141
|
296
|
|
|
|
|
432
|
$self->redo_search(); |
142
|
296
|
|
|
|
|
407
|
$self->_init_pager(); |
143
|
296
|
|
|
|
|
8954
|
$self->{'itemscount'} = 0; |
144
|
296
|
|
|
|
|
403
|
$self->{'tables'} = ""; |
145
|
296
|
|
|
|
|
493
|
$self->{'auxillary_tables'} = ""; |
146
|
296
|
|
|
|
|
289
|
$self->{'where_clause'} = ""; |
147
|
296
|
|
|
|
|
412
|
$self->{'limit_clause'} = ""; |
148
|
296
|
|
|
|
|
308
|
$self->{'order'} = ""; |
149
|
296
|
|
|
|
|
276
|
$self->{'alias_count'} = 0; |
150
|
296
|
|
|
|
|
301
|
$self->{'first_row'} = 0; |
151
|
|
|
|
|
|
|
|
152
|
296
|
|
|
|
|
1267
|
delete $self->{$_} for qw( |
153
|
|
|
|
|
|
|
items |
154
|
|
|
|
|
|
|
joins |
155
|
|
|
|
|
|
|
raw_rows |
156
|
|
|
|
|
|
|
count_all |
157
|
|
|
|
|
|
|
subclauses |
158
|
|
|
|
|
|
|
restrictions |
159
|
|
|
|
|
|
|
_open_parens |
160
|
|
|
|
|
|
|
criteria_count |
161
|
|
|
|
|
|
|
); |
162
|
|
|
|
|
|
|
|
163
|
296
|
|
|
|
|
570
|
$self->rows_per_page(0); |
164
|
296
|
|
|
|
|
1214
|
$self->implicit_clauses(%args); |
165
|
296
|
|
|
|
|
506
|
$self->_is_limited(0); |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=head2 implicit_clauses |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
Called by L to set up any implicit clauses that the |
171
|
|
|
|
|
|
|
collection B has. Defaults to doing nothing. Is passed the |
172
|
|
|
|
|
|
|
paramhash passed into L. |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
=cut |
175
|
|
|
|
|
|
|
|
176
|
296
|
|
|
296
|
1
|
228
|
sub implicit_clauses { } |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
=head2 _handle [DBH] |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
Get or set this object's L object. |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=cut |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=head2 _do_search |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
This internal private method actually executes the search on the |
187
|
|
|
|
|
|
|
database; it is called automatically the first time that you actually |
188
|
|
|
|
|
|
|
need results (such as a call to L). |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=cut |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
sub _do_search { |
193
|
48
|
|
|
48
|
|
57
|
my $self = shift; |
194
|
|
|
|
|
|
|
|
195
|
48
|
|
|
|
|
119
|
my $query_string = $self->build_select_query(); |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
# If we're about to redo the search, we need an empty set of items |
198
|
48
|
|
|
|
|
1001
|
delete $self->{'items'}; |
199
|
|
|
|
|
|
|
|
200
|
48
|
|
|
|
|
95
|
my $records = $self->_handle->simple_query($query_string); |
201
|
48
|
50
|
|
|
|
110
|
return 0 unless $records; |
202
|
48
|
|
|
|
|
57
|
my @names = @{ $records->{NAME_lc} }; |
|
48
|
|
|
|
|
748
|
|
203
|
48
|
|
|
|
|
137
|
my $data = {}; |
204
|
|
|
|
|
|
|
|
205
|
48
|
100
|
|
|
|
56
|
my @tables = map { $_->{alias} } values %{ $self->prefetch_related || {} }; |
|
3
|
|
|
|
|
25
|
|
|
48
|
|
|
|
|
130
|
|
206
|
|
|
|
|
|
|
|
207
|
48
|
100
|
|
|
|
426
|
unless ( @tables ) { |
208
|
45
|
|
|
|
|
781
|
while ( my $row = $records->fetchrow_hashref() ) { |
209
|
|
|
|
|
|
|
$row->{ substr($_, 5) } = delete $row->{ $_ } |
210
|
130
|
|
|
|
|
1197
|
foreach grep rindex($_, "main_", 0) == 0, keys %$row; |
211
|
130
|
|
|
|
|
297
|
my $item = $self->new_item; |
212
|
130
|
|
|
|
|
320
|
$item->load_from_hash($row, fast => 1); |
213
|
130
|
|
|
|
|
250
|
$self->add_record($item); |
214
|
|
|
|
|
|
|
} |
215
|
45
|
50
|
|
|
|
189
|
if ( $records->err ) { |
216
|
0
|
|
|
|
|
0
|
$self->{'must_redo_search'} = 0; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
45
|
|
|
|
|
119
|
return $self->_record_count; |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
3
|
|
|
|
|
3
|
my @order; |
223
|
3
|
|
|
|
|
5
|
my $i = 1; |
224
|
3
|
|
|
|
|
60
|
while ( my $base_row = $records->fetchrow_hashref() ) { |
225
|
18
|
|
|
|
|
23
|
my $main_pkey = $base_row->{ $names[0] }; |
226
|
18
|
50
|
|
|
|
29
|
$main_pkey = 'unique-'.$i++ if $self->{group_by}; |
227
|
18
|
100
|
100
|
|
|
61
|
push @order, $main_pkey |
228
|
|
|
|
|
|
|
unless ( $order[0] && $order[-1] eq $main_pkey ); |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
# let's chop the row into subrows; |
231
|
18
|
|
|
|
|
20
|
foreach my $table ('main', @tables) { |
232
|
36
|
|
|
|
|
34
|
my %tmp = (); |
233
|
36
|
|
|
|
|
111
|
for my $k( grep rindex($_, $table ."_", 0) == 0, keys %$base_row ) { |
234
|
90
|
|
|
|
|
139
|
$tmp{ substr($k, length($table)+1) } = $base_row->{ $k }; |
235
|
|
|
|
|
|
|
} |
236
|
36
|
50
|
33
|
|
|
375
|
$data->{$main_pkey}{$table}{ $base_row->{ $table . '_id' } || $main_pkey } |
237
|
|
|
|
|
|
|
= \%tmp if keys %tmp; |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
|
241
|
3
|
|
|
|
|
7
|
foreach my $row_id (@order) { |
242
|
10
|
|
|
|
|
8
|
my $item; |
243
|
10
|
|
|
|
|
10
|
foreach my $row ( values %{ $data->{$row_id}->{'main'} } ) { |
|
10
|
|
|
|
|
22
|
|
244
|
10
|
|
|
|
|
24
|
$item = $self->new_item(); |
245
|
10
|
|
|
|
|
23
|
$item->load_from_hash($row, fast => 1); |
246
|
|
|
|
|
|
|
} |
247
|
10
|
|
|
|
|
9
|
foreach my $alias ( grep { $_ ne 'main' } keys %{ $data->{$row_id} } ) |
|
20
|
|
|
|
|
28
|
|
|
10
|
|
|
|
|
22
|
|
248
|
|
|
|
|
|
|
{ |
249
|
|
|
|
|
|
|
|
250
|
10
|
|
|
|
|
14
|
my $related_rows = $data->{$row_id}->{$alias}; |
251
|
10
|
|
|
|
|
24
|
my ( $class, $col_name ) |
252
|
|
|
|
|
|
|
= $self->class_and_column_for_alias($alias); |
253
|
10
|
50
|
|
|
|
15
|
next unless $class; |
254
|
|
|
|
|
|
|
|
255
|
17
|
|
|
|
|
20
|
my @rows = sort { $a->{id} <=> $b->{id} } |
|
18
|
|
|
|
|
33
|
|
256
|
10
|
|
|
|
|
18
|
grep { $_->{id} } values %$related_rows; |
257
|
|
|
|
|
|
|
|
258
|
10
|
100
|
|
|
|
90
|
if ( $class->isa('Jifty::DBI::Collection') ) { |
|
|
50
|
|
|
|
|
|
259
|
4
|
|
|
|
|
9
|
my $collection = $class->new( $self->_new_collection_args, |
260
|
|
|
|
|
|
|
derived => 1 ); |
261
|
4
|
|
|
|
|
7
|
foreach my $row (@rows) { |
262
|
12
|
|
|
|
|
14
|
my $entry = $collection->new_item; |
263
|
12
|
|
|
|
|
26
|
$entry->load_from_hash($row, fast => 1); |
264
|
12
|
|
|
|
|
21
|
$collection->add_record($entry); |
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
|
267
|
4
|
|
|
|
|
13
|
$item->prefetched( $col_name => $collection ); |
268
|
|
|
|
|
|
|
} elsif ( $class->isa('Jifty::DBI::Record') ) { |
269
|
6
|
50
|
|
|
|
9
|
warn "Multiple rows returned for $class in prefetch" |
270
|
|
|
|
|
|
|
if @rows > 1; |
271
|
6
|
|
|
|
|
7
|
my $entry = $class->new( $self->_new_record_args ); |
272
|
6
|
50
|
|
|
|
16
|
$entry->load_from_hash( shift(@rows), fast => 1 ) if @rows; |
273
|
6
|
|
|
|
|
12
|
$item->prefetched( $col_name => $entry ); |
274
|
|
|
|
|
|
|
} else { |
275
|
0
|
|
|
|
|
0
|
Carp::cluck( |
276
|
|
|
|
|
|
|
"Asked to prefetch $alias as a $class. Don't know how to handle $class" |
277
|
|
|
|
|
|
|
); |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
} |
280
|
10
|
|
|
|
|
19
|
$self->add_record($item); |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
} |
283
|
3
|
50
|
|
|
|
19
|
if ( $records->err ) { |
284
|
0
|
|
|
|
|
0
|
$self->{'must_redo_search'} = 0; |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
|
287
|
3
|
|
|
|
|
9
|
return $self->_record_count; |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
sub _new_record_args { |
291
|
192
|
|
|
192
|
|
168
|
my $self = shift; |
292
|
192
|
|
|
|
|
373
|
return ( handle => $self->_handle ); |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
sub _new_collection_args { |
296
|
5
|
|
|
5
|
|
6
|
my $self = shift; |
297
|
5
|
|
|
|
|
8
|
return ( handle => $self->_handle ); |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
=head2 add_record RECORD |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
Adds a record object to this collection. |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
This method automatically sets our "must redo search" flag to 0 and our "we have limits" flag to 1. |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
Without those two flags, counting the number of items wouldn't work. |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
=cut |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
sub add_record { |
311
|
152
|
|
|
152
|
1
|
140
|
my $self = shift; |
312
|
152
|
|
|
|
|
115
|
my $record = shift; |
313
|
152
|
|
|
|
|
291
|
$self->_is_limited(1); |
314
|
152
|
|
|
|
|
614
|
$self->{'must_redo_search'} = 0; |
315
|
152
|
|
|
|
|
120
|
push @{ $self->{'items'} }, $record; |
|
152
|
|
|
|
|
2711
|
|
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=head2 _record_count |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
This private internal method returns the number of |
321
|
|
|
|
|
|
|
L objects saved as a result of the last query. |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
=cut |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
sub _record_count { |
326
|
240
|
|
|
240
|
|
1697
|
my $self = shift; |
327
|
240
|
100
|
|
|
|
502
|
return 0 unless defined $self->{'items'}; |
328
|
220
|
|
|
|
|
162
|
return scalar @{ $self->{'items'} }; |
|
220
|
|
|
|
|
876
|
|
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
=head2 _do_count |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
This internal private method actually executes a counting operation on |
334
|
|
|
|
|
|
|
the database; it is used by L and L. |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
=cut |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
sub _do_count { |
339
|
78
|
|
|
78
|
|
84
|
my $self = shift; |
340
|
78
|
|
50
|
|
|
218
|
my $all = shift || 0; |
341
|
|
|
|
|
|
|
|
342
|
78
|
|
|
|
|
166
|
my $query_string = $self->build_select_count_query(); |
343
|
78
|
|
|
|
|
144
|
my $records = $self->_handle->simple_query($query_string); |
344
|
78
|
50
|
|
|
|
164
|
return 0 unless $records; |
345
|
|
|
|
|
|
|
|
346
|
78
|
|
|
|
|
858
|
my @row = $records->fetchrow_array(); |
347
|
78
|
50
|
|
|
|
351
|
return 0 if $records->err; |
348
|
|
|
|
|
|
|
|
349
|
78
|
50
|
|
|
|
251
|
$self->{ $all ? 'count_all' : 'raw_rows' } = $row[0]; |
350
|
|
|
|
|
|
|
|
351
|
78
|
|
|
|
|
779
|
return ( $row[0] ); |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
=head2 _apply_limits STATEMENTREF |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
This routine takes a reference to a scalar containing an SQL |
357
|
|
|
|
|
|
|
statement. It massages the statement to limit the returned rows to |
358
|
|
|
|
|
|
|
only C<< $self->rows_per_page >> rows, skipping C<< $self->first_row >> |
359
|
|
|
|
|
|
|
rows. (That is, if rows are numbered starting from 0, row number |
360
|
|
|
|
|
|
|
C<< $self->first_row >> will be the first row returned.) Note that it |
361
|
|
|
|
|
|
|
probably makes no sense to set these variables unless you are also |
362
|
|
|
|
|
|
|
enforcing an ordering on the rows (with L, say). |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
=cut |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
sub _apply_limits { |
367
|
246
|
|
|
246
|
|
210
|
my $self = shift; |
368
|
246
|
|
|
|
|
185
|
my $statementref = shift; |
369
|
246
|
|
|
|
|
433
|
$self->_handle->apply_limits( $statementref, $self->rows_per_page, |
370
|
|
|
|
|
|
|
$self->first_row ); |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
} |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
=head2 _distinct_query STATEMENTREF |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
This routine takes a reference to a scalar containing an SQL |
377
|
|
|
|
|
|
|
statement. It massages the statement to ensure a distinct result set |
378
|
|
|
|
|
|
|
is returned. |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
=cut |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
sub _distinct_query { |
383
|
5
|
|
|
5
|
|
5
|
my $self = shift; |
384
|
5
|
|
|
|
|
4
|
my $statementref = shift; |
385
|
5
|
|
|
|
|
12
|
$self->_handle->distinct_query( $statementref, $self ); |
386
|
|
|
|
|
|
|
} |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
=head2 _build_joins |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
Build up all of the joins we need to perform this query. |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
=cut |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
sub _build_joins { |
395
|
328
|
|
|
328
|
|
250
|
my $self = shift; |
396
|
|
|
|
|
|
|
|
397
|
328
|
|
|
|
|
513
|
return ( $self->_handle->_build_joins($self) ); |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
} |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
=head2 _is_joined |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
Returns true if this collection will be joining multiple tables |
404
|
|
|
|
|
|
|
together. |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
=cut |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
sub _is_joined { |
409
|
333
|
|
|
333
|
|
5763
|
my $self = shift; |
410
|
333
|
100
|
100
|
|
|
680
|
if ( $self->{'joins'} && keys %{ $self->{'joins'} } ) { |
|
325
|
|
|
|
|
785
|
|
411
|
19
|
|
|
|
|
59
|
return (1); |
412
|
|
|
|
|
|
|
} else { |
413
|
314
|
|
|
|
|
797
|
return 0; |
414
|
|
|
|
|
|
|
} |
415
|
|
|
|
|
|
|
} |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
=head2 _is_distinctly_joined |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
Returns true if this collection is joining multiple table, but is |
420
|
|
|
|
|
|
|
joining other table's distinct fields, hence resulting in distinct |
421
|
|
|
|
|
|
|
resultsets. The behaviour is undefined if called on a non-joining |
422
|
|
|
|
|
|
|
collection. |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
=cut |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
sub _is_distinctly_joined { |
427
|
18
|
|
|
18
|
|
19
|
my $self = shift; |
428
|
18
|
50
|
|
|
|
48
|
if ( $self->{'joins'} ) { |
429
|
18
|
|
|
|
|
14
|
for ( values %{ $self->{'joins'} } ) { |
|
18
|
|
|
|
|
33
|
|
430
|
18
|
100
|
|
|
|
65
|
return 0 unless $_->{is_distinct}; |
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
|
433
|
4
|
|
|
|
|
13
|
return 1; |
434
|
|
|
|
|
|
|
} |
435
|
|
|
|
|
|
|
} |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
=head2 _is_limited |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
If we've limited down this search, return true. Otherwise, return |
440
|
|
|
|
|
|
|
false. |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
C<1> means "we have limits" |
443
|
|
|
|
|
|
|
C<-1> means "we should return all rows. We want no where clause" |
444
|
|
|
|
|
|
|
C<0> means "no limits have been applied yet. |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
=cut |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
=head2 build_select_query |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
Builds a query string for a "SELECT rows from Tables" statement for |
451
|
|
|
|
|
|
|
this collection |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
=cut |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
sub build_select_query { |
456
|
246
|
|
|
246
|
1
|
471
|
my $self = shift; |
457
|
|
|
|
|
|
|
|
458
|
246
|
50
|
|
|
|
409
|
return "" if $self->derived; |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
# The initial SELECT or SELECT DISTINCT is decided later |
461
|
|
|
|
|
|
|
|
462
|
246
|
|
|
|
|
909
|
my $query_string = $self->_build_joins . " "; |
463
|
|
|
|
|
|
|
|
464
|
246
|
50
|
|
|
|
457
|
if ( $self->_is_limited ) { |
465
|
246
|
|
|
|
|
964
|
$query_string .= $self->_where_clause . " "; |
466
|
|
|
|
|
|
|
} |
467
|
246
|
100
|
|
|
|
409
|
if ( $self->distinct_required ) { |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
# DISTINCT query only required for multi-table selects |
470
|
5
|
|
|
|
|
20
|
$self->_distinct_query( \$query_string ); |
471
|
|
|
|
|
|
|
} else { |
472
|
241
|
|
|
|
|
370
|
$query_string |
473
|
|
|
|
|
|
|
= "SELECT " . $self->query_columns . " FROM $query_string"; |
474
|
241
|
|
|
|
|
409
|
$query_string .= $self->_group_clause; |
475
|
241
|
|
|
|
|
316
|
$query_string .= $self->_order_clause; |
476
|
|
|
|
|
|
|
} |
477
|
|
|
|
|
|
|
|
478
|
246
|
|
|
|
|
423
|
$self->_apply_limits( \$query_string ); |
479
|
|
|
|
|
|
|
|
480
|
246
|
|
|
|
|
1088
|
return ($query_string) |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
} |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
=head2 query_columns |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
The columns that the query would load for result items. By default |
487
|
|
|
|
|
|
|
it's everything. |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
=cut |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
sub query_columns { |
492
|
246
|
|
|
246
|
1
|
200
|
my $self = shift; |
493
|
|
|
|
|
|
|
|
494
|
246
|
|
|
|
|
245
|
my @cols = (); |
495
|
246
|
100
|
66
|
|
|
1295
|
if ( $self->{columns} and @{ $self->{columns} } ) { |
|
3
|
|
|
|
|
17
|
|
496
|
3
|
|
|
|
|
7
|
push @cols, @{ $self->{columns} }; |
|
3
|
|
|
|
|
9
|
|
497
|
|
|
|
|
|
|
} else { |
498
|
243
|
|
|
|
|
338
|
push @cols, $self->_qualified_record_columns( 'main' => $self->record_class ); |
499
|
|
|
|
|
|
|
} |
500
|
246
|
100
|
|
|
|
237
|
my %prefetch_related = %{ $self->prefetch_related || {} }; |
|
246
|
|
|
|
|
456
|
|
501
|
246
|
|
|
|
|
1704
|
foreach my $alias ( keys %prefetch_related ) { |
502
|
3
|
|
|
|
|
6
|
my $class = $prefetch_related{$alias}{class}; |
503
|
|
|
|
|
|
|
|
504
|
3
|
|
|
|
|
4
|
my $reference; |
505
|
3
|
100
|
|
|
|
23
|
if ( $class->isa('Jifty::DBI::Collection') ) { |
|
|
50
|
|
|
|
|
|
506
|
2
|
|
|
|
|
4
|
$reference = $class->record_class; |
507
|
|
|
|
|
|
|
} elsif ( $class->isa('Jifty::DBI::Record') ) { |
508
|
1
|
|
|
|
|
2
|
$reference = $class; |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
|
511
|
3
|
|
|
|
|
5
|
my $only_cols = $prefetch_related{$alias}{columns}; |
512
|
|
|
|
|
|
|
|
513
|
3
|
|
|
|
|
5
|
push @cols, $self->_qualified_record_columns( $alias => $reference, $only_cols ); |
514
|
|
|
|
|
|
|
} |
515
|
246
|
|
|
|
|
817
|
return CORE::join( ', ', @cols ); |
516
|
|
|
|
|
|
|
} |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
=head2 class_and_column_for_alias |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
Takes the alias you've assigned to a prefetched related |
521
|
|
|
|
|
|
|
object. Returns the class of the column we've declared that alias |
522
|
|
|
|
|
|
|
prefetches. |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
=cut |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
sub class_and_column_for_alias { |
527
|
10
|
|
|
10
|
1
|
9
|
my $self = shift; |
528
|
10
|
|
|
|
|
8
|
my $alias = shift; |
529
|
10
|
50
|
|
|
|
7
|
my %prefetch = %{ $self->prefetch_related || {} }; |
|
10
|
|
|
|
|
18
|
|
530
|
10
|
|
|
|
|
48
|
my $related = $prefetch{$alias}; |
531
|
10
|
50
|
|
|
|
16
|
return unless $related; |
532
|
|
|
|
|
|
|
|
533
|
10
|
|
|
|
|
21
|
return $related->{class}, $related->{name}; |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
sub _qualified_record_columns { |
537
|
246
|
|
|
246
|
|
203
|
my $self = shift; |
538
|
246
|
|
|
|
|
349
|
my $alias = shift; |
539
|
246
|
|
|
|
|
184
|
my $item = shift; |
540
|
246
|
|
|
|
|
190
|
my $only_cols = shift; |
541
|
246
|
|
66
|
|
|
544
|
my @columns = map { $_->name } grep { !$_->virtual && !$_->computed } $item->columns; |
|
998
|
|
|
|
|
3635
|
|
|
1016
|
|
|
|
|
6789
|
|
542
|
246
|
50
|
|
|
|
956
|
if ($only_cols) { |
543
|
0
|
|
|
|
|
0
|
my %wanted = map { +($_ => 1) } @{ $only_cols }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
544
|
0
|
|
|
|
|
0
|
@columns = grep { $wanted{$_} } @columns; |
|
0
|
|
|
|
|
0
|
|
545
|
|
|
|
|
|
|
} |
546
|
246
|
|
|
|
|
237
|
return map {$alias ."." . $_ ." as ". $alias ."_". $_} @columns |
|
998
|
|
|
|
|
1583
|
|
547
|
|
|
|
|
|
|
} |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
=head2 prefetch PARAMHASH |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
Prefetches properties of a related table, in the same query. Possible |
552
|
|
|
|
|
|
|
keys in the paramhash are: |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
=over |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
=item name |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
This argument is required; it specifies the name of the collection or |
559
|
|
|
|
|
|
|
record that is to be prefetched. If the name matches a column with a |
560
|
|
|
|
|
|
|
C relationship, the other arguments can be inferred, and |
561
|
|
|
|
|
|
|
this is the only parameter which needs to be passed. |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
It is possible to pass values for C which are not real columns |
564
|
|
|
|
|
|
|
in the model; these, while they won't be accessible by calling |
565
|
|
|
|
|
|
|
C<< $record-> I >> on records in this collection, will |
566
|
|
|
|
|
|
|
still be accessible by calling C<< $record->prefetched( I ) >>. |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
=item reference |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
Specifies the series of column names to traverse to extract the |
571
|
|
|
|
|
|
|
information. For instance, if groups referred to multiple users, and |
572
|
|
|
|
|
|
|
users referred to multiple phone numbers, then providing |
573
|
|
|
|
|
|
|
C would do the two necessary joins to produce a phone |
574
|
|
|
|
|
|
|
collection for all users in each group. |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
This option defaults to the name, and is irrelevant if an C is |
577
|
|
|
|
|
|
|
provided. |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
=item alias |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
Specifies an alias which has already been joined to this collection as |
582
|
|
|
|
|
|
|
the source of the prefetched data. C will also need to be |
583
|
|
|
|
|
|
|
specified. |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
=item class |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
Specifies the class of the data to preload. This is only necessary if |
588
|
|
|
|
|
|
|
C is provided, and C is not the name of a column which |
589
|
|
|
|
|
|
|
provides C information. |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
=back |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
For backwards compatibility, C can instead be called with |
594
|
|
|
|
|
|
|
C and C as its two arguments, instead of a paramhash. |
595
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
=cut |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
sub prefetch { |
599
|
3
|
|
|
3
|
1
|
25
|
my $self = shift; |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
# Back-compat |
602
|
3
|
100
|
66
|
|
|
20
|
if ( @_ and $self->{joins}{ $_[0] } ) { |
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
# First argument appears to be an alias |
605
|
1
|
|
|
|
|
4
|
@_ = ( alias => $_[0], name => $_[1] ); |
606
|
|
|
|
|
|
|
} |
607
|
|
|
|
|
|
|
|
608
|
3
|
|
|
|
|
13
|
my %args = ( |
609
|
|
|
|
|
|
|
alias => undef, |
610
|
|
|
|
|
|
|
name => undef, |
611
|
|
|
|
|
|
|
class => undef, |
612
|
|
|
|
|
|
|
reference => undef, |
613
|
|
|
|
|
|
|
columns => undef, |
614
|
|
|
|
|
|
|
@_, |
615
|
|
|
|
|
|
|
); |
616
|
|
|
|
|
|
|
|
617
|
3
|
50
|
|
|
|
8
|
die "Must at least provide name to prefetch" |
618
|
|
|
|
|
|
|
unless $args{name}; |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
# Reference defaults to name |
621
|
3
|
|
33
|
|
|
16
|
$args{reference} ||= $args{name}; |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
# If we don't have an alias, do the join |
624
|
3
|
100
|
|
|
|
8
|
if ( not $args{alias} ) { |
625
|
2
|
|
|
|
|
14
|
my ( $class, @columns ) |
626
|
|
|
|
|
|
|
= $self->find_class( split /\./, $args{reference} ); |
627
|
2
|
|
|
|
|
4
|
$args{class} = ref $class; |
628
|
2
|
|
|
|
|
23
|
( $args{alias} ) = $self->resolve_join(@columns); |
629
|
|
|
|
|
|
|
} |
630
|
|
|
|
|
|
|
|
631
|
3
|
100
|
|
|
|
9
|
if ( not $args{class} ) { |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
# Check the column |
634
|
1
|
|
|
|
|
3
|
my $column = $self->record_class->column( $args{name} ); |
635
|
1
|
50
|
|
|
|
6
|
$args{class} = $column->refers_to if $column; |
636
|
|
|
|
|
|
|
|
637
|
1
|
50
|
|
|
|
6
|
die "Don't know class" unless $args{class}; |
638
|
|
|
|
|
|
|
} |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
# Check that the class is a Jifty::DBI::Record or Jifty::DBI::Collection |
641
|
3
|
50
|
66
|
|
|
27
|
unless ( UNIVERSAL::isa( $args{class}, "Jifty::DBI::Record" ) |
642
|
|
|
|
|
|
|
or UNIVERSAL::isa( $args{class}, "Jifty::DBI::Collection" ) ) |
643
|
|
|
|
|
|
|
{ |
644
|
0
|
|
|
|
|
0
|
warn |
645
|
|
|
|
|
|
|
"Class ($args{class}) isn't a Jifty::DBI::Record or Jifty::DBI::Collection"; |
646
|
0
|
|
|
|
|
0
|
return undef; |
647
|
|
|
|
|
|
|
} |
648
|
|
|
|
|
|
|
|
649
|
3
|
50
|
|
|
|
15
|
$self->prefetch_related( {} ) unless $self->prefetch_related; |
650
|
3
|
|
|
|
|
31
|
$self->prefetch_related->{ $args{alias} } = {}; |
651
|
|
|
|
|
|
|
$self->prefetch_related->{ $args{alias} }{$_} = $args{$_} |
652
|
3
|
|
|
|
|
18
|
for qw/alias class name columns/; |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
# Return the alias, in case we made it |
655
|
3
|
|
|
|
|
50
|
return $args{alias}; |
656
|
|
|
|
|
|
|
} |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
=head2 find_column NAMES |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
Tales a chained list of column names, where all but the last element |
661
|
|
|
|
|
|
|
is the name of a column on the previous class which refers to the next |
662
|
|
|
|
|
|
|
collection or record. Returns a list of L objects |
663
|
|
|
|
|
|
|
for the list. |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
=cut |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
sub find_column { |
668
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
669
|
0
|
|
|
|
|
0
|
my @names = @_; |
670
|
|
|
|
|
|
|
|
671
|
0
|
|
|
|
|
0
|
my $last = pop @names; |
672
|
0
|
|
|
|
|
0
|
my ( $class, @columns ) = $self->find_class(@names); |
673
|
0
|
0
|
|
|
|
0
|
$class = $class->record_class |
674
|
|
|
|
|
|
|
if UNIVERSAL::isa( $class, "Jifty::DBI::Collection" ); |
675
|
0
|
|
|
|
|
0
|
my $column = $class->column($last); |
676
|
0
|
0
|
|
|
|
0
|
die "$class has no column '$last'" unless $column; |
677
|
0
|
|
|
|
|
0
|
return @columns, $column; |
678
|
|
|
|
|
|
|
} |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
=head2 find_class NAMES |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
Tales a chained list of column names, where each element is the name |
683
|
|
|
|
|
|
|
of a column on the previous class which refers to the next collection |
684
|
|
|
|
|
|
|
or record. Returns an instance of the ending class, followed by the |
685
|
|
|
|
|
|
|
list of L objects traversed to get there. |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
=cut |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
sub find_class { |
690
|
2
|
|
|
2
|
1
|
2
|
my $self = shift; |
691
|
2
|
|
|
|
|
3
|
my @names = @_; |
692
|
|
|
|
|
|
|
|
693
|
2
|
|
|
|
|
3
|
my @res; |
694
|
2
|
|
|
|
|
1
|
my $object = $self; |
695
|
2
|
|
|
|
|
4
|
my $itemclass = $self->record_class; |
696
|
2
|
|
|
|
|
5
|
while ( my $name = shift @names ) { |
697
|
2
|
|
|
|
|
6
|
my $column = $itemclass->column($name); |
698
|
2
|
50
|
|
|
|
5
|
die "$itemclass has no column '$name'" unless $column; |
699
|
|
|
|
|
|
|
|
700
|
2
|
|
|
|
|
2
|
push @res, $column; |
701
|
|
|
|
|
|
|
|
702
|
2
|
|
|
|
|
4
|
my $classname = $column->refers_to; |
703
|
2
|
50
|
|
|
|
10
|
unless ($classname) { |
704
|
0
|
|
|
|
|
0
|
die "column '$name' of $itemclass is not a reference"; |
705
|
|
|
|
|
|
|
} |
706
|
|
|
|
|
|
|
|
707
|
2
|
100
|
|
|
|
12
|
if ( UNIVERSAL::isa( $classname, 'Jifty::DBI::Collection' ) ) { |
|
|
50
|
|
|
|
|
|
708
|
1
|
|
|
|
|
3
|
$object = $classname->new( $self->_new_collection_args ); |
709
|
1
|
|
|
|
|
2
|
$itemclass = $object->record_class; |
710
|
|
|
|
|
|
|
} elsif ( UNIVERSAL::isa( $classname, 'Jifty::DBI::Record' ) ) { |
711
|
1
|
|
|
|
|
2
|
$object = $classname->new( $self->_new_record_args ); |
712
|
1
|
|
|
|
|
3
|
$itemclass = $classname; |
713
|
|
|
|
|
|
|
} else { |
714
|
0
|
|
|
|
|
0
|
die |
715
|
|
|
|
|
|
|
"Column '$name' refers to '$classname' which is not record or collection"; |
716
|
|
|
|
|
|
|
} |
717
|
|
|
|
|
|
|
} |
718
|
|
|
|
|
|
|
|
719
|
2
|
|
|
|
|
4
|
return $object, @res; |
720
|
|
|
|
|
|
|
} |
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
=head2 resolve_join COLUMNS |
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
Takes a chained list of L objects, and performs |
725
|
|
|
|
|
|
|
the requisite joins to join all of them. Returns the alias of the |
726
|
|
|
|
|
|
|
last join. |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
=cut |
729
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
sub resolve_join { |
731
|
2
|
|
|
2
|
1
|
3
|
my $self = shift; |
732
|
2
|
|
|
|
|
3
|
my @chain = @_; |
733
|
|
|
|
|
|
|
|
734
|
2
|
|
|
|
|
3
|
my $last_alias = 'main'; |
735
|
|
|
|
|
|
|
|
736
|
2
|
|
|
|
|
3
|
foreach my $column (@chain) { |
737
|
2
|
|
|
|
|
7
|
my $name = $column->name; |
738
|
|
|
|
|
|
|
|
739
|
2
|
|
|
|
|
9
|
my $classname = $column->refers_to; |
740
|
2
|
50
|
|
|
|
8
|
unless ($classname) { |
741
|
0
|
|
|
|
|
0
|
die "column '$name' of is not a reference"; |
742
|
|
|
|
|
|
|
} |
743
|
|
|
|
|
|
|
|
744
|
2
|
100
|
|
|
|
11
|
if ( UNIVERSAL::isa( $classname, 'Jifty::DBI::Collection' ) ) { |
|
|
50
|
|
|
|
|
|
745
|
1
|
|
|
|
|
2
|
my $right_alias = $self->new_alias($classname->record_class); |
746
|
1
|
|
50
|
|
|
4
|
$self->join( |
747
|
|
|
|
|
|
|
type => 'left', |
748
|
|
|
|
|
|
|
alias1 => $last_alias, |
749
|
|
|
|
|
|
|
column1 => 'id', |
750
|
|
|
|
|
|
|
alias2 => $right_alias, |
751
|
|
|
|
|
|
|
column2 => $column->by || 'id', |
752
|
|
|
|
|
|
|
is_distinct => 1, |
753
|
|
|
|
|
|
|
); |
754
|
1
|
|
|
|
|
2
|
$last_alias = $right_alias; |
755
|
|
|
|
|
|
|
} elsif ( UNIVERSAL::isa( $classname, 'Jifty::DBI::Record' ) ) { |
756
|
1
|
|
|
|
|
5
|
my $right_alias = $self->new_alias($classname); |
757
|
1
|
|
50
|
|
|
3
|
$self->join( |
758
|
|
|
|
|
|
|
type => 'left', |
759
|
|
|
|
|
|
|
alias1 => $last_alias, |
760
|
|
|
|
|
|
|
column1 => $name, |
761
|
|
|
|
|
|
|
alias2 => $right_alias, |
762
|
|
|
|
|
|
|
column2 => $column->by || 'id', |
763
|
|
|
|
|
|
|
is_distinct => 1, |
764
|
|
|
|
|
|
|
); |
765
|
1
|
|
|
|
|
3
|
$last_alias = $right_alias; |
766
|
|
|
|
|
|
|
} else { |
767
|
0
|
|
|
|
|
0
|
die |
768
|
|
|
|
|
|
|
"Column '$name' refers to '$classname' which is not record or collection"; |
769
|
|
|
|
|
|
|
} |
770
|
|
|
|
|
|
|
} |
771
|
2
|
|
|
|
|
9
|
return $last_alias; |
772
|
|
|
|
|
|
|
} |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
=head2 distinct_required |
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
Returns true if Jifty::DBI expects that this result set will end up |
777
|
|
|
|
|
|
|
with repeated rows and should be "condensed" down to a single row for |
778
|
|
|
|
|
|
|
each unique primary key. |
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
Out of the box, this method returns true if you've joined to another table. |
781
|
|
|
|
|
|
|
To add additional logic, feel free to override this method in your subclass. |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
XXX TODO: it should be possible to create a better heuristic than the simple |
784
|
|
|
|
|
|
|
"is it joined?" question we're asking now. Something along the lines of "are we |
785
|
|
|
|
|
|
|
joining this table to something that is not the other table's primary key" |
786
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
=cut |
788
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
sub distinct_required { |
790
|
324
|
|
|
324
|
1
|
263
|
my $self = shift; |
791
|
324
|
100
|
|
|
|
440
|
return ( $self->_is_joined ? !$self->_is_distinctly_joined : 0 ); |
792
|
|
|
|
|
|
|
} |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
=head2 build_select_count_query |
795
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
Builds a SELECT statement to find the number of rows this collection |
797
|
|
|
|
|
|
|
would find. |
798
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
=cut |
800
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
sub build_select_count_query { |
802
|
78
|
|
|
78
|
1
|
94
|
my $self = shift; |
803
|
|
|
|
|
|
|
|
804
|
78
|
50
|
|
|
|
146
|
return "" if $self->derived; |
805
|
|
|
|
|
|
|
|
806
|
78
|
|
|
|
|
328
|
my $query_string = $self->_build_joins . " "; |
807
|
|
|
|
|
|
|
|
808
|
78
|
50
|
|
|
|
155
|
if ( $self->_is_limited ) { |
809
|
78
|
|
|
|
|
342
|
$query_string .= $self->_where_clause . " "; |
810
|
|
|
|
|
|
|
} |
811
|
|
|
|
|
|
|
|
812
|
|
|
|
|
|
|
# DISTINCT query only required for multi-table selects |
813
|
78
|
100
|
100
|
|
|
175
|
if ( $self->distinct_required or $self->prefetch_related ) { |
814
|
11
|
|
|
|
|
32
|
$query_string = $self->_handle->distinct_count( \$query_string ); |
815
|
|
|
|
|
|
|
} else { |
816
|
67
|
|
|
|
|
299
|
$query_string = "SELECT count(main.id) FROM " . $query_string; |
817
|
|
|
|
|
|
|
} |
818
|
|
|
|
|
|
|
|
819
|
78
|
|
|
|
|
143
|
return ($query_string); |
820
|
|
|
|
|
|
|
} |
821
|
|
|
|
|
|
|
|
822
|
|
|
|
|
|
|
=head2 do_search |
823
|
|
|
|
|
|
|
|
824
|
|
|
|
|
|
|
C usually does searches "lazily". That is, it |
825
|
|
|
|
|
|
|
does a C |
826
|
|
|
|
|
|
|
for results that would need one or the other. Sometimes, you need to |
827
|
|
|
|
|
|
|
display a count of results found before you iterate over a collection, |
828
|
|
|
|
|
|
|
but you know you're about to do that too. To save a bit of wear and tear |
829
|
|
|
|
|
|
|
on your database, call C before that C. |
830
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
=cut |
832
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
sub do_search { |
834
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
835
|
0
|
0
|
|
|
|
0
|
return if $self->derived; |
836
|
0
|
0
|
|
|
|
0
|
$self->_do_search() if $self->{'must_redo_search'}; |
837
|
|
|
|
|
|
|
|
838
|
|
|
|
|
|
|
} |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
=head2 next |
841
|
|
|
|
|
|
|
|
842
|
|
|
|
|
|
|
Returns the next row from the set as an object of the type defined by |
843
|
|
|
|
|
|
|
sub new_item. When the complete set has been iterated through, |
844
|
|
|
|
|
|
|
returns undef and resets the search such that the following call to |
845
|
|
|
|
|
|
|
L will start over with the first item retrieved from the |
846
|
|
|
|
|
|
|
database. |
847
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
You may also call this method via the built-in iterator syntax. |
849
|
|
|
|
|
|
|
The two lines below are equivalent: |
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
while ($_ = $collection->next) { ... } |
852
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
while (<$collection>) { ... } |
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
=cut |
856
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
sub next { |
858
|
83
|
|
|
83
|
1
|
1958
|
my $self = shift; |
859
|
|
|
|
|
|
|
|
860
|
83
|
|
|
|
|
159
|
my $item = $self->peek; |
861
|
|
|
|
|
|
|
|
862
|
83
|
100
|
|
|
|
181
|
if ( $self->{'itemscount'} < $self->_record_count ) { |
863
|
65
|
|
|
|
|
108
|
$self->{'itemscount'}++; |
864
|
|
|
|
|
|
|
} else { #we've gone through the whole list. reset the count. |
865
|
18
|
|
|
|
|
40
|
$self->goto_first_item(); |
866
|
|
|
|
|
|
|
} |
867
|
|
|
|
|
|
|
|
868
|
83
|
|
|
|
|
223
|
return ($item); |
869
|
|
|
|
|
|
|
} |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
=head2 peek |
872
|
|
|
|
|
|
|
|
873
|
|
|
|
|
|
|
Exactly the same as next, only it doesn't move the iterator. |
874
|
|
|
|
|
|
|
|
875
|
|
|
|
|
|
|
=cut |
876
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
sub peek { |
878
|
91
|
|
|
91
|
1
|
95
|
my $self = shift; |
879
|
|
|
|
|
|
|
|
880
|
91
|
100
|
|
|
|
196
|
return (undef) unless ( $self->_is_limited ); |
881
|
|
|
|
|
|
|
|
882
|
83
|
100
|
|
|
|
555
|
$self->_do_search() if $self->{'must_redo_search'}; |
883
|
|
|
|
|
|
|
|
884
|
83
|
100
|
|
|
|
199
|
if ( $self->{'itemscount'} < $self->_record_count ) |
885
|
|
|
|
|
|
|
{ #return the next item |
886
|
67
|
|
|
|
|
120
|
my $item = ( $self->{'items'}[ $self->{'itemscount'} ] ); |
887
|
67
|
|
|
|
|
99
|
return ($item); |
888
|
|
|
|
|
|
|
} else { #no more items! |
889
|
16
|
|
|
|
|
33
|
return (undef); |
890
|
|
|
|
|
|
|
} |
891
|
|
|
|
|
|
|
} |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
=head2 goto_first_item |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
Starts the recordset counter over from the first item. The next time |
896
|
|
|
|
|
|
|
you call L, you'll get the first item returned by the database, |
897
|
|
|
|
|
|
|
as if you'd just started iterating through the result set. |
898
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
=cut |
900
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
sub goto_first_item { |
902
|
59
|
|
|
59
|
1
|
66
|
my $self = shift; |
903
|
59
|
|
|
|
|
151
|
$self->goto_item(0); |
904
|
|
|
|
|
|
|
} |
905
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
=head2 goto_item |
907
|
|
|
|
|
|
|
|
908
|
|
|
|
|
|
|
Takes an integer, n. Sets the record counter to n. the next time you |
909
|
|
|
|
|
|
|
call L, you'll get the nth item. |
910
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
=cut |
912
|
|
|
|
|
|
|
|
913
|
|
|
|
|
|
|
sub goto_item { |
914
|
65
|
|
|
65
|
1
|
80
|
my $self = shift; |
915
|
65
|
|
|
|
|
65
|
my $item = shift; |
916
|
65
|
|
|
|
|
115
|
$self->{'itemscount'} = $item; |
917
|
|
|
|
|
|
|
} |
918
|
|
|
|
|
|
|
|
919
|
|
|
|
|
|
|
=head2 first |
920
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
Returns the first item |
922
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
=cut |
924
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
sub first { |
926
|
35
|
|
|
35
|
1
|
1868
|
my $self = shift; |
927
|
35
|
|
|
|
|
260
|
$self->goto_first_item(); |
928
|
35
|
|
|
|
|
83
|
return ( $self->next ); |
929
|
|
|
|
|
|
|
} |
930
|
|
|
|
|
|
|
|
931
|
|
|
|
|
|
|
=head2 last |
932
|
|
|
|
|
|
|
|
933
|
|
|
|
|
|
|
Returns the last item |
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
=cut |
936
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
sub last { |
938
|
6
|
|
|
6
|
1
|
10
|
my $self = shift; |
939
|
6
|
|
|
|
|
13
|
$self->goto_item( ( $self->count ) - 1 ); |
940
|
6
|
|
|
|
|
10
|
return ( $self->next ); |
941
|
|
|
|
|
|
|
} |
942
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
=head2 distinct_column_values |
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
Takes a column name and returns distinct values of the column. |
946
|
|
|
|
|
|
|
Only values in the current collection are returned. |
947
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
Optional arguments are C and C to limit number of |
949
|
|
|
|
|
|
|
values returned and it makes sense to sort results. |
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
$col->distinct_column_values('column'); |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
$col->distinct_column_values(column => 'column'); |
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
$col->distinct_column_values('column', max => 10, sort => 'asc'); |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
=cut |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
sub distinct_column_values { |
960
|
4
|
|
|
4
|
1
|
881
|
my $self = shift; |
961
|
4
|
50
|
|
|
|
24
|
my %args = ( |
962
|
|
|
|
|
|
|
column => undef, |
963
|
|
|
|
|
|
|
sort => undef, |
964
|
|
|
|
|
|
|
max => undef, |
965
|
|
|
|
|
|
|
@_%2 ? (column => @_) : (@_) |
966
|
|
|
|
|
|
|
); |
967
|
|
|
|
|
|
|
|
968
|
4
|
50
|
|
|
|
14
|
return () if $self->derived; |
969
|
|
|
|
|
|
|
|
970
|
4
|
|
|
|
|
29
|
my $query_string = $self->_build_joins; |
971
|
4
|
50
|
|
|
|
8
|
if ( $self->_is_limited ) { |
972
|
0
|
|
|
|
|
0
|
$query_string .= ' '. $self->_where_clause . " "; |
973
|
|
|
|
|
|
|
} |
974
|
|
|
|
|
|
|
|
975
|
4
|
|
|
|
|
19
|
my $column = 'main.'. $args{'column'}; |
976
|
4
|
|
|
|
|
6
|
$query_string = 'SELECT DISTINCT '. $column .' FROM '. $query_string; |
977
|
|
|
|
|
|
|
|
978
|
4
|
100
|
|
|
|
8
|
if ( $args{'sort'} ) { |
979
|
3
|
100
|
|
|
|
15
|
$query_string .= ' ORDER BY '. $column |
980
|
|
|
|
|
|
|
.' '. ($args{'sort'} =~ /^des/i ? 'DESC' : 'ASC'); |
981
|
|
|
|
|
|
|
} |
982
|
|
|
|
|
|
|
|
983
|
4
|
50
|
|
|
|
27
|
my $sth = $self->_handle->simple_query( $query_string ) or return; |
984
|
4
|
|
|
|
|
6
|
my $value; |
985
|
4
|
50
|
|
|
|
25
|
$sth->bind_col(1, \$value) or return; |
986
|
4
|
|
|
|
|
4
|
my @col; |
987
|
4
|
100
|
|
|
|
8
|
if ($args{max}) { |
988
|
1
|
|
66
|
|
|
38
|
push @col, $value while 0 < $args{max}-- && $sth->fetch; |
989
|
|
|
|
|
|
|
} else { |
990
|
3
|
|
|
|
|
79
|
push @col, $value while $sth->fetch; |
991
|
|
|
|
|
|
|
} |
992
|
4
|
|
|
|
|
85
|
return @col; |
993
|
|
|
|
|
|
|
} |
994
|
|
|
|
|
|
|
|
995
|
|
|
|
|
|
|
=head2 items_array_ref |
996
|
|
|
|
|
|
|
|
997
|
|
|
|
|
|
|
Return a reference to an array containing all objects found by this |
998
|
|
|
|
|
|
|
search. |
999
|
|
|
|
|
|
|
|
1000
|
|
|
|
|
|
|
You may also call this method via the built-in array dereference syntax. |
1001
|
|
|
|
|
|
|
The two lines below are equivalent: |
1002
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
for (@{$collection->items_array_ref}) { ... } |
1004
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
for (@$collection) { ... } |
1006
|
|
|
|
|
|
|
|
1007
|
|
|
|
|
|
|
=cut |
1008
|
|
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
sub items_array_ref { |
1010
|
8
|
|
|
8
|
1
|
1626
|
my $self = shift; |
1011
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
# If we're not limited, return an empty array |
1013
|
8
|
100
|
|
|
|
17
|
return [] unless $self->_is_limited; |
1014
|
|
|
|
|
|
|
|
1015
|
|
|
|
|
|
|
# Do a search if we need to. |
1016
|
6
|
100
|
|
|
|
41
|
$self->_do_search() if $self->{'must_redo_search'}; |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
# If we've got any items in the array, return them. Otherwise, |
1019
|
|
|
|
|
|
|
# return an empty array |
1020
|
6
|
|
50
|
|
|
24
|
return ( $self->{'items'} || [] ); |
1021
|
|
|
|
|
|
|
} |
1022
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
=head2 new_item |
1024
|
|
|
|
|
|
|
|
1025
|
|
|
|
|
|
|
Should return a new object of the correct type for the current collection. |
1026
|
|
|
|
|
|
|
L method is used to determine class of the object. |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
Each record class at least once is loaded using require. This method is |
1029
|
|
|
|
|
|
|
called each time a record fetched so load attempts are cached to avoid |
1030
|
|
|
|
|
|
|
penalties. If you're sure that all record classes are loaded before |
1031
|
|
|
|
|
|
|
first use then you can override this method. |
1032
|
|
|
|
|
|
|
|
1033
|
|
|
|
|
|
|
=cut |
1034
|
|
|
|
|
|
|
|
1035
|
|
|
|
|
|
|
{ my %cache = (); |
1036
|
|
|
|
|
|
|
sub new_item { |
1037
|
185
|
|
|
185
|
1
|
337
|
my $self = shift; |
1038
|
185
|
|
|
|
|
264
|
my $class = $self->record_class(); |
1039
|
|
|
|
|
|
|
|
1040
|
185
|
50
|
|
|
|
285
|
die "Jifty::DBI::Collection needs to be subclassed; override new_item\n" |
1041
|
|
|
|
|
|
|
unless $class; |
1042
|
|
|
|
|
|
|
|
1043
|
185
|
100
|
|
|
|
324
|
unless ( exists $cache{$class} ) { |
1044
|
9
|
|
|
|
|
111
|
$class->require; |
1045
|
9
|
|
|
|
|
122
|
$cache{$class} = undef; |
1046
|
|
|
|
|
|
|
} |
1047
|
185
|
|
|
|
|
346
|
return $class->new( $self->_new_record_args ); |
1048
|
|
|
|
|
|
|
} } |
1049
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
=head2 record_class |
1051
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
Returns the record class which this is a collection of; override this |
1053
|
|
|
|
|
|
|
to subclass. Or, pass it the name of a class as an argument after |
1054
|
|
|
|
|
|
|
creating a C object to create an 'anonymous' |
1055
|
|
|
|
|
|
|
collection class. |
1056
|
|
|
|
|
|
|
|
1057
|
|
|
|
|
|
|
If you haven't specified a record class, this returns a best guess at |
1058
|
|
|
|
|
|
|
the name of the record class for this collection. |
1059
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
It uses a simple heuristic to determine the record class name -- It |
1061
|
|
|
|
|
|
|
chops "Collection" or "s" off its own name. If you want to name your |
1062
|
|
|
|
|
|
|
records and collections differently, go right ahead, but don't say we |
1063
|
|
|
|
|
|
|
didn't warn you. |
1064
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
=cut |
1066
|
|
|
|
|
|
|
|
1067
|
|
|
|
|
|
|
sub record_class { |
1068
|
948
|
|
|
948
|
1
|
804
|
my $self = shift; |
1069
|
948
|
100
|
100
|
|
|
3896
|
if (@_) { |
|
|
100
|
|
|
|
|
|
1070
|
2
|
50
|
|
|
|
7
|
$self->{record_class} = shift if (@_); |
1071
|
2
|
50
|
|
|
|
6
|
$self->{record_class} = ref $self->{record_class} |
1072
|
|
|
|
|
|
|
if ref $self->{record_class}; |
1073
|
|
|
|
|
|
|
} elsif ( not ref $self or not $self->{record_class} ) { |
1074
|
231
|
|
66
|
|
|
1065
|
my $class = ref($self) || $self; |
1075
|
231
|
50
|
|
|
|
1092
|
$class =~ s/(?
|
1076
|
|
|
|
|
|
|
|| die "Can't guess record class from $class"; |
1077
|
231
|
100
|
|
|
|
424
|
return $class unless ref $self; |
1078
|
228
|
|
|
|
|
383
|
$self->{record_class} = $class; |
1079
|
|
|
|
|
|
|
} |
1080
|
945
|
|
|
|
|
2231
|
return $self->{record_class}; |
1081
|
|
|
|
|
|
|
} |
1082
|
|
|
|
|
|
|
|
1083
|
|
|
|
|
|
|
=head2 redo_search |
1084
|
|
|
|
|
|
|
|
1085
|
|
|
|
|
|
|
Takes no arguments. Tells Jifty::DBI::Collection that the next time |
1086
|
|
|
|
|
|
|
it is asked for a record, it should re-execute the query. |
1087
|
|
|
|
|
|
|
|
1088
|
|
|
|
|
|
|
=cut |
1089
|
|
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
sub redo_search { |
1091
|
597
|
|
|
597
|
1
|
3670
|
my $self = shift; |
1092
|
597
|
|
|
|
|
765
|
$self->{'must_redo_search'} = 1; |
1093
|
597
|
|
|
|
|
1857
|
delete $self->{$_} for qw(items raw_rows count_all); |
1094
|
597
|
|
|
|
|
810
|
$self->{'itemscount'} = 0; |
1095
|
|
|
|
|
|
|
} |
1096
|
|
|
|
|
|
|
|
1097
|
|
|
|
|
|
|
=head2 unlimit |
1098
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
Unlimit clears all restrictions on this collection and resets |
1100
|
|
|
|
|
|
|
it to a "default" pristine state. Note, in particular, that |
1101
|
|
|
|
|
|
|
this means C will erase ordering and grouping |
1102
|
|
|
|
|
|
|
metadata. To find all rows without resetting this metadata, |
1103
|
|
|
|
|
|
|
use the C method. |
1104
|
|
|
|
|
|
|
|
1105
|
|
|
|
|
|
|
=cut |
1106
|
|
|
|
|
|
|
|
1107
|
|
|
|
|
|
|
sub unlimit { |
1108
|
7
|
|
|
7
|
1
|
3235
|
my $self = shift; |
1109
|
|
|
|
|
|
|
|
1110
|
7
|
|
|
|
|
19
|
$self->clean_slate(); |
1111
|
7
|
|
|
|
|
40
|
$self->_is_limited(-1); |
1112
|
|
|
|
|
|
|
} |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
=head2 find_all_rows |
1115
|
|
|
|
|
|
|
|
1116
|
|
|
|
|
|
|
C instructs this collection class to return all rows in |
1117
|
|
|
|
|
|
|
the table. (It removes the WHERE clause from your query). |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
=cut |
1120
|
|
|
|
|
|
|
|
1121
|
|
|
|
|
|
|
sub find_all_rows { |
1122
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1123
|
0
|
|
|
|
|
0
|
$self->_is_limited(-1); |
1124
|
|
|
|
|
|
|
} |
1125
|
|
|
|
|
|
|
|
1126
|
|
|
|
|
|
|
=head2 limit |
1127
|
|
|
|
|
|
|
|
1128
|
|
|
|
|
|
|
Takes a hash of parameters with the following keys: |
1129
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
=over 4 |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
=item table |
1133
|
|
|
|
|
|
|
|
1134
|
|
|
|
|
|
|
Can be set to something different than this table if a join is |
1135
|
|
|
|
|
|
|
wanted (that means we can't do recursive joins as for now). |
1136
|
|
|
|
|
|
|
|
1137
|
|
|
|
|
|
|
=item alias |
1138
|
|
|
|
|
|
|
|
1139
|
|
|
|
|
|
|
Unless alias is set, the join criteria will be taken from EXT_LINKcolumn |
1140
|
|
|
|
|
|
|
and INT_LINKcolumn and added to the criteria. If alias is set, new |
1141
|
|
|
|
|
|
|
criteria about the foreign table will be added. |
1142
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
=item column |
1144
|
|
|
|
|
|
|
|
1145
|
|
|
|
|
|
|
Column to be checked against. |
1146
|
|
|
|
|
|
|
|
1147
|
|
|
|
|
|
|
=item value |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
Should always be set and will always be quoted. If the value is a |
1150
|
|
|
|
|
|
|
subclass of Jifty::DBI::Object, the value will be interpreted to be |
1151
|
|
|
|
|
|
|
the object's id. |
1152
|
|
|
|
|
|
|
|
1153
|
|
|
|
|
|
|
=item operator |
1154
|
|
|
|
|
|
|
|
1155
|
|
|
|
|
|
|
operator is the SQL operator to use for this phrase. Possible choices include: |
1156
|
|
|
|
|
|
|
|
1157
|
|
|
|
|
|
|
=over 4 |
1158
|
|
|
|
|
|
|
|
1159
|
|
|
|
|
|
|
=item "=" |
1160
|
|
|
|
|
|
|
|
1161
|
|
|
|
|
|
|
=item "!=" |
1162
|
|
|
|
|
|
|
|
1163
|
|
|
|
|
|
|
Any other standard SQL comparison operators that your underlying |
1164
|
|
|
|
|
|
|
database supports are also valid. |
1165
|
|
|
|
|
|
|
|
1166
|
|
|
|
|
|
|
=item "LIKE" |
1167
|
|
|
|
|
|
|
|
1168
|
|
|
|
|
|
|
=item "NOT LIKE" |
1169
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
=item "MATCHES" |
1171
|
|
|
|
|
|
|
|
1172
|
|
|
|
|
|
|
MATCHES is like LIKE, except it surrounds the value with % signs. |
1173
|
|
|
|
|
|
|
|
1174
|
|
|
|
|
|
|
=item "starts_with" |
1175
|
|
|
|
|
|
|
|
1176
|
|
|
|
|
|
|
starts_with is like LIKE, except it only appends a % at the end of the string |
1177
|
|
|
|
|
|
|
|
1178
|
|
|
|
|
|
|
=item "ends_with" |
1179
|
|
|
|
|
|
|
|
1180
|
|
|
|
|
|
|
ends_with is like LIKE, except it prepends a % to the beginning of the string |
1181
|
|
|
|
|
|
|
|
1182
|
|
|
|
|
|
|
=item "IN" |
1183
|
|
|
|
|
|
|
|
1184
|
|
|
|
|
|
|
IN matches a column within a set of values. The value specified in the limit |
1185
|
|
|
|
|
|
|
should be an array reference of values. |
1186
|
|
|
|
|
|
|
|
1187
|
|
|
|
|
|
|
=item "IS" |
1188
|
|
|
|
|
|
|
|
1189
|
|
|
|
|
|
|
=item "IS NOT" |
1190
|
|
|
|
|
|
|
|
1191
|
|
|
|
|
|
|
This is useful for when you wish to match columns that contain NULL (or ones that don't). Use this operator and a value of "NULL". |
1192
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
=back |
1194
|
|
|
|
|
|
|
|
1195
|
|
|
|
|
|
|
=item escape |
1196
|
|
|
|
|
|
|
|
1197
|
|
|
|
|
|
|
If you need to escape wildcard characters (usually _ or %) in the value *explicitly* with |
1198
|
|
|
|
|
|
|
"ESCAPE", set the escape character here. Note that backslashes may require special treatment |
1199
|
|
|
|
|
|
|
(e.g. Postgres dislikes \ or \\ in queries unless we use the E'' syntax). |
1200
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
=item entry_aggregator |
1202
|
|
|
|
|
|
|
|
1203
|
|
|
|
|
|
|
Can be AND or OR (or anything else valid to aggregate two clauses in SQL) |
1204
|
|
|
|
|
|
|
|
1205
|
|
|
|
|
|
|
=item case_sensitive |
1206
|
|
|
|
|
|
|
|
1207
|
|
|
|
|
|
|
on some databases, such as postgres, setting case_sensitive to 1 will make |
1208
|
|
|
|
|
|
|
this search case sensitive. Note that this flag is ignored if the column |
1209
|
|
|
|
|
|
|
is numeric. |
1210
|
|
|
|
|
|
|
|
1211
|
|
|
|
|
|
|
=back |
1212
|
|
|
|
|
|
|
|
1213
|
|
|
|
|
|
|
=cut |
1214
|
|
|
|
|
|
|
|
1215
|
|
|
|
|
|
|
sub limit { |
1216
|
279
|
|
|
279
|
1
|
7463
|
my $self = shift; |
1217
|
279
|
|
|
|
|
1421
|
my %args = ( |
1218
|
|
|
|
|
|
|
table => undef, |
1219
|
|
|
|
|
|
|
alias => undef, |
1220
|
|
|
|
|
|
|
column => undef, |
1221
|
|
|
|
|
|
|
value => undef, |
1222
|
|
|
|
|
|
|
quote_value => 1, |
1223
|
|
|
|
|
|
|
entry_aggregator => 'or', |
1224
|
|
|
|
|
|
|
case_sensitive => undef, |
1225
|
|
|
|
|
|
|
operator => '=', |
1226
|
|
|
|
|
|
|
escape => undef, |
1227
|
|
|
|
|
|
|
subclause => undef, |
1228
|
|
|
|
|
|
|
leftjoin => undef, |
1229
|
|
|
|
|
|
|
@_ # get the real argumentlist |
1230
|
|
|
|
|
|
|
); |
1231
|
|
|
|
|
|
|
|
1232
|
279
|
50
|
|
|
|
552
|
return if $self->derived; |
1233
|
|
|
|
|
|
|
|
1234
|
|
|
|
|
|
|
#If we're performing a left join, we really want the alias to be the |
1235
|
|
|
|
|
|
|
#left join criterion. |
1236
|
|
|
|
|
|
|
|
1237
|
279
|
50
|
33
|
|
|
1413
|
if ( ( defined $args{'leftjoin'} ) |
1238
|
|
|
|
|
|
|
&& ( not defined $args{'alias'} ) ) |
1239
|
|
|
|
|
|
|
{ |
1240
|
0
|
|
|
|
|
0
|
$args{'alias'} = $args{'leftjoin'}; |
1241
|
|
|
|
|
|
|
} |
1242
|
|
|
|
|
|
|
|
1243
|
|
|
|
|
|
|
# {{{ if there's no alias set, we need to set it |
1244
|
|
|
|
|
|
|
|
1245
|
279
|
100
|
|
|
|
440
|
unless ( defined $args{'alias'} ) { |
1246
|
|
|
|
|
|
|
|
1247
|
|
|
|
|
|
|
#if the table we're looking at is the same as the main table |
1248
|
271
|
50
|
33
|
|
|
545
|
if ( !defined $args{'table'} || $args{'table'} eq $self->table ) { |
1249
|
|
|
|
|
|
|
|
1250
|
|
|
|
|
|
|
# TODO this code assumes no self joins on that table. |
1251
|
|
|
|
|
|
|
# if someone can name a case where we'd want to do that, |
1252
|
|
|
|
|
|
|
# I'll change it. |
1253
|
|
|
|
|
|
|
|
1254
|
271
|
|
|
|
|
315
|
$args{'alias'} = 'main'; |
1255
|
|
|
|
|
|
|
} |
1256
|
|
|
|
|
|
|
|
1257
|
|
|
|
|
|
|
else { |
1258
|
0
|
|
|
|
|
0
|
$args{'alias'} = $self->new_alias( $args{'table'} ); |
1259
|
|
|
|
|
|
|
} |
1260
|
|
|
|
|
|
|
} |
1261
|
|
|
|
|
|
|
|
1262
|
|
|
|
|
|
|
# }}} |
1263
|
|
|
|
|
|
|
|
1264
|
|
|
|
|
|
|
# $column_obj is undefined when the table2 argument to the join is a table |
1265
|
|
|
|
|
|
|
# name and not a collection model class. In that case, the class key |
1266
|
|
|
|
|
|
|
# doesn't exist for the join. |
1267
|
279
|
50
|
66
|
|
|
949
|
my $class |
1268
|
|
|
|
|
|
|
= $self->{joins}{ $args{alias} } |
1269
|
|
|
|
|
|
|
&& $self->{joins}{ $args{alias} }{class} |
1270
|
|
|
|
|
|
|
? $self->{joins}{ $args{alias} }{class} |
1271
|
|
|
|
|
|
|
->new( $self->_new_collection_args ) |
1272
|
|
|
|
|
|
|
: $self; |
1273
|
279
|
|
|
|
|
391
|
my $column_obj = $class->record_class->column( $args{column} ); |
1274
|
|
|
|
|
|
|
|
1275
|
279
|
100
|
100
|
|
|
848
|
$self->new_item->_apply_input_filters( |
|
|
|
66
|
|
|
|
|
1276
|
|
|
|
|
|
|
column => $column_obj, |
1277
|
|
|
|
|
|
|
value_ref => \$args{'value'}, |
1278
|
|
|
|
|
|
|
) if $column_obj && $column_obj->encode_on_select && $args{operator} !~ /IS/; |
1279
|
|
|
|
|
|
|
|
1280
|
|
|
|
|
|
|
# Ensure that the column has nothing fishy going on. We can't |
1281
|
|
|
|
|
|
|
# simply check $column_obj's truth because joins mostly join by |
1282
|
|
|
|
|
|
|
# table name, not class, and we don't track table_name -> class. |
1283
|
279
|
50
|
|
|
|
1633
|
if ($args{column} =~ /\W/) { |
1284
|
0
|
|
|
|
|
0
|
warn "Possible SQL injection on column '$args{column}' in limit at @{[join(',',(caller)[1,2])]}\n"; |
|
0
|
|
|
|
|
0
|
|
1285
|
0
|
|
|
|
|
0
|
%args = ( |
1286
|
|
|
|
|
|
|
%args, |
1287
|
|
|
|
|
|
|
column => 'id', |
1288
|
|
|
|
|
|
|
operator => '<', |
1289
|
|
|
|
|
|
|
value => 0, |
1290
|
|
|
|
|
|
|
); |
1291
|
|
|
|
|
|
|
} |
1292
|
279
|
50
|
|
|
|
1249
|
if ($args{operator} !~ /^(=|<|>|!=|<>|<=|>= |
1293
|
|
|
|
|
|
|
|(NOT\s*)?LIKE |
1294
|
|
|
|
|
|
|
|(NOT\s*)?(STARTS|ENDS)_?WITH |
1295
|
|
|
|
|
|
|
|(NOT\s*)?MATCHES |
1296
|
|
|
|
|
|
|
|IS(\s*NOT)? |
1297
|
|
|
|
|
|
|
|IN)$/ix) { |
1298
|
0
|
|
|
|
|
0
|
warn "Unknown operator '$args{operator}' in limit at @{[join(',',(caller)[1,2])]}\n"; |
|
0
|
|
|
|
|
0
|
|
1299
|
0
|
|
|
|
|
0
|
%args = ( |
1300
|
|
|
|
|
|
|
%args, |
1301
|
|
|
|
|
|
|
column => 'id', |
1302
|
|
|
|
|
|
|
operator => '<', |
1303
|
|
|
|
|
|
|
value => 0, |
1304
|
|
|
|
|
|
|
); |
1305
|
|
|
|
|
|
|
} |
1306
|
|
|
|
|
|
|
|
1307
|
|
|
|
|
|
|
|
1308
|
|
|
|
|
|
|
# Set this to the name of the column and the alias, unless we've been |
1309
|
|
|
|
|
|
|
# handed a subclause name |
1310
|
279
|
50
|
|
|
|
628
|
my $qualified_column |
1311
|
|
|
|
|
|
|
= $args{'alias'} |
1312
|
|
|
|
|
|
|
? $args{'alias'} . "." . $args{'column'} |
1313
|
|
|
|
|
|
|
: $args{'column'}; |
1314
|
279
|
|
66
|
|
|
734
|
my $clause_id = $args{'subclause'} || $qualified_column; |
1315
|
|
|
|
|
|
|
|
1316
|
|
|
|
|
|
|
|
1317
|
|
|
|
|
|
|
# make passing in an object DTRT |
1318
|
279
|
|
|
|
|
276
|
my $value_ref = ref( $args{value} ); |
1319
|
279
|
100
|
|
|
|
392
|
if ($value_ref) { |
1320
|
31
|
100
|
66
|
|
|
112
|
if ( ( $value_ref ne 'ARRAY' ) |
|
|
50
|
|
|
|
|
|
1321
|
|
|
|
|
|
|
&& $args{value}->isa('Jifty::DBI::Record') ) |
1322
|
|
|
|
|
|
|
{ |
1323
|
2
|
50
|
33
|
|
|
8
|
my $by = (defined $column_obj and defined $column_obj->by) |
1324
|
|
|
|
|
|
|
? $column_obj->by |
1325
|
|
|
|
|
|
|
: 'id'; |
1326
|
2
|
|
|
|
|
18
|
$args{value} = $args{value}->$by; |
1327
|
|
|
|
|
|
|
} elsif ( $value_ref eq 'ARRAY' ) { |
1328
|
|
|
|
|
|
|
|
1329
|
|
|
|
|
|
|
# Don't modify the original reference, it isn't polite |
1330
|
29
|
|
|
|
|
29
|
$args{value} = [ @{ $args{value} } ]; |
|
29
|
|
|
|
|
66
|
|
1331
|
62
|
100
|
66
|
|
|
174
|
map { |
1332
|
29
|
|
|
|
|
46
|
my $by = (defined $column_obj and defined $column_obj->by) |
1333
|
|
|
|
|
|
|
? $column_obj->by |
1334
|
|
|
|
|
|
|
: 'id'; |
1335
|
62
|
100
|
66
|
|
|
341
|
$_ = ( |
1336
|
|
|
|
|
|
|
( ref $_ && $_->isa('Jifty::DBI::Record') ) |
1337
|
|
|
|
|
|
|
? ( $_->$by ) |
1338
|
|
|
|
|
|
|
: $_ |
1339
|
|
|
|
|
|
|
) |
1340
|
29
|
|
|
|
|
31
|
} @{ $args{value} }; |
1341
|
|
|
|
|
|
|
} |
1342
|
|
|
|
|
|
|
} |
1343
|
|
|
|
|
|
|
|
1344
|
|
|
|
|
|
|
#since we're changing the search criteria, we need to redo the search |
1345
|
279
|
|
|
|
|
410
|
$self->redo_search(); |
1346
|
|
|
|
|
|
|
|
1347
|
|
|
|
|
|
|
#If it's a like, we supply the %s around the search term |
1348
|
279
|
100
|
|
|
|
885
|
if ( $args{'operator'} =~ /MATCHES/i ) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
1349
|
25
|
|
|
|
|
46
|
$args{'value'} = "%" . $args{'value'} . "%"; |
1350
|
|
|
|
|
|
|
} elsif ( $args{'operator'} =~ /STARTS_?WITH/i ) { |
1351
|
17
|
|
|
|
|
30
|
$args{'value'} = $args{'value'} . "%"; |
1352
|
|
|
|
|
|
|
} elsif ( $args{'operator'} =~ /ENDS_?WITH/i ) { |
1353
|
17
|
|
|
|
|
27
|
$args{'value'} = "%" . $args{'value'}; |
1354
|
|
|
|
|
|
|
} |
1355
|
279
|
|
|
|
|
536
|
$args{'operator'} =~ s/(?:MATCHES|ENDS_?WITH|STARTS_?WITH)/LIKE/i; |
1356
|
|
|
|
|
|
|
|
1357
|
|
|
|
|
|
|
# Force the value to NULL (non-quoted) if the operator is IS. |
1358
|
279
|
100
|
|
|
|
606
|
if ($args{'operator'} =~ /^IS(\s*NOT)?$/i) { |
1359
|
54
|
|
|
|
|
53
|
$args{'quote_value'} = 0; |
1360
|
54
|
|
|
|
|
58
|
$args{'value'} = 'NULL'; |
1361
|
|
|
|
|
|
|
} |
1362
|
|
|
|
|
|
|
|
1363
|
|
|
|
|
|
|
# Quote the value |
1364
|
279
|
100
|
|
|
|
396
|
if ( $args{'quote_value'} ) { |
1365
|
223
|
100
|
|
|
|
287
|
if ( $value_ref eq 'ARRAY' ) { |
1366
|
29
|
|
|
|
|
23
|
map { $_ = $self->_handle->quote_value($_) } @{ $args{'value'} }; |
|
62
|
|
|
|
|
97
|
|
|
29
|
|
|
|
|
42
|
|
1367
|
|
|
|
|
|
|
} else { |
1368
|
194
|
|
|
|
|
375
|
$args{'value'} = $self->_handle->quote_value( $args{'value'} ); |
1369
|
|
|
|
|
|
|
} |
1370
|
|
|
|
|
|
|
} |
1371
|
|
|
|
|
|
|
|
1372
|
279
|
100
|
|
|
|
440
|
if ( $args{'escape'} ) { |
1373
|
4
|
|
|
|
|
8
|
$args{'escape'} = 'ESCAPE ' . $self->_handle->quote_value( $args{escape} ); |
1374
|
|
|
|
|
|
|
} |
1375
|
|
|
|
|
|
|
|
1376
|
|
|
|
|
|
|
# If we're trying to get a leftjoin restriction, lets set |
1377
|
|
|
|
|
|
|
# $restriction to point there. otherwise, lets construct normally |
1378
|
|
|
|
|
|
|
|
1379
|
279
|
|
|
|
|
207
|
my $restriction; |
1380
|
279
|
50
|
|
|
|
399
|
if ( $args{'leftjoin'} ) { |
1381
|
0
|
|
0
|
|
|
0
|
$restriction |
1382
|
|
|
|
|
|
|
= $self->{'joins'}{ $args{'leftjoin'} }{'criteria'}{$clause_id} |
1383
|
|
|
|
|
|
|
||= []; |
1384
|
|
|
|
|
|
|
} else { |
1385
|
279
|
|
100
|
|
|
1203
|
$restriction = $self->{'restrictions'}{$clause_id} ||= []; |
1386
|
|
|
|
|
|
|
} |
1387
|
|
|
|
|
|
|
|
1388
|
|
|
|
|
|
|
# If it's a new value or we're overwriting this sort of restriction, |
1389
|
|
|
|
|
|
|
|
1390
|
279
|
100
|
66
|
|
|
863
|
if ( defined $args{'value'} && $args{'quote_value'} ) { |
1391
|
223
|
|
|
|
|
179
|
my $case_sensitive = 0; |
1392
|
223
|
100
|
|
|
|
390
|
if ( defined $args{'case_sensitive'} ) { |
|
|
100
|
|
|
|
|
|
1393
|
114
|
|
|
|
|
99
|
$case_sensitive = $args{'case_sensitive'}; |
1394
|
|
|
|
|
|
|
} |
1395
|
|
|
|
|
|
|
elsif ( $column_obj ) { |
1396
|
103
|
|
|
|
|
215
|
$case_sensitive = $column_obj->case_sensitive; |
1397
|
|
|
|
|
|
|
} |
1398
|
|
|
|
|
|
|
# don't worry about case for numeric columns_in_db |
1399
|
|
|
|
|
|
|
# only be case insensitive when we KNOW it's a text |
1400
|
223
|
100
|
100
|
|
|
1148
|
if ( $column_obj && !$case_sensitive && !$column_obj->is_string ) { |
|
|
|
100
|
|
|
|
|
1401
|
68
|
|
|
|
|
62
|
$case_sensitive = 1; |
1402
|
|
|
|
|
|
|
} |
1403
|
|
|
|
|
|
|
|
1404
|
223
|
100
|
66
|
|
|
494
|
if ( !$case_sensitive && $self->_handle->case_sensitive ) { |
1405
|
67
|
|
|
|
|
116
|
( $qualified_column, $args{'operator'}, $args{'value'} ) |
1406
|
|
|
|
|
|
|
= $self->_handle->_make_clause_case_insensitive( |
1407
|
|
|
|
|
|
|
$qualified_column, $args{'operator'}, $args{'value'} ); |
1408
|
|
|
|
|
|
|
} |
1409
|
|
|
|
|
|
|
} |
1410
|
|
|
|
|
|
|
|
1411
|
279
|
100
|
|
|
|
443
|
if ( $value_ref eq 'ARRAY' ) { |
1412
|
29
|
50
|
|
|
|
96
|
croak |
1413
|
|
|
|
|
|
|
'Limits with an array ref are only allowed with operator \'IN\' or \'=\'' |
1414
|
|
|
|
|
|
|
unless $args{'operator'} =~ /^(IN|=)$/i; |
1415
|
29
|
|
|
|
|
30
|
$args{'value'} = '( ' . join( ',', @{ $args{'value'} } ) . ' )'; |
|
29
|
|
|
|
|
78
|
|
1416
|
29
|
|
|
|
|
43
|
$args{'operator'} = 'IN'; |
1417
|
|
|
|
|
|
|
} |
1418
|
|
|
|
|
|
|
|
1419
|
279
|
|
|
|
|
770
|
my $clause = { |
1420
|
|
|
|
|
|
|
column => $qualified_column, |
1421
|
|
|
|
|
|
|
operator => $args{'operator'}, |
1422
|
|
|
|
|
|
|
value => $args{'value'}, |
1423
|
|
|
|
|
|
|
escape => $args{'escape'}, |
1424
|
|
|
|
|
|
|
}; |
1425
|
|
|
|
|
|
|
|
1426
|
|
|
|
|
|
|
# Juju because this should come _AFTER_ the EA |
1427
|
279
|
|
|
|
|
236
|
my @prefix; |
1428
|
279
|
100
|
|
|
|
567
|
if ( $self->{'_open_parens'}{$clause_id} ) { |
1429
|
1
|
|
|
|
|
3
|
@prefix = ('(') x delete $self->{'_open_parens'}{$clause_id}; |
1430
|
|
|
|
|
|
|
} |
1431
|
|
|
|
|
|
|
|
1432
|
279
|
100
|
50
|
|
|
1127
|
if ( lc( $args{'entry_aggregator'} || "" ) eq 'none' || !@$restriction ) { |
|
|
|
66
|
|
|
|
|
1433
|
274
|
|
|
|
|
419
|
@$restriction = ( @prefix, $clause ); |
1434
|
|
|
|
|
|
|
} else { |
1435
|
5
|
|
|
|
|
11
|
push @$restriction, $args{'entry_aggregator'}, @prefix, $clause; |
1436
|
|
|
|
|
|
|
} |
1437
|
|
|
|
|
|
|
|
1438
|
|
|
|
|
|
|
# We're now limited. people can do searches. |
1439
|
|
|
|
|
|
|
|
1440
|
279
|
|
|
|
|
538
|
$self->_is_limited(1); |
1441
|
|
|
|
|
|
|
|
1442
|
279
|
50
|
|
|
|
1159
|
if ( defined( $args{'alias'} ) ) { |
1443
|
279
|
|
|
|
|
780
|
return ( $args{'alias'} ); |
1444
|
|
|
|
|
|
|
} else { |
1445
|
0
|
|
|
|
|
0
|
return (1); |
1446
|
|
|
|
|
|
|
} |
1447
|
|
|
|
|
|
|
} |
1448
|
|
|
|
|
|
|
|
1449
|
|
|
|
|
|
|
=head2 open_paren CLAUSE |
1450
|
|
|
|
|
|
|
|
1451
|
|
|
|
|
|
|
Places an open parenthesis at the current location in the given C. |
1452
|
|
|
|
|
|
|
Note that this can be used for Deep Magic, and has a high likelihood |
1453
|
|
|
|
|
|
|
of allowing you to construct malformed SQL queries. Its interface |
1454
|
|
|
|
|
|
|
will probably change in the near future, but its presence allows for |
1455
|
|
|
|
|
|
|
arbitrarily complex queries. |
1456
|
|
|
|
|
|
|
|
1457
|
|
|
|
|
|
|
Here's an example, to construct a SQL WHERE clause roughly equivalent to (depending on your SQL dialect): |
1458
|
|
|
|
|
|
|
|
1459
|
|
|
|
|
|
|
parent = 12 AND task_type = 'action' |
1460
|
|
|
|
|
|
|
AND (status = 'open' |
1461
|
|
|
|
|
|
|
OR (status = 'done' |
1462
|
|
|
|
|
|
|
AND completed_on >= '2008-06-26 11:39:22')) |
1463
|
|
|
|
|
|
|
|
1464
|
|
|
|
|
|
|
You can use sub-clauses and C and C as follows: |
1465
|
|
|
|
|
|
|
|
1466
|
|
|
|
|
|
|
$col->limit( column => 'parent', value => 12 ); |
1467
|
|
|
|
|
|
|
$col->limit( column => 'task_type', value => 'action' ); |
1468
|
|
|
|
|
|
|
|
1469
|
|
|
|
|
|
|
$col->open_paren("my_clause"); |
1470
|
|
|
|
|
|
|
|
1471
|
|
|
|
|
|
|
$col->limit( subclause => "my_clause", column => 'status', value => 'open' ); |
1472
|
|
|
|
|
|
|
|
1473
|
|
|
|
|
|
|
$col->open_paren("my_clause"); |
1474
|
|
|
|
|
|
|
|
1475
|
|
|
|
|
|
|
$col->limit( subclause => "my_clause", column => 'status', |
1476
|
|
|
|
|
|
|
value => 'done', entry_aggregator => 'OR' ); |
1477
|
|
|
|
|
|
|
$col->limit( subclause => "my_clause", column => 'completed_on', |
1478
|
|
|
|
|
|
|
operator => '>=', value => '2008-06-26 11:39:22' ); |
1479
|
|
|
|
|
|
|
|
1480
|
|
|
|
|
|
|
$col->close_paren("my_clause"); |
1481
|
|
|
|
|
|
|
|
1482
|
|
|
|
|
|
|
$col->close_paren("my_clause"); |
1483
|
|
|
|
|
|
|
|
1484
|
|
|
|
|
|
|
Where the C<"my_clause"> can be any name you choose. |
1485
|
|
|
|
|
|
|
|
1486
|
|
|
|
|
|
|
=cut |
1487
|
|
|
|
|
|
|
|
1488
|
|
|
|
|
|
|
sub open_paren { |
1489
|
1
|
|
|
1
|
1
|
7
|
my ( $self, $clause ) = @_; |
1490
|
1
|
|
|
|
|
3
|
$self->{_open_parens}{$clause}++; |
1491
|
|
|
|
|
|
|
} |
1492
|
|
|
|
|
|
|
|
1493
|
|
|
|
|
|
|
=head2 close_paren CLAUSE |
1494
|
|
|
|
|
|
|
|
1495
|
|
|
|
|
|
|
Places a close parenthesis at the current location in the given C. |
1496
|
|
|
|
|
|
|
Note that this can be used for Deep Magic, and has a high likelihood |
1497
|
|
|
|
|
|
|
of allowing you to construct malformed SQL queries. Its interface |
1498
|
|
|
|
|
|
|
will probably change in the near future, but its presence allows for |
1499
|
|
|
|
|
|
|
arbitrarily complex queries. |
1500
|
|
|
|
|
|
|
|
1501
|
|
|
|
|
|
|
=cut |
1502
|
|
|
|
|
|
|
|
1503
|
|
|
|
|
|
|
# Immediate Action |
1504
|
|
|
|
|
|
|
sub close_paren { |
1505
|
1
|
|
|
1
|
1
|
6
|
my ( $self, $clause ) = @_; |
1506
|
1
|
|
50
|
|
|
4
|
my $restriction = $self->{'restrictions'}{$clause} ||= []; |
1507
|
1
|
|
|
|
|
2
|
push @$restriction, ')'; |
1508
|
|
|
|
|
|
|
} |
1509
|
|
|
|
|
|
|
|
1510
|
|
|
|
|
|
|
sub _add_subclause { |
1511
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1512
|
0
|
|
|
|
|
0
|
my $clauseid = shift; |
1513
|
0
|
|
|
|
|
0
|
my $subclause = shift; |
1514
|
|
|
|
|
|
|
|
1515
|
0
|
|
|
|
|
0
|
$self->{'subclauses'}{"$clauseid"} = $subclause; |
1516
|
|
|
|
|
|
|
|
1517
|
|
|
|
|
|
|
} |
1518
|
|
|
|
|
|
|
|
1519
|
|
|
|
|
|
|
sub _where_clause { |
1520
|
324
|
|
|
324
|
|
277
|
my $self = shift; |
1521
|
324
|
|
|
|
|
266
|
my $where_clause = ''; |
1522
|
|
|
|
|
|
|
|
1523
|
|
|
|
|
|
|
# Go through all the generic restrictions and build up the |
1524
|
|
|
|
|
|
|
# "generic_restrictions" subclause. That's the only one that the |
1525
|
|
|
|
|
|
|
# collection builds itself. Arguably, the abstraction should be |
1526
|
|
|
|
|
|
|
# better, but I don't really see where to put it. |
1527
|
324
|
|
|
|
|
411
|
$self->_compile_generic_restrictions(); |
1528
|
|
|
|
|
|
|
|
1529
|
|
|
|
|
|
|
#Go through all restriction types. Build the where clause from the |
1530
|
|
|
|
|
|
|
#Various subclauses. |
1531
|
|
|
|
|
|
|
|
1532
|
324
|
|
|
|
|
1414
|
my @subclauses = grep defined && length, |
1533
|
324
|
|
66
|
|
|
238
|
values %{ $self->{'subclauses'} }; |
1534
|
|
|
|
|
|
|
|
1535
|
324
|
100
|
|
|
|
779
|
$where_clause = " WHERE " . CORE::join( ' AND ', @subclauses ) |
1536
|
|
|
|
|
|
|
if (@subclauses); |
1537
|
|
|
|
|
|
|
|
1538
|
324
|
|
|
|
|
528
|
return ($where_clause); |
1539
|
|
|
|
|
|
|
|
1540
|
|
|
|
|
|
|
} |
1541
|
|
|
|
|
|
|
|
1542
|
|
|
|
|
|
|
#Compile the restrictions to a WHERE Clause |
1543
|
|
|
|
|
|
|
|
1544
|
|
|
|
|
|
|
sub _compile_generic_restrictions { |
1545
|
324
|
|
|
324
|
|
243
|
my $self = shift; |
1546
|
|
|
|
|
|
|
|
1547
|
324
|
|
|
|
|
494
|
delete $self->{'subclauses'}{'generic_restrictions'}; |
1548
|
|
|
|
|
|
|
|
1549
|
|
|
|
|
|
|
# Go through all the restrictions of this type. Buld up the generic subclause |
1550
|
324
|
|
|
|
|
233
|
my $result = ''; |
1551
|
324
|
|
33
|
|
|
343
|
foreach my $restriction ( grep $_ && @$_, |
|
324
|
|
|
|
|
1501
|
|
1552
|
|
|
|
|
|
|
values %{ $self->{'restrictions'} } ) |
1553
|
|
|
|
|
|
|
{ |
1554
|
312
|
100
|
|
|
|
420
|
$result .= ' AND ' if $result; |
1555
|
312
|
|
|
|
|
307
|
$result .= '('; |
1556
|
312
|
|
|
|
|
319
|
foreach my $entry (@$restriction) { |
1557
|
328
|
100
|
|
|
|
457
|
unless ( ref $entry ) { |
1558
|
10
|
|
|
|
|
14
|
$result .= ' ' . $entry . ' '; |
1559
|
|
|
|
|
|
|
} else { |
1560
|
1272
|
|
|
|
|
1613
|
$result .= join ' ', |
1561
|
318
|
|
|
|
|
421
|
grep {defined} |
1562
|
318
|
|
|
|
|
308
|
@{$entry}{qw(column operator value escape)}; |
1563
|
|
|
|
|
|
|
} |
1564
|
|
|
|
|
|
|
} |
1565
|
312
|
|
|
|
|
388
|
$result .= ')'; |
1566
|
|
|
|
|
|
|
} |
1567
|
324
|
|
|
|
|
671
|
return ( $self->{'subclauses'}{'generic_restrictions'} = $result ); |
1568
|
|
|
|
|
|
|
} |
1569
|
|
|
|
|
|
|
|
1570
|
|
|
|
|
|
|
# set $self->{$type .'_clause'} to new value |
1571
|
|
|
|
|
|
|
# redo_search only if new value is really new |
1572
|
|
|
|
|
|
|
sub _set_clause { |
1573
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1574
|
0
|
|
|
|
|
0
|
my ( $type, $value ) = @_; |
1575
|
0
|
|
|
|
|
0
|
$type .= '_clause'; |
1576
|
0
|
0
|
0
|
|
|
0
|
if ( ( $self->{$type} || '' ) ne ( $value || '' ) ) { |
|
|
|
0
|
|
|
|
|
1577
|
0
|
|
|
|
|
0
|
$self->redo_search; |
1578
|
|
|
|
|
|
|
} |
1579
|
0
|
|
|
|
|
0
|
$self->{$type} = $value; |
1580
|
|
|
|
|
|
|
} |
1581
|
|
|
|
|
|
|
|
1582
|
|
|
|
|
|
|
# stub for back-compat |
1583
|
|
|
|
|
|
|
sub _quote_value { |
1584
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1585
|
0
|
|
|
|
|
0
|
return $self->_handle->quote_value(@_); |
1586
|
|
|
|
|
|
|
} |
1587
|
|
|
|
|
|
|
|
1588
|
|
|
|
|
|
|
=head2 order_by_cols DEPRECATED |
1589
|
|
|
|
|
|
|
|
1590
|
|
|
|
|
|
|
*DEPRECATED*. Use C method. |
1591
|
|
|
|
|
|
|
|
1592
|
|
|
|
|
|
|
=cut |
1593
|
|
|
|
|
|
|
|
1594
|
|
|
|
|
|
|
sub order_by_cols { |
1595
|
0
|
|
|
0
|
1
|
0
|
require Carp; |
1596
|
0
|
|
|
|
|
0
|
Carp::cluck("order_by_cols is deprecated, use order_by method"); |
1597
|
0
|
|
|
|
|
0
|
goto &order_by; |
1598
|
|
|
|
|
|
|
} |
1599
|
|
|
|
|
|
|
|
1600
|
|
|
|
|
|
|
=head2 order_by EMPTY|HASH|ARRAY_OF_HASHES |
1601
|
|
|
|
|
|
|
|
1602
|
|
|
|
|
|
|
Orders the returned results by column(s) and/or function(s) on column(s). |
1603
|
|
|
|
|
|
|
|
1604
|
|
|
|
|
|
|
Takes a paramhash of C, C and C |
1605
|
|
|
|
|
|
|
or C and C. |
1606
|
|
|
|
|
|
|
C defaults to main. |
1607
|
|
|
|
|
|
|
C defaults to ASC(ending), DES(cending) is also a valid value. |
1608
|
|
|
|
|
|
|
C and C have no default values. |
1609
|
|
|
|
|
|
|
|
1610
|
|
|
|
|
|
|
Use C instead of C and C to order by |
1611
|
|
|
|
|
|
|
the function value. Note that if you want use a column as argument of |
1612
|
|
|
|
|
|
|
the function then you have to build correct reference with alias |
1613
|
|
|
|
|
|
|
in the C format. |
1614
|
|
|
|
|
|
|
|
1615
|
|
|
|
|
|
|
If you specify C and C, the column (and C) will be |
1616
|
|
|
|
|
|
|
wrapped in the function. This is useful for simple functions like C or |
1617
|
|
|
|
|
|
|
C. |
1618
|
|
|
|
|
|
|
|
1619
|
|
|
|
|
|
|
Use array of hashes to order by many columns/functions. |
1620
|
|
|
|
|
|
|
|
1621
|
|
|
|
|
|
|
Calling this I the ordering, it doesn't refine it. If you want to keep |
1622
|
|
|
|
|
|
|
previous ordering, use C. |
1623
|
|
|
|
|
|
|
|
1624
|
|
|
|
|
|
|
The results would be unordered if method called without arguments. |
1625
|
|
|
|
|
|
|
|
1626
|
|
|
|
|
|
|
Returns the current list of columns. |
1627
|
|
|
|
|
|
|
|
1628
|
|
|
|
|
|
|
=cut |
1629
|
|
|
|
|
|
|
|
1630
|
|
|
|
|
|
|
sub order_by { |
1631
|
7
|
|
|
7
|
1
|
32
|
my $self = shift; |
1632
|
7
|
50
|
|
|
|
47
|
return if $self->derived; |
1633
|
7
|
100
|
|
|
|
162
|
if (@_) { |
1634
|
6
|
|
|
|
|
12
|
$self->{'order_by'} = []; |
1635
|
6
|
|
|
|
|
29
|
$self->add_order_by(@_); |
1636
|
|
|
|
|
|
|
} |
1637
|
7
|
|
50
|
|
|
26
|
return ( $self->{'order_by'} || [] ); |
1638
|
|
|
|
|
|
|
} |
1639
|
|
|
|
|
|
|
|
1640
|
|
|
|
|
|
|
=head2 add_order_by EMPTY|HASH|ARRAY_OF_HASHES |
1641
|
|
|
|
|
|
|
|
1642
|
|
|
|
|
|
|
Same as order_by, except it will not reset the ordering you have already set. |
1643
|
|
|
|
|
|
|
|
1644
|
|
|
|
|
|
|
=cut |
1645
|
|
|
|
|
|
|
|
1646
|
|
|
|
|
|
|
sub add_order_by { |
1647
|
8
|
|
|
8
|
1
|
22
|
my $self = shift; |
1648
|
8
|
50
|
|
|
|
24
|
return if $self->derived; |
1649
|
8
|
50
|
|
|
|
42
|
if (@_) { |
1650
|
8
|
|
|
|
|
14
|
my @args = @_; |
1651
|
|
|
|
|
|
|
|
1652
|
8
|
100
|
|
|
|
40
|
unless ( UNIVERSAL::isa( $args[0], 'HASH' ) ) { |
1653
|
7
|
|
|
|
|
19
|
@args = {@args}; |
1654
|
|
|
|
|
|
|
} |
1655
|
8
|
|
50
|
|
|
9
|
push @{ $self->{'order_by'} ||= [] }, @args; |
|
8
|
|
|
|
|
25
|
|
1656
|
8
|
|
|
|
|
833
|
$self->redo_search(); |
1657
|
|
|
|
|
|
|
} |
1658
|
8
|
|
50
|
|
|
25
|
return ( $self->{'order_by'} || [] ); |
1659
|
|
|
|
|
|
|
} |
1660
|
|
|
|
|
|
|
|
1661
|
|
|
|
|
|
|
=head2 clear_order_by |
1662
|
|
|
|
|
|
|
|
1663
|
|
|
|
|
|
|
Clears whatever would normally get set in the ORDER BY clause. |
1664
|
|
|
|
|
|
|
|
1665
|
|
|
|
|
|
|
=cut |
1666
|
|
|
|
|
|
|
|
1667
|
|
|
|
|
|
|
sub clear_order_by { |
1668
|
1
|
|
|
1
|
1
|
1
|
my $self = shift; |
1669
|
|
|
|
|
|
|
|
1670
|
1
|
|
|
|
|
3
|
$self->{'order_by'} = []; |
1671
|
|
|
|
|
|
|
} |
1672
|
|
|
|
|
|
|
|
1673
|
|
|
|
|
|
|
=head2 _order_clause |
1674
|
|
|
|
|
|
|
|
1675
|
|
|
|
|
|
|
returns the ORDER BY clause for the search. |
1676
|
|
|
|
|
|
|
|
1677
|
|
|
|
|
|
|
=cut |
1678
|
|
|
|
|
|
|
|
1679
|
|
|
|
|
|
|
sub _order_clause { |
1680
|
256
|
|
|
256
|
|
224
|
my $self = shift; |
1681
|
|
|
|
|
|
|
|
1682
|
256
|
100
|
|
|
|
565
|
return '' unless $self->{'order_by'}; |
1683
|
|
|
|
|
|
|
|
1684
|
13
|
|
|
|
|
16
|
my $clause = ''; |
1685
|
13
|
|
|
|
|
14
|
foreach my $row ( @{ $self->{'order_by'} } ) { |
|
13
|
|
|
|
|
21
|
|
1686
|
|
|
|
|
|
|
|
1687
|
29
|
|
|
|
|
78
|
my %rowhash = ( |
1688
|
|
|
|
|
|
|
alias => 'main', |
1689
|
|
|
|
|
|
|
column => undef, |
1690
|
|
|
|
|
|
|
order => 'ASC', |
1691
|
|
|
|
|
|
|
%$row |
1692
|
|
|
|
|
|
|
); |
1693
|
29
|
100
|
|
|
|
69
|
if ( $rowhash{'order'} =~ /^des/i ) { |
1694
|
12
|
|
|
|
|
13
|
$rowhash{'order'} = "DESC"; |
1695
|
|
|
|
|
|
|
} else { |
1696
|
17
|
|
|
|
|
18
|
$rowhash{'order'} = "ASC"; |
1697
|
|
|
|
|
|
|
} |
1698
|
|
|
|
|
|
|
|
1699
|
29
|
100
|
100
|
|
|
130
|
if ( $rowhash{'function'} and not defined $rowhash{'column'} ) { |
|
|
50
|
33
|
|
|
|
|
1700
|
6
|
100
|
|
|
|
9
|
$clause .= ( $clause ? ", " : " " ); |
1701
|
6
|
|
|
|
|
8
|
$clause .= $rowhash{'function'} . ' '; |
1702
|
6
|
|
|
|
|
9
|
$clause .= $rowhash{'order'}; |
1703
|
|
|
|
|
|
|
|
1704
|
|
|
|
|
|
|
} elsif ( ( defined $rowhash{'alias'} ) |
1705
|
|
|
|
|
|
|
and ( $rowhash{'column'} ) ) |
1706
|
|
|
|
|
|
|
{ |
1707
|
23
|
50
|
|
|
|
74
|
if ($rowhash{'column'} =~ /\W/) { |
1708
|
0
|
|
|
|
|
0
|
warn "Possible SQL injection in column '$rowhash{column}' in order_by\n"; |
1709
|
0
|
|
|
|
|
0
|
next; |
1710
|
|
|
|
|
|
|
} |
1711
|
|
|
|
|
|
|
|
1712
|
23
|
100
|
|
|
|
32
|
$clause .= ( $clause ? ", " : " " ); |
1713
|
23
|
100
|
|
|
|
35
|
$clause .= $rowhash{'function'} . "(" if $rowhash{'function'}; |
1714
|
23
|
100
|
|
|
|
39
|
$clause .= $rowhash{'alias'} . "." if $rowhash{'alias'}; |
1715
|
23
|
|
|
|
|
21
|
$clause .= $rowhash{'column'}; |
1716
|
23
|
100
|
|
|
|
54
|
$clause .= ")" if $rowhash{'function'}; |
1717
|
23
|
|
|
|
|
44
|
$clause .= " " . $rowhash{'order'}; |
1718
|
|
|
|
|
|
|
} |
1719
|
|
|
|
|
|
|
} |
1720
|
13
|
100
|
|
|
|
37
|
$clause = " ORDER BY$clause " if $clause; |
1721
|
13
|
|
|
|
|
34
|
return $clause; |
1722
|
|
|
|
|
|
|
} |
1723
|
|
|
|
|
|
|
|
1724
|
|
|
|
|
|
|
=head2 group_by_cols DEPRECATED |
1725
|
|
|
|
|
|
|
|
1726
|
|
|
|
|
|
|
*DEPRECATED*. Use group_by method. |
1727
|
|
|
|
|
|
|
|
1728
|
|
|
|
|
|
|
=cut |
1729
|
|
|
|
|
|
|
|
1730
|
|
|
|
|
|
|
sub group_by_cols { |
1731
|
0
|
|
|
0
|
1
|
0
|
require Carp; |
1732
|
0
|
|
|
|
|
0
|
Carp::cluck("group_by_cols is deprecated, use group_by method"); |
1733
|
0
|
|
|
|
|
0
|
goto &group_by; |
1734
|
|
|
|
|
|
|
} |
1735
|
|
|
|
|
|
|
|
1736
|
|
|
|
|
|
|
=head2 group_by EMPTY|HASH|ARRAY_OF_HASHES |
1737
|
|
|
|
|
|
|
|
1738
|
|
|
|
|
|
|
Groups the search results by column(s) and/or function(s) on column(s). |
1739
|
|
|
|
|
|
|
|
1740
|
|
|
|
|
|
|
Takes a paramhash of C and C or C. |
1741
|
|
|
|
|
|
|
C defaults to main. |
1742
|
|
|
|
|
|
|
C and C have no default values. |
1743
|
|
|
|
|
|
|
|
1744
|
|
|
|
|
|
|
Use C instead of C and C to group by |
1745
|
|
|
|
|
|
|
the function value. Note that if you want use a column as argument |
1746
|
|
|
|
|
|
|
of the function then you have to build correct reference with alias |
1747
|
|
|
|
|
|
|
in the C format. |
1748
|
|
|
|
|
|
|
|
1749
|
|
|
|
|
|
|
Use array of hashes to group by many columns/functions. |
1750
|
|
|
|
|
|
|
|
1751
|
|
|
|
|
|
|
The method is EXPERIMENTAL and subject to change. |
1752
|
|
|
|
|
|
|
|
1753
|
|
|
|
|
|
|
=cut |
1754
|
|
|
|
|
|
|
|
1755
|
|
|
|
|
|
|
sub group_by { |
1756
|
2
|
|
|
2
|
1
|
28
|
my $self = shift; |
1757
|
|
|
|
|
|
|
|
1758
|
2
|
50
|
|
|
|
9
|
return if $self->derived; |
1759
|
2
|
|
|
|
|
19
|
my @args = @_; |
1760
|
|
|
|
|
|
|
|
1761
|
2
|
50
|
|
|
|
14
|
unless ( UNIVERSAL::isa( $args[0], 'HASH' ) ) { |
1762
|
2
|
|
|
|
|
7
|
@args = {@args}; |
1763
|
|
|
|
|
|
|
} |
1764
|
2
|
|
|
|
|
7
|
$self->{'group_by'} = \@args; |
1765
|
2
|
|
|
|
|
6
|
$self->redo_search(); |
1766
|
|
|
|
|
|
|
} |
1767
|
|
|
|
|
|
|
|
1768
|
|
|
|
|
|
|
=head2 _group_clause |
1769
|
|
|
|
|
|
|
|
1770
|
|
|
|
|
|
|
Private function to return the "GROUP BY" clause for this query. |
1771
|
|
|
|
|
|
|
|
1772
|
|
|
|
|
|
|
=cut |
1773
|
|
|
|
|
|
|
|
1774
|
|
|
|
|
|
|
sub _group_clause { |
1775
|
247
|
|
|
247
|
|
217
|
my $self = shift; |
1776
|
247
|
100
|
|
|
|
545
|
return '' unless $self->{'group_by'}; |
1777
|
|
|
|
|
|
|
|
1778
|
3
|
|
|
|
|
5
|
my $row; |
1779
|
|
|
|
|
|
|
my $clause; |
1780
|
|
|
|
|
|
|
|
1781
|
3
|
|
|
|
|
4
|
foreach $row ( @{ $self->{'group_by'} } ) { |
|
3
|
|
|
|
|
7
|
|
1782
|
3
|
|
|
|
|
12
|
my %rowhash = ( |
1783
|
|
|
|
|
|
|
alias => 'main', |
1784
|
|
|
|
|
|
|
|
1785
|
|
|
|
|
|
|
column => undef, |
1786
|
|
|
|
|
|
|
%$row |
1787
|
|
|
|
|
|
|
); |
1788
|
3
|
50
|
33
|
|
|
32
|
if ( $rowhash{'function'} ) { |
|
|
50
|
|
|
|
|
|
1789
|
0
|
0
|
|
|
|
0
|
$clause .= ( $clause ? ", " : " " ); |
1790
|
0
|
|
|
|
|
0
|
$clause .= $rowhash{'function'}; |
1791
|
|
|
|
|
|
|
|
1792
|
|
|
|
|
|
|
} elsif ( ( $rowhash{'alias'} ) |
1793
|
|
|
|
|
|
|
and ( $rowhash{'column'} ) ) |
1794
|
|
|
|
|
|
|
{ |
1795
|
3
|
50
|
|
|
|
11
|
if ($rowhash{'column'} =~ /\W/) { |
1796
|
0
|
|
|
|
|
0
|
warn "Possible SQL injection in column '$rowhash{column}' in group_by\n"; |
1797
|
0
|
|
|
|
|
0
|
next; |
1798
|
|
|
|
|
|
|
} |
1799
|
|
|
|
|
|
|
|
1800
|
3
|
50
|
|
|
|
7
|
$clause .= ( $clause ? ", " : " " ); |
1801
|
3
|
|
|
|
|
7
|
$clause .= $rowhash{'alias'} . "."; |
1802
|
3
|
|
|
|
|
10
|
$clause .= $rowhash{'column'}; |
1803
|
|
|
|
|
|
|
} |
1804
|
|
|
|
|
|
|
} |
1805
|
3
|
50
|
|
|
|
7
|
if ($clause) { |
1806
|
3
|
|
|
|
|
8
|
return " GROUP BY" . $clause . " "; |
1807
|
|
|
|
|
|
|
} else { |
1808
|
0
|
|
|
|
|
0
|
return ''; |
1809
|
|
|
|
|
|
|
} |
1810
|
|
|
|
|
|
|
} |
1811
|
|
|
|
|
|
|
|
1812
|
|
|
|
|
|
|
=head2 new_alias table_OR_CLASS |
1813
|
|
|
|
|
|
|
|
1814
|
|
|
|
|
|
|
Takes the name of a table or a Jifty::DBI::Record subclass. |
1815
|
|
|
|
|
|
|
Returns the string of a new Alias for that table, which can be used |
1816
|
|
|
|
|
|
|
to Join tables or to limit what gets found by |
1817
|
|
|
|
|
|
|
a search. |
1818
|
|
|
|
|
|
|
|
1819
|
|
|
|
|
|
|
=cut |
1820
|
|
|
|
|
|
|
|
1821
|
|
|
|
|
|
|
sub new_alias { |
1822
|
5
|
|
|
5
|
1
|
6
|
my $self = shift; |
1823
|
5
|
|
50
|
|
|
14
|
my $refers_to = shift || die "Missing parameter"; |
1824
|
5
|
|
|
|
|
5
|
my $table; |
1825
|
5
|
|
|
|
|
5
|
my $class = undef; |
1826
|
5
|
100
|
|
|
|
27
|
if ( $refers_to->can('table') ) { |
1827
|
2
|
|
|
|
|
4
|
$table = $refers_to->table; |
1828
|
2
|
|
|
|
|
8
|
$class = $refers_to; |
1829
|
|
|
|
|
|
|
} else { |
1830
|
3
|
|
|
|
|
3
|
$table = $refers_to; |
1831
|
|
|
|
|
|
|
} |
1832
|
|
|
|
|
|
|
|
1833
|
5
|
|
|
|
|
12
|
my $alias = $self->_get_alias($table); |
1834
|
|
|
|
|
|
|
|
1835
|
5
|
100
|
|
|
|
29
|
$self->{'joins'}{$alias} = { |
1836
|
|
|
|
|
|
|
alias => $alias, |
1837
|
|
|
|
|
|
|
table => $table, |
1838
|
|
|
|
|
|
|
type => 'CROSS', |
1839
|
|
|
|
|
|
|
( $class ? ( class => $class ) : () ), |
1840
|
|
|
|
|
|
|
alias_string => " CROSS JOIN $table $alias ", |
1841
|
|
|
|
|
|
|
}; |
1842
|
|
|
|
|
|
|
|
1843
|
5
|
|
|
|
|
12
|
return $alias; |
1844
|
|
|
|
|
|
|
} |
1845
|
|
|
|
|
|
|
|
1846
|
|
|
|
|
|
|
# _get_alias is a private function which takes an tablename and |
1847
|
|
|
|
|
|
|
# returns a new alias for that table without adding something to |
1848
|
|
|
|
|
|
|
# self->{'joins'}. This function is used by new_alias and the |
1849
|
|
|
|
|
|
|
# as-yet-unnamed left join code |
1850
|
|
|
|
|
|
|
|
1851
|
|
|
|
|
|
|
sub _get_alias { |
1852
|
14
|
|
|
14
|
|
15
|
my $self = shift; |
1853
|
14
|
|
|
|
|
12
|
my $table = shift; |
1854
|
|
|
|
|
|
|
|
1855
|
14
|
|
|
|
|
49
|
return $table . "_" . ++$self->{'alias_count'}; |
1856
|
|
|
|
|
|
|
} |
1857
|
|
|
|
|
|
|
|
1858
|
|
|
|
|
|
|
=head2 join |
1859
|
|
|
|
|
|
|
|
1860
|
|
|
|
|
|
|
Join instructs Jifty::DBI::Collection to join two tables. |
1861
|
|
|
|
|
|
|
|
1862
|
|
|
|
|
|
|
The standard form takes a paramhash with keys C, C, C |
1863
|
|
|
|
|
|
|
and C. C and C are column aliases obtained from |
1864
|
|
|
|
|
|
|
$self->new_alias or a $self->limit. C and C are the columns |
1865
|
|
|
|
|
|
|
in C and C that should be linked, respectively. For this |
1866
|
|
|
|
|
|
|
type of join, this method has no return value. |
1867
|
|
|
|
|
|
|
|
1868
|
|
|
|
|
|
|
Supplying the parameter C => 'left' causes Join to perform a left |
1869
|
|
|
|
|
|
|
join. in this case, it takes C, C, C and |
1870
|
|
|
|
|
|
|
C. Because of the way that left joins work, this method needs a |
1871
|
|
|
|
|
|
|
table for the second column rather than merely an alias. For this type |
1872
|
|
|
|
|
|
|
of join, it will return the alias generated by the join. |
1873
|
|
|
|
|
|
|
|
1874
|
|
|
|
|
|
|
The parameter C defaults C<=>, but you can specify other |
1875
|
|
|
|
|
|
|
operators to join with. |
1876
|
|
|
|
|
|
|
|
1877
|
|
|
|
|
|
|
Passing a true value for the C parameter allows one to |
1878
|
|
|
|
|
|
|
specify that, despite the join, the original table's rows are will all |
1879
|
|
|
|
|
|
|
still be distinct. |
1880
|
|
|
|
|
|
|
|
1881
|
|
|
|
|
|
|
Instead of C/C, it's possible to specify expression, to join |
1882
|
|
|
|
|
|
|
C/C on an arbitrary expression. |
1883
|
|
|
|
|
|
|
|
1884
|
|
|
|
|
|
|
=cut |
1885
|
|
|
|
|
|
|
|
1886
|
|
|
|
|
|
|
sub join { |
1887
|
12
|
|
|
12
|
1
|
580
|
my $self = shift; |
1888
|
12
|
|
|
|
|
65
|
my %args = ( |
1889
|
|
|
|
|
|
|
type => 'normal', |
1890
|
|
|
|
|
|
|
column1 => undef, |
1891
|
|
|
|
|
|
|
alias1 => 'main', |
1892
|
|
|
|
|
|
|
table2 => undef, |
1893
|
|
|
|
|
|
|
column2 => undef, |
1894
|
|
|
|
|
|
|
alias2 => undef, |
1895
|
|
|
|
|
|
|
@_ |
1896
|
|
|
|
|
|
|
); |
1897
|
|
|
|
|
|
|
|
1898
|
12
|
50
|
|
|
|
39
|
return if $self->derived; |
1899
|
12
|
|
|
|
|
71
|
$self->_handle->join( collection => $self, %args ); |
1900
|
|
|
|
|
|
|
|
1901
|
|
|
|
|
|
|
} |
1902
|
|
|
|
|
|
|
|
1903
|
|
|
|
|
|
|
=head2 set_page_info [per_page => NUMBER,] [current_page => NUMBER] |
1904
|
|
|
|
|
|
|
|
1905
|
|
|
|
|
|
|
Sets the current page (one-based) and number of items per page on the |
1906
|
|
|
|
|
|
|
pager object, and pulls the number of elements from the collection. |
1907
|
|
|
|
|
|
|
This both sets up the collection's L object so that you |
1908
|
|
|
|
|
|
|
can use its calculations, and sets the L |
1909
|
|
|
|
|
|
|
C and C so that queries return values from |
1910
|
|
|
|
|
|
|
the selected page. |
1911
|
|
|
|
|
|
|
|
1912
|
|
|
|
|
|
|
If a C of C is passed, then paging is basically disabled |
1913
|
|
|
|
|
|
|
(by setting C to the number of entries, and C to 1) |
1914
|
|
|
|
|
|
|
|
1915
|
|
|
|
|
|
|
=cut |
1916
|
|
|
|
|
|
|
|
1917
|
|
|
|
|
|
|
sub set_page_info { |
1918
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1919
|
0
|
|
|
|
|
0
|
my %args = ( |
1920
|
|
|
|
|
|
|
per_page => 0, |
1921
|
|
|
|
|
|
|
current_page => 1, # 1-based |
1922
|
|
|
|
|
|
|
@_ |
1923
|
|
|
|
|
|
|
); |
1924
|
0
|
0
|
|
|
|
0
|
return if $self->derived; |
1925
|
|
|
|
|
|
|
|
1926
|
0
|
|
|
|
|
0
|
my $weakself = $self; |
1927
|
0
|
|
|
|
|
0
|
weaken($weakself); |
1928
|
|
|
|
|
|
|
|
1929
|
0
|
|
|
0
|
|
0
|
my $total_entries = lazy { $weakself->count_all }; |
|
0
|
|
|
|
|
0
|
|
1930
|
|
|
|
|
|
|
|
1931
|
0
|
0
|
|
|
|
0
|
if ($args{'current_page'} eq 'all') { |
1932
|
0
|
|
|
|
|
0
|
$args{'current_page'} = 1; |
1933
|
0
|
|
|
|
|
0
|
$args{'per_page'} = $total_entries; |
1934
|
|
|
|
|
|
|
} |
1935
|
|
|
|
|
|
|
|
1936
|
0
|
|
|
|
|
0
|
$self->pager->total_entries($total_entries) |
1937
|
|
|
|
|
|
|
->entries_per_page( $args{'per_page'} ) |
1938
|
|
|
|
|
|
|
->current_page( $args{'current_page'} ); |
1939
|
|
|
|
|
|
|
|
1940
|
0
|
|
|
|
|
0
|
$self->rows_per_page( $args{'per_page'} ); |
1941
|
|
|
|
|
|
|
|
1942
|
|
|
|
|
|
|
# We're not using $pager->first because it automatically does a count_all |
1943
|
|
|
|
|
|
|
# to correctly return '0' for empty collections |
1944
|
0
|
|
|
|
|
0
|
$self->first_row( ( $args{'current_page'} - 1 ) * $args{'per_page'} + 1 ); |
1945
|
|
|
|
|
|
|
|
1946
|
|
|
|
|
|
|
} |
1947
|
|
|
|
|
|
|
|
1948
|
|
|
|
|
|
|
=head2 rows_per_page |
1949
|
|
|
|
|
|
|
|
1950
|
|
|
|
|
|
|
limits the number of rows returned by the database. Optionally, takes |
1951
|
|
|
|
|
|
|
an integer which restricts the # of rows returned in a result Returns |
1952
|
|
|
|
|
|
|
the number of rows the database should display. |
1953
|
|
|
|
|
|
|
|
1954
|
|
|
|
|
|
|
=cut |
1955
|
|
|
|
|
|
|
|
1956
|
|
|
|
|
|
|
=head2 first_row |
1957
|
|
|
|
|
|
|
|
1958
|
|
|
|
|
|
|
Get or set the first row of the result set the database should return. |
1959
|
|
|
|
|
|
|
Takes an optional single integer argument. Returns the currently set |
1960
|
|
|
|
|
|
|
integer first row that the database should return. |
1961
|
|
|
|
|
|
|
|
1962
|
|
|
|
|
|
|
|
1963
|
|
|
|
|
|
|
=cut |
1964
|
|
|
|
|
|
|
|
1965
|
|
|
|
|
|
|
# returns the first row |
1966
|
|
|
|
|
|
|
sub first_row { |
1967
|
246
|
|
|
246
|
1
|
1099
|
my $self = shift; |
1968
|
246
|
50
|
|
|
|
358
|
if (@_) { |
1969
|
0
|
|
|
|
|
0
|
$self->{'first_row'} = shift; |
1970
|
|
|
|
|
|
|
|
1971
|
|
|
|
|
|
|
#SQL starts counting at 0 |
1972
|
0
|
|
|
|
|
0
|
$self->{'first_row'}--; |
1973
|
|
|
|
|
|
|
|
1974
|
|
|
|
|
|
|
#gotta redo the search if changing pages |
1975
|
0
|
|
|
|
|
0
|
$self->redo_search(); |
1976
|
|
|
|
|
|
|
} |
1977
|
246
|
|
|
|
|
622
|
return ( $self->{'first_row'} ); |
1978
|
|
|
|
|
|
|
} |
1979
|
|
|
|
|
|
|
|
1980
|
|
|
|
|
|
|
=head2 _items_counter |
1981
|
|
|
|
|
|
|
|
1982
|
|
|
|
|
|
|
Returns the current position in the record set. |
1983
|
|
|
|
|
|
|
|
1984
|
|
|
|
|
|
|
=cut |
1985
|
|
|
|
|
|
|
|
1986
|
|
|
|
|
|
|
sub _items_counter { |
1987
|
10
|
|
|
10
|
|
9
|
my $self = shift; |
1988
|
10
|
|
|
|
|
17
|
return $self->{'itemscount'}; |
1989
|
|
|
|
|
|
|
} |
1990
|
|
|
|
|
|
|
|
1991
|
|
|
|
|
|
|
=head2 count |
1992
|
|
|
|
|
|
|
|
1993
|
|
|
|
|
|
|
Returns the number of records in the set. |
1994
|
|
|
|
|
|
|
|
1995
|
|
|
|
|
|
|
=cut |
1996
|
|
|
|
|
|
|
|
1997
|
|
|
|
|
|
|
sub count { |
1998
|
119
|
|
|
119
|
1
|
1294
|
my $self = shift; |
1999
|
|
|
|
|
|
|
|
2000
|
|
|
|
|
|
|
# An unlimited search returns no tickets |
2001
|
119
|
100
|
|
|
|
217
|
return 0 unless ( $self->_is_limited ); |
2002
|
|
|
|
|
|
|
|
2003
|
|
|
|
|
|
|
# If we haven't actually got all objects loaded in memory, we |
2004
|
|
|
|
|
|
|
# really just want to do a quick count from the database. |
2005
|
106
|
100
|
|
|
|
489
|
if ( $self->{'must_redo_search'} ) { |
2006
|
|
|
|
|
|
|
|
2007
|
|
|
|
|
|
|
# If we haven't already asked the database for the row count, do that |
2008
|
82
|
100
|
|
|
|
269
|
$self->_do_count unless ( $self->{'raw_rows'} ); |
2009
|
|
|
|
|
|
|
|
2010
|
|
|
|
|
|
|
#Report back the raw # of rows in the database |
2011
|
82
|
|
|
|
|
343
|
return ( $self->{'raw_rows'} ); |
2012
|
|
|
|
|
|
|
} |
2013
|
|
|
|
|
|
|
|
2014
|
|
|
|
|
|
|
# If we have loaded everything from the DB we have an |
2015
|
|
|
|
|
|
|
# accurate count already. |
2016
|
|
|
|
|
|
|
else { |
2017
|
24
|
|
|
|
|
35
|
return $self->_record_count; |
2018
|
|
|
|
|
|
|
} |
2019
|
|
|
|
|
|
|
} |
2020
|
|
|
|
|
|
|
|
2021
|
|
|
|
|
|
|
=head2 count_all |
2022
|
|
|
|
|
|
|
|
2023
|
|
|
|
|
|
|
Returns the total number of potential records in the set, ignoring any |
2024
|
|
|
|
|
|
|
limit_clause. |
2025
|
|
|
|
|
|
|
|
2026
|
|
|
|
|
|
|
=cut |
2027
|
|
|
|
|
|
|
|
2028
|
|
|
|
|
|
|
# 22:24 [Robrt(500@outer.space)] It has to do with Caching. |
2029
|
|
|
|
|
|
|
# 22:25 [Robrt(500@outer.space)] The documentation says it ignores the limit. |
2030
|
|
|
|
|
|
|
# 22:25 [Robrt(500@outer.space)] But I don't believe thats true. |
2031
|
|
|
|
|
|
|
# 22:26 [msg(Robrt)] yeah. I |
2032
|
|
|
|
|
|
|
# 22:26 [msg(Robrt)] yeah. I'm not convinced it does anything useful right now |
2033
|
|
|
|
|
|
|
# 22:26 [msg(Robrt)] especially since until a week ago, it was setting one variable and returning another |
2034
|
|
|
|
|
|
|
# 22:27 [Robrt(500@outer.space)] I remember. |
2035
|
|
|
|
|
|
|
# 22:27 [Robrt(500@outer.space)] It had to do with which Cached value was returned. |
2036
|
|
|
|
|
|
|
# 22:27 [msg(Robrt)] (given that every time we try to explain it, we get it Wrong) |
2037
|
|
|
|
|
|
|
# 22:27 [Robrt(500@outer.space)] Because Count can return a different number than actual NumberOfResults |
2038
|
|
|
|
|
|
|
# 22:28 [msg(Robrt)] in what case? |
2039
|
|
|
|
|
|
|
# 22:28 [Robrt(500@outer.space)] count_all _always_ used the return value of _do_count(), as opposed to Count which would return the cached number of |
2040
|
|
|
|
|
|
|
# results returned. |
2041
|
|
|
|
|
|
|
# 22:28 [Robrt(500@outer.space)] IIRC, if you do a search with a limit, then raw_rows will == limit. |
2042
|
|
|
|
|
|
|
# 22:31 [msg(Robrt)] ah. |
2043
|
|
|
|
|
|
|
# 22:31 [msg(Robrt)] that actually makes sense |
2044
|
|
|
|
|
|
|
# 22:31 [Robrt(500@outer.space)] You should paste this conversation into the count_all docs. |
2045
|
|
|
|
|
|
|
# 22:31 [msg(Robrt)] perhaps I'll create a new method that _actually_ do that. |
2046
|
|
|
|
|
|
|
# 22:32 [msg(Robrt)] since I'm not convinced it's been doing that correctly |
2047
|
|
|
|
|
|
|
|
2048
|
|
|
|
|
|
|
sub count_all { |
2049
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
2050
|
|
|
|
|
|
|
|
2051
|
|
|
|
|
|
|
# An unlimited search returns no tickets |
2052
|
0
|
0
|
|
|
|
0
|
return 0 unless ( $self->_is_limited ); |
2053
|
|
|
|
|
|
|
|
2054
|
|
|
|
|
|
|
# If we haven't actually got all objects loaded in memory, we |
2055
|
|
|
|
|
|
|
# really just want to do a quick count from the database. |
2056
|
0
|
0
|
0
|
|
|
0
|
if ( $self->{'must_redo_search'} || !$self->{'count_all'} ) { |
2057
|
|
|
|
|
|
|
|
2058
|
|
|
|
|
|
|
# If we haven't already asked the database for the row count, do that |
2059
|
0
|
0
|
|
|
|
0
|
$self->_do_count(1) unless ( $self->{'count_all'} ); |
2060
|
|
|
|
|
|
|
|
2061
|
|
|
|
|
|
|
#Report back the raw # of rows in the database |
2062
|
0
|
|
|
|
|
0
|
return ( $self->{'count_all'} ); |
2063
|
|
|
|
|
|
|
} |
2064
|
|
|
|
|
|
|
|
2065
|
|
|
|
|
|
|
# If we have loaded everything from the DB we have an |
2066
|
|
|
|
|
|
|
# accurate count already. |
2067
|
|
|
|
|
|
|
else { |
2068
|
0
|
|
|
|
|
0
|
return $self->_record_count; |
2069
|
|
|
|
|
|
|
} |
2070
|
|
|
|
|
|
|
} |
2071
|
|
|
|
|
|
|
|
2072
|
|
|
|
|
|
|
=head2 is_last |
2073
|
|
|
|
|
|
|
|
2074
|
|
|
|
|
|
|
Returns true if the current row is the last record in the set. |
2075
|
|
|
|
|
|
|
|
2076
|
|
|
|
|
|
|
=cut |
2077
|
|
|
|
|
|
|
|
2078
|
|
|
|
|
|
|
sub is_last { |
2079
|
18
|
|
|
18
|
1
|
2750
|
my $self = shift; |
2080
|
|
|
|
|
|
|
|
2081
|
18
|
100
|
|
|
|
51
|
return undef unless $self->count; |
2082
|
|
|
|
|
|
|
|
2083
|
10
|
100
|
|
|
|
22
|
if ( $self->_items_counter == $self->count ) { |
2084
|
6
|
|
|
|
|
18
|
return (1); |
2085
|
|
|
|
|
|
|
} else { |
2086
|
4
|
|
|
|
|
13
|
return (0); |
2087
|
|
|
|
|
|
|
} |
2088
|
|
|
|
|
|
|
} |
2089
|
|
|
|
|
|
|
|
2090
|
|
|
|
|
|
|
=head2 DEBUG |
2091
|
|
|
|
|
|
|
|
2092
|
|
|
|
|
|
|
Gets/sets the DEBUG flag. |
2093
|
|
|
|
|
|
|
|
2094
|
|
|
|
|
|
|
=cut |
2095
|
|
|
|
|
|
|
|
2096
|
|
|
|
|
|
|
sub DEBUG { |
2097
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
2098
|
0
|
0
|
|
|
|
0
|
if (@_) { |
2099
|
0
|
|
|
|
|
0
|
$self->{'DEBUG'} = shift; |
2100
|
|
|
|
|
|
|
} |
2101
|
0
|
|
|
|
|
0
|
return ( $self->{'DEBUG'} ); |
2102
|
|
|
|
|
|
|
} |
2103
|
|
|
|
|
|
|
|
2104
|
|
|
|
|
|
|
=head2 column |
2105
|
|
|
|
|
|
|
|
2106
|
|
|
|
|
|
|
Normally a collection object contains record objects populated with all columns |
2107
|
|
|
|
|
|
|
in the database, but you can restrict the records to only contain some |
2108
|
|
|
|
|
|
|
particular columns, by calling the C method once for each column you |
2109
|
|
|
|
|
|
|
are interested in. |
2110
|
|
|
|
|
|
|
|
2111
|
|
|
|
|
|
|
Takes a hash of parameters; the C, C and C keys means
2112
|
|
|
|
|
|
|
the same as in the C method. A special C key may contain |
2113
|
|
|
|
|
|
|
one of several possible kinds of expressions: |
2114
|
|
|
|
|
|
|
|
2115
|
|
|
|
|
|
|
=over 4 |
2116
|
|
|
|
|
|
|
|
2117
|
|
|
|
|
|
|
=item C |
2118
|
|
|
|
|
|
|
|
2119
|
|
|
|
|
|
|
Same as C. |
2120
|
|
|
|
|
|
|
|
2121
|
|
|
|
|
|
|
=item Expression with C> in it |
2122
|
|
|
|
|
|
|
|
2123
|
|
|
|
|
|
|
The C> is substituted with the column name, then passed verbatim to the |
2124
|
|
|
|
|
|
|
underlying C statement. |
2125
|
|
|
|
|
|
|
|
2126
|
|
|
|
|
|
|
=item Expression with C<(> in it |
2127
|
|
|
|
|
|
|
|
2128
|
|
|
|
|
|
|
The expression is passed verbatim to the underlying C. |
2129
|
|
|
|
|
|
|
|
2130
|
|
|
|
|
|
|
=item Any other expression |
2131
|
|
|
|
|
|
|
|
2132
|
|
|
|
|
|
|
The expression is taken to be a function name. For example, C means |
2133
|
|
|
|
|
|
|
the same thing as C. |
2134
|
|
|
|
|
|
|
|
2135
|
|
|
|
|
|
|
=back |
2136
|
|
|
|
|
|
|
|
2137
|
|
|
|
|
|
|
=cut |
2138
|
|
|
|
|
|
|
|
2139
|
|
|
|
|
|
|
sub column { |
2140
|
3
|
|
|
3
|
1
|
21
|
my $self = shift; |
2141
|
3
|
|
|
|
|
18
|
my %args = ( |
2142
|
|
|
|
|
|
|
table => undef, |
2143
|
|
|
|
|
|
|
alias => undef, |
2144
|
|
|
|
|
|
|
column => undef, |
2145
|
|
|
|
|
|
|
function => undef, |
2146
|
|
|
|
|
|
|
@_ |
2147
|
|
|
|
|
|
|
); |
2148
|
|
|
|
|
|
|
|
2149
|
3
|
|
33
|
|
|
14
|
my $table = $args{table} || do { |
2150
|
|
|
|
|
|
|
if ( my $alias = $args{alias} ) { |
2151
|
|
|
|
|
|
|
$alias =~ s/_\d+$//; |
2152
|
|
|
|
|
|
|
$alias; |
2153
|
|
|
|
|
|
|
} else { |
2154
|
|
|
|
|
|
|
$self->table; |
2155
|
|
|
|
|
|
|
} |
2156
|
|
|
|
|
|
|
}; |
2157
|
|
|
|
|
|
|
|
2158
|
3
|
|
50
|
|
|
26
|
my $name = ( $args{alias} || 'main' ) . '.' . $args{column}; |
2159
|
3
|
50
|
|
|
|
13
|
if ( my $func = $args{function} ) { |
2160
|
0
|
0
|
|
|
|
0
|
if ( $func =~ /^DISTINCT\s*COUNT$/i ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
2161
|
0
|
|
|
|
|
0
|
$name = "COUNT(DISTINCT $name)"; |
2162
|
|
|
|
|
|
|
} |
2163
|
|
|
|
|
|
|
|
2164
|
|
|
|
|
|
|
# If we want to substitute |
2165
|
|
|
|
|
|
|
elsif ( $func =~ /\?/ ) { |
2166
|
0
|
|
|
|
|
0
|
$name =~ s/\?/$name/g; |
2167
|
|
|
|
|
|
|
} |
2168
|
|
|
|
|
|
|
|
2169
|
|
|
|
|
|
|
# If we want to call a simple function on the column |
2170
|
|
|
|
|
|
|
elsif ( $func !~ /\(/ ) { |
2171
|
0
|
|
|
|
|
0
|
$name = "\U$func\E($name)"; |
2172
|
|
|
|
|
|
|
} else { |
2173
|
0
|
|
|
|
|
0
|
$name = $func; |
2174
|
|
|
|
|
|
|
} |
2175
|
|
|
|
|
|
|
|
2176
|
|
|
|
|
|
|
} |
2177
|
|
|
|
|
|
|
|
2178
|
3
|
|
50
|
|
|
6
|
my $column = "col" . @{ $self->{columns} ||= [] }; |
|
3
|
|
|
|
|
29
|
|
2179
|
3
|
50
|
33
|
|
|
16
|
$column = $args{column} if $table eq $self->table and !$args{alias}; |
2180
|
3
|
|
50
|
|
|
30
|
$column = ( $args{'alias'} || 'main' ) . "_" . $column; |
2181
|
3
|
|
|
|
|
4
|
push @{ $self->{columns} }, "$name AS \L$column"; |
|
3
|
|
|
|
|
18
|
|
2182
|
3
|
|
|
|
|
10
|
return $column; |
2183
|
|
|
|
|
|
|
} |
2184
|
|
|
|
|
|
|
|
2185
|
|
|
|
|
|
|
=head2 columns LIST |
2186
|
|
|
|
|
|
|
|
2187
|
|
|
|
|
|
|
Specify that we want to load only the columns in LIST, which should be |
2188
|
|
|
|
|
|
|
a list of column names. |
2189
|
|
|
|
|
|
|
|
2190
|
|
|
|
|
|
|
=cut |
2191
|
|
|
|
|
|
|
|
2192
|
|
|
|
|
|
|
sub columns { |
2193
|
1
|
|
|
1
|
1
|
6
|
my $self = shift; |
2194
|
1
|
|
|
|
|
5
|
$self->column( column => $_ ) for @_; |
2195
|
|
|
|
|
|
|
} |
2196
|
|
|
|
|
|
|
|
2197
|
|
|
|
|
|
|
=head2 columns_in_db table |
2198
|
|
|
|
|
|
|
|
2199
|
|
|
|
|
|
|
Return a list of columns in table, in lowercase. |
2200
|
|
|
|
|
|
|
|
2201
|
|
|
|
|
|
|
TODO: Why are they in lowercase? |
2202
|
|
|
|
|
|
|
|
2203
|
|
|
|
|
|
|
=cut |
2204
|
|
|
|
|
|
|
|
2205
|
|
|
|
|
|
|
sub columns_in_db { |
2206
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
2207
|
0
|
|
|
|
|
0
|
my $table = shift; |
2208
|
|
|
|
|
|
|
|
2209
|
0
|
|
|
|
|
0
|
my $dbh = $self->_handle->dbh; |
2210
|
|
|
|
|
|
|
|
2211
|
|
|
|
|
|
|
# TODO: memoize this |
2212
|
|
|
|
|
|
|
|
2213
|
0
|
|
|
|
|
0
|
return map lc( $_->[0] ), @{ ( |
2214
|
0
|
0
|
0
|
|
|
0
|
eval { |
|
|
|
0
|
|
|
|
|
2215
|
0
|
|
|
|
|
0
|
$dbh->column_info( '', '', $table, '' )->fetchall_arrayref( [3] ); |
2216
|
|
|
|
|
|
|
} |
2217
|
|
|
|
|
|
|
|| $dbh->selectall_arrayref("DESCRIBE $table;") |
2218
|
|
|
|
|
|
|
|| $dbh->selectall_arrayref("DESCRIBE \u$table;") |
2219
|
|
|
|
|
|
|
|| [] |
2220
|
|
|
|
|
|
|
) }; |
2221
|
|
|
|
|
|
|
} |
2222
|
|
|
|
|
|
|
|
2223
|
|
|
|
|
|
|
=head2 has_column { table => undef, column => undef } |
2224
|
|
|
|
|
|
|
|
2225
|
|
|
|
|
|
|
Returns true if table has column column. |
2226
|
|
|
|
|
|
|
Return false otherwise |
2227
|
|
|
|
|
|
|
|
2228
|
|
|
|
|
|
|
=cut |
2229
|
|
|
|
|
|
|
|
2230
|
|
|
|
|
|
|
sub has_column { |
2231
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
2232
|
0
|
|
|
|
|
0
|
my %args = ( |
2233
|
|
|
|
|
|
|
column => undef, |
2234
|
|
|
|
|
|
|
table => undef, |
2235
|
|
|
|
|
|
|
@_ |
2236
|
|
|
|
|
|
|
); |
2237
|
|
|
|
|
|
|
|
2238
|
0
|
0
|
|
|
|
0
|
my $table = $args{table} or die; |
2239
|
0
|
0
|
|
|
|
0
|
my $column = $args{column} or die; |
2240
|
0
|
|
|
|
|
0
|
return grep { $_ eq $column } $self->columns_in_db($table); |
|
0
|
|
|
|
|
0
|
|
2241
|
|
|
|
|
|
|
} |
2242
|
|
|
|
|
|
|
|
2243
|
|
|
|
|
|
|
=head2 table [table] |
2244
|
|
|
|
|
|
|
|
2245
|
|
|
|
|
|
|
If called with an argument, sets this collection's table. |
2246
|
|
|
|
|
|
|
|
2247
|
|
|
|
|
|
|
Always returns this collection's table. |
2248
|
|
|
|
|
|
|
|
2249
|
|
|
|
|
|
|
=cut |
2250
|
|
|
|
|
|
|
|
2251
|
|
|
|
|
|
|
sub table { |
2252
|
559
|
|
|
559
|
1
|
1285
|
my $self = shift; |
2253
|
559
|
100
|
|
|
|
1173
|
$self->{table} = shift if (@_); |
2254
|
559
|
|
|
|
|
1166
|
return $self->{table}; |
2255
|
|
|
|
|
|
|
} |
2256
|
|
|
|
|
|
|
|
2257
|
|
|
|
|
|
|
=head2 clone |
2258
|
|
|
|
|
|
|
|
2259
|
|
|
|
|
|
|
Returns copy of the current object with all search restrictions. |
2260
|
|
|
|
|
|
|
|
2261
|
|
|
|
|
|
|
=cut |
2262
|
|
|
|
|
|
|
|
2263
|
|
|
|
|
|
|
sub clone { |
2264
|
2
|
|
|
2
|
1
|
15
|
my $self = shift; |
2265
|
|
|
|
|
|
|
|
2266
|
2
|
|
|
|
|
5
|
my $obj = bless {}, ref($self); |
2267
|
2
|
|
|
|
|
25
|
%$obj = %$self; |
2268
|
|
|
|
|
|
|
|
2269
|
2
|
|
|
|
|
20
|
$obj->redo_search(); # clean out the object of data |
2270
|
|
|
|
|
|
|
|
2271
|
|
|
|
|
|
|
$obj->{$_} = Clone::clone( $obj->{$_} ) |
2272
|
2
|
|
|
|
|
12
|
for grep exists $self->{$_}, $self->_cloned_attributes; |
2273
|
2
|
|
|
|
|
6
|
return $obj; |
2274
|
|
|
|
|
|
|
} |
2275
|
|
|
|
|
|
|
|
2276
|
|
|
|
|
|
|
=head2 _cloned_attributes |
2277
|
|
|
|
|
|
|
|
2278
|
|
|
|
|
|
|
Returns list of the object's fields that should be copied. |
2279
|
|
|
|
|
|
|
|
2280
|
|
|
|
|
|
|
If your subclass store references in the object that should be copied while |
2281
|
|
|
|
|
|
|
cloning then you probably want override this method and add own values to |
2282
|
|
|
|
|
|
|
the list. |
2283
|
|
|
|
|
|
|
|
2284
|
|
|
|
|
|
|
=cut |
2285
|
|
|
|
|
|
|
|
2286
|
|
|
|
|
|
|
sub _cloned_attributes { |
2287
|
2
|
|
|
2
|
|
13
|
return qw( |
2288
|
|
|
|
|
|
|
joins |
2289
|
|
|
|
|
|
|
subclauses |
2290
|
|
|
|
|
|
|
restrictions |
2291
|
|
|
|
|
|
|
); |
2292
|
|
|
|
|
|
|
} |
2293
|
|
|
|
|
|
|
|
2294
|
|
|
|
|
|
|
=head2 each CALLBACK |
2295
|
|
|
|
|
|
|
|
2296
|
|
|
|
|
|
|
Executes the callback for each item in the collection. The callback receives as |
2297
|
|
|
|
|
|
|
arguments each record, its zero-based index, and the collection. The return |
2298
|
|
|
|
|
|
|
value of C is the original collection. |
2299
|
|
|
|
|
|
|
|
2300
|
|
|
|
|
|
|
If the callback returns zero, the iteration ends. |
2301
|
|
|
|
|
|
|
|
2302
|
|
|
|
|
|
|
=cut |
2303
|
|
|
|
|
|
|
|
2304
|
|
|
|
|
|
|
sub each { |
2305
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
2306
|
0
|
|
|
|
|
|
my $cb = shift; |
2307
|
|
|
|
|
|
|
|
2308
|
0
|
|
|
|
|
|
my $idx = 0; |
2309
|
0
|
|
|
|
|
|
$self->goto_first_item; |
2310
|
|
|
|
|
|
|
|
2311
|
0
|
|
|
|
|
|
while (my $record = $self->next) { |
2312
|
0
|
|
|
|
|
|
my $ret = $cb->($record, $idx++, $self); |
2313
|
0
|
0
|
0
|
|
|
|
last if defined($ret) && !$ret; |
2314
|
|
|
|
|
|
|
} |
2315
|
|
|
|
|
|
|
|
2316
|
0
|
|
|
|
|
|
return $self; |
2317
|
|
|
|
|
|
|
} |
2318
|
|
|
|
|
|
|
|
2319
|
|
|
|
|
|
|
1; |
2320
|
|
|
|
|
|
|
__END__ |
|