add on_or_(before|after) ops
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / SQLMaker.pm
CommitLineData
d5dedbd6 1package DBIx::Class::SQLMaker;
6f4ddea1 2
a697fa31 3use strict;
4use warnings;
5
d5dedbd6 6=head1 NAME
7
8DBIx::Class::SQLMaker - An SQL::Abstract-based SQL maker class
9
10=head1 DESCRIPTION
11
12This module is a subclass of L<SQL::Abstract> and includes a number of
13DBIC-specific workarounds, not yet suitable for inclusion into the
14L<SQL::Abstract> core. It also provides all (and more than) the functionality
15of L<SQL::Abstract::Limit>, see L<DBIx::Class::SQLMaker::LimitDialects> for
16more info.
17
18Currently 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
2bb4c37b 30=item * The L</-ident> operator
e6600283 31
2bb4c37b 32=item * The L</-value> operator
41519379 33
dff53b7e 34=item * Date Functions:
35
36Note that for the following functions use different functions for different
37RDBMS'. See the SQLMaker docs for your database to see what functions are
38used.
39
40=over
41
42=item * -dt => $date_time_obj
43
44This function will convert the passed datetime to whatever format the current
45database prefers
46
47=item * -dt_diff => [$unit, \'foo.date_from', \'foo.date_to']
48
49This function will diff two dates and return the units requested. Note that
50it correctly recurses if you pass it something like a function or a date value.
51Also note that not all RDBMS' are equal; some units supported on some databases
52and some are supported on others. See the documentation for the SQLMaker class
53for your database.
54
55=item * -dt_get => [$part, \'foo.date_col']
56
57This function will extract the passed part from the passed column. Note that
58it correctly recurses if you pass it something like a function or a date value.
59Also note that not all RDBMS' are equal; some parts supported on some databases
60and some are supported on others. See the documentation for the SQLMaker class
61for your database.
62
63=item * -dt_year => \'foo.date_col'
64
65A shortcut for -dt_get => [year => ...]
66
67=item * -dt_month => \'foo.date_col'
68
69A shortcut for -dt_get => [month => ...]
70
71=item * -dt_day => \'foo.date_col'
72
73A shortcut for -dt_get => [day_of_month => ...]
74
75=item * -dt_hour => \'foo.date_col'
76
77A shortcut for -dt_get => [hour => ...]
78
79=item * -dt_minute => \'foo.date_col'
80
81A shortcut for -dt_get => [minute => ...]
82
83=item * -dt_second => \'foo.date_col'
84
85A shortcut for -dt_get => [second => ...]
86
87=back
88
d5dedbd6 89=back
90
7b924a44 91Another operator is C<-func> that allows you to call SQL functions with
92arguments. It receives an array reference containing the function name
93as the 0th argument and the other arguments being its parameters. For example:
94
95 my %where = {
96 -func => ['substr', 'Hello', 50, 5],
97 };
98
99Would give you:
100
101 $stmt = "WHERE (substr(?,?,?))";
102 @bind = ("Hello", 50, 5);
103
104Yet another operator is C<-op> that allows you to use SQL operators. It
105receives an array reference containing the operator 0th argument and the other
106arguments being its operands. For example:
107
108 my %where = {
109 foo => { -op => ['+', \'bar', 50, 5] },
110 };
111
112Would give you:
113
114 $stmt = "WHERE (foo = bar + ? + ?)";
115 @bind = (50, 5);
116
d5dedbd6 117=cut
6a247f33 118
119use base qw/
d5dedbd6 120 DBIx::Class::SQLMaker::LimitDialects
c173ce76 121 DBIx::Class::SQLMaker::DateOps
6a247f33 122 SQL::Abstract
70c28808 123 DBIx::Class
6a247f33 124/;
125use mro 'c3';
a697fa31 126
6298a324 127use Sub::Name 'subname';
70c28808 128use DBIx::Class::Carp;
129use DBIx::Class::Exception;
e8fc51c7 130use namespace::clean;
b2b22cd6 131
c173ce76 132__PACKAGE__->mk_group_accessors (simple => qw/quote_char name_sep limit_dialect/);
6a247f33 133
3f5b99fe 134# for when I need a normalized l/r pair
135sub _quote_chars {
136 map
137 { defined $_ ? $_ : '' }
138 ( ref $_[0]->{quote_char} ? (@{$_[0]->{quote_char}}) : ( ($_[0]->{quote_char}) x 2 ) )
139 ;
140}
141
70c28808 142# FIXME when we bring in the storage weaklink, check its schema
143# weaklink and channel through $schema->throw_exception
144sub throw_exception { DBIx::Class::Exception->throw($_[1]) }
145
b2b22cd6 146BEGIN {
2ea6032a 147 # reinstall the belch()/puke() functions of SQL::Abstract with custom versions
70c28808 148 # that use DBIx::Class::Carp/DBIx::Class::Exception instead of plain Carp
b2b22cd6 149 no warnings qw/redefine/;
2ea6032a 150
151 *SQL::Abstract::belch = subname 'SQL::Abstract::belch' => sub (@) {
152 my($func) = (caller(1))[3];
153 carp "[$func] Warning: ", @_;
154 };
155
156 *SQL::Abstract::puke = subname 'SQL::Abstract::puke' => sub (@) {
157 my($func) = (caller(1))[3];
70c28808 158 __PACKAGE__->throw_exception("[$func] Fatal: " . join ('', @_));
2ea6032a 159 };
9c1700e3 160
161 # Current SQLA pollutes its namespace - clean for the time being
162 namespace::clean->clean_subroutines(qw/SQL::Abstract carp croak confess/);
b2b22cd6 163}
6f4ddea1 164
e9657379 165# the "oh noes offset/top without limit" constant
fcb7fcbb 166# limited to 31 bits for sanity (and consistency,
167# since it may be handed to the like of sprintf %u)
168#
169# Also *some* builds of SQLite fail the test
170# some_column BETWEEN ? AND ?: 1, 4294967295
171# with the proper integer bind attrs
172#
6a247f33 173# Implemented as a method, since ::Storage::DBI also
174# refers to it (i.e. for the case of software_limit or
175# as the value to abuse with MSSQL ordered subqueries)
fcb7fcbb 176sub __max_int () { 0x7FFFFFFF };
e9657379 177
e39f188a 178# poor man's de-qualifier
179sub _quote {
180 $_[0]->next::method( ( $_[0]{_dequalify_idents} and ! ref $_[1] )
181 ? $_[1] =~ / ([^\.]+) $ /x
182 : $_[1]
183 );
184}
185
e6600283 186sub new {
187 my $self = shift->next::method(@_);
188
41519379 189 # use the same coderefs, they are prepared to handle both cases
190 my @extra_dbic_syntax = (
191 { regex => qr/^ ident $/xi, handler => '_where_op_IDENT' },
192 { regex => qr/^ value $/xi, handler => '_where_op_VALUE' },
7b924a44 193 { regex => qr/^ func $/ix, handler => '_where_op_FUNC' },
194 { regex => qr/^ op $/ix, handler => '_where_op_OP' },
5e6893d4 195 { regex => qr/^ dt $/xi, handler => '_where_op_CONVERT_DATETIME' },
196 { regex => qr/^ dt_get $/xi, handler => '_where_op_GET_DATETIME' },
197 { regex => qr/^ dt_diff $/xi, handler => '_where_op_DIFF_DATETIME' },
c173ce76 198 { regex => qr/^ dt_add $/xi, handler => '_where_op_ADD_DATETIME' },
199 { regex => qr/^ dt_now $/xi, handler => '_where_op_DATETIME_NOW' },
69320ddf 200 { regex => qr/^ dt_(:?on_or_)?(:?before|after) $/xi, handler => '_where_op_CIRCA_DATETIME' },
5e6893d4 201 map +{ regex => qr/^ dt_$_ $/xi, handler => '_where_op_GET_DATETIME_'.uc($_) },
d6e3e773 202 qw(year month day hour minute second)
41519379 203 );
204
205 push @{$self->{special_ops}}, @extra_dbic_syntax;
206 push @{$self->{unary_ops}}, @extra_dbic_syntax;
e6600283 207
208 $self;
209}
210
211sub _where_op_IDENT {
212 my $self = shift;
213 my ($op, $rhs) = splice @_, -2;
214 if (ref $rhs) {
70c28808 215 $self->throw_exception("-$op takes a single scalar argument (a quotable identifier)");
e6600283 216 }
217
41519379 218 # in case we are called as a top level special op (no '=')
e6600283 219 my $lhs = shift;
220
221 $_ = $self->_convert($self->_quote($_)) for ($lhs, $rhs);
222
223 return $lhs
224 ? "$lhs = $rhs"
225 : $rhs
226 ;
227}
228
41519379 229sub _where_op_VALUE {
230 my $self = shift;
231 my ($op, $rhs) = splice @_, -2;
232
233 # in case we are called as a top level special op (no '=')
234 my $lhs = shift;
235
236 my @bind = [
70c28808 237 ($lhs || $self->{_nested_func_lhs} || $self->throw_exception("Unable to find bindtype for -value $rhs") ),
41519379 238 $rhs
239 ];
240
241 return $lhs
242 ? (
243 $self->_convert($self->_quote($lhs)) . ' = ' . $self->_convert('?'),
244 @bind
245 )
246 : (
247 $self->_convert('?'),
248 @bind,
249 )
250 ;
251}
252
b1d821de 253sub _where_op_NEST {
70c28808 254 carp_unique ("-nest in search conditions is deprecated, you most probably wanted:\n"
b1d821de 255 .q|{..., -and => [ \%cond0, \@cond1, \'cond2', \[ 'cond3', [ col => bind ] ], etc. ], ... }|
70c28808 256 );
b1d821de 257
258 shift->next::method(@_);
259}
260
6a247f33 261# Handle limit-dialect selection
6f4ddea1 262sub select {
6a247f33 263 my ($self, $table, $fields, $where, $rs_attrs, $limit, $offset) = @_;
264
265
266 $fields = $self->_recurse_fields($fields);
267
268 if (defined $offset) {
70c28808 269 $self->throw_exception('A supplied offset must be a non-negative integer')
6a247f33 270 if ( $offset =~ /\D/ or $offset < 0 );
271 }
272 $offset ||= 0;
1cbd3034 273
6a247f33 274 if (defined $limit) {
70c28808 275 $self->throw_exception('A supplied limit must be a positive integer')
6a247f33 276 if ( $limit =~ /\D/ or $limit <= 0 );
277 }
278 elsif ($offset) {
279 $limit = $self->__max_int;
6f4ddea1 280 }
c2b7c5dc 281
a6b68a60 282
6a247f33 283 my ($sql, @bind);
284 if ($limit) {
285 # this is legacy code-flow from SQLA::Limit, it is not set in stone
286
287 ($sql, @bind) = $self->next::method ($table, $fields, $where);
288
289 my $limiter =
290 $self->can ('emulate_limit') # also backcompat hook from SQLA::Limit
291 ||
292 do {
293 my $dialect = $self->limit_dialect
70c28808 294 or $self->throw_exception( "Unable to generate SQL-limit - no limit dialect specified on $self, and no emulate_limit method found" );
6a247f33 295 $self->can ("_$dialect")
70c28808 296 or $self->throw_exception(__PACKAGE__ . " does not implement the requested dialect '$dialect'");
6a247f33 297 }
298 ;
299
f74d22e2 300 $sql = $self->$limiter (
301 $sql,
302 { %{$rs_attrs||{}}, _selector_sql => $fields },
303 $limit,
304 $offset
305 );
6a247f33 306 }
307 else {
308 ($sql, @bind) = $self->next::method ($table, $fields, $where, $rs_attrs);
309 }
310
49afd714 311 push @{$self->{where_bind}}, @bind;
583a0c65 312
313# this *must* be called, otherwise extra binds will remain in the sql-maker
49afd714 314 my @all_bind = $self->_assemble_binds;
583a0c65 315
e5372da4 316 $sql .= $self->_lock_select ($rs_attrs->{for})
317 if $rs_attrs->{for};
318
49afd714 319 return wantarray ? ($sql, @all_bind) : $sql;
583a0c65 320}
321
322sub _assemble_binds {
323 my $self = shift;
fcb7fcbb 324 return map { @{ (delete $self->{"${_}_bind"}) || [] } } (qw/select from where group having order limit/);
6f4ddea1 325}
326
e5372da4 327my $for_syntax = {
328 update => 'FOR UPDATE',
329 shared => 'FOR SHARE',
330};
331sub _lock_select {
332 my ($self, $type) = @_;
70c28808 333 my $sql = $for_syntax->{$type} || $self->throw_exception( "Unknown SELECT .. FOR type '$type' requested" );
e5372da4 334 return " $sql";
335}
336
6a247f33 337# Handle default inserts
6f4ddea1 338sub insert {
6a247f33 339# optimized due to hotttnesss
340# my ($self, $table, $data, $options) = @_;
7a72e5a5 341
342 # SQLA will emit INSERT INTO $table ( ) VALUES ( )
343 # which is sadly understood only by MySQL. Change default behavior here,
344 # until SQLA2 comes with proper dialect support
6a247f33 345 if (! $_[2] or (ref $_[2] eq 'HASH' and !keys %{$_[2]} ) ) {
bf51641f 346 my @bind;
20595c02 347 my $sql = sprintf(
348 'INSERT INTO %s DEFAULT VALUES', $_[0]->_quote($_[1])
349 );
28d28903 350
bf51641f 351 if ( ($_[3]||{})->{returning} ) {
352 my $s;
353 ($s, @bind) = $_[0]->_insert_returning ($_[3]);
354 $sql .= $s;
28d28903 355 }
356
bf51641f 357 return ($sql, @bind);
7a72e5a5 358 }
359
6a247f33 360 next::method(@_);
6f4ddea1 361}
362
363sub _recurse_fields {
e0971d1e 364 my ($self, $fields, $depth) = @_;
365 $depth ||= 0;
6f4ddea1 366 my $ref = ref $fields;
367 return $self->_quote($fields) unless $ref;
368 return $$fields if $ref eq 'SCALAR';
369
370 if ($ref eq 'ARRAY') {
e0971d1e 371 return join(', ', map { $self->_recurse_fields($_, $depth + 1) } @$fields)
372 if $depth != 1;
373
374 my ($sql, @bind) = $self->_recurse_where({@$fields});
375
376 push @{$self->{select_bind}}, @bind;
377 return $sql;
83e09b5b 378 }
379 elsif ($ref eq 'HASH') {
81446c4f 380 my %hash = %$fields; # shallow copy
83e09b5b 381
50136dd9 382 my $as = delete $hash{-as}; # if supplied
383
81446c4f 384 my ($func, $args, @toomany) = %hash;
385
386 # there should be only one pair
387 if (@toomany) {
70c28808 388 $self->throw_exception( "Malformed select argument - too many keys in hash: " . join (',', keys %$fields ) );
81446c4f 389 }
50136dd9 390
391 if (lc ($func) eq 'distinct' && ref $args eq 'ARRAY' && @$args > 1) {
70c28808 392 $self->throw_exception (
50136dd9 393 'The select => { distinct => ... } syntax is not supported for multiple columns.'
394 .' Instead please use { group_by => [ qw/' . (join ' ', @$args) . '/ ] }'
395 .' or { select => [ qw/' . (join ' ', @$args) . '/ ], distinct => 1 }'
83e09b5b 396 );
6f4ddea1 397 }
83e09b5b 398
50136dd9 399 my $select = sprintf ('%s( %s )%s',
400 $self->_sqlcase($func),
e0971d1e 401 $self->_recurse_fields($args, $depth + 1),
50136dd9 402 $as
0491b597 403 ? sprintf (' %s %s', $self->_sqlcase('as'), $self->_quote ($as) )
50136dd9 404 : ''
405 );
406
83e09b5b 407 return $select;
6f4ddea1 408 }
409 # Is the second check absolutely necessary?
410 elsif ( $ref eq 'REF' and ref($$fields) eq 'ARRAY' ) {
4c2b30d6 411 push @{$self->{select_bind}}, @{$$fields}[1..$#$$fields];
412 return $$fields->[0];
6f4ddea1 413 }
414 else {
70c28808 415 $self->throw_exception( $ref . qq{ unexpected in _recurse_fields()} );
6f4ddea1 416 }
417}
418
a6b68a60 419
420# this used to be a part of _order_by but is broken out for clarity.
421# What we have been doing forever is hijacking the $order arg of
422# SQLA::select to pass in arbitrary pieces of data (first the group_by,
423# then pretty much the entire resultset attr-hash, as more and more
424# things in the SQLA space need to have mopre info about the $rs they
425# create SQL for. The alternative would be to keep expanding the
426# signature of _select with more and more positional parameters, which
427# is just gross. All hail SQLA2!
428sub _parse_rs_attrs {
1cbd3034 429 my ($self, $arg) = @_;
15827712 430
a6b68a60 431 my $sql = '';
1cbd3034 432
0542ec57 433 if ($arg->{group_by}) {
434 # horible horrible, waiting for refactor
435 local $self->{select_bind};
436 if (my $g = $self->_recurse_fields($arg->{group_by}) ) {
437 $sql .= $self->_sqlcase(' group by ') . $g;
438 push @{$self->{group_bind} ||= []}, @{$self->{select_bind}||[]};
439 }
a6b68a60 440 }
1cbd3034 441
a6b68a60 442 if (defined $arg->{having}) {
443 my ($frag, @bind) = $self->_recurse_where($arg->{having});
444 push(@{$self->{having_bind}}, @bind);
445 $sql .= $self->_sqlcase(' having ') . $frag;
446 }
15827712 447
a6b68a60 448 if (defined $arg->{order_by}) {
449 $sql .= $self->_order_by ($arg->{order_by});
450 }
15827712 451
a6b68a60 452 return $sql;
453}
454
455sub _order_by {
456 my ($self, $arg) = @_;
15827712 457
a6b68a60 458 # check that we are not called in legacy mode (order_by as 4th argument)
459 if (ref $arg eq 'HASH' and not grep { $_ =~ /^-(?:desc|asc)/i } keys %$arg ) {
460 return $self->_parse_rs_attrs ($arg);
fde3719a 461 }
1cbd3034 462 else {
6a247f33 463 my ($sql, @bind) = $self->next::method($arg);
a6b68a60 464 push @{$self->{order_bind}}, @bind;
1cbd3034 465 return $sql;
fd4cb60a 466 }
6f4ddea1 467}
468
469sub _table {
6a247f33 470# optimized due to hotttnesss
471# my ($self, $from) = @_;
472 if (my $ref = ref $_[1] ) {
473 if ($ref eq 'ARRAY') {
474 return $_[0]->_recurse_from(@{$_[1]});
475 }
476 elsif ($ref eq 'HASH') {
4c2b30d6 477 return $_[0]->_recurse_from($_[1]);
6a247f33 478 }
1bffc6b8 479 elsif ($ref eq 'REF' && ref ${$_[1]} eq 'ARRAY') {
480 my ($sql, @bind) = @{ ${$_[1]} };
481 push @{$_[0]->{from_bind}}, @bind;
482 return $sql
483 }
6f4ddea1 484 }
6a247f33 485 return $_[0]->next::method ($_[1]);
6f4ddea1 486}
487
b8391c87 488sub _generate_join_clause {
489 my ($self, $join_type) = @_;
490
726c8f65 491 $join_type = $self->{_default_jointype}
492 if ! defined $join_type;
493
b8391c87 494 return sprintf ('%s JOIN ',
726c8f65 495 $join_type ? $self->_sqlcase($join_type) : ''
b8391c87 496 );
497}
498
7b924a44 499sub _where_op_FUNC {
500 my ($self) = @_;
501
502 my ($k, $vals);
503
504 if (@_ == 3) {
505 # $_[1] gets set to "op"
506 $vals = $_[2];
507 $k = '';
508 } elsif (@_ == 4) {
509 $k = $_[1];
510 # $_[2] gets set to "op"
511 $vals = $_[3];
512 }
513
514 my $label = $self->_convert($self->_quote($k));
515 my $placeholder = $self->_convert('?');
516
90676d46 517 $self->throw_exception('-func must be an array') unless ref $vals eq 'ARRAY';
518 $self->throw_exception('first arg for -func must be a scalar') unless !ref $vals->[0];
7b924a44 519
520 my ($func,@rest_of_vals) = @$vals;
521
522 $self->_assert_pass_injection_guard($func);
523
524 my (@all_sql, @all_bind);
525 foreach my $val (@rest_of_vals) {
526 my ($sql, @bind) = $self->_SWITCH_refkind($val, {
527 SCALAR => sub {
528 return ($placeholder, $self->_bindtype($k, $val) );
529 },
530 SCALARREF => sub {
531 return $$val;
532 },
533 ARRAYREFREF => sub {
534 my ($sql, @bind) = @$$val;
535 $self->_assert_bindval_matches_bindtype(@bind);
536 return ($sql, @bind);
537 },
538 HASHREF => sub {
539 my $method = $self->_METHOD_FOR_refkind("_where_hashpair", $val);
540 $self->$method('', $val);
541 }
542 });
543 push @all_sql, $sql;
544 push @all_bind, @bind;
545 }
546
547 my ($clause, @bind) = ("$func(" . (join ",", @all_sql) . ")", @all_bind);
548
549 my $sql = $k ? "( $label = $clause )" : "( $clause )";
550 return ($sql, @bind)
551}
552
553sub _where_op_OP {
554 my ($self) = @_;
555
556 my ($k, $vals);
557
558 if (@_ == 3) {
559 # $_[1] gets set to "op"
560 $vals = $_[2];
561 $k = '';
562 } elsif (@_ == 4) {
563 $k = $_[1];
564 # $_[2] gets set to "op"
565 $vals = $_[3];
566 }
567
568 my $label = $self->_convert($self->_quote($k));
569 my $placeholder = $self->_convert('?');
570
90676d46 571 $self->throw_exception('argument to -op must be an arrayref') unless ref $vals eq 'ARRAY';
572 $self->throw_exception('first arg for -op must be a scalar') unless !ref $vals->[0];
7b924a44 573
574 my ($op, @rest_of_vals) = @$vals;
575
576 $self->_assert_pass_injection_guard($op);
577
578 my (@all_sql, @all_bind);
579 foreach my $val (@rest_of_vals) {
580 my ($sql, @bind) = $self->_SWITCH_refkind($val, {
581 SCALAR => sub {
582 return ($placeholder, $self->_bindtype($k, $val) );
583 },
584 SCALARREF => sub {
585 return $$val;
586 },
587 ARRAYREFREF => sub {
588 my ($sql, @bind) = @$$val;
589 $self->_assert_bindval_matches_bindtype(@bind);
590 return ($sql, @bind);
591 },
592 HASHREF => sub {
593 my $method = $self->_METHOD_FOR_refkind("_where_hashpair", $val);
594 $self->$method('', $val);
595 }
596 });
597 push @all_sql, $sql;
598 push @all_bind, @bind;
599 }
600
601 my ($clause, @bind) = ((join " $op ", @all_sql), @all_bind);
602
603 my $sql = $k ? "( $label = $clause )" : "( $clause )";
604 return ($sql, @bind)
605}
606
6f4ddea1 607sub _recurse_from {
726c8f65 608 my $self = shift;
609
610 return join (' ', $self->_gen_from_blocks(@_) );
611}
612
613sub _gen_from_blocks {
614 my ($self, $from, @joins) = @_;
615
616 my @fchunks = $self->_from_chunk_to_sql($from);
6f4ddea1 617
726c8f65 618 for (@joins) {
4c2b30d6 619 my ($to, $on) = @$_;
aa82ce29 620
6f4ddea1 621 # check whether a join type exists
6f4ddea1 622 my $to_jt = ref($to) eq 'ARRAY' ? $to->[0] : $to;
aa82ce29 623 my $join_type;
624 if (ref($to_jt) eq 'HASH' and defined($to_jt->{-join_type})) {
625 $join_type = $to_jt->{-join_type};
626 $join_type =~ s/^\s+ | \s+$//xg;
6f4ddea1 627 }
aa82ce29 628
726c8f65 629 my @j = $self->_generate_join_clause( $join_type );
6f4ddea1 630
631 if (ref $to eq 'ARRAY') {
726c8f65 632 push(@j, '(', $self->_recurse_from(@$to), ')');
633 }
634 else {
635 push(@j, $self->_from_chunk_to_sql($to));
6f4ddea1 636 }
726c8f65 637
a697fa31 638 my ($sql, @bind) = $self->_join_condition($on);
b4e9f590 639 push(@j, ' ON ', $sql);
a697fa31 640 push @{$self->{from_bind}}, @bind;
726c8f65 641
642 push @fchunks, join '', @j;
6f4ddea1 643 }
726c8f65 644
645 return @fchunks;
6f4ddea1 646}
647
4c2b30d6 648sub _from_chunk_to_sql {
649 my ($self, $fromspec) = @_;
650
651 return join (' ', $self->_SWITCH_refkind($fromspec, {
652 SCALARREF => sub {
653 $$fromspec;
654 },
655 ARRAYREFREF => sub {
656 push @{$self->{from_bind}}, @{$$fromspec}[1..$#$$fromspec];
657 $$fromspec->[0];
658 },
659 HASHREF => sub {
660 my ($as, $table, $toomuch) = ( map
661 { $_ => $fromspec->{$_} }
662 ( grep { $_ !~ /^\-/ } keys %$fromspec )
663 );
6f4ddea1 664
70c28808 665 $self->throw_exception( "Only one table/as pair expected in from-spec but an exra '$toomuch' key present" )
4c2b30d6 666 if defined $toomuch;
6f4ddea1 667
4c2b30d6 668 ($self->_from_chunk_to_sql($table), $self->_quote($as) );
669 },
670 SCALAR => sub {
671 $self->_quote($fromspec);
672 },
673 }));
6f4ddea1 674}
675
676sub _join_condition {
677 my ($self, $cond) = @_;
4c2b30d6 678
a697fa31 679 # Backcompat for the old days when a plain hashref
680 # { 't1.col1' => 't2.col2' } meant ON t1.col1 = t2.col2
681 # Once things settle we should start warning here so that
682 # folks unroll their hacks
683 if (
684 ref $cond eq 'HASH'
685 and
686 keys %$cond == 1
687 and
688 (keys %$cond)[0] =~ /\./
689 and
690 ! ref ( (values %$cond)[0] )
691 ) {
692 $cond = { keys %$cond => { -ident => values %$cond } }
6f4ddea1 693 }
a697fa31 694 elsif ( ref $cond eq 'ARRAY' ) {
695 # do our own ORing so that the hashref-shim above is invoked
9aae3566 696 my @parts;
697 my @binds;
698 foreach my $c (@$cond) {
699 my ($sql, @bind) = $self->_join_condition($c);
700 push @binds, @bind;
701 push @parts, $sql;
702 }
703 return join(' OR ', @parts), @binds;
6f4ddea1 704 }
a697fa31 705
706 return $self->_recurse_where($cond);
6f4ddea1 707}
708
6f4ddea1 7091;
d5dedbd6 710
2bb4c37b 711=head1 OPERATORS
712
713=head2 -ident
714
715Used to explicitly specify an SQL identifier. Takes a plain string as value
716which is then invariably treated as a column name (and is being properly
717quoted if quoting has been requested). Most useful for comparison of two
718columns:
719
720 my %where = (
721 priority => { '<', 2 },
722 requestor => { -ident => 'submitter' }
723 );
724
725which results in:
726
727 $stmt = 'WHERE "priority" < ? AND "requestor" = "submitter"';
728 @bind = ('2');
729
730=head2 -value
731
732The -value operator signals that the argument to the right is a raw bind value.
733It will be passed straight to DBI, without invoking any of the SQL::Abstract
734condition-parsing logic. This allows you to, for example, pass an array as a
735column value for databases that support array datatypes, e.g.:
736
737 my %where = (
738 array => { -value => [1, 2, 3] }
739 );
740
741which results in:
742
743 $stmt = 'WHERE array = ?';
744 @bind = ([1, 2, 3]);
745
d5dedbd6 746=head1 AUTHORS
747
748See L<DBIx::Class/CONTRIBUTORS>.
749
750=head1 LICENSE
751
752You may distribute this code under the same terms as Perl itself.
753
754=cut