File Coverage

blib/lib/DBIx/ORM/Declarative/Join.pm
Criterion Covered Total %
statement 9 179 5.0
branch 0 78 0.0
condition 0 22 0.0
subroutine 3 10 30.0
pod 4 5 80.0
total 16 294 5.4


line stmt bran cond sub pod time code
1             package DBIx::ORM::Declarative::Join;
2 1     1   6 use strict;
  1         1  
  1         41  
3 1     1   6 use Carp;
  1         3  
  1         122  
4              
5 1     1   6 use vars qw(@ISA);
  1         3  
  1         4532  
6             @ISA = qw(DBIx::ORM::Declarative::Table);
7              
8             # For compatibility with old-style join syntax
9 0     0     sub _join_clause { ''; }
10              
11             # this is a join
12 0     0     sub _isjoin { 1; }
13              
14             # Create a row in a multi-table join, observing column constraints
15             sub create
16             {
17 0     0 1   my ($self, %params) = @_;
18 0           my $handle = $self->handle;
19 0 0 0       carp "can't create without a database handle" and return unless $handle;
20              
21             # Get table information
22 0           my $primary = $self->_primary;
23 0           my @table_info = $self->_join_info;
24              
25             # Check the primary table first
26 0           my $tab_obj = $self->table($primary);
27 0           my ($flag) = $self->__check_constraints($tab_obj, %params);
28 0 0         return unless $flag;
29 0           my %primary_map = $tab_obj->_column_map;
30              
31             # Assemble the data
32 0           my @row_data;
33              
34             # Do each other table in turn
35             my @backout_cmds;
36 0           for my $tab (@table_info)
37             {
38             # Get a table object
39 0           my $table = $tab->{table};
40 0           $tab_obj = $self->table($table);
41 0           my $tab_name = $tab_obj->_sql_name;
42              
43             # Copy the primary table parameters to where they map on the secondary
44 0           my %p = %params;
45 0           $p{$tab->{columns}{$_}} = delete $p{$_}
46 0           foreach grep { exists $p{$_} } keys %{$tab->{columns}};
  0            
47              
48             # Override with any long-named parameters
49 0           for my $col (grep { $_->{table} eq $table } $self->_columns)
  0            
50             {
51             # Construct the name, skip this entry if the name is already
52             # constructed
53 0           my $nm = $col->{name};
54 0           my $tab_alias = $col->{table_alias};
55 0 0 0       next if length($nm)>length($tab_alias)
56             and $tab_alias eq substr($nm, length($tab_alias));
57              
58 0           my $augmented_name = $tab_alias . '_' . $nm;
59 0 0         $p{$nm} = $params{$augmented_name}
60             if exists $params{$augmented_name};
61             }
62              
63 0           my ($flag, $keys, $values, $npk, @binds)
64             = $self->__check_constraints($tab_obj, %p);
65 0 0         if(not $flag)
66             {
67             # We have a constraint violation
68 0           $self->__do_rollback(@backout_cmds);
69 0           return;
70             }
71              
72             # Might as well conditionally create the row
73 0           my $sql = "INSERT INTO $tab_name ($keys) SELECT $values FROM DUAL";
74 0           my %map = $tab_obj->_column_map;
75              
76             # Check for a defined primary key
77 0           my @pk = $tab_obj->_primary_key;
78 0           my @conditions;
79 0 0 0       if(@pk and not $npk)
80             {
81 0           my @wk;
82 0           for my $k (@pk)
83             {
84 0 0         if(exists $p{$k})
85             {
86 0           push @wk, $map{$k} . '=?';
87 0           push @binds, $p{$k};
88             }
89             }
90 0 0         if(@wk)
91             {
92             # We have part or all of the primary key
93 0           push @conditions, join(' AND ', @wk);
94             }
95             }
96              
97             # Check for other unique keys
98 0           my @uniques = $tab_obj->_unique_keys;
99 0 0         shift @uniques if @pk;
100 0           for my $un (@uniques)
101             {
102 0           my @wk;
103 0           for my $k (@$un)
104             {
105 0 0         if(exists $p{$k})
106             {
107 0           push @wk, $map{$k} . '=?';
108 0           push @binds, $p{$k};
109             }
110             else
111             {
112 0           push @wk, $map{$k} . ' IS NULL';
113             }
114             }
115             # save it if we've got it
116 0 0         push @conditions, join(' AND ', @wk) if @wk;
117             }
118              
119             # Add the conditional part
120 0 0         if(@conditions)
121             {
122 0           $sql .= " WHERE NOT EXISTS (SELECT 1 FROM $tab_name WHERE "
123 0           . join(' OR ', map { "($_)" } @conditions)
124             . ')';
125             }
126              
127             # We have the command - now create the row
128 0 0         unshift @binds, undef if @binds; # Deal with DBI bone-headedness
129 0           my $dbres = $handle->do($sql, @binds);
130 0 0         if(not $dbres)
131             {
132 0           carp "Database error: " . $handle->errstr;
133 0           $self->__do_rollback(@backout_cmds);
134 0           return;
135             }
136              
137             # Get the primary key, if we have one
138 0 0         if($npk)
139             {
140             # Set the data return to a string so we know if we never tried
141             # to get any data from the database.
142 0           my $data = 'never called';
143              
144             # See if we actually created a row
145 0 0         if($dbres != 0)
146             {
147 0           my $np = $tab_obj->_select_null_primary;
148 0 0         if($np)
149             {
150 0           $data = $handle->selectall_arrayref($np);
151             }
152             }
153             # See if we can find the conflicting row
154             else
155             {
156             # Use the first non-primary unique key we have, or
157             # everything if we don't have one.
158 0           my ($ign, $un) = $tab_obj->_unique_keys;
159 0           my @cols;
160 0 0         if($un)
161             {
162 0           @cols = @$un;
163             }
164             else
165             {
166 0           @cols = grep { exists $p{$_} }
  0            
167 0           map { $_->{name} }
168             $tab_obj->_columns;
169             }
170              
171 0           @binds = ();
172             # Generate the SQL
173 0           $sql = 'SELECT ' . join(',', map { $map{$_} } @pk)
  0            
174             . " FROM $tab_name WHERE ";
175            
176 0           my @wk;
177             push @wk, $map{$_} . (defined $p{$_}?'=?':' IS NULL')
178 0 0         foreach @cols;
179 0           push @binds, $p{$_} foreach grep { defined $p{$_} } @cols;
  0            
180              
181 0           $sql .= join(' AND ', @wk);
182 0 0         unshift @binds, undef if @binds;
183              
184 0           $data = $handle->selectall_arrayref($sql, @binds);
185             }
186              
187             # check for errors
188 0 0         if(not $data)
189             {
190 0           carp "Database error: " . $handle->errstr;
191 0           $self->__do_rollback(@backout_cmds);
192 0           return;
193             }
194 0 0 0       if(ref $data and not defined $data->[0][0])
195             {
196 0           carp "Database error: can't find primary key";
197 0           $self->__do_rollback(@backout_cmds);
198 0           return;
199             }
200             # Save the primary key data
201 0 0         @p{@pk} = @{$data->[0]} if ref $data;
  0            
202             }
203              
204             # We're gonna have problems if we don't have anything in %p by now...
205 0 0         if(not %p)
206             {
207 0           carp "Database error: no search parameters";
208 0           $self->__do_rollback(@backout_cmds);
209 0           return;
210             }
211              
212             # Find the row for this join
213             # First - create the "WHERE" clause
214             # Note that we're literalizing the values so we can reuse this later
215 0           my @cols = map { $_->{name} } $tab_obj->_columns;
  0            
216 0 0         my @wk = map { $map{$_}
  0            
217             . ((defined $p{$_})?('=' . $handle->quote($p{$_})):(' IS NULL')) }
218 0           grep { exists $p{$_} } @cols;
219              
220 0           my $table_name = $tab_obj->_sql_name;
221 0           my $wclause = " FROM $table_name WHERE " . join(' AND ', @wk);
222              
223             # Create the SQL
224 0           $sql = 'SELECT ' . join(',', map { $map{$_} } @cols) . $wclause;
  0            
225              
226             # Get the data
227 0           my $data = $handle->selectall_arrayref($sql);
228              
229             # blow up if we can't find it
230 0 0 0       if(not $data or not $data->[0])
231             {
232 0 0         carp $self->E_NOROWSOUND if $data;
233 0 0         carp 'Database error: ', $handle->errstr unless $data;
234 0           $self->__do_rollback(@backout_cmds);
235 0           return;
236             }
237              
238             # Blow up if there's too much of a good thing
239 0 0         if(@$data > 1)
240             {
241 0           carp $self->E_TOOMANYROWS;
242 0           $self->__do_rollback(@backout_cmds);
243 0           return;
244             }
245              
246             # Copy stuff back to the %p hash
247 0           @p{@cols} = @{$data->[0]};
  0            
248              
249             # Rename it back to what's expected by the primary table
250 0           $p{$_} = delete $p{$tab->{columns}{$_}}
251 0           foreach grep { exists $p{$tab->{columns}{$_}} }
  0            
252             keys %{$tab->{columns}};
253              
254             # Copy it back to the %params hash
255 0           $params{$_} = $p{$_} foreach grep { exists $p{$_} }
  0            
  0            
256             keys %{$tab->{columns}};
257              
258             # Save it to the results object
259 0           push @row_data, @{$data->[0]};
  0            
260              
261             # Save undo instructions
262 0           push @backout_cmds, "DELETE $wclause";
263             }
264              
265             # Now that we have the secondary rows, create the main one
266 0           my ($keys, $values, $npk, @binds);
267 0           $tab_obj = $self->table($primary);
268 0           ($flag, $keys, $values, $npk, @binds) =
269             $self->__check_constraints($tab_obj, %params);
270              
271 0 0 0       $self->__do_rollback(@backout_cmds) and return unless $flag;
272              
273             # Prepare & execute the statement
274 0           my $table_name = $tab_obj->_sql_name;
275 0           my $sql = "INSERT INTO $table_name ($keys) VALUES ($values)";
276 0 0         unshift @binds, undef if @binds;
277 0           my $dbres = $handle->do($sql, @binds);
278              
279             # Get any null primary key info
280 0 0         if($npk)
281             {
282 0           my @pk = $tab_obj->_primary_key;
283 0           my $np = $tab_obj->_select_null_primary;
284 0 0 0       if(@pk and $np)
285             {
286 0           my $data = $handle->selectall_arrayref($np);
287 0 0 0       if(not $data or not defined $data->[0][0])
288             {
289 0 0         carp $self->E_NOROWSFOUND if $data;
290 0 0         carp "Database error: " . $handle->errstr unless $data;
291 0           $self->__do_rollback(@backout_cmds);
292 0           return;
293             }
294              
295             # Save the primary key data
296 0           @params{@pk} = @{$data->[0]};
  0            
297             }
298             }
299              
300             # Finally, look for what we just inserted
301 0 0         my @search_params =
302 0           map {($_, (defined $params{$_}?(eq => $params{$_}):('isnull')))}
303 0           grep { exists $params{$_} }
304 0           map { $_->{name} }
305             $self->_columns;
306              
307 0           my @res = $self->search(\@search_params);
308              
309 0 0         if(not @res)
310             {
311 0           $self->__do_rollback(@backout_cmds);
312 0           return;
313             }
314              
315 0 0         if(@res>1)
316             {
317             # Now we're in somewhat murky water: we have too many matching
318             # rows. We can't delete what we've inserted, because we may
319             # nuke too many rows. We can't delete the secondary table rows
320             # because that may screw up referential integrity. So, just call
321             # rollback on the handle, and if the database is transactional, let
322             # it handle the fallout. Otherwise, there's not a whole lot we can
323             # do...
324 0           carp $self->E_TOOMANYROWS;
325 0           $self->__do_rollback;
326 0           return;
327             }
328              
329             # Turn off warnings and commit
330 0           local ($SIG{__WARN__}) = $self->w__noop;
331 0           $handle->commit;
332 0           return $res[0];
333             }
334              
335             sub no_can_do
336             {
337 0     0 0   my ($self, $method) = @_;
338 0           carp "Can't $method via a join - use the individual tables for that";
339             return
340 0           }
341              
342 0     0 1   sub delete { $_[0]->no_can_do('delete'); }
343 0     0 1   sub bulk_create { $_[0]->no_can_do('bulk_create'); }
344 0     0 1   sub create_only { $_[0]->no_can_do('create_only'); }
345              
346             1;
347             __END__