bumped version, added keywords
[dbsrgits/SQL-Abstract.git] / lib / SQL / Abstract.pm
CommitLineData
32eab2da 1
2package SQL::Abstract;
3
4=head1 NAME
5
6SQL::Abstract - Generate SQL from Perl data structures
7
8=head1 SYNOPSIS
9
10 use SQL::Abstract;
11
12 my $sql = SQL::Abstract->new;
13
14 my($stmt, @bind) = $sql->select($table, \@fields, \%where, \@order);
15
16 my($stmt, @bind) = $sql->insert($table, \%fieldvals || \@values);
17
18 my($stmt, @bind) = $sql->update($table, \%fieldvals, \%where);
19
20 my($stmt, @bind) = $sql->delete($table, \%where);
21
22 # Then, use these in your DBI statements
23 my $sth = $dbh->prepare($stmt);
24 $sth->execute(@bind);
25
26 # Just generate the WHERE clause
27 my($stmt, @bind) = $sql->where(\%where, \@order);
28
29 # Return values in the same order, for hashed queries
30 # See PERFORMANCE section for more details
31 my @bind = $sql->values(\%fieldvals);
32
33=head1 DESCRIPTION
34
35This module was inspired by the excellent L<DBIx::Abstract>.
36However, in using that module I found that what I really wanted
37to do was generate SQL, but still retain complete control over my
38statement handles and use the DBI interface. So, I set out to
39create an abstract SQL generation module.
40
41While based on the concepts used by L<DBIx::Abstract>, there are
42several important differences, especially when it comes to WHERE
43clauses. I have modified the concepts used to make the SQL easier
44to generate from Perl data structures and, IMO, more intuitive.
45The underlying idea is for this module to do what you mean, based
46on the data structures you provide it. The big advantage is that
47you don't have to modify your code every time your data changes,
48as this module figures it out.
49
50To begin with, an SQL INSERT is as easy as just specifying a hash
51of C<key=value> pairs:
52
53 my %data = (
54 name => 'Jimbo Bobson',
55 phone => '123-456-7890',
56 address => '42 Sister Lane',
57 city => 'St. Louis',
58 state => 'Louisiana',
59 );
60
61The SQL can then be generated with this:
62
63 my($stmt, @bind) = $sql->insert('people', \%data);
64
65Which would give you something like this:
66
67 $stmt = "INSERT INTO people
68 (address, city, name, phone, state)
69 VALUES (?, ?, ?, ?, ?)";
70 @bind = ('42 Sister Lane', 'St. Louis', 'Jimbo Bobson',
71 '123-456-7890', 'Louisiana');
72
73These are then used directly in your DBI code:
74
75 my $sth = $dbh->prepare($stmt);
76 $sth->execute(@bind);
77
78In addition, you can apply SQL functions to elements of your C<%data>
79by specifying an arrayref for the given hash value. For example, if
80you need to execute the Oracle C<to_date> function on a value, you
81can say something like this:
82
83 my %data = (
84 name => 'Bill',
85 date_entered => ["to_date(?,'MM/DD/YYYY')", "03/02/2003"],
86 );
87
88The first value in the array is the actual SQL. Any other values are
89optional and would be included in the bind values array. This gives
90you:
91
92 my($stmt, @bind) = $sql->insert('people', \%data);
93
94 $stmt = "INSERT INTO people (name, date_entered)
95 VALUES (?, to_date(?,'MM/DD/YYYY'))";
96 @bind = ('Bill', '03/02/2003');
97
98An UPDATE is just as easy, all you change is the name of the function:
99
100 my($stmt, @bind) = $sql->update('people', \%data);
101
102Notice that your C<%data> isn't touched; the module will generate
103the appropriately quirky SQL for you automatically. Usually you'll
104want to specify a WHERE clause for your UPDATE, though, which is
105where handling C<%where> hashes comes in handy...
106
107This module can generate pretty complicated WHERE statements
108easily. For example, simple C<key=value> pairs are taken to mean
109equality, and if you want to see if a field is within a set
110of values, you can use an arrayref. Let's say we wanted to
111SELECT some data based on this criteria:
112
113 my %where = (
114 requestor => 'inna',
115 worker => ['nwiger', 'rcwe', 'sfz'],
116 status => { '!=', 'completed' }
117 );
118
119 my($stmt, @bind) = $sql->select('tickets', '*', \%where);
120
121The above would give you something like this:
122
123 $stmt = "SELECT * FROM tickets WHERE
124 ( requestor = ? ) AND ( status != ? )
125 AND ( worker = ? OR worker = ? OR worker = ? )";
126 @bind = ('inna', 'completed', 'nwiger', 'rcwe', 'sfz');
127
128Which you could then use in DBI code like so:
129
130 my $sth = $dbh->prepare($stmt);
131 $sth->execute(@bind);
132
133Easy, eh?
134
135=head1 FUNCTIONS
136
137The functions are simple. There's one for each major SQL operation,
138and a constructor you use first. The arguments are specified in a
139similar order to each function (table, then fields, then a where
140clause) to try and simplify things.
141
142=cut
143
144use Carp;
145use strict;
146
c1909b9f 147our $VERSION = '1.22';
32eab2da 148our $AUTOLOAD;
149
150# Fix SQL case, if so requested
151sub _sqlcase {
152 my $self = shift;
153 return $self->{case} ? $_[0] : uc($_[0]);
154}
155
156# Anon copies of arrays/hashes
157# Based on deep_copy example by merlyn
158# http://www.stonehenge.com/merlyn/UnixReview/col30.html
159sub _anoncopy {
160 my $orig = shift;
161 return (ref $orig eq 'HASH') ? +{map { $_ => _anoncopy($orig->{$_}) } keys %$orig}
162 : (ref $orig eq 'ARRAY') ? [map _anoncopy($_), @$orig]
163 : $orig;
164}
165
166# Debug
167sub _debug {
168 return unless $_[0]->{debug}; shift; # a little faster
169 my $func = (caller(1))[3];
170 warn "[$func] ", @_, "\n";
171}
172
173sub belch (@) {
174 my($func) = (caller(1))[3];
175 carp "[$func] Warning: ", @_;
176}
177
178sub puke (@) {
179 my($func) = (caller(1))[3];
180 croak "[$func] Fatal: ", @_;
181}
182
183# Utility functions
184sub _table {
185 my $self = shift;
186 my $tab = shift;
187 if (ref $tab eq 'ARRAY') {
188 return join ', ', map { $self->_quote($_) } @$tab;
189 } else {
190 return $self->_quote($tab);
191 }
192}
193
194sub _quote {
195 my $self = shift;
196 my $label = shift;
197
198 return $label
199 if $label eq '*';
200
201 return $self->{quote_char} . $label . $self->{quote_char}
202 if !defined $self->{name_sep};
203
204 return join $self->{name_sep},
205 map { $self->{quote_char} . $_ . $self->{quote_char} }
206 split /\Q$self->{name_sep}\E/, $label;
207}
208
209# Conversion, if applicable
210sub _convert ($) {
211 my $self = shift;
212 return @_ unless $self->{convert};
213 my $conv = $self->_sqlcase($self->{convert});
214 my @ret = map { $conv.'('.$_.')' } @_;
215 return wantarray ? @ret : $ret[0];
216}
217
218# And bindtype
219sub _bindtype (@) {
220 my $self = shift;
221 my($col,@val) = @_;
222 return $self->{bindtype} eq 'columns' ? [ @_ ] : @val;
223}
224
225# Modified -logic or -nest
226sub _modlogic ($) {
227 my $self = shift;
228 my $sym = @_ ? lc(shift) : $self->{logic};
229 $sym =~ tr/_/ /;
230 $sym = $self->{logic} if $sym eq 'nest';
231 return $self->_sqlcase($sym); # override join
232}
233
234=head2 new(option => 'value')
235
236The C<new()> function takes a list of options and values, and returns
237a new B<SQL::Abstract> object which can then be used to generate SQL
238through the methods below. The options accepted are:
239
240=over
241
242=item case
243
244If set to 'lower', then SQL will be generated in all lowercase. By
245default SQL is generated in "textbook" case meaning something like:
246
247 SELECT a_field FROM a_table WHERE some_field LIKE '%someval%'
248
249=item cmp
250
251This determines what the default comparison operator is. By default
252it is C<=>, meaning that a hash like this:
253
254 %where = (name => 'nwiger', email => 'nate@wiger.org');
255
256Will generate SQL like this:
257
258 WHERE name = 'nwiger' AND email = 'nate@wiger.org'
259
260However, you may want loose comparisons by default, so if you set
261C<cmp> to C<like> you would get SQL such as:
262
263 WHERE name like 'nwiger' AND email like 'nate@wiger.org'
264
265You can also override the comparsion on an individual basis - see
266the huge section on L</"WHERE CLAUSES"> at the bottom.
267
268=item logic
269
270This determines the default logical operator for multiple WHERE
271statements in arrays. By default it is "or", meaning that a WHERE
272array of the form:
273
274 @where = (
275 event_date => {'>=', '2/13/99'},
276 event_date => {'<=', '4/24/03'},
277 );
278
279Will generate SQL like this:
280
281 WHERE event_date >= '2/13/99' OR event_date <= '4/24/03'
282
283This is probably not what you want given this query, though (look
284at the dates). To change the "OR" to an "AND", simply specify:
285
286 my $sql = SQL::Abstract->new(logic => 'and');
287
288Which will change the above C<WHERE> to:
289
290 WHERE event_date >= '2/13/99' AND event_date <= '4/24/03'
291
292=item convert
293
294This will automatically convert comparisons using the specified SQL
295function for both column and value. This is mostly used with an argument
296of C<upper> or C<lower>, so that the SQL will have the effect of
297case-insensitive "searches". For example, this:
298
299 $sql = SQL::Abstract->new(convert => 'upper');
300 %where = (keywords => 'MaKe iT CAse inSeNSItive');
301
302Will turn out the following SQL:
303
304 WHERE upper(keywords) like upper('MaKe iT CAse inSeNSItive')
305
306The conversion can be C<upper()>, C<lower()>, or any other SQL function
307that can be applied symmetrically to fields (actually B<SQL::Abstract> does
308not validate this option; it will just pass through what you specify verbatim).
309
310=item bindtype
311
312This is a kludge because many databases suck. For example, you can't
313just bind values using DBI's C<execute()> for Oracle C<CLOB> or C<BLOB> fields.
314Instead, you have to use C<bind_param()>:
315
316 $sth->bind_param(1, 'reg data');
317 $sth->bind_param(2, $lots, {ora_type => ORA_CLOB});
318
319The problem is, B<SQL::Abstract> will normally just return a C<@bind> array,
320which loses track of which field each slot refers to. Fear not.
321
322If you specify C<bindtype> in new, you can determine how C<@bind> is returned.
323Currently, you can specify either C<normal> (default) or C<columns>. If you
324specify C<columns>, you will get an array that looks like this:
325
326 my $sql = SQL::Abstract->new(bindtype => 'columns');
327 my($stmt, @bind) = $sql->insert(...);
328
329 @bind = (
330 [ 'column1', 'value1' ],
331 [ 'column2', 'value2' ],
332 [ 'column3', 'value3' ],
333 );
334
335You can then iterate through this manually, using DBI's C<bind_param()>.
336
337 $sth->prepare($stmt);
338 my $i = 1;
339 for (@bind) {
340 my($col, $data) = @$_;
341 if ($col eq 'details' || $col eq 'comments') {
342 $sth->bind_param($i, $data, {ora_type => ORA_CLOB});
343 } elsif ($col eq 'image') {
344 $sth->bind_param($i, $data, {ora_type => ORA_BLOB});
345 } else {
346 $sth->bind_param($i, $data);
347 }
348 $i++;
349 }
350 $sth->execute; # execute without @bind now
351
352Now, why would you still use B<SQL::Abstract> if you have to do this crap?
353Basically, the advantage is still that you don't have to care which fields
354are or are not included. You could wrap that above C<for> loop in a simple
355sub called C<bind_fields()> or something and reuse it repeatedly. You still
356get a layer of abstraction over manual SQL specification.
357
358=item quote_char
359
360This is the character that a table or column name will be quoted
361with. By default this is an empty string, but you could set it to
362the character C<`>, to generate SQL like this:
363
364 SELECT `a_field` FROM `a_table` WHERE `some_field` LIKE '%someval%'
365
366This is useful if you have tables or columns that are reserved words
367in your database's SQL dialect.
368
369=item name_sep
370
371This is the character that separates a table and column name. It is
372necessary to specify this when the C<quote_char> option is selected,
373so that tables and column names can be individually quoted like this:
374
375 SELECT `table`.`one_field` FROM `table` WHERE `table`.`other_field` = 1
376
377=back
378
379=cut
380
381sub new {
382 my $self = shift;
383 my $class = ref($self) || $self;
384 my %opt = (ref $_[0] eq 'HASH') ? %{$_[0]} : @_;
385
386 # choose our case by keeping an option around
387 delete $opt{case} if $opt{case} && $opt{case} ne 'lower';
388
389 # override logical operator
390 $opt{logic} = uc $opt{logic} if $opt{logic};
391
392 # how to return bind vars
393 $opt{bindtype} ||= delete($opt{bind_type}) || 'normal';
394
395 # default comparison is "=", but can be overridden
396 $opt{cmp} ||= '=';
397
398 # default quotation character around tables/columns
399 $opt{quote_char} ||= '';
400
401 return bless \%opt, $class;
402}
403
404=head2 insert($table, \@values || \%fieldvals)
405
406This is the simplest function. You simply give it a table name
407and either an arrayref of values or hashref of field/value pairs.
408It returns an SQL INSERT statement and a list of bind values.
409
410=cut
411
412sub insert {
413 my $self = shift;
414 my $table = $self->_table(shift);
415 my $data = shift || return;
416
417 my $sql = $self->_sqlcase('insert into') . " $table ";
418 my(@sqlf, @sqlv, @sqlq) = ();
419
420 my $ref = ref $data;
421 if ($ref eq 'HASH') {
422 for my $k (sort keys %$data) {
423 my $v = $data->{$k};
424 my $r = ref $v;
425 # named fields, so must save names in order
426 push @sqlf, $self->_quote($k);
427 if ($r eq 'ARRAY') {
428 # SQL included for values
429 my @val = @$v;
430 push @sqlq, shift @val;
431 push @sqlv, $self->_bindtype($k, @val);
432 } elsif ($r eq 'SCALAR') {
433 # embedded literal SQL
434 push @sqlq, $$v;
435 } else {
436 push @sqlq, '?';
437 push @sqlv, $self->_bindtype($k, $v);
438 }
439 }
440 $sql .= '(' . join(', ', @sqlf) .') '. $self->_sqlcase('values') . ' ('. join(', ', @sqlq) .')';
441 } elsif ($ref eq 'ARRAY') {
442 # just generate values(?,?) part
443 # no names (arrayref) so can't generate bindtype
444 carp "Warning: ",__PACKAGE__,"->insert called with arrayref when bindtype set"
445 if $self->{bindtype} ne 'normal';
446 for my $v (@$data) {
447 my $r = ref $v;
448 if ($r eq 'ARRAY') {
449 my @val = @$v;
450 push @sqlq, shift @val;
451 push @sqlv, @val;
452 } elsif ($r eq 'SCALAR') {
453 # embedded literal SQL
454 push @sqlq, $$v;
455 } else {
456 push @sqlq, '?';
457 push @sqlv, $v;
458 }
459 }
460 $sql .= $self->_sqlcase('values') . ' ('. join(', ', @sqlq) .')';
461 } elsif ($ref eq 'SCALAR') {
462 # literal SQL
463 $sql .= $$data;
464 } else {
465 puke "Unsupported data type specified to \$sql->insert";
466 }
467
468 return wantarray ? ($sql, @sqlv) : $sql;
469}
470
471=head2 update($table, \%fieldvals, \%where)
472
473This takes a table, hashref of field/value pairs, and an optional
474hashref WHERE clause. It returns an SQL UPDATE function and a list
475of bind values.
476
477=cut
478
479sub update {
480 my $self = shift;
481 my $table = $self->_table(shift);
482 my $data = shift || return;
483 my $where = shift;
484
485 my $sql = $self->_sqlcase('update') . " $table " . $self->_sqlcase('set ');
486 my(@sqlf, @sqlv) = ();
487
488 puke "Unsupported data type specified to \$sql->update"
489 unless ref $data eq 'HASH';
490
491 for my $k (sort keys %$data) {
492 my $v = $data->{$k};
493 my $r = ref $v;
494 my $label = $self->_quote($k);
495 if ($r eq 'ARRAY') {
496 # SQL included for values
497 my @bind = @$v;
498 my $sql = shift @bind;
499 push @sqlf, "$label = $sql";
500 push @sqlv, $self->_bindtype($k, @bind);
501 } elsif ($r eq 'SCALAR') {
502 # embedded literal SQL
503 push @sqlf, "$label = $$v";
504 } else {
505 push @sqlf, "$label = ?";
506 push @sqlv, $self->_bindtype($k, $v);
507 }
508 }
509
510 $sql .= join ', ', @sqlf;
511
512 if ($where) {
513 my($wsql, @wval) = $self->where($where);
514 $sql .= $wsql;
515 push @sqlv, @wval;
516 }
517
518 return wantarray ? ($sql, @sqlv) : $sql;
519}
520
521=head2 select($table, \@fields, \%where, \@order)
522
523This takes a table, arrayref of fields (or '*'), optional hashref
524WHERE clause, and optional arrayref order by, and returns the
525corresponding SQL SELECT statement and list of bind values.
526
527=cut
528
529sub select {
530 my $self = shift;
531 my $table = $self->_table(shift);
532 my $fields = shift || '*';
533 my $where = shift;
534 my $order = shift;
535
536 my $f = (ref $fields eq 'ARRAY') ? join ', ', map { $self->_quote($_) } @$fields : $fields;
537 my $sql = join ' ', $self->_sqlcase('select'), $f, $self->_sqlcase('from'), $table;
538
539 my(@sqlf, @sqlv) = ();
540 my($wsql, @wval) = $self->where($where, $order);
541 $sql .= $wsql;
542 push @sqlv, @wval;
543
544 return wantarray ? ($sql, @sqlv) : $sql;
545}
546
547=head2 delete($table, \%where)
548
549This takes a table name and optional hashref WHERE clause.
550It returns an SQL DELETE statement and list of bind values.
551
552=cut
553
554sub delete {
555 my $self = shift;
556 my $table = $self->_table(shift);
557 my $where = shift;
558
559 my $sql = $self->_sqlcase('delete from') . " $table";
560 my(@sqlf, @sqlv) = ();
561
562 if ($where) {
563 my($wsql, @wval) = $self->where($where);
564 $sql .= $wsql;
565 push @sqlv, @wval;
566 }
567
568 return wantarray ? ($sql, @sqlv) : $sql;
569}
570
571=head2 where(\%where, \@order)
572
573This is used to generate just the WHERE clause. For example,
574if you have an arbitrary data structure and know what the
575rest of your SQL is going to look like, but want an easy way
576to produce a WHERE clause, use this. It returns an SQL WHERE
577clause and list of bind values.
578
579=cut
580
581# Finally, a separate routine just to handle WHERE clauses
582sub where {
583 my $self = shift;
584 my $where = shift;
585 my $order = shift;
586
587 # Need a separate routine to properly wrap w/ "where"
588 my $sql = '';
589 my @ret = $self->_recurse_where($where);
590 if (@ret) {
591 my $wh = shift @ret;
592 $sql .= $self->_sqlcase(' where ') . $wh if $wh;
593 }
594
595 # order by?
596 if ($order) {
597 $sql .= $self->_order_by($order);
598 }
599
600 return wantarray ? ($sql, @ret) : $sql;
601}
602
603
604sub _recurse_where {
605 local $^W = 0; # really, you've gotta be fucking kidding me
606 my $self = shift;
607 my $where = _anoncopy(shift); # prevent destroying original
608 my $ref = ref $where || '';
609 my $join = shift || $self->{logic} ||
610 ($ref eq 'ARRAY' ? $self->_sqlcase('or') : $self->_sqlcase('and'));
611
612 # For assembling SQL fields and values
613 my(@sqlf, @sqlv) = ();
614
615 # If an arrayref, then we join each element
616 if ($ref eq 'ARRAY') {
617 # need to use while() so can shift() for arrays
618 my $subjoin;
619 while (my $el = shift @$where) {
620
621 # skip empty elements, otherwise get invalid trailing AND stuff
622 if (my $ref2 = ref $el) {
623 if ($ref2 eq 'ARRAY') {
624 next unless @$el;
625 } elsif ($ref2 eq 'HASH') {
626 next unless %$el;
627 $subjoin ||= $self->_sqlcase('and');
628 } elsif ($ref2 eq 'SCALAR') {
629 # literal SQL
630 push @sqlf, $$el;
631 next;
632 }
633 $self->_debug("$ref2(*top) means join with $subjoin");
634 } else {
635 # top-level arrayref with scalars, recurse in pairs
636 $self->_debug("NOREF(*top) means join with $subjoin");
637 $el = {$el => shift(@$where)};
638 }
639 my @ret = $self->_recurse_where($el, $subjoin);
640 push @sqlf, shift @ret;
641 push @sqlv, @ret;
642 }
643 }
644 elsif ($ref eq 'HASH') {
645 # Note: during recursion, the last element will always be a hashref,
646 # since it needs to point a column => value. So this be the end.
647 for my $k (sort keys %$where) {
648 my $v = $where->{$k};
649 my $label = $self->_quote($k);
650 if ($k =~ /^-(\D+)/) {
651 # special nesting, like -and, -or, -nest, so shift over
652 my $subjoin = $self->_modlogic($1);
653 $self->_debug("OP(-$1) means special logic ($subjoin), recursing...");
654 my @ret = $self->_recurse_where($v, $subjoin);
655 push @sqlf, shift @ret;
656 push @sqlv, @ret;
657 } elsif (! defined($v)) {
658 # undef = null
659 $self->_debug("UNDEF($k) means IS NULL");
660 push @sqlf, $label . $self->_sqlcase(' is null');
661 } elsif (ref $v eq 'ARRAY') {
662 my @v = @$v;
663
664 # multiple elements: multiple options
665 $self->_debug("ARRAY($k) means multiple elements: [ @v ]");
666
667 # special nesting, like -and, -or, -nest, so shift over
668 my $subjoin = $self->_sqlcase('or');
669 if ($v[0] =~ /^-(\D+)/) {
670 $subjoin = $self->_modlogic($1); # override subjoin
671 $self->_debug("OP(-$1) means special logic ($subjoin), shifting...");
672 shift @v;
673 }
674
675 # map into an array of hashrefs and recurse
676 my @ret = $self->_recurse_where([map { {$k => $_} } @v], $subjoin);
677
678 # push results into our structure
679 push @sqlf, shift @ret;
680 push @sqlv, @ret;
681 } elsif (ref $v eq 'HASH') {
682 # modified operator { '!=', 'completed' }
683 for my $f (sort keys %$v) {
684 my $x = $v->{$f};
685 $self->_debug("HASH($k) means modified operator: { $f }");
686
687 # check for the operator being "IN" or "BETWEEN" or whatever
688 if (ref $x eq 'ARRAY') {
689 if ($f =~ /^-?\s*(not[\s_]+)?(in|between)\s*$/i) {
690 my $u = $self->_modlogic($1 . $2);
691 $self->_debug("HASH($f => $x) uses special operator: [ $u ]");
692 if ($u =~ /between/i) {
693 # SQL sucks
694 push @sqlf, join ' ', $self->_convert($label), $u, $self->_convert('?'),
695 $self->_sqlcase('and'), $self->_convert('?');
696 } else {
697 push @sqlf, join ' ', $self->_convert($label), $u, '(',
698 join(', ', map { $self->_convert('?') } @$x),
699 ')';
700 }
701 push @sqlv, $self->_bindtype($k, @$x);
702 } else {
703 # multiple elements: multiple options
704 $self->_debug("ARRAY($x) means multiple elements: [ @$x ]");
705
706 # map into an array of hashrefs and recurse
707 my @ret = $self->_recurse_where([map { {$k => {$f, $_}} } @$x]);
708
709 # push results into our structure
710 push @sqlf, shift @ret;
711 push @sqlv, @ret;
712 }
713 } elsif (! defined($x)) {
714 # undef = NOT null
715 my $not = ($f eq '!=' || $f eq 'not like') ? ' not' : '';
716 push @sqlf, $label . $self->_sqlcase(" is$not null");
717 } else {
718 # regular ol' value
719 $f =~ s/^-//; # strip leading -like =>
720 $f =~ s/_/ /; # _ => " "
721 push @sqlf, join ' ', $self->_convert($label), $self->_sqlcase($f), $self->_convert('?');
722 push @sqlv, $self->_bindtype($k, $x);
723 }
724 }
725 } elsif (ref $v eq 'SCALAR') {
726 # literal SQL
727 $self->_debug("SCALAR($k) means literal SQL: $$v");
728 push @sqlf, "$label $$v";
729 } else {
730 # standard key => val
731 $self->_debug("NOREF($k) means simple key=val: $k $self->{cmp} $v");
732 push @sqlf, join ' ', $self->_convert($label), $self->_sqlcase($self->{cmp}), $self->_convert('?');
733 push @sqlv, $self->_bindtype($k, $v);
734 }
735 }
736 }
737 elsif ($ref eq 'SCALAR') {
738 # literal sql
739 $self->_debug("SCALAR(*top) means literal SQL: $$where");
740 push @sqlf, $$where;
741 }
742 elsif (defined $where) {
743 # literal sql
744 $self->_debug("NOREF(*top) means literal SQL: $where");
745 push @sqlf, $where;
746 }
747
748 # assemble and return sql
749 my $wsql = @sqlf ? '( ' . join(" $join ", @sqlf) . ' )' : '';
750 return wantarray ? ($wsql, @sqlv) : $wsql;
751}
752
753sub _order_by {
754 my $self = shift;
755 my $ref = ref $_[0];
756
757 my @vals = $ref eq 'ARRAY' ? @{$_[0]} :
758 $ref eq 'SCALAR' ? ${$_[0]} :
759 $ref eq '' ? $_[0] :
760 puke "Unsupported data struct $ref for ORDER BY";
761
762 my $val = join ', ', map { $self->_quote($_) } @vals;
763 return $val ? $self->_sqlcase(' order by')." $val" : '';
764}
765
766=head2 values(\%data)
767
768This just returns the values from the hash C<%data>, in the same
769order that would be returned from any of the other above queries.
770Using this allows you to markedly speed up your queries if you
771are affecting lots of rows. See below under the L</"PERFORMANCE"> section.
772
773=cut
774
775sub values {
776 my $self = shift;
777 my $data = shift || return;
778 puke "Argument to ", __PACKAGE__, "->values must be a \\%hash"
779 unless ref $data eq 'HASH';
780 return map { $self->_bindtype($_, $data->{$_}) } sort keys %$data;
781}
782
783=head2 generate($any, 'number', $of, \@data, $struct, \%types)
784
785Warning: This is an experimental method and subject to change.
786
787This returns arbitrarily generated SQL. It's a really basic shortcut.
788It will return two different things, depending on return context:
789
790 my($stmt, @bind) = $sql->generate('create table', \$table, \@fields);
791 my $stmt_and_val = $sql->generate('create table', \$table, \@fields);
792
793These would return the following:
794
795 # First calling form
796 $stmt = "CREATE TABLE test (?, ?)";
797 @bind = (field1, field2);
798
799 # Second calling form
800 $stmt_and_val = "CREATE TABLE test (field1, field2)";
801
802Depending on what you're trying to do, it's up to you to choose the correct
803format. In this example, the second form is what you would want.
804
805By the same token:
806
807 $sql->generate('alter session', { nls_date_format => 'MM/YY' });
808
809Might give you:
810
811 ALTER SESSION SET nls_date_format = 'MM/YY'
812
813You get the idea. Strings get their case twiddled, but everything
814else remains verbatim.
815
816=cut
817
818sub generate {
819 my $self = shift;
820
821 my(@sql, @sqlq, @sqlv);
822
823 for (@_) {
824 my $ref = ref $_;
825 if ($ref eq 'HASH') {
826 for my $k (sort keys %$_) {
827 my $v = $_->{$k};
828 my $r = ref $v;
829 my $label = $self->_quote($k);
830 if ($r eq 'ARRAY') {
831 # SQL included for values
832 my @bind = @$v;
833 my $sql = shift @bind;
834 push @sqlq, "$label = $sql";
835 push @sqlv, $self->_bindtype($k, @bind);
836 } elsif ($r eq 'SCALAR') {
837 # embedded literal SQL
838 push @sqlq, "$label = $$v";
839 } else {
840 push @sqlq, "$label = ?";
841 push @sqlv, $self->_bindtype($k, $v);
842 }
843 }
844 push @sql, $self->_sqlcase('set'), join ', ', @sqlq;
845 } elsif ($ref eq 'ARRAY') {
846 # unlike insert(), assume these are ONLY the column names, i.e. for SQL
847 for my $v (@$_) {
848 my $r = ref $v;
849 if ($r eq 'ARRAY') {
850 my @val = @$v;
851 push @sqlq, shift @val;
852 push @sqlv, @val;
853 } elsif ($r eq 'SCALAR') {
854 # embedded literal SQL
855 push @sqlq, $$v;
856 } else {
857 push @sqlq, '?';
858 push @sqlv, $v;
859 }
860 }
861 push @sql, '(' . join(', ', @sqlq) . ')';
862 } elsif ($ref eq 'SCALAR') {
863 # literal SQL
864 push @sql, $$_;
865 } else {
866 # strings get case twiddled
867 push @sql, $self->_sqlcase($_);
868 }
869 }
870
871 my $sql = join ' ', @sql;
872
873 # this is pretty tricky
874 # if ask for an array, return ($stmt, @bind)
875 # otherwise, s/?/shift @sqlv/ to put it inline
876 if (wantarray) {
877 return ($sql, @sqlv);
878 } else {
879 1 while $sql =~ s/\?/my $d = shift(@sqlv);
880 ref $d ? $d->[1] : $d/e;
881 return $sql;
882 }
883}
884
885sub DESTROY { 1 }
886sub AUTOLOAD {
887 # This allows us to check for a local, then _form, attr
888 my $self = shift;
889 my($name) = $AUTOLOAD =~ /.*::(.+)/;
890 return $self->generate($name, @_);
891}
892
8931;
894
895__END__
896
897=head1 WHERE CLAUSES
898
899This module uses a variation on the idea from L<DBIx::Abstract>. It
900is B<NOT>, repeat I<not> 100% compatible. B<The main logic of this
901module is that things in arrays are OR'ed, and things in hashes
902are AND'ed.>
903
904The easiest way to explain is to show lots of examples. After
905each C<%where> hash shown, it is assumed you used:
906
907 my($stmt, @bind) = $sql->where(\%where);
908
909However, note that the C<%where> hash can be used directly in any
910of the other functions as well, as described above.
911
912So, let's get started. To begin, a simple hash:
913
914 my %where = (
915 user => 'nwiger',
916 status => 'completed'
917 );
918
919Is converted to SQL C<key = val> statements:
920
921 $stmt = "WHERE user = ? AND status = ?";
922 @bind = ('nwiger', 'completed');
923
924One common thing I end up doing is having a list of values that
925a field can be in. To do this, simply specify a list inside of
926an arrayref:
927
928 my %where = (
929 user => 'nwiger',
930 status => ['assigned', 'in-progress', 'pending'];
931 );
932
933This simple code will create the following:
934
935 $stmt = "WHERE user = ? AND ( status = ? OR status = ? OR status = ? )";
936 @bind = ('nwiger', 'assigned', 'in-progress', 'pending');
937
938If you want to specify a different type of operator for your comparison,
939you can use a hashref for a given column:
940
941 my %where = (
942 user => 'nwiger',
943 status => { '!=', 'completed' }
944 );
945
946Which would generate:
947
948 $stmt = "WHERE user = ? AND status != ?";
949 @bind = ('nwiger', 'completed');
950
951To test against multiple values, just enclose the values in an arrayref:
952
953 status => { '!=', ['assigned', 'in-progress', 'pending'] };
954
955Which would give you:
956
957 "WHERE status != ? OR status != ? OR status != ?"
958
959But, this is probably not what you want in this case (look at it). So
960the hashref can also contain multiple pairs, in which case it is expanded
961into an C<AND> of its elements:
962
963 my %where = (
964 user => 'nwiger',
965 status => { '!=', 'completed', -not_like => 'pending%' }
966 );
967
968 # Or more dynamically, like from a form
969 $where{user} = 'nwiger';
970 $where{status}{'!='} = 'completed';
971 $where{status}{'-not_like'} = 'pending%';
972
973 # Both generate this
974 $stmt = "WHERE user = ? AND status != ? AND status NOT LIKE ?";
975 @bind = ('nwiger', 'completed', 'pending%');
976
977To get an OR instead, you can combine it with the arrayref idea:
978
979 my %where => (
980 user => 'nwiger',
981 priority => [ {'=', 2}, {'!=', 1} ]
982 );
983
984Which would generate:
985
986 $stmt = "WHERE user = ? AND priority = ? OR priority != ?";
987 @bind = ('nwiger', '2', '1');
988
989However, there is a subtle trap if you want to say something like
990this (notice the C<AND>):
991
992 WHERE priority != ? AND priority != ?
993
994Because, in Perl you I<can't> do this:
995
996 priority => { '!=', 2, '!=', 1 }
997
998As the second C<!=> key will obliterate the first. The solution
999is to use the special C<-modifier> form inside an arrayref:
1000
1001 priority => [ -and => {'!=', 2}, {'!=', 1} ]
1002
1003Normally, these would be joined by C<OR>, but the modifier tells it
1004to use C<AND> instead. (Hint: You can use this in conjunction with the
1005C<logic> option to C<new()> in order to change the way your queries
1006work by default.) B<Important:> Note that the C<-modifier> goes
1007B<INSIDE> the arrayref, as an extra first element. This will
1008B<NOT> do what you think it might:
1009
1010 priority => -and => [{'!=', 2}, {'!=', 1}] # WRONG!
1011
1012Here is a quick list of equivalencies, since there is some overlap:
1013
1014 # Same
1015 status => {'!=', 'completed', 'not like', 'pending%' }
1016 status => [ -and => {'!=', 'completed'}, {'not like', 'pending%'}]
1017
1018 # Same
1019 status => {'=', ['assigned', 'in-progress']}
1020 status => [ -or => {'=', 'assigned'}, {'=', 'in-progress'}]
1021 status => [ {'=', 'assigned'}, {'=', 'in-progress'} ]
1022
1023In addition to C<-and> and C<-or>, there is also a special C<-nest>
1024operator which adds an additional set of parens, to create a subquery.
1025For example, to get something like this:
1026
1027 $stmt = WHERE user = ? AND ( workhrs > ? OR geo = ? )
1028 @bind = ('nwiger', '20', 'ASIA');
1029
1030You would do:
1031
1032 my %where = (
1033 user => 'nwiger',
1034 -nest => [ workhrs => {'>', 20}, geo => 'ASIA' ],
1035 );
1036
1037You can also use the hashref format to compare a list of fields using the
1038C<IN> comparison operator, by specifying the list as an arrayref:
1039
1040 my %where = (
1041 status => 'completed',
1042 reportid => { -in => [567, 2335, 2] }
1043 );
1044
1045Which would generate:
1046
1047 $stmt = "WHERE status = ? AND reportid IN (?,?,?)";
1048 @bind = ('completed', '567', '2335', '2');
1049
1050You can use this same format to use other grouping functions, such
1051as C<BETWEEN>, C<SOME>, and so forth. For example:
1052
1053 my %where = (
1054 user => 'nwiger',
1055 completion_date => {
1056 -not_between => ['2002-10-01', '2003-02-06']
1057 }
1058 );
1059
1060Would give you:
1061
1062 WHERE user = ? AND completion_date NOT BETWEEN ( ? AND ? )
1063
1064So far, we've seen how multiple conditions are joined with a top-level
1065C<AND>. We can change this by putting the different conditions we want in
1066hashes and then putting those hashes in an array. For example:
1067
1068 my @where = (
1069 {
1070 user => 'nwiger',
1071 status => { -like => ['pending%', 'dispatched'] },
1072 },
1073 {
1074 user => 'robot',
1075 status => 'unassigned',
1076 }
1077 );
1078
1079This data structure would create the following:
1080
1081 $stmt = "WHERE ( user = ? AND ( status LIKE ? OR status LIKE ? ) )
1082 OR ( user = ? AND status = ? ) )";
1083 @bind = ('nwiger', 'pending', 'dispatched', 'robot', 'unassigned');
1084
1085This can be combined with the C<-nest> operator to properly group
1086SQL statements:
1087
1088 my @where = (
1089 -and => [
1090 user => 'nwiger',
1091 -nest => [
1092 -and => [workhrs => {'>', 20}, geo => 'ASIA' ],
1093 -and => [workhrs => {'<', 50}, geo => 'EURO' ]
1094 ],
1095 ],
1096 );
1097
1098That would yield:
1099
1100 WHERE ( user = ? AND
1101 ( ( workhrs > ? AND geo = ? )
1102 OR ( workhrs < ? AND geo = ? ) ) )
1103
1104Finally, sometimes only literal SQL will do. If you want to include
1105literal SQL verbatim, you can specify it as a scalar reference, namely:
1106
1107 my $inn = 'is Not Null';
1108 my %where = (
1109 priority => { '<', 2 },
1110 requestor => \$inn
1111 );
1112
1113This would create:
1114
1115 $stmt = "WHERE priority < ? AND requestor is Not Null";
1116 @bind = ('2');
1117
1118Note that in this example, you only get one bind parameter back, since
1119the verbatim SQL is passed as part of the statement.
1120
1121Of course, just to prove a point, the above can also be accomplished
1122with this:
1123
1124 my %where = (
1125 priority => { '<', 2 },
1126 requestor => { '!=', undef },
1127 );
1128
1129TMTOWTDI.
1130
1131These pages could go on for a while, since the nesting of the data
1132structures this module can handle are pretty much unlimited (the
1133module implements the C<WHERE> expansion as a recursive function
1134internally). Your best bet is to "play around" with the module a
1135little to see how the data structures behave, and choose the best
1136format for your data based on that.
1137
1138And of course, all the values above will probably be replaced with
1139variables gotten from forms or the command line. After all, if you
1140knew everything ahead of time, you wouldn't have to worry about
1141dynamically-generating SQL and could just hardwire it into your
1142script.
1143
1144=head1 PERFORMANCE
1145
1146Thanks to some benchmarking by Mark Stosberg, it turns out that
1147this module is many orders of magnitude faster than using C<DBIx::Abstract>.
1148I must admit this wasn't an intentional design issue, but it's a
1149byproduct of the fact that you get to control your C<DBI> handles
1150yourself.
1151
1152To maximize performance, use a code snippet like the following:
1153
1154 # prepare a statement handle using the first row
1155 # and then reuse it for the rest of the rows
1156 my($sth, $stmt);
1157 for my $href (@array_of_hashrefs) {
1158 $stmt ||= $sql->insert('table', $href);
1159 $sth ||= $dbh->prepare($stmt);
1160 $sth->execute($sql->values($href));
1161 }
1162
1163The reason this works is because the keys in your C<$href> are sorted
1164internally by B<SQL::Abstract>. Thus, as long as your data retains
1165the same structure, you only have to generate the SQL the first time
1166around. On subsequent queries, simply use the C<values> function provided
1167by this module to return your values in the correct order.
1168
1169=head1 FORMBUILDER
1170
1171If you use my C<CGI::FormBuilder> module at all, you'll hopefully
1172really like this part (I do, at least). Building up a complex query
1173can be as simple as the following:
1174
1175 #!/usr/bin/perl
1176
1177 use CGI::FormBuilder;
1178 use SQL::Abstract;
1179
1180 my $form = CGI::FormBuilder->new(...);
1181 my $sql = SQL::Abstract->new;
1182
1183 if ($form->submitted) {
1184 my $field = $form->field;
1185 my $id = delete $field->{id};
1186 my($stmt, @bind) = $sql->update('table', $field, {id => $id});
1187 }
1188
1189Of course, you would still have to connect using C<DBI> to run the
1190query, but the point is that if you make your form look like your
1191table, the actual query script can be extremely simplistic.
1192
1193If you're B<REALLY> lazy (I am), check out C<HTML::QuickTable> for
1194a fast interface to returning and formatting data. I frequently
1195use these three modules together to write complex database query
1196apps in under 50 lines.
1197
1198=head1 NOTES
1199
1200There is not (yet) any explicit support for SQL compound logic
1201statements like "AND NOT". Instead, just do the de Morgan's
1202law transformations yourself. For example, this:
1203
1204 "lname LIKE '%son%' AND NOT ( age < 10 OR age > 20 )"
1205
1206Becomes:
1207
1208 "lname LIKE '%son%' AND ( age >= 10 AND age <= 20 )"
1209
1210With the corresponding C<%where> hash:
1211
1212 %where = (
1213 lname => {like => '%son%'},
1214 age => [-and => {'>=', 10}, {'<=', 20}],
1215 );
1216
1217Again, remember that the C<-and> goes I<inside> the arrayref.
1218
1219=head1 ACKNOWLEDGEMENTS
1220
1221There are a number of individuals that have really helped out with
1222this module. Unfortunately, most of them submitted bugs via CPAN
1223so I have no idea who they are! But the people I do know are:
1224
1225 Mark Stosberg (benchmarking)
1226 Chas Owens (initial "IN" operator support)
1227 Philip Collins (per-field SQL functions)
1228 Eric Kolve (hashref "AND" support)
1229 Mike Fragassi (enhancements to "BETWEEN" and "LIKE")
1230 Dan Kubb (support for "quote_char" and "name_sep")
1231
1232Thanks!
1233
1234=head1 BUGS
1235
1236If found, please DO NOT submit anything via C<rt.cpan.org> - that
1237just causes me a ton of work. Email me a patch (or script demonstrating
1238the problem) to the below address, and include the VERSION string you'll
1239be seeing shortly.
1240
1241=head1 SEE ALSO
1242
1243L<DBIx::Abstract>, L<DBI|DBI>, L<CGI::FormBuilder>, L<HTML::QuickTable>
1244
1245=head1 VERSION
1246
c1909b9f 1247$Id$
32eab2da 1248
1249=head1 AUTHOR
1250
1251Copyright (c) 2001-2006 Nathan Wiger <nate@wiger.org>. All Rights Reserved.
1252
1253This module is free software; you may copy this under the terms of
1254the GNU General Public License, or the Artistic License, copies of
1255which should have accompanied your Perl kit.
1256
1257=cut
1258