93aa7c9cfef16b8ca0a99a16093a48d8da33af74
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / SQLMaker.pm
1 package DBIx::Class::SQLMaker;
2
3 use strict;
4 use warnings;
5
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
30 =item * The L</-ident> operator
31
32 =item * The L</-value> operator
33
34 =item * Date Functions:
35
36 Note that for the following functions use different functions for different
37 RDBMS'.  See the SQLMaker docs for your database to see what functions are
38 used.
39
40 =over
41
42 =item * -dt => $date_time_obj
43
44 This function will convert the passed datetime to whatever format the current
45 database prefers
46
47 =item * -dt_diff => [$unit, \'foo.date_from', \'foo.date_to']
48
49 This function will diff two dates and return the units requested. Note that
50 it correctly recurses if you pass it something like a function or a date value.
51 Also note that not all RDBMS' are equal; some units supported on some databases
52 and some are supported on others.  See the documentation for the SQLMaker class
53 for your database.
54
55 =item * -dt_get => [$part, \'foo.date_col']
56
57 This function will extract the passed part from the passed column.  Note that
58 it correctly recurses if you pass it something like a function or a date value.
59 Also note that not all RDBMS' are equal; some parts supported on some databases
60 and some are supported on others.  See the documentation for the SQLMaker class
61 for your database.
62
63 =item * -dt_year => \'foo.date_col'
64
65 A shortcut for -dt_get => [year => ...]
66
67 =item * -dt_month => \'foo.date_col'
68
69 A shortcut for -dt_get => [month => ...]
70
71 =item * -dt_day => \'foo.date_col'
72
73 A shortcut for -dt_get => [day_of_month => ...]
74
75 =item * -dt_hour => \'foo.date_col'
76
77 A shortcut for -dt_get => [hour => ...]
78
79 =item * -dt_minute => \'foo.date_col'
80
81 A shortcut for -dt_get => [minute => ...]
82
83 =item * -dt_second => \'foo.date_col'
84
85 A shortcut for -dt_get => [second => ...]
86
87 =back
88
89 =back
90
91 Another operator is C<-func> that allows you to call SQL functions with
92 arguments. It receives an array reference containing the function name
93 as the 0th argument and the other arguments being its parameters. For example:
94
95     my %where = {
96       -func => ['substr', 'Hello', 50, 5],
97     };
98
99 Would give you:
100
101    $stmt = "WHERE (substr(?,?,?))";
102    @bind = ("Hello", 50, 5);
103
104 Yet another operator is C<-op> that allows you to use SQL operators. It
105 receives an array reference containing the operator 0th argument and the other
106 arguments being its operands. For example:
107
108     my %where = {
109       foo => { -op => ['+', \'bar', 50, 5] },
110     };
111
112 Would give you:
113
114    $stmt = "WHERE (foo = bar + ? + ?)";
115    @bind = (50, 5);
116
117 =cut
118
119 use base qw/
120   DBIx::Class::SQLMaker::LimitDialects
121   DBIx::Class::SQLMaker::DateOps
122   SQL::Abstract
123   DBIx::Class
124 /;
125 use mro 'c3';
126
127 use Sub::Name 'subname';
128 use DBIx::Class::Carp;
129 use DBIx::Class::Exception;
130 use namespace::clean;
131
132 __PACKAGE__->mk_group_accessors (simple => qw/quote_char name_sep limit_dialect/);
133
134 # for when I need a normalized l/r pair
135 sub _quote_chars {
136   map
137     { defined $_ ? $_ : '' }
138     ( ref $_[0]->{quote_char} ? (@{$_[0]->{quote_char}}) : ( ($_[0]->{quote_char}) x 2 ) )
139   ;
140 }
141
142 # FIXME when we bring in the storage weaklink, check its schema
143 # weaklink and channel through $schema->throw_exception
144 sub throw_exception { DBIx::Class::Exception->throw($_[1]) }
145
146 BEGIN {
147   # reinstall the belch()/puke() functions of SQL::Abstract with custom versions
148   # that use DBIx::Class::Carp/DBIx::Class::Exception instead of plain Carp
149   no warnings qw/redefine/;
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];
158     __PACKAGE__->throw_exception("[$func] Fatal: " . join ('',  @_));
159   };
160
161   # Current SQLA pollutes its namespace - clean for the time being
162   namespace::clean->clean_subroutines(qw/SQL::Abstract carp croak confess/);
163 }
164
165 # the "oh noes offset/top without limit" constant
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 #
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)
176 sub __max_int () { 0x7FFFFFFF };
177
178 # poor man's de-qualifier
179 sub _quote {
180   $_[0]->next::method( ( $_[0]{_dequalify_idents} and ! ref $_[1] )
181     ? $_[1] =~ / ([^\.]+) $ /x
182     : $_[1]
183   );
184 }
185
186 sub new {
187   my $self = shift->next::method(@_);
188
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' },
193     { regex => qr/^ func  $/ix, handler => '_where_op_FUNC'  },
194     { regex => qr/^ op    $/ix, handler => '_where_op_OP'    },
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' },
198     { regex => qr/^ dt_add  $/xi, handler => '_where_op_ADD_DATETIME' },
199     { regex => qr/^ dt_now  $/xi, handler => '_where_op_DATETIME_NOW' },
200     map +{ regex => qr/^ dt_$_ $/xi, handler => '_where_op_GET_DATETIME_'.uc($_) },
201       qw(year month day hour minute second)
202   );
203
204   push @{$self->{special_ops}}, @extra_dbic_syntax;
205   push @{$self->{unary_ops}}, @extra_dbic_syntax;
206
207   $self;
208 }
209
210 sub _where_op_IDENT {
211   my $self = shift;
212   my ($op, $rhs) = splice @_, -2;
213   if (ref $rhs) {
214     $self->throw_exception("-$op takes a single scalar argument (a quotable identifier)");
215   }
216
217   # in case we are called as a top level special op (no '=')
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
228 sub _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 = [
236     ($lhs || $self->{_nested_func_lhs} || $self->throw_exception("Unable to find bindtype for -value $rhs") ),
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
252 sub _where_op_NEST {
253   carp_unique ("-nest in search conditions is deprecated, you most probably wanted:\n"
254       .q|{..., -and => [ \%cond0, \@cond1, \'cond2', \[ 'cond3', [ col => bind ] ], etc. ], ... }|
255   );
256
257   shift->next::method(@_);
258 }
259
260 # Handle limit-dialect selection
261 sub select {
262   my ($self, $table, $fields, $where, $rs_attrs, $limit, $offset) = @_;
263
264
265   $fields = $self->_recurse_fields($fields);
266
267   if (defined $offset) {
268     $self->throw_exception('A supplied offset must be a non-negative integer')
269       if ( $offset =~ /\D/ or $offset < 0 );
270   }
271   $offset ||= 0;
272
273   if (defined $limit) {
274     $self->throw_exception('A supplied limit must be a positive integer')
275       if ( $limit =~ /\D/ or $limit <= 0 );
276   }
277   elsif ($offset) {
278     $limit = $self->__max_int;
279   }
280
281
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
293           or $self->throw_exception( "Unable to generate SQL-limit - no limit dialect specified on $self, and no emulate_limit method found" );
294         $self->can ("_$dialect")
295           or $self->throw_exception(__PACKAGE__ . " does not implement the requested dialect '$dialect'");
296       }
297     ;
298
299     $sql = $self->$limiter (
300       $sql,
301       { %{$rs_attrs||{}}, _selector_sql => $fields },
302       $limit,
303       $offset
304     );
305   }
306   else {
307     ($sql, @bind) = $self->next::method ($table, $fields, $where, $rs_attrs);
308   }
309
310   push @{$self->{where_bind}}, @bind;
311
312 # this *must* be called, otherwise extra binds will remain in the sql-maker
313   my @all_bind = $self->_assemble_binds;
314
315   $sql .= $self->_lock_select ($rs_attrs->{for})
316     if $rs_attrs->{for};
317
318   return wantarray ? ($sql, @all_bind) : $sql;
319 }
320
321 sub _assemble_binds {
322   my $self = shift;
323   return map { @{ (delete $self->{"${_}_bind"}) || [] } } (qw/select from where group having order limit/);
324 }
325
326 my $for_syntax = {
327   update => 'FOR UPDATE',
328   shared => 'FOR SHARE',
329 };
330 sub _lock_select {
331   my ($self, $type) = @_;
332   my $sql = $for_syntax->{$type} || $self->throw_exception( "Unknown SELECT .. FOR type '$type' requested" );
333   return " $sql";
334 }
335
336 # Handle default inserts
337 sub insert {
338 # optimized due to hotttnesss
339 #  my ($self, $table, $data, $options) = @_;
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
344   if (! $_[2] or (ref $_[2] eq 'HASH' and !keys %{$_[2]} ) ) {
345     my @bind;
346     my $sql = sprintf(
347       'INSERT INTO %s DEFAULT VALUES', $_[0]->_quote($_[1])
348     );
349
350     if ( ($_[3]||{})->{returning} ) {
351       my $s;
352       ($s, @bind) = $_[0]->_insert_returning ($_[3]);
353       $sql .= $s;
354     }
355
356     return ($sql, @bind);
357   }
358
359   next::method(@_);
360 }
361
362 sub _recurse_fields {
363   my ($self, $fields, $depth) = @_;
364   $depth ||= 0;
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') {
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;
377   }
378   elsif ($ref eq 'HASH') {
379     my %hash = %$fields;  # shallow copy
380
381     my $as = delete $hash{-as};   # if supplied
382
383     my ($func, $args, @toomany) = %hash;
384
385     # there should be only one pair
386     if (@toomany) {
387       $self->throw_exception( "Malformed select argument - too many keys in hash: " . join (',', keys %$fields ) );
388     }
389
390     if (lc ($func) eq 'distinct' && ref $args eq 'ARRAY' && @$args > 1) {
391       $self->throw_exception (
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 }'
395       );
396     }
397
398     my $select = sprintf ('%s( %s )%s',
399       $self->_sqlcase($func),
400       $self->_recurse_fields($args, $depth + 1),
401       $as
402         ? sprintf (' %s %s', $self->_sqlcase('as'), $self->_quote ($as) )
403         : ''
404     );
405
406     return $select;
407   }
408   # Is the second check absolutely necessary?
409   elsif ( $ref eq 'REF' and ref($$fields) eq 'ARRAY' ) {
410     push @{$self->{select_bind}}, @{$$fields}[1..$#$$fields];
411     return $$fields->[0];
412   }
413   else {
414     $self->throw_exception( $ref . qq{ unexpected in _recurse_fields()} );
415   }
416 }
417
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!
427 sub _parse_rs_attrs {
428   my ($self, $arg) = @_;
429
430   my $sql = '';
431
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     }
439   }
440
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   }
446
447   if (defined $arg->{order_by}) {
448     $sql .= $self->_order_by ($arg->{order_by});
449   }
450
451   return $sql;
452 }
453
454 sub _order_by {
455   my ($self, $arg) = @_;
456
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);
460   }
461   else {
462     my ($sql, @bind) = $self->next::method($arg);
463     push @{$self->{order_bind}}, @bind;
464     return $sql;
465   }
466 }
467
468 sub _table {
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') {
476       return $_[0]->_recurse_from($_[1]);
477     }
478     elsif ($ref eq 'REF' && ref ${$_[1]} eq 'ARRAY') {
479       my ($sql, @bind) = @{ ${$_[1]} };
480       push @{$_[0]->{from_bind}}, @bind;
481       return $sql
482     }
483   }
484   return $_[0]->next::method ($_[1]);
485 }
486
487 sub _generate_join_clause {
488     my ($self, $join_type) = @_;
489
490     $join_type = $self->{_default_jointype}
491       if ! defined $join_type;
492
493     return sprintf ('%s JOIN ',
494       $join_type ?  $self->_sqlcase($join_type) : ''
495     );
496 }
497
498 sub _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
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];
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
552 sub _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
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];
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
606 sub _recurse_from {
607   my $self = shift;
608
609   return join (' ', $self->_gen_from_blocks(@_) );
610 }
611
612 sub _gen_from_blocks {
613   my ($self, $from, @joins) = @_;
614
615   my @fchunks = $self->_from_chunk_to_sql($from);
616
617   for (@joins) {
618     my ($to, $on) = @$_;
619
620     # check whether a join type exists
621     my $to_jt = ref($to) eq 'ARRAY' ? $to->[0] : $to;
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;
626     }
627
628     my @j = $self->_generate_join_clause( $join_type );
629
630     if (ref $to eq 'ARRAY') {
631       push(@j, '(', $self->_recurse_from(@$to), ')');
632     }
633     else {
634       push(@j, $self->_from_chunk_to_sql($to));
635     }
636
637     my ($sql, @bind) = $self->_join_condition($on);
638     push(@j, ' ON ', $sql);
639     push @{$self->{from_bind}}, @bind;
640
641     push @fchunks, join '', @j;
642   }
643
644   return @fchunks;
645 }
646
647 sub _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       );
663
664       $self->throw_exception( "Only one table/as pair expected in from-spec but an exra '$toomuch' key present" )
665         if defined $toomuch;
666
667       ($self->_from_chunk_to_sql($table), $self->_quote($as) );
668     },
669     SCALAR => sub {
670       $self->_quote($fromspec);
671     },
672   }));
673 }
674
675 sub _join_condition {
676   my ($self, $cond) = @_;
677
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 } }
692   }
693   elsif ( ref $cond eq 'ARRAY' ) {
694     # do our own ORing so that the hashref-shim above is invoked
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;
703   }
704
705   return $self->_recurse_where($cond);
706 }
707
708 1;
709
710 =head1 OPERATORS
711
712 =head2 -ident
713
714 Used to explicitly specify an SQL identifier. Takes a plain string as value
715 which is then invariably treated as a column name (and is being properly
716 quoted if quoting has been requested). Most useful for comparison of two
717 columns:
718
719     my %where = (
720         priority => { '<', 2 },
721         requestor => { -ident => 'submitter' }
722     );
723
724 which results in:
725
726     $stmt = 'WHERE "priority" < ? AND "requestor" = "submitter"';
727     @bind = ('2');
728
729 =head2 -value
730
731 The -value operator signals that the argument to the right is a raw bind value.
732 It will be passed straight to DBI, without invoking any of the SQL::Abstract
733 condition-parsing logic. This allows you to, for example, pass an array as a
734 column value for databases that support array datatypes, e.g.:
735
736     my %where = (
737         array => { -value => [1, 2, 3] }
738     );
739
740 which results in:
741
742     $stmt = 'WHERE array = ?';
743     @bind = ([1, 2, 3]);
744
745 =head1 AUTHORS
746
747 See L<DBIx::Class/CONTRIBUTORS>.
748
749 =head1 LICENSE
750
751 You may distribute this code under the same terms as Perl itself.
752
753 =cut