File Coverage

blib/lib/DBIx/Class/Helper/ResultSet/MySQLHacks.pm
Criterion Covered Total %
statement 50 55 90.9
branch 3 6 50.0
condition 1 2 50.0
subroutine 5 6 83.3
pod 2 2 100.0
total 61 71 85.9


line stmt bran cond sub pod time code
1             package DBIx::Class::Helper::ResultSet::MySQLHacks;
2              
3 1     1   434835 use v5.10;
  1         12  
4              
5 1     1   6 use base 'DBIx::Class::ResultSet';
  1         3  
  1         91  
6              
7             # ABSTRACT: Useful MySQL-specific operations for DBIx::Class
8 1     1   550 use version;
  1         2090  
  1         7  
9             our $VERSION = 'v1.0.0'; # VERSION
10              
11             #pod =head1 SYNOPSIS
12             #pod
13             #pod # Your base resultset
14             #pod package MySchema::ResultSet;
15             #pod
16             #pod use strict;
17             #pod use warnings;
18             #pod
19             #pod use parent 'DBIx::Class::ResultSet';
20             #pod
21             #pod __PACKAGE__->load_components('Helper::ResultSet::MySQLHacks');
22             #pod
23             #pod # In other resultset classes
24             #pod package MySchema::ResultSet::Bar;
25             #pod
26             #pod use strict;
27             #pod use warnings;
28             #pod
29             #pod use parent 'MySchema::ResultSet';
30             #pod
31             #pod # In code using the resultset
32             #pod $rs->multi_table_delete(qw< rel1 rel2 >);
33             #pod $rs->multi_table_update(\%values);
34             #pod
35             #pod =head1 DESCRIPTION
36             #pod
37             #pod This MySQL-specific ResultSet helper contains a series of hacks for various SQL
38             #pod operations that only work for MySQL. These hacks are exactly that, so it's possible that
39             #pod the SQL manipulation isn't as clean as it should be.
40             #pod
41             #pod =head1 METHODS
42             #pod
43             #pod =head2 multi_table_delete
44             #pod
45             #pod my $underlying_storage_rv = $rs->multi_table_delete; # deletes rows from the current table
46             #pod my $underlying_storage_rv = $rs->multi_table_delete(qw< rel1 rel2 >);
47             #pod
48             #pod Runs a delete using the multiple table syntax, which supports join operations. This is
49             #pod useful in cases with a joined ResultSet that require rows to be deleted, and using
50             #pod L would be too slow.
51             #pod
52             #pod Without arguments, it will delete rows from the current table, ie: L.
53             #pod Otherwise, it can take a list of B to delete from. These must be existing
54             #pod relationship aliases tied to the joins, not table names.
55             #pod
56             #pod This method works by taking a count ResultSet, removing the C<< SELECT COUNT(*) >>
57             #pod portion, and splicing in the C<< DELETE @aliases >> part.
58             #pod
59             #pod The return value is a pass through of what the underlying storage backend returned, and
60             #pod may vary. See L for the most common case.
61             #pod
62             #pod B This method will not delete from views, per MySQL limitations.
63             #pod
64             #pod =cut
65              
66             sub multi_table_delete {
67 8     8 1 275656 my ($self, @rel_aliases) = @_;
68 8 100       76 @rel_aliases = ( $self->current_source_alias ) unless @rel_aliases;
69              
70 8         87 my $sql_maker = $self->result_source->storage->sql_maker;
71              
72             my $alias_str = join ', ', map {
73 8         850 $sql_maker->_from_chunk_to_sql($_)
  9         124  
74             } @rel_aliases;
75              
76 8         480 my ($sql, $bind);
77 8         23 ($sql, @$bind) = @${ $self->count_rs->as_query };
  8         67  
78              
79             # Remove (useless) outside parentheses
80 8         60811 $sql =~ s/^\(\s*(.+)\s*\)$/$1/s;
81              
82             # Convert "SELECT COUNT(*) FROM" to "DELETE @aliases"
83 8         671 $sql =~ s/^SELECT COUNT[()*\s]+(?= FROM)/DELETE $alias_str/;
84              
85 8         75 my $rv = $self->dbh_execute($sql, $bind);
86 8         184 return $rv;
87             }
88              
89             #pod =head2 multi_table_update
90             #pod
91             #pod my $underlying_storage_rv = $rs->multi_table_update(\%values);
92             #pod
93             #pod Runs a update using the multiple table syntax, which supports join operations. This is
94             #pod useful in cases with a joined ResultSet that require rows to be updated, and using
95             #pod L would be too slow.
96             #pod
97             #pod A values hashref is required. It's highly recommended that the keys are named as
98             #pod C pairs, since multiple tables are involved.
99             #pod
100             #pod This method works by acquiring the C, C, and C clauses separately and
101             #pod merging them back into a proper multi-table C query.
102             #pod
103             #pod The return value is a pass through of what the underlying storage backend returned, and
104             #pod may vary. See L for the most common case.
105             #pod
106             #pod =cut
107              
108             sub multi_table_update {
109 6     6 1 39817 my ($self, $values) = @_;
110              
111 6 50       40 $self->throw_exception('Values for multi_table_update must be a hash')
112             unless ref $values eq 'HASH';
113              
114 6         28 my $rsrc = $self->result_source;
115 6         40 my $storage = $rsrc->storage;
116 6         442 my $sql_maker = $storage->sql_maker;
117              
118             ### NOTE: Much of this is based on deep-analysis of DBIx::Class::Storage::DBI, especially
119             ### $result->update / $result->single and how that eventually ends up to their respective
120             ### $storage->_execute calls.
121              
122             ### XXX: This FROM/WHERE piece might be replaced with a less private-heavy count_rs->query
123             ### hack, similar to multi_table_delete. However, the SET is going to be going
124             ### *in-between* the FROM/WHERE piece, so binds and SQL insertion might make things more
125             ### difficult. If this code breaks hard, we might have to revert to that model.
126              
127             # Collect attrs for various calls
128 6         148 my $resolved_attrs = { %{$self->_resolved_attrs} };
  6         44  
129              
130             # Use the resolved SELECT args here, since prune_unused_joins may be turned on, and
131             # we don't want that overtrimming the FROM/WHERE lists.
132 6   50     3286 my $select_args = delete $resolved_attrs->{select} // ['*'];
133 6         31 push @$select_args, keys %$values;
134              
135             # Need a more complex set than just $rsrc->columns_info, since relationship
136             # aliases are probably being used.
137 6         47 my $colinfo = $storage->_resolve_column_info($resolved_attrs->{from});
138              
139             # Get the SQL/binds for the SET part
140 6         3968 my ($set_sql, $set_bind);
141 6         66 ($set_sql, @$set_bind) = $sql_maker->update('DUAL', $values); # no WHERE
142 6         1922 $set_sql =~ s/^UPDATE `?DUAL`? //; # no UPDATE header
143              
144 6         52 $set_bind = $storage->_resolve_bindattrs( $rsrc, $set_bind, $colinfo );
145              
146             # Get the SQL/binds for the FROM part
147             my $from_attrs = (
148 6         375 $storage->_select_args( $resolved_attrs->{from}, $select_args, {}, $resolved_attrs )
149             )[4]; # just get the $attrs hash back again
150              
151 6         16317 my ($from_sql, $from_bind);
152 6         43 ($from_sql, @$from_bind) = $sql_maker->select($from_attrs->{from}); # no WHERE, just * for the column list
153 6         5985 $from_sql =~ s/^\(\s*(.+)\s*\)$/$1/s; # remove (useless) outside parentheses
154 6         40 $from_sql =~ s/^SELECT \* FROM //;
155              
156 6         45 $from_bind = $storage->_resolve_bindattrs( $from_attrs->{from}, $from_bind );
157              
158             # Get the SQL/binds for the WHERE part
159             my $where_attrs = (
160 6         167 $storage->_select_args( $resolved_attrs->{from}, $select_args, $resolved_attrs->{where}, $resolved_attrs )
161             )[4]; # just get the $attrs hash back again
162              
163 6         17039 my ($where_sql, $where_bind);
164 6         38 ($where_sql, @$where_bind) = $sql_maker->where($where_attrs->{where}); # just the WHERE clause
165 6         1247 $where_sql =~ s/^ //;
166              
167 6         70 $where_bind = $storage->_resolve_bindattrs( $from_attrs->{from}, $where_bind );
168              
169             # Mash them together!
170 6         3350 my $update_sql = join ' ', 'UPDATE', $from_sql, $set_sql, $where_sql;
171 6         139 $update_sql =~ s/^\s+|\s+$//g;
172              
173 6         23 my $update_bind = [ @$from_bind, @$set_bind, @$where_bind ];
174              
175 6         48 my $rv = $self->dbh_execute($update_sql, $update_bind);
176 6         285 return $rv;
177             }
178              
179             #pod =head2 dbh_execute
180             #pod
181             #pod my $rv = $rs->dbh_execute($sql, $bind);
182             #pod my ($rv, $sth, @bind) = $rs->dbh_execute($sql, $bind);
183             #pod
184             #pod Sends any SQL statement to the C<$dbh> via L while
185             #pod running the usual query loggers and re-connection protections that come with DBIC.
186             #pod
187             #pod This runs code similar to L's C<_execute> method, except that
188             #pod it takes SQL and binds as input. Like C<_dbh_execute> and C<_execute>, it returns
189             #pod different outputs, depending on the context.
190             #pod
191             #pod =cut
192              
193             sub dbh_execute {
194 0     0     my ($self, $sql, $bind) = @_;
195              
196 0           my $rsrc = $self->result_source;
197 0           my $storage = $rsrc->storage;
198              
199 0 0         $storage->_populate_dbh unless $storage->_dbh;
200              
201 0           return $storage->dbh_do( _dbh_execute => # retry over disconnects
202             $sql,
203             $bind,
204             $storage->_dbi_attrs_for_bind($rsrc, $bind),
205             );
206             }
207              
208             1;
209              
210             __END__