fix mssql
[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' },
5e6893d4 200 map +{ regex => qr/^ dt_$_ $/xi, handler => '_where_op_GET_DATETIME_'.uc($_) },
d6e3e773 201 qw(year month day hour minute second)
41519379 202 );
203
204 push @{$self->{special_ops}}, @extra_dbic_syntax;
205 push @{$self->{unary_ops}}, @extra_dbic_syntax;
e6600283 206
207 $self;
208}
209
210sub _where_op_IDENT {
211 my $self = shift;
212 my ($op, $rhs) = splice @_, -2;
213 if (ref $rhs) {
70c28808 214 $self->throw_exception("-$op takes a single scalar argument (a quotable identifier)");
e6600283 215 }
216
41519379 217 # in case we are called as a top level special op (no '=')
e6600283 218 my $lhs = shift;
219
220 $_ = $self->_convert($self->_quote($_)) for ($lhs, $rhs);
221
222 return $lhs
223 ? "$lhs = $rhs"
224 : $rhs
225 ;
226}
227
41519379 228sub _where_op_VALUE {
229 my $self = shift;
230 my ($op, $rhs) = splice @_, -2;
231
232 # in case we are called as a top level special op (no '=')
233 my $lhs = shift;
234
235 my @bind = [
70c28808 236 ($lhs || $self->{_nested_func_lhs} || $self->throw_exception("Unable to find bindtype for -value $rhs") ),
41519379 237 $rhs
238 ];
239
240 return $lhs
241 ? (
242 $self->_convert($self->_quote($lhs)) . ' = ' . $self->_convert('?'),
243 @bind
244 )
245 : (
246 $self->_convert('?'),
247 @bind,
248 )
249 ;
250}
251
b1d821de 252sub _where_op_NEST {
70c28808 253 carp_unique ("-nest in search conditions is deprecated, you most probably wanted:\n"
b1d821de 254 .q|{..., -and => [ \%cond0, \@cond1, \'cond2', \[ 'cond3', [ col => bind ] ], etc. ], ... }|
70c28808 255 );
b1d821de 256
257 shift->next::method(@_);
258}
259
6a247f33 260# Handle limit-dialect selection
6f4ddea1 261sub select {
6a247f33 262 my ($self, $table, $fields, $where, $rs_attrs, $limit, $offset) = @_;
263
264
265 $fields = $self->_recurse_fields($fields);
266
267 if (defined $offset) {
70c28808 268 $self->throw_exception('A supplied offset must be a non-negative integer')
6a247f33 269 if ( $offset =~ /\D/ or $offset < 0 );
270 }
271 $offset ||= 0;
1cbd3034 272
6a247f33 273 if (defined $limit) {
70c28808 274 $self->throw_exception('A supplied limit must be a positive integer')
6a247f33 275 if ( $limit =~ /\D/ or $limit <= 0 );
276 }
277 elsif ($offset) {
278 $limit = $self->__max_int;
6f4ddea1 279 }
c2b7c5dc 280
a6b68a60 281
6a247f33 282 my ($sql, @bind);
283 if ($limit) {
284 # this is legacy code-flow from SQLA::Limit, it is not set in stone
285
286 ($sql, @bind) = $self->next::method ($table, $fields, $where);
287
288 my $limiter =
289 $self->can ('emulate_limit') # also backcompat hook from SQLA::Limit
290 ||
291 do {
292 my $dialect = $self->limit_dialect
70c28808 293 or $self->throw_exception( "Unable to generate SQL-limit - no limit dialect specified on $self, and no emulate_limit method found" );
6a247f33 294 $self->can ("_$dialect")
70c28808 295 or $self->throw_exception(__PACKAGE__ . " does not implement the requested dialect '$dialect'");
6a247f33 296 }
297 ;
298
f74d22e2 299 $sql = $self->$limiter (
300 $sql,
301 { %{$rs_attrs||{}}, _selector_sql => $fields },
302 $limit,
303 $offset
304 );
6a247f33 305 }
306 else {
307 ($sql, @bind) = $self->next::method ($table, $fields, $where, $rs_attrs);
308 }
309
49afd714 310 push @{$self->{where_bind}}, @bind;
583a0c65 311
312# this *must* be called, otherwise extra binds will remain in the sql-maker
49afd714 313 my @all_bind = $self->_assemble_binds;
583a0c65 314
e5372da4 315 $sql .= $self->_lock_select ($rs_attrs->{for})
316 if $rs_attrs->{for};
317
49afd714 318 return wantarray ? ($sql, @all_bind) : $sql;
583a0c65 319}
320
321sub _assemble_binds {
322 my $self = shift;
fcb7fcbb 323 return map { @{ (delete $self->{"${_}_bind"}) || [] } } (qw/select from where group having order limit/);
6f4ddea1 324}
325
e5372da4 326my $for_syntax = {
327 update => 'FOR UPDATE',
328 shared => 'FOR SHARE',
329};
330sub _lock_select {
331 my ($self, $type) = @_;
70c28808 332 my $sql = $for_syntax->{$type} || $self->throw_exception( "Unknown SELECT .. FOR type '$type' requested" );
e5372da4 333 return " $sql";
334}
335
6a247f33 336# Handle default inserts
6f4ddea1 337sub insert {
6a247f33 338# optimized due to hotttnesss
339# my ($self, $table, $data, $options) = @_;
7a72e5a5 340
341 # SQLA will emit INSERT INTO $table ( ) VALUES ( )
342 # which is sadly understood only by MySQL. Change default behavior here,
343 # until SQLA2 comes with proper dialect support
6a247f33 344 if (! $_[2] or (ref $_[2] eq 'HASH' and !keys %{$_[2]} ) ) {
bf51641f 345 my @bind;
20595c02 346 my $sql = sprintf(
347 'INSERT INTO %s DEFAULT VALUES', $_[0]->_quote($_[1])
348 );
28d28903 349
bf51641f 350 if ( ($_[3]||{})->{returning} ) {
351 my $s;
352 ($s, @bind) = $_[0]->_insert_returning ($_[3]);
353 $sql .= $s;
28d28903 354 }
355
bf51641f 356 return ($sql, @bind);
7a72e5a5 357 }
358
6a247f33 359 next::method(@_);
6f4ddea1 360}
361
362sub _recurse_fields {
e0971d1e 363 my ($self, $fields, $depth) = @_;
364 $depth ||= 0;
6f4ddea1 365 my $ref = ref $fields;
366 return $self->_quote($fields) unless $ref;
367 return $$fields if $ref eq 'SCALAR';
368
369 if ($ref eq 'ARRAY') {
e0971d1e 370 return join(', ', map { $self->_recurse_fields($_, $depth + 1) } @$fields)
371 if $depth != 1;
372
373 my ($sql, @bind) = $self->_recurse_where({@$fields});
374
375 push @{$self->{select_bind}}, @bind;
376 return $sql;
83e09b5b 377 }
378 elsif ($ref eq 'HASH') {
81446c4f 379 my %hash = %$fields; # shallow copy
83e09b5b 380
50136dd9 381 my $as = delete $hash{-as}; # if supplied
382
81446c4f 383 my ($func, $args, @toomany) = %hash;
384
385 # there should be only one pair
386 if (@toomany) {
70c28808 387 $self->throw_exception( "Malformed select argument - too many keys in hash: " . join (',', keys %$fields ) );
81446c4f 388 }
50136dd9 389
390 if (lc ($func) eq 'distinct' && ref $args eq 'ARRAY' && @$args > 1) {
70c28808 391 $self->throw_exception (
50136dd9 392 'The select => { distinct => ... } syntax is not supported for multiple columns.'
393 .' Instead please use { group_by => [ qw/' . (join ' ', @$args) . '/ ] }'
394 .' or { select => [ qw/' . (join ' ', @$args) . '/ ], distinct => 1 }'
83e09b5b 395 );
6f4ddea1 396 }
83e09b5b 397
50136dd9 398 my $select = sprintf ('%s( %s )%s',
399 $self->_sqlcase($func),
e0971d1e 400 $self->_recurse_fields($args, $depth + 1),
50136dd9 401 $as
0491b597 402 ? sprintf (' %s %s', $self->_sqlcase('as'), $self->_quote ($as) )
50136dd9 403 : ''
404 );
405
83e09b5b 406 return $select;
6f4ddea1 407 }
408 # Is the second check absolutely necessary?
409 elsif ( $ref eq 'REF' and ref($$fields) eq 'ARRAY' ) {
4c2b30d6 410 push @{$self->{select_bind}}, @{$$fields}[1..$#$$fields];
411 return $$fields->[0];
6f4ddea1 412 }
413 else {
70c28808 414 $self->throw_exception( $ref . qq{ unexpected in _recurse_fields()} );
6f4ddea1 415 }
416}
417
a6b68a60 418
419# this used to be a part of _order_by but is broken out for clarity.
420# What we have been doing forever is hijacking the $order arg of
421# SQLA::select to pass in arbitrary pieces of data (first the group_by,
422# then pretty much the entire resultset attr-hash, as more and more
423# things in the SQLA space need to have mopre info about the $rs they
424# create SQL for. The alternative would be to keep expanding the
425# signature of _select with more and more positional parameters, which
426# is just gross. All hail SQLA2!
427sub _parse_rs_attrs {
1cbd3034 428 my ($self, $arg) = @_;
15827712 429
a6b68a60 430 my $sql = '';
1cbd3034 431
0542ec57 432 if ($arg->{group_by}) {
433 # horible horrible, waiting for refactor
434 local $self->{select_bind};
435 if (my $g = $self->_recurse_fields($arg->{group_by}) ) {
436 $sql .= $self->_sqlcase(' group by ') . $g;
437 push @{$self->{group_bind} ||= []}, @{$self->{select_bind}||[]};
438 }
a6b68a60 439 }
1cbd3034 440
a6b68a60 441 if (defined $arg->{having}) {
442 my ($frag, @bind) = $self->_recurse_where($arg->{having});
443 push(@{$self->{having_bind}}, @bind);
444 $sql .= $self->_sqlcase(' having ') . $frag;
445 }
15827712 446
a6b68a60 447 if (defined $arg->{order_by}) {
448 $sql .= $self->_order_by ($arg->{order_by});
449 }
15827712 450
a6b68a60 451 return $sql;
452}
453
454sub _order_by {
455 my ($self, $arg) = @_;
15827712 456
a6b68a60 457 # check that we are not called in legacy mode (order_by as 4th argument)
458 if (ref $arg eq 'HASH' and not grep { $_ =~ /^-(?:desc|asc)/i } keys %$arg ) {
459 return $self->_parse_rs_attrs ($arg);
fde3719a 460 }
1cbd3034 461 else {
6a247f33 462 my ($sql, @bind) = $self->next::method($arg);
a6b68a60 463 push @{$self->{order_bind}}, @bind;
1cbd3034 464 return $sql;
fd4cb60a 465 }
6f4ddea1 466}
467
468sub _table {
6a247f33 469# optimized due to hotttnesss
470# my ($self, $from) = @_;
471 if (my $ref = ref $_[1] ) {
472 if ($ref eq 'ARRAY') {
473 return $_[0]->_recurse_from(@{$_[1]});
474 }
475 elsif ($ref eq 'HASH') {
4c2b30d6 476 return $_[0]->_recurse_from($_[1]);
6a247f33 477 }
1bffc6b8 478 elsif ($ref eq 'REF' && ref ${$_[1]} eq 'ARRAY') {
479 my ($sql, @bind) = @{ ${$_[1]} };
480 push @{$_[0]->{from_bind}}, @bind;
481 return $sql
482 }
6f4ddea1 483 }
6a247f33 484 return $_[0]->next::method ($_[1]);
6f4ddea1 485}
486
b8391c87 487sub _generate_join_clause {
488 my ($self, $join_type) = @_;
489
726c8f65 490 $join_type = $self->{_default_jointype}
491 if ! defined $join_type;
492
b8391c87 493 return sprintf ('%s JOIN ',
726c8f65 494 $join_type ? $self->_sqlcase($join_type) : ''
b8391c87 495 );
496}
497
7b924a44 498sub _where_op_FUNC {
499 my ($self) = @_;
500
501 my ($k, $vals);
502
503 if (@_ == 3) {
504 # $_[1] gets set to "op"
505 $vals = $_[2];
506 $k = '';
507 } elsif (@_ == 4) {
508 $k = $_[1];
509 # $_[2] gets set to "op"
510 $vals = $_[3];
511 }
512
513 my $label = $self->_convert($self->_quote($k));
514 my $placeholder = $self->_convert('?');
515
90676d46 516 $self->throw_exception('-func must be an array') unless ref $vals eq 'ARRAY';
517 $self->throw_exception('first arg for -func must be a scalar') unless !ref $vals->[0];
7b924a44 518
519 my ($func,@rest_of_vals) = @$vals;
520
521 $self->_assert_pass_injection_guard($func);
522
523 my (@all_sql, @all_bind);
524 foreach my $val (@rest_of_vals) {
525 my ($sql, @bind) = $self->_SWITCH_refkind($val, {
526 SCALAR => sub {
527 return ($placeholder, $self->_bindtype($k, $val) );
528 },
529 SCALARREF => sub {
530 return $$val;
531 },
532 ARRAYREFREF => sub {
533 my ($sql, @bind) = @$$val;
534 $self->_assert_bindval_matches_bindtype(@bind);
535 return ($sql, @bind);
536 },
537 HASHREF => sub {
538 my $method = $self->_METHOD_FOR_refkind("_where_hashpair", $val);
539 $self->$method('', $val);
540 }
541 });
542 push @all_sql, $sql;
543 push @all_bind, @bind;
544 }
545
546 my ($clause, @bind) = ("$func(" . (join ",", @all_sql) . ")", @all_bind);
547
548 my $sql = $k ? "( $label = $clause )" : "( $clause )";
549 return ($sql, @bind)
550}
551
552sub _where_op_OP {
553 my ($self) = @_;
554
555 my ($k, $vals);
556
557 if (@_ == 3) {
558 # $_[1] gets set to "op"
559 $vals = $_[2];
560 $k = '';
561 } elsif (@_ == 4) {
562 $k = $_[1];
563 # $_[2] gets set to "op"
564 $vals = $_[3];
565 }
566
567 my $label = $self->_convert($self->_quote($k));
568 my $placeholder = $self->_convert('?');
569
90676d46 570 $self->throw_exception('argument to -op must be an arrayref') unless ref $vals eq 'ARRAY';
571 $self->throw_exception('first arg for -op must be a scalar') unless !ref $vals->[0];
7b924a44 572
573 my ($op, @rest_of_vals) = @$vals;
574
575 $self->_assert_pass_injection_guard($op);
576
577 my (@all_sql, @all_bind);
578 foreach my $val (@rest_of_vals) {
579 my ($sql, @bind) = $self->_SWITCH_refkind($val, {
580 SCALAR => sub {
581 return ($placeholder, $self->_bindtype($k, $val) );
582 },
583 SCALARREF => sub {
584 return $$val;
585 },
586 ARRAYREFREF => sub {
587 my ($sql, @bind) = @$$val;
588 $self->_assert_bindval_matches_bindtype(@bind);
589 return ($sql, @bind);
590 },
591 HASHREF => sub {
592 my $method = $self->_METHOD_FOR_refkind("_where_hashpair", $val);
593 $self->$method('', $val);
594 }
595 });
596 push @all_sql, $sql;
597 push @all_bind, @bind;
598 }
599
600 my ($clause, @bind) = ((join " $op ", @all_sql), @all_bind);
601
602 my $sql = $k ? "( $label = $clause )" : "( $clause )";
603 return ($sql, @bind)
604}
605
6f4ddea1 606sub _recurse_from {
726c8f65 607 my $self = shift;
608
609 return join (' ', $self->_gen_from_blocks(@_) );
610}
611
612sub _gen_from_blocks {
613 my ($self, $from, @joins) = @_;
614
615 my @fchunks = $self->_from_chunk_to_sql($from);
6f4ddea1 616
726c8f65 617 for (@joins) {
4c2b30d6 618 my ($to, $on) = @$_;
aa82ce29 619
6f4ddea1 620 # check whether a join type exists
6f4ddea1 621 my $to_jt = ref($to) eq 'ARRAY' ? $to->[0] : $to;
aa82ce29 622 my $join_type;
623 if (ref($to_jt) eq 'HASH' and defined($to_jt->{-join_type})) {
624 $join_type = $to_jt->{-join_type};
625 $join_type =~ s/^\s+ | \s+$//xg;
6f4ddea1 626 }
aa82ce29 627
726c8f65 628 my @j = $self->_generate_join_clause( $join_type );
6f4ddea1 629
630 if (ref $to eq 'ARRAY') {
726c8f65 631 push(@j, '(', $self->_recurse_from(@$to), ')');
632 }
633 else {
634 push(@j, $self->_from_chunk_to_sql($to));
6f4ddea1 635 }
726c8f65 636
a697fa31 637 my ($sql, @bind) = $self->_join_condition($on);
b4e9f590 638 push(@j, ' ON ', $sql);
a697fa31 639 push @{$self->{from_bind}}, @bind;
726c8f65 640
641 push @fchunks, join '', @j;
6f4ddea1 642 }
726c8f65 643
644 return @fchunks;
6f4ddea1 645}
646
4c2b30d6 647sub _from_chunk_to_sql {
648 my ($self, $fromspec) = @_;
649
650 return join (' ', $self->_SWITCH_refkind($fromspec, {
651 SCALARREF => sub {
652 $$fromspec;
653 },
654 ARRAYREFREF => sub {
655 push @{$self->{from_bind}}, @{$$fromspec}[1..$#$$fromspec];
656 $$fromspec->[0];
657 },
658 HASHREF => sub {
659 my ($as, $table, $toomuch) = ( map
660 { $_ => $fromspec->{$_} }
661 ( grep { $_ !~ /^\-/ } keys %$fromspec )
662 );
6f4ddea1 663
70c28808 664 $self->throw_exception( "Only one table/as pair expected in from-spec but an exra '$toomuch' key present" )
4c2b30d6 665 if defined $toomuch;
6f4ddea1 666
4c2b30d6 667 ($self->_from_chunk_to_sql($table), $self->_quote($as) );
668 },
669 SCALAR => sub {
670 $self->_quote($fromspec);
671 },
672 }));
6f4ddea1 673}
674
675sub _join_condition {
676 my ($self, $cond) = @_;
4c2b30d6 677
a697fa31 678 # Backcompat for the old days when a plain hashref
679 # { 't1.col1' => 't2.col2' } meant ON t1.col1 = t2.col2
680 # Once things settle we should start warning here so that
681 # folks unroll their hacks
682 if (
683 ref $cond eq 'HASH'
684 and
685 keys %$cond == 1
686 and
687 (keys %$cond)[0] =~ /\./
688 and
689 ! ref ( (values %$cond)[0] )
690 ) {
691 $cond = { keys %$cond => { -ident => values %$cond } }
6f4ddea1 692 }
a697fa31 693 elsif ( ref $cond eq 'ARRAY' ) {
694 # do our own ORing so that the hashref-shim above is invoked
9aae3566 695 my @parts;
696 my @binds;
697 foreach my $c (@$cond) {
698 my ($sql, @bind) = $self->_join_condition($c);
699 push @binds, @bind;
700 push @parts, $sql;
701 }
702 return join(' OR ', @parts), @binds;
6f4ddea1 703 }
a697fa31 704
705 return $self->_recurse_where($cond);
6f4ddea1 706}
707
6f4ddea1 7081;
d5dedbd6 709
2bb4c37b 710=head1 OPERATORS
711
712=head2 -ident
713
714Used to explicitly specify an SQL identifier. Takes a plain string as value
715which is then invariably treated as a column name (and is being properly
716quoted if quoting has been requested). Most useful for comparison of two
717columns:
718
719 my %where = (
720 priority => { '<', 2 },
721 requestor => { -ident => 'submitter' }
722 );
723
724which results in:
725
726 $stmt = 'WHERE "priority" < ? AND "requestor" = "submitter"';
727 @bind = ('2');
728
729=head2 -value
730
731The -value operator signals that the argument to the right is a raw bind value.
732It will be passed straight to DBI, without invoking any of the SQL::Abstract
733condition-parsing logic. This allows you to, for example, pass an array as a
734column value for databases that support array datatypes, e.g.:
735
736 my %where = (
737 array => { -value => [1, 2, 3] }
738 );
739
740which results in:
741
742 $stmt = 'WHERE array = ?';
743 @bind = ([1, 2, 3]);
744
d5dedbd6 745=head1 AUTHORS
746
747See L<DBIx::Class/CONTRIBUTORS>.
748
749=head1 LICENSE
750
751You may distribute this code under the same terms as Perl itself.
752
753=cut