Commit | Line | Data |
d5dedbd6 |
1 | package DBIx::Class::SQLMaker; |
6f4ddea1 |
2 | |
a697fa31 |
3 | use strict; |
4 | use warnings; |
5 | |
d5dedbd6 |
6 | =head1 NAME |
7 | |
8 | DBIx::Class::SQLMaker - An SQL::Abstract-based SQL maker class |
9 | |
10 | =head1 DESCRIPTION |
11 | |
12 | This module is a subclass of L<SQL::Abstract> and includes a number of |
13 | DBIC-specific workarounds, not yet suitable for inclusion into the |
14 | L<SQL::Abstract> core. It also provides all (and more than) the functionality |
15 | of L<SQL::Abstract::Limit>, see L<DBIx::Class::SQLMaker::LimitDialects> for |
16 | more info. |
17 | |
18 | Currently the enhancements to L<SQL::Abstract> are: |
19 | |
20 | =over |
21 | |
22 | =item * Support for C<JOIN> statements (via extended C<table/from> support) |
23 | |
24 | =item * Support of functions in C<SELECT> lists |
25 | |
26 | =item * C<GROUP BY>/C<HAVING> support (via extensions to the order_by parameter) |
27 | |
28 | =item * Support of C<...FOR UPDATE> type of select statement modifiers |
29 | |
10cef607 |
30 | =item * The L</-ident> operator |
31 | |
32 | =item * The L</-value> operator |
33 | |
d5dedbd6 |
34 | =back |
35 | |
36 | =cut |
6a247f33 |
37 | |
38 | use base qw/ |
39 | SQL::Abstract |
10cef607 |
40 | DBIx::Class::SQLMaker::LimitDialects |
6a247f33 |
41 | /; |
42 | use mro 'c3'; |
a697fa31 |
43 | |
10cef607 |
44 | use Module::Runtime qw(use_module); |
6298a324 |
45 | use Sub::Name 'subname'; |
70c28808 |
46 | use DBIx::Class::Carp; |
10cef607 |
47 | use DBIx::Class::Exception; |
48 | use Moo; |
e8fc51c7 |
49 | use namespace::clean; |
b2b22cd6 |
50 | |
10cef607 |
51 | has limit_dialect => ( |
52 | is => 'rw', default => sub { 'LimitOffset' }, |
53 | trigger => sub { shift->clear_renderer_class } |
54 | ); |
55 | |
7027fcdb |
56 | sub BUILD { |
57 | if ($_[0]->can('emulate_limit')) { |
58 | die <<EODIE; |
59 | The ancient and horrible emulate_limit method was deprecated for many moons. |
60 | Now, it is no more. Time to rewrite the code in ${\ref($_[0])} |
61 | EODIE |
62 | } |
63 | } |
64 | |
10cef607 |
65 | our %LIMIT_DIALECT_MAP = ( |
66 | 'GenericSubQ' => 'GenericSubquery', |
10cef607 |
67 | ); |
68 | |
69 | sub mapped_limit_dialect { |
70 | my ($self) = @_; |
71 | my $unmapped = $self->limit_dialect; |
72 | $LIMIT_DIALECT_MAP{$unmapped}||$unmapped; |
73 | } |
74 | |
75 | around _build_renderer_roles => sub { |
76 | my ($orig, $self) = (shift, shift); |
77 | return ( |
78 | $self->$orig(@_), |
79 | 'Data::Query::Renderer::SQL::Slice::'.$self->mapped_limit_dialect |
80 | ); |
81 | }; |
6a247f33 |
82 | |
3f5b99fe |
83 | # for when I need a normalized l/r pair |
84 | sub _quote_chars { |
85 | map |
86 | { defined $_ ? $_ : '' } |
87 | ( ref $_[0]->{quote_char} ? (@{$_[0]->{quote_char}}) : ( ($_[0]->{quote_char}) x 2 ) ) |
88 | ; |
89 | } |
90 | |
10cef607 |
91 | sub _build_converter_class { |
92 | Module::Runtime::use_module('DBIx::Class::SQLMaker::Converter') |
93 | } |
94 | |
70c28808 |
95 | # FIXME when we bring in the storage weaklink, check its schema |
96 | # weaklink and channel through $schema->throw_exception |
97 | sub throw_exception { DBIx::Class::Exception->throw($_[1]) } |
98 | |
b2b22cd6 |
99 | BEGIN { |
2ea6032a |
100 | # reinstall the belch()/puke() functions of SQL::Abstract with custom versions |
70c28808 |
101 | # that use DBIx::Class::Carp/DBIx::Class::Exception instead of plain Carp |
b2b22cd6 |
102 | no warnings qw/redefine/; |
2ea6032a |
103 | |
104 | *SQL::Abstract::belch = subname 'SQL::Abstract::belch' => sub (@) { |
105 | my($func) = (caller(1))[3]; |
106 | carp "[$func] Warning: ", @_; |
107 | }; |
108 | |
109 | *SQL::Abstract::puke = subname 'SQL::Abstract::puke' => sub (@) { |
110 | my($func) = (caller(1))[3]; |
70c28808 |
111 | __PACKAGE__->throw_exception("[$func] Fatal: " . join ('', @_)); |
2ea6032a |
112 | }; |
10cef607 |
113 | |
114 | # Current SQLA pollutes its namespace - clean for the time being |
115 | namespace::clean->clean_subroutines(qw/SQL::Abstract carp croak confess/); |
b2b22cd6 |
116 | } |
6f4ddea1 |
117 | |
e9657379 |
118 | # the "oh noes offset/top without limit" constant |
fcb7fcbb |
119 | # limited to 31 bits for sanity (and consistency, |
120 | # since it may be handed to the like of sprintf %u) |
121 | # |
122 | # Also *some* builds of SQLite fail the test |
123 | # some_column BETWEEN ? AND ?: 1, 4294967295 |
124 | # with the proper integer bind attrs |
125 | # |
6a247f33 |
126 | # Implemented as a method, since ::Storage::DBI also |
127 | # refers to it (i.e. for the case of software_limit or |
128 | # as the value to abuse with MSSQL ordered subqueries) |
fcb7fcbb |
129 | sub __max_int () { 0x7FFFFFFF }; |
e9657379 |
130 | |
e39f188a |
131 | # poor man's de-qualifier |
132 | sub _quote { |
133 | $_[0]->next::method( ( $_[0]{_dequalify_idents} and ! ref $_[1] ) |
134 | ? $_[1] =~ / ([^\.]+) $ /x |
135 | : $_[1] |
136 | ); |
137 | } |
138 | |
b1d821de |
139 | sub _where_op_NEST { |
70c28808 |
140 | carp_unique ("-nest in search conditions is deprecated, you most probably wanted:\n" |
b1d821de |
141 | .q|{..., -and => [ \%cond0, \@cond1, \'cond2', \[ 'cond3', [ col => bind ] ], etc. ], ... }| |
70c28808 |
142 | ); |
b1d821de |
143 | |
144 | shift->next::method(@_); |
145 | } |
146 | |
6a247f33 |
147 | # Handle limit-dialect selection |
6f4ddea1 |
148 | sub select { |
6a247f33 |
149 | my ($self, $table, $fields, $where, $rs_attrs, $limit, $offset) = @_; |
150 | |
6a247f33 |
151 | if (defined $offset) { |
70c28808 |
152 | $self->throw_exception('A supplied offset must be a non-negative integer') |
6a247f33 |
153 | if ( $offset =~ /\D/ or $offset < 0 ); |
154 | } |
155 | $offset ||= 0; |
1cbd3034 |
156 | |
6a247f33 |
157 | if (defined $limit) { |
70c28808 |
158 | $self->throw_exception('A supplied limit must be a positive integer') |
6a247f33 |
159 | if ( $limit =~ /\D/ or $limit <= 0 ); |
160 | } |
161 | elsif ($offset) { |
162 | $limit = $self->__max_int; |
6f4ddea1 |
163 | } |
c2b7c5dc |
164 | |
10cef607 |
165 | my %final_attrs = (%{$rs_attrs||{}}, limit => $limit, offset => $offset); |
166 | |
167 | if ($limit or $offset) { |
168 | my %slice_stability = $self->renderer->slice_stability; |
169 | |
170 | if (my $stability = $slice_stability{$offset ? 'offset' : 'limit'}) { |
171 | my $source = $rs_attrs->{_rsroot_rsrc}; |
172 | unless ( |
173 | $final_attrs{order_is_stable} |
174 | = $final_attrs{preserve_order} |
175 | = $source->schema->storage |
176 | ->_order_by_is_stable( |
177 | @final_attrs{qw(from order_by where)} |
178 | ) |
179 | ) { |
180 | if ($stability eq 'requires') { |
181 | if ($self->converter->_order_by_to_dq($final_attrs{order_by})) { |
182 | $self->throw_exception( |
458332d9 |
183 | $self->limit_dialect.' limit/offset implementation requires a stable order for '.($offset ? 'offset' : 'limit').' but you gave me '.$self->_render_sqla(order_by => $final_attrs{order_by}) |
10cef607 |
184 | ); |
185 | } |
186 | if (my $ident_cols = $source->_identifying_column_set) { |
187 | $final_attrs{order_by} = [ |
188 | map "$final_attrs{alias}.$_", @$ident_cols |
189 | ]; |
190 | $final_attrs{order_is_stable} = 1; |
191 | } else { |
192 | $self->throw_exception(sprintf( |
193 | 'Unable to auto-construct stable order criteria for "skimming type" |
194 | limit ' |
195 | . "dialect based on source '%s'", $source->name) ); |
196 | } |
197 | } |
198 | } |
a6b68a60 |
199 | |
67341081 |
200 | } |
67341081 |
201 | |
10cef607 |
202 | my %slice_subquery = $self->renderer->slice_subquery; |
203 | |
204 | if (my $subquery = $slice_subquery{$offset ? 'offset' : 'limit'}) { |
205 | $fields = [ map { |
206 | my $f = $fields->[$_]; |
207 | if (ref $f) { |
208 | $f = { '' => $f } unless ref($f) eq 'HASH'; |
209 | ($f->{-as} ||= $final_attrs{as}[$_]) =~ s/\Q${\$self->name_sep}/__/g; |
210 | } elsif ($f !~ /^\Q$final_attrs{alias}${\$self->name_sep}/) { |
211 | $f = { '' => $f }; |
212 | ($f->{-as} ||= $final_attrs{as}[$_]) =~ s/\Q${\$self->name_sep}/__/g; |
213 | } |
214 | $f; |
215 | } 0 .. $#$fields ]; |
67341081 |
216 | } |
6a247f33 |
217 | } |
583a0c65 |
218 | |
10cef607 |
219 | my ($sql, @bind) = $self->next::method ($table, $fields, $where, $final_attrs{order_by}, \%final_attrs ); |
583a0c65 |
220 | |
e5372da4 |
221 | $sql .= $self->_lock_select ($rs_attrs->{for}) |
222 | if $rs_attrs->{for}; |
223 | |
10cef607 |
224 | return wantarray ? ($sql, @bind) : $sql; |
583a0c65 |
225 | } |
226 | |
227 | sub _assemble_binds { |
228 | my $self = shift; |
8b31f62e |
229 | return map { @{ (delete $self->{"${_}_bind"}) || [] } } (qw/pre_select select from where group having order limit/); |
6f4ddea1 |
230 | } |
231 | |
e5372da4 |
232 | my $for_syntax = { |
233 | update => 'FOR UPDATE', |
234 | shared => 'FOR SHARE', |
235 | }; |
236 | sub _lock_select { |
237 | my ($self, $type) = @_; |
8249c09b |
238 | |
239 | my $sql; |
240 | if (ref($type) eq 'SCALAR') { |
241 | $sql = "FOR $$type"; |
242 | } |
243 | else { |
10cef607 |
244 | $sql = $for_syntax->{$type} || $self->throw_exception( "Unknown SELECT .. FO |
245 | R type '$type' requested" ); |
8249c09b |
246 | } |
247 | |
e5372da4 |
248 | return " $sql"; |
249 | } |
250 | |
10cef607 |
251 | sub _recurse_from { |
252 | scalar shift->_render_sqla(table => \@_); |
6f4ddea1 |
253 | } |
254 | |
10cef607 |
255 | 1; |
50136dd9 |
256 | |
10cef607 |
257 | =head1 OPERATORS |
81446c4f |
258 | |
10cef607 |
259 | =head2 -ident |
50136dd9 |
260 | |
10cef607 |
261 | Used to explicitly specify an SQL identifier. Takes a plain string as value |
262 | which is then invariably treated as a column name (and is being properly |
263 | quoted if quoting has been requested). Most useful for comparison of two |
264 | columns: |
83e09b5b |
265 | |
10cef607 |
266 | my %where = ( |
267 | priority => { '<', 2 }, |
268 | requestor => { -ident => 'submitter' } |
50136dd9 |
269 | ); |
270 | |
10cef607 |
271 | which results in: |
6f4ddea1 |
272 | |
10cef607 |
273 | $stmt = 'WHERE "priority" < ? AND "requestor" = "submitter"'; |
274 | @bind = ('2'); |
a6b68a60 |
275 | |
10cef607 |
276 | =head2 -value |
1cbd3034 |
277 | |
10cef607 |
278 | The -value operator signals that the argument to the right is a raw bind value. |
279 | It will be passed straight to DBI, without invoking any of the SQL::Abstract |
280 | condition-parsing logic. This allows you to, for example, pass an array as a |
281 | column value for databases that support array datatypes, e.g.: |
15827712 |
282 | |
10cef607 |
283 | my %where = ( |
284 | array => { -value => [1, 2, 3] } |
b8391c87 |
285 | ); |
6f4ddea1 |
286 | |
10cef607 |
287 | which results in: |
aa82ce29 |
288 | |
10cef607 |
289 | $stmt = 'WHERE array = ?'; |
290 | @bind = ([1, 2, 3]); |
d5dedbd6 |
291 | |
292 | =head1 AUTHORS |
293 | |
294 | See L<DBIx::Class/CONTRIBUTORS>. |
295 | |
296 | =head1 LICENSE |
297 | |
298 | You may distribute this code under the same terms as Perl itself. |
299 | |
300 | =cut |