Commit | Line | Data |
ea2e61bf |
1 | package DBIx::Class::Table; |
2 | |
3 | use strict; |
4 | use warnings; |
5 | |
95a70f01 |
6 | use DBIx::Class::Cursor; |
7 | |
8 | use base qw/Class::Data::Inheritable/; |
ea2e61bf |
9 | |
10 | __PACKAGE__->mk_classdata('_columns' => {}); |
11 | |
ea2e61bf |
12 | __PACKAGE__->mk_classdata('_table_name'); |
13 | |
34d52be2 |
14 | __PACKAGE__->mk_classdata('table_alias'); # FIXME: Doesn't actually do anything yet! |
15 | |
95a70f01 |
16 | __PACKAGE__->mk_classdata('_cursor_class' => 'DBIx::Class::Cursor'); |
17 | |
525035fb |
18 | sub iterator_class { shift->_cursor_class(@_) } |
19 | |
34d52be2 |
20 | =head1 NAME |
21 | |
22 | DBIx::Class::Table - Basic table methods |
23 | |
24 | =head1 SYNOPSIS |
25 | |
26 | =head1 DESCRIPTION |
27 | |
28 | This class is responsible for defining and doing basic operations on |
29 | L<DBIx::Class> objects. |
30 | |
31 | =head1 METHODS |
32 | |
33 | =over 4 |
34 | |
39fe0e65 |
35 | =item new |
36 | |
37 | my $obj = My::Class->new($attrs); |
38 | |
39 | Creates a new object from column => value mappings passed as a hash ref |
40 | |
34d52be2 |
41 | =cut |
9bc6db13 |
42 | |
ea2e61bf |
43 | sub new { |
44 | my ($class, $attrs) = @_; |
45 | $class = ref $class if ref $class; |
46 | my $new = bless({ _column_data => { } }, $class); |
47 | if ($attrs) { |
78bab9ca |
48 | $new->throw("attrs must be a hashref" ) unless ref($attrs) eq 'HASH'; |
ea2e61bf |
49 | while (my ($k, $v) = each %{$attrs}) { |
510ca912 |
50 | $new->store_column($k => $v); |
ea2e61bf |
51 | } |
52 | } |
8fe001e1 |
53 | return $new; |
ea2e61bf |
54 | } |
55 | |
39fe0e65 |
56 | =item insert |
57 | |
58 | $obj->insert; |
59 | |
60 | Inserts an object into the database if it isn't already in there. Returns |
61 | the object itself. |
62 | |
63 | =cut |
64 | |
ea2e61bf |
65 | sub insert { |
66 | my ($self) = @_; |
39fe0e65 |
67 | return $self if $self->in_database; |
c1d23573 |
68 | #use Data::Dumper; warn Dumper($self); |
ea2e61bf |
69 | my $sth = $self->_get_sth('insert', [ keys %{$self->{_column_data}} ], |
70 | $self->_table_name, undef); |
71 | $sth->execute(values %{$self->{_column_data}}); |
39fe0e65 |
72 | $sth->finish; |
c687b87e |
73 | $self->in_database(1); |
8fe001e1 |
74 | $self->{_dirty_columns} = {}; |
ea2e61bf |
75 | return $self; |
76 | } |
77 | |
39fe0e65 |
78 | =item in_database |
79 | |
80 | $obj->in_database; # Get value |
81 | $obj->in_database(1); # Set value |
82 | |
83 | Indicated whether the object exists as a row in the database or not |
84 | |
85 | =cut |
86 | |
604d9f38 |
87 | sub in_database { |
c687b87e |
88 | my ($self, $val) = @_; |
89 | $self->{_in_database} = $val if @_ > 1; |
90 | return $self->{_in_database}; |
604d9f38 |
91 | } |
92 | |
39fe0e65 |
93 | =item create |
94 | |
95 | my $new = My::Class->create($attrs); |
96 | |
97 | A shortcut for My::Class->new($attrs)->insert; |
98 | |
99 | =cut |
100 | |
ea2e61bf |
101 | sub create { |
102 | my ($class, $attrs) = @_; |
78bab9ca |
103 | $class->throw( "create needs a hashref" ) unless ref $attrs eq 'HASH'; |
ea2e61bf |
104 | return $class->new($attrs)->insert; |
105 | } |
106 | |
39fe0e65 |
107 | =item update |
108 | |
109 | $obj->update; |
110 | |
111 | Must be run on an object that is already in the database; issues an SQL |
112 | UPDATE query to commit any changes to the object to the db if required. |
113 | |
114 | =cut |
115 | |
ea2e61bf |
116 | sub update { |
117 | my ($self) = @_; |
78bab9ca |
118 | $self->throw( "Not in database" ) unless $self->in_database; |
ea2e61bf |
119 | my @to_update = keys %{$self->{_dirty_columns} || {}}; |
a3018bd3 |
120 | return -1 unless @to_update; |
ea2e61bf |
121 | my $sth = $self->_get_sth('update', \@to_update, |
122 | $self->_table_name, $self->_ident_cond); |
a3018bd3 |
123 | my $rows = $sth->execute( (map { $self->{_column_data}{$_} } @to_update), |
ea2e61bf |
124 | $self->_ident_values ); |
39fe0e65 |
125 | $sth->finish; |
a3018bd3 |
126 | if ($rows == 0) { |
78bab9ca |
127 | $self->throw( "Can't update $self: row not found" ); |
a3018bd3 |
128 | } elsif ($rows > 1) { |
78bab9ca |
129 | $self->throw("Can't update $self: updated more than one row"); |
a3018bd3 |
130 | } |
ea2e61bf |
131 | $self->{_dirty_columns} = {}; |
132 | return $self; |
133 | } |
134 | |
39fe0e65 |
135 | =item delete |
136 | |
137 | $obj->delete |
138 | |
139 | Deletes the object from the database. The object is still perfectly usable |
140 | accessor-wise etc. but ->in_database will now return 0 and the object must |
141 | be re ->insert'ed before it can be ->update'ed |
142 | |
143 | =cut |
144 | |
ea2e61bf |
145 | sub delete { |
a3018bd3 |
146 | my $self = shift; |
147 | if (ref $self) { |
78bab9ca |
148 | $self->throw( "Not in database" ) unless $self->in_database; |
b8e1e21f |
149 | #warn $self->_ident_cond.' '.join(', ', $self->_ident_values); |
a3018bd3 |
150 | my $sth = $self->_get_sth('delete', undef, |
151 | $self->_table_name, $self->_ident_cond); |
152 | $sth->execute($self->_ident_values); |
39fe0e65 |
153 | $sth->finish; |
c687b87e |
154 | $self->in_database(undef); |
39fe0e65 |
155 | # Should probably also arrange to trash PK if auto |
a3018bd3 |
156 | } else { |
12bbb339 |
157 | my $attrs = { }; |
158 | if (@_ > 1 && ref $_[$#_] eq 'HASH') { |
159 | $attrs = { %{ pop(@_) } }; |
160 | } |
a3018bd3 |
161 | my $query = (ref $_[0] eq 'HASH' ? $_[0] : {@_}); |
12bbb339 |
162 | my ($cond, @param) = $self->_cond_resolve($query, $attrs); |
a3018bd3 |
163 | my $sth = $self->_get_sth('delete', undef, $self->_table_name, $cond); |
12bbb339 |
164 | $sth->execute(@param); |
39fe0e65 |
165 | $sth->finish; |
a3018bd3 |
166 | } |
ea2e61bf |
167 | return $self; |
168 | } |
169 | |
39fe0e65 |
170 | =item get_column |
171 | |
172 | my $val = $obj->get_column($col); |
173 | |
174 | Fetches a column value |
175 | |
176 | =cut |
177 | |
510ca912 |
178 | sub get_column { |
ea2e61bf |
179 | my ($self, $column) = @_; |
78bab9ca |
180 | $self->throw( "Can't fetch data as class method" ) unless ref $self; |
181 | $self->throw( "No such column '${column}'" ) unless $self->_columns->{$column}; |
c1d23573 |
182 | return $self->{_column_data}{$column} |
183 | if exists $self->{_column_data}{$column}; |
184 | return undef; |
ea2e61bf |
185 | } |
186 | |
39fe0e65 |
187 | =item set_column |
188 | |
189 | $obj->set_column($col => $val); |
190 | |
191 | Sets a column value; if the new value is different to the old the column |
192 | is marked as dirty for when you next call $obj->update |
193 | |
194 | =cut |
195 | |
510ca912 |
196 | sub set_column { |
197 | my $self = shift; |
198 | my ($column) = @_; |
cc0f266f |
199 | my $old = $self->get_column($column); |
510ca912 |
200 | my $ret = $self->store_column(@_); |
cc0f266f |
201 | $self->{_dirty_columns}{$column} = 1 unless defined $old && $old eq $ret; |
510ca912 |
202 | return $ret; |
203 | } |
204 | |
39fe0e65 |
205 | =item store_column |
206 | |
207 | $obj->store_column($col => $val); |
208 | |
209 | Sets a column value without marking it as dirty |
210 | |
211 | =cut |
212 | |
510ca912 |
213 | sub store_column { |
ea2e61bf |
214 | my ($self, $column, $value) = @_; |
78bab9ca |
215 | $self->throw( "No such column '${column}'" ) |
216 | unless $self->_columns->{$column}; |
217 | $self->throw( "set_column called for ${column} without value" ) |
218 | if @_ < 3; |
510ca912 |
219 | return $self->{_column_data}{$column} = $value; |
ea2e61bf |
220 | } |
221 | |
ea2e61bf |
222 | sub _register_columns { |
223 | my ($class, @cols) = @_; |
224 | my $names = { %{$class->_columns} }; |
225 | $names->{$_} ||= {} for @cols; |
226 | $class->_columns($names); |
227 | } |
228 | |
229 | sub _mk_column_accessors { |
230 | my ($class, @cols) = @_; |
510ca912 |
231 | $class->mk_group_accessors('column' => @cols); |
ea2e61bf |
232 | } |
233 | |
39fe0e65 |
234 | =item add_columns |
235 | |
236 | __PACKAGE__->add_columns(qw/col1 col2 col3/); |
237 | |
238 | Adds columns to the current package, and creates accessors for them |
239 | |
240 | =cut |
241 | |
510ca912 |
242 | sub add_columns { |
8fe001e1 |
243 | my ($class, @cols) = @_; |
244 | $class->_register_columns(@cols); |
245 | $class->_mk_column_accessors(@cols); |
246 | } |
247 | |
39fe0e65 |
248 | =item retrieve_from_sql |
249 | |
250 | my @obj = $class->retrieve_from_sql($sql_where_cond, @bind); |
251 | my $cursor = $class->retrieve_from_sql($sql_where_cond, @bind); |
252 | |
253 | =cut |
254 | |
8fe001e1 |
255 | sub retrieve_from_sql { |
256 | my ($class, $cond, @vals) = @_; |
a3018bd3 |
257 | $cond =~ s/^\s*WHERE//i; |
604d9f38 |
258 | my $attrs = (ref $vals[$#vals] eq 'HASH' ? pop(@vals) : {}); |
259 | my @cols = $class->_select_columns($attrs); |
8fe001e1 |
260 | my $sth = $class->_get_sth( 'select', \@cols, $class->_table_name, $cond); |
b8e1e21f |
261 | #warn "$cond @vals"; |
54644855 |
262 | return $class->sth_to_objects($sth, \@vals, \@cols, { where => $cond }); |
510ca912 |
263 | } |
264 | |
39fe0e65 |
265 | =item count_from_sql |
266 | |
267 | my $count = $class->count($sql_where_cond); |
268 | |
269 | =cut |
270 | |
fcbc5f29 |
271 | sub count_from_sql { |
272 | my ($class, $cond, @vals) = @_; |
273 | $cond =~ s/^\s*WHERE//i; |
274 | my $attrs = (ref $vals[$#vals] eq 'HASH' ? pop(@vals) : {}); |
275 | my @cols = 'COUNT(*)'; |
276 | my $sth = $class->_get_sth( 'select', \@cols, $class->_table_name, $cond); |
277 | #warn "$cond @vals"; |
278 | $sth->execute(@vals); |
279 | my ($count) = $sth->fetchrow_array; |
39fe0e65 |
280 | $sth->finish; |
fcbc5f29 |
281 | return $count; |
282 | } |
283 | |
39fe0e65 |
284 | =item count |
285 | |
286 | my $count = $class->count({ foo => 3 }); |
287 | |
288 | =cut |
289 | |
06d90c6b |
290 | sub count { |
291 | my $class = shift; |
292 | my $attrs = { }; |
293 | if (@_ > 1 && ref $_[$#_] eq 'HASH') { |
294 | $attrs = { %{ pop(@_) } }; |
295 | } |
296 | my $query = ref $_[0] eq "HASH" ? shift: {@_}; |
297 | my ($cond, @param) = $class->_cond_resolve($query, $attrs); |
fcbc5f29 |
298 | return $class->count_from_sql($cond, @param, $attrs); |
06d90c6b |
299 | } |
300 | |
39fe0e65 |
301 | =item sth_to_objects |
302 | |
303 | my @obj = $class->sth_to_objects($sth, \@bind, \@columns, $attrs); |
304 | my $cursor = $class->sth_to_objects($sth, \@bind, \@columns, $attrs); |
305 | |
306 | =cut |
307 | |
510ca912 |
308 | sub sth_to_objects { |
54644855 |
309 | my ($class, $sth, $args, $cols, $attrs) = @_; |
510ca912 |
310 | my @cols = ((ref $cols eq 'ARRAY') ? @$cols : @{$sth->{NAME_lc}} ); |
4a07648a |
311 | my @args = map { ref $_ ? ''.$_ : $_ } @$args; # Stringify objects |
95a70f01 |
312 | my $cursor_class = $class->_cursor_class; |
313 | eval "use $cursor_class;"; |
4a07648a |
314 | my $cursor = $cursor_class->new($class, $sth, \@args, \@cols, $attrs); |
95a70f01 |
315 | return (wantarray ? $cursor->all : $cursor); |
8fe001e1 |
316 | } |
317 | |
c1d23573 |
318 | sub _row_to_object { # WARNING: Destructive to @$row |
319 | my ($class, $cols, $row) = @_; |
320 | my $new = $class->new; |
321 | $new->store_column($_, shift @$row) for @$cols; |
322 | $new->in_database(1); |
323 | return $new; |
324 | } |
325 | |
39fe0e65 |
326 | =item search |
327 | |
328 | my @obj = $class->search({ foo => 3 }); |
329 | my $cursor = $class->search({ foo => 3 }); |
330 | |
331 | =cut |
332 | |
8fe001e1 |
333 | sub search { |
12bbb339 |
334 | my $class = shift; |
335 | my $attrs = { }; |
336 | if (@_ > 1 && ref $_[$#_] eq 'HASH') { |
337 | $attrs = { %{ pop(@_) } }; |
338 | } |
339 | my $query = ref $_[0] eq "HASH" ? shift: {@_}; |
340 | my ($cond, @param) = $class->_cond_resolve($query, $attrs); |
c687b87e |
341 | return $class->retrieve_from_sql($cond, @param, $attrs); |
a3018bd3 |
342 | } |
343 | |
39fe0e65 |
344 | =item search_like |
345 | |
346 | Identical to search except defaults to 'LIKE' instead of '=' in condition |
347 | |
348 | =cut |
349 | |
a3018bd3 |
350 | sub search_like { |
351 | my $class = shift; |
12bbb339 |
352 | my $attrs = { }; |
353 | if (@_ > 1 && ref $_[$#_] eq 'HASH') { |
354 | $attrs = pop(@_); |
355 | } |
356 | return $class->search(@_, { %$attrs, cmp => 'LIKE' }); |
8fe001e1 |
357 | } |
358 | |
359 | sub _select_columns { |
360 | return keys %{$_[0]->_columns}; |
361 | } |
362 | |
39fe0e65 |
363 | =item copy |
364 | |
365 | my $copy = $orig->copy({ change => $to, ... }); |
366 | |
367 | =cut |
368 | |
8fe001e1 |
369 | sub copy { |
370 | my ($self, $changes) = @_; |
371 | my $new = bless({ _column_data => { %{$self->{_column_data}}} }, ref $self); |
510ca912 |
372 | $new->set_column($_ => $changes->{$_}) for keys %$changes; |
a3018bd3 |
373 | return $new->insert; |
374 | } |
375 | |
12bbb339 |
376 | sub _cond_resolve { |
377 | my ($self, $query, $attrs) = @_; |
604d9f38 |
378 | return '1 = 1' unless keys %$query; |
12bbb339 |
379 | my $op = $attrs->{'cmp'} || '='; |
a3018bd3 |
380 | my $cond = join(' AND ', |
381 | map { (defined $query->{$_} |
382 | ? "$_ $op ?" |
383 | : (do { delete $query->{$_}; "$_ IS NULL"; })); |
384 | } keys %$query); |
12bbb339 |
385 | return ($cond, values %$query); |
8fe001e1 |
386 | } |
387 | |
39fe0e65 |
388 | =item table |
389 | |
390 | __PACKAGE__->table('tbl_name'); |
391 | |
392 | =cut |
393 | |
510ca912 |
394 | sub table { |
395 | shift->_table_name(@_); |
396 | } |
397 | |
39fe0e65 |
398 | =item find_or_create |
399 | |
400 | $class->find_or_create({ key => $val, ... }); |
401 | |
402 | Searches for a record matching the search condition; if it doesn't find one, |
403 | creates one and returns that instead |
404 | |
405 | =cut |
406 | |
95a70f01 |
407 | sub find_or_create { |
408 | my $class = shift; |
409 | my $hash = ref $_[0] eq "HASH" ? shift: {@_}; |
410 | my ($exists) = $class->search($hash); |
411 | return defined($exists) ? $exists : $class->create($hash); |
412 | } |
413 | |
39fe0e65 |
414 | =item insert_or_update |
415 | |
416 | $obj->insert_or_update |
417 | |
418 | Updates the object if it's already in the db, else inserts it |
419 | |
420 | =cut |
421 | |
b28cc0ba |
422 | sub insert_or_update { |
423 | my $self = shift; |
424 | return ($self->in_database ? $self->update : $self->insert); |
425 | } |
426 | |
39fe0e65 |
427 | =item retrieve_all |
428 | |
429 | my @all = $class->retrieve_all; |
430 | |
431 | =cut |
432 | |
95a70f01 |
433 | sub retrieve_all { |
434 | my ($class) = @_; |
435 | return $class->retrieve_from_sql( '1' ); |
436 | } |
437 | |
39fe0e65 |
438 | =item is_changed |
439 | |
440 | my @changed_col_names = $obj->is_changed |
441 | |
442 | =cut |
443 | |
cc0f266f |
444 | sub is_changed { |
445 | return keys %{shift->{_dirty_columns} || {}}; |
446 | } |
447 | |
ea2e61bf |
448 | 1; |
34d52be2 |
449 | |
450 | =back |
451 | |
452 | =head1 AUTHORS |
453 | |
454 | Matt S. Trout <perl-stuff@trout.me.uk> |
455 | |
456 | =head1 LICENSE |
457 | |
458 | You may distribute this code under the same terms as Perl itself. |
459 | |
460 | =cut |
461 | |