File Coverage

blib/lib/Sql/Simple.pm
Criterion Covered Total %
statement 292 653 44.7
branch 124 338 36.6
condition 24 81 29.6
subroutine 13 26 50.0
pod 4 16 25.0
total 457 1114 41.0


line stmt bran cond sub pod time code
1             =head1 Sql::Simple
2            
3             An Sql statement generation and execution library (for simple tasks)
4            
5             =head2 SYNOPSIS:
6            
7             Sql::Simple->setdbh($dbh);
8             # select price, weight from fruit where onSale = 'true'
9             my $result = Sql::Simple->query($dbh, [ qw(price, weight) ], 'fruit', { 'onSale' => 'true' });
10            
11             =head2 DESCRIPTION:
12            
13             This module is a re-hash of like fifty different modules already on CPAN. TWMTODI is a good thing, but sadly this is more of the case of being tired of seeing patch submissions being rejected into similar modules. The upside to this module is that it handles table joins rather elegantly (ie: no creation of seperate classes, config files, xml, GPS coordinates, or cat scans). The downside is probably the fact that I wrote it, as there are a few inefficient quirks to my coding style (comments appreciated).
14            
15             Of course, this module covers the basics of sql... insert, update, delete, and select's. The ability to do things in mass is available as well, to assist you with large data maniuplations. (taking advantage of a database that takes advantage of placeholders, and setting AutoCommit to 0).
16            
17             IMHO, this module is almost mostly DWIM. A nice OO approach is in the works, just needed some pointers from some friends before I could break ground on it. (I do not do a lot of web programming with perl anymore, more data transformation stuff, so this module suits me for what I do).
18            
19             This module currently ONLY SUPPORTS ANSI SQL-92, there has been suggestions to make this more modular, but I think the db's will catch up before I do.
20            
21             This module will only work with the following database servers (that I have personally tested so far)
22            
23             Microsoft SQL Server 7, 2000
24             (tested via DBD::ODBC, supports full outer join)
25            
26             Sybase 12 (11 does not support ANSI SQL 92)
27             Does not support FULL OUTER JOIN
28            
29             PostgreSQL 7.3.X and above supports
30             (no FULL OUTER support)
31            
32             MySQL 3.23 and [4.1 required if you intend on using subqueries]).
33             * Notes on MySQL
34             LEFT JOIN and RIGHT JOIN are not ANSI compliant. Sql::Simple
35             isn't going to stop you from using that syntax, however, this
36             document will refer to joins in ANSI syntax only. MySQL 4
37             treats LEFT JOIN and LEFT OUTER JOIN synonmously. In my
38             research, it appears that certain versions of 3.23 act this
39             way as well, but do not support FULL OUTER joins as a part
40             of their syntax. Sql::Simple does not support Union (yet).
41            
42             Oracle 9i
43             (supports full outer join)
44            
45             If there is some weird incompatability, you'll see it, as I cluck out the errors back to you by die'ing...
46            
47             Here is a simple example...
48            
49             Sql::Simple->delete($dbh, 'tablename', { 'whereColumn' => 'whereValue' });
50            
51             Instead of...
52            
53             eval {
54             my $sql = 'delete from tablename where whereColumn = ?';
55             my $sth = $dbh->prepare($sql);
56             $sth->execute('whereValue');
57             $sth->finish();
58             $dbh->commit() unless ( $dbh->{'AutoCommit'} );
59             };
60             if ( $@ ) {
61             $dbh->rollback();
62             # do something to handle the exception...
63             }
64            
65             Ok, am I gaining you anything by using this module? I think so..
66             1. No declaration of any of those variables into your scope
67             2. Code reduction
68             3. No SQL will sit in your perl code (perl is good at formatting text, why get in it's way?)
69             4. Results from other routines can easily map into the argument stream, avoiding temporary placeholders (which for me tends to gain some performance)
70             5. I find that Sql abstraction layers, if done properly, can make simple tasks such as these short concise statements, instead of slightly larger blocks of code that are prone to irritating syntax and scope related issues.
71             6. I find writing sql tedious.
72             7. I find writing the code to execute sql tedious.
73             8. It's nice to be able to generate an sql statement. even if you intend on using your own execution methods..
74            
75             =head1 "WHERE" clause structure
76            
77             =head2 the generic where clause "structure" defining a constraint within a query
78            
79             There is a unified function that generates clauses for all of the functions within Sql::Simple.
80             I figure explaining it once will make update, delete, and query a bit easier to digest. (as it is used in insert, update, delete and subqueries within query). It is also used in the "FROM" clause within a query (specifically in the "ON" element structure).
81            
82             [
83             {
84             'column1' => 'value1',
85             'column2' => \'is null',
86             'column3' => [ 'val3-1', 'val3-2', 'val3-3' ],
87             },
88             {
89             'column4' => {
90             'op' => 'like',
91             'val' => '%value4%'
92             },
93             'column5' => {
94             'columns' => 'value5column',
95             'table' => 'value5table',
96             'where' => {
97             'column5sub' => 'value5sub'
98             }
99             }
100             }
101             ]
102            
103             This statment will generate the following clause. (or close to it, as the formatting might be off)
104            
105             (
106             column1 = ?
107             AND column2 is null
108             AND column3 in (?,?,?)
109             ) OR (
110             column4 like ?
111             AND column5 in (
112             SELECT value5column FROM value5table WHERE column5sub = ?
113             )
114             )
115            
116             column1 is a simple "=" operator between the column name and the placeholder.
117             column2 is a scalar reference forcing what you want verbatim (abuse to this function is kind of expected)
118             column3 creates a "where in" clause which is interpreted as an OR statement
119            
120             OR statement kicks in, creating another level of nesting for the next hashref
121            
122             column4 creates a specific relationship, such as "greater than" or the "like" operator shown above.
123             column5 creates a subquery with a where clause that can any level of depth of complexity as shown above.. (it is fully explained in the query function documentation)
124            
125             Of course, if you don't have an or clause, just pass in the hash..
126            
127             {
128             'column1' => 'value1'
129             'column2' => 'value2'
130             }
131            
132             Which would generate ...
133            
134             column1 = ? AND column2 = ?
135            
136             This module will "auto-magically" figure out if your argument stream contains multiple objects to execute the statement with, or if it's just one really big statement.
137            
138             (hmm, maybe I should have named this thing Sql::KindaSimple)
139            
140             =head1 Logical operators
141            
142             To retouch on the above, if you needed to do something like the following..
143            
144             SELECT fruit FROM produce WHERE price > 10
145            
146             The "logical operator" syntax (anything besides the basic "=" operator is executed by the following)..
147            
148             Sql::Simple->query('fruit', 'produce', {
149             'price' => {
150             'op' => '>',
151             'val' => 10
152             }
153             } );
154            
155             To do a "between" query simply execute the follwing.
156            
157             Sql::Simple->query('fruit', 'produce', {
158             'price' => {
159             'op' => 'between',
160             'val' => 10,
161             'val2' => 20
162             }
163             } );
164            
165             UPDATE (0.7): If you wanted to generate a clause that allowed a value, or a couple of values, OR allow null, you can now "allowNull"
166            
167             'price' => {
168             'op' => 'between',
169             'val' => 10,
170             'val2' => 20
171             'allowNull' => 'true' # anything boolean true will work here
172             }
173            
174             # This generates...
175            
176             ( price between ? AND ? OR price is null )
177            
178             =head1 Variables
179            
180             $DBH - (the database handle to use) You can modify this directly, or for your convenience you may call this helper method
181            
182             Sql::Simple->setdbh($dbh);
183            
184             $RETURNSTH - (if "true" it returns the statement handle, and the map)
185            
186             Sql::Simple->returnsth(1); # returns a statement handle for the generated sql statement
187             Sql::Simple->returnsth(0); # default (execute)
188            
189             $RETURNSQL - (if "true" just return the SQL statement generated, don't actually execute it) Or use the following.
190            
191             Sql::Simple->setreturn(1); # for "return"
192             Sql::Simple->setreturn(0); # for "execute" [default]
193            
194             $DEBUGSQL - (if "true" warn out the SQL being executed)
195            
196             Sql::Simple->setdebug(1); # for "warn sql before execution"
197             Sql::Simple->setdebug(0); # for "no warn" [default]
198            
199             B
200            
201             =cut
202            
203             package Sql::Simple;
204 4     4   10121 use vars qw($version $DBH $RETURNSQL @EXPORT @EXPORT_OK $DEBUGSQL $RETURNSTH $ASARRAY);
  4         7  
  4         511  
205             $VERSION = "0.07";
206 4     4   19 use strict;
  4         7  
  4         117  
207 4     4   4217 use Data::Dumper;
  4         40783  
  4         304  
208 4     4   35 use Carp qw(cluck croak);
  4         8  
  4         232  
209            
210 4     4   21 use Exporter;
  4         9  
  4         28384  
211             @EXPORT = qw($DBH $RETURNSQL $DEBUGSQL $RETURNSTH $ASARRAY);
212             @EXPORT_OK = qw($DBH $RETURNSQL $DEBUGSQL $RETURNSTH $ASARRAY);
213            
214             =head1 Sql::Simple->delete
215            
216             =head2 provide an easy interface to delete row(s) from a table.
217            
218             Two methods of invocation.
219            
220             1. as a list of arguments
221            
222             Sql::Simple->delete(
223             databaseHandle,
224             'scalar tablename',
225             WHERE_CLAUSE_STRUCTURE (see documentation above),
226             );
227            
228             2. or as a hash
229            
230             Sql::Simple->delete(
231             databaseHandle,
232             'table' => $tablename,
233             WHERE_CLAUSE_STRUCTURE (see documentation above),
234             );
235            
236             =item Examples
237            
238             create table books (
239             title varchar(20)
240             author varchar(20)
241             )
242            
243             # set our dbh
244             Sql::Simple->setdbh($dbh);
245             # delete from books where title = 'Java Programming'
246             Sql::Simple->delete('books', { 'title' => 'Java Programming' });
247            
248             =cut
249            
250             sub delete {
251 6     6 1 1038 my $class = shift;
252 6         7 my ( $table, $where, $sth, $dbh );
253            
254 6 50       13 if ( ref($_[0]) eq 'DBI::db' ) {
255 0         0 $dbh = shift;
256             } else {
257 6         6 $dbh = $DBH;
258             }
259 6 0 33     11 croak("No database handle given!") if ( ! $RETURNSQL && ! ref($dbh) );
260            
261 6 100       12 if ( scalar(@_) <= 4 ) {
262 3         4 ( $table, $where ) = @_;
263             } else {
264 3         10 my %temp = @_;
265 3         4 $table = $temp{'table'};
266 3         5 $where = $temp{'where'};
267             }
268 6 100 33     27 if ( ref($where) eq 'HASH' ) {
  4 100 66     22  
269             # hmmm........ if it's a hash.. then package it up
270 2         12 $where = [ $where ];
271             } elsif ( ref($where) eq 'ARRAY' && scalar(@{$where}) > 1 && &_clause('', $where->[0], []) ne &_clause('', $where->[1], []) ) {
272             # if we have two elements in the inbound array, and they aren't exactly the same, then it's not a mass call, but an "OR" clause
273 2         5 $where = [ $where ];
274             }
275            
276 6         18 my $sql = "DELETE FROM $table";
277 6 50       12 if ( $where ) {
278 6         9 $sql .= " WHERE\n";
279             } else {
280 0         0 $sth = $dbh->prepare($sql);
281 0         0 eval {
282 0         0 $sth->execute();
283             };
284 0         0 $sth->finish();
285 0 0       0 if ( $@ ) {
286 0 0       0 $dbh->rollback() unless ( $dbh->{'Autocommit'} );
287             } else {
288 0 0       0 $dbh->commit() unless ( $dbh->{'AutoCommit'} );
289             }
290             }
291 6         8 my $map = [];
292             # generate the where clause
293 6         15 $sql = &_clause($sql, $where->[0], $map);
294            
295 6 50       13 warn $sql if ( $DEBUGSQL );
296 6 50       27 return $sql if ( $RETURNSQL );
297             # do simple test here
298 0         0 my $simple = 0;
299 0 0       0 $simple++ if ( $sql =~ /OR\n/ );
300 0 0       0 map { $simple++ if ( $_ ne 'VALUE' ) } @{$map};
  0         0  
  0         0  
301            
302 0         0 eval { $sth = $dbh->prepare($sql); };
  0         0  
303 0 0       0 die(&cluck() . "\n" . $@) if ( $@ );
304 0 0       0 if ( $simple == 0 ) {
305             # no weirdness, just map and execute
306 0         0 eval {
307 0         0 foreach my $c ( 0..$#{$where} ) {
  0         0  
308 0         0 $sth->execute( map { $where->[$c]{$_} } sort(keys(%{$where->[$c]})) );
  0         0  
  0         0  
309             }
310             };
311 0 0       0 die(&cluck() . "\n" . $@) if ( $@ );
312             } else {
313             # use the value routine to map the data to the execution function
314 0         0 eval {
315 0         0 foreach my $c ( 0..$#{$where} ) {
  0         0  
316 0         0 $sth->execute( &_value($where->[$c], $map ));
317             }
318             };
319 0 0       0 die(&cluck() . "\n" . $@) if ( $@ );
320             }
321 0         0 $sth->finish();
322 0 0       0 $dbh->commit() unless ( $dbh->{'AutoCommit'} );
323 0         0 return;
324             }
325            
326             =head1 Sql::Simple->update
327            
328             =head2 provide an easy interface to update row(s) in a table
329            
330             The "set" structure and "where" structure can also be arrayRef's of hashRefs. This allows you to perform multiple executions on a single prepared statement handle. This is shown in better detail in the examples
331            
332             1. as a list of arguments
333            
334             Sql::Simple->update(
335             databaseHandle,
336             'scalar tablename',
337             [ { 'setColumn' => 'setValue' }, { 'setColumn' => 'setValue' } ],
338             [ WHERE CLAUSE STRUCTURE (see above) ]
339             );
340            
341             2. or as a hash
342            
343             Sql::Simple->update(
344             databaseHandle,
345             'table' => $tablename,
346             'set' => { 'setColumn' => 'setValue' },
347             WHERE CLAUSE STRUCTURE (see above)
348             );
349            
350             =item Examples
351            
352             create table produce (
353             price float,
354             name varchar(20),
355             color varchar(10)
356             )
357            
358             # set the database handle for these transactions
359             Sql::Simple->setdbh($dbh);
360            
361             # set green tomatoes to 75 cents
362             # update produce set price = ? where name = ? and color = ?
363             Sql::Simple->update('produce', { 'price' => .75 },
364             { 'name' => 'tomatoe', 'color' => { 'green' });
365            
366             # set olives to 1.35 and pickles to 1.50
367             # update produce set price = ? where name = ?
368             Sql::Simple->update('produce',
369             [ { 'price' => 1.35 }, { 'price' => 1.50 } ],
370             [ { 'name' => 'olive' }, { 'name' => 'pickles' } ]
371             );
372            
373             # if you have a reason (and I can't think of one) to execute
374             # update multiple times with one set value. (a where "in" is advisable)
375             Sql::Simple->update('produce', { 'price' = .50 },
376             [ { 'name' => 'lettuce' }, { 'name' => 'onions' } );
377            
378             =cut
379            
380             sub update {
381 8     8 1 315 my $class = shift;
382            
383 8         11 my ( $table, $set, $where, $sth, $singleset, $singlewhere, $dbh );
384 8 50       17 if ( ref($_[0]) eq 'DBI::db' ) {
385 0         0 $dbh = shift;
386             } else {
387 8         13 $dbh = $DBH;
388             }
389 8 0 33     17 croak("No database handle given!") if ( ! $RETURNSQL && ! ref($dbh) );
390            
391 8 100       16 if ( scalar(@_) <= 5 ) {
392 4         11 ( $table, $set, $where ) = @_;
393             } else {
394 4         14 my %temp = @_;
395 4         7 $table = $temp{'table'};
396 4         6 $set = $temp{'set'};
397 4         9 $where = $temp{'where'};
398             }
399             # see if there are multiple set clauses (for mass calls)
400 8 100       19 if ( ref($set) eq 'HASH' ) {
401 6         7 $singleset = 1;
402 6         12 $set = [ $set ];
403             } else {
404 2         3 $singleset = 0
405             }
406             # see if there are multiple where clauses (for mass calls)
407 8 100 33     25 if ( ref($where) eq 'HASH' ) {
  4 50 33     25  
408 4         5 $singlewhere = 1;
409 4         15 $where = [ $where ];
410             } elsif ( ref($where) eq 'ARRAY' && scalar(@{$where}) > 1 && &_clause('', $where->[0], []) ne &_clause('', $where->[1], []) ) {
411 0         0 $singlewhere = 1;
412 0         0 $where = [ $where ];
413             } else {
414 4         5 $singlewhere = 0;
415             }
416            
417 8 50 66     31 die("multiple set clause set with one where clause, that makes no sense...") if ( ! $singleset && $singlewhere );
418            
419 8         14 my $map = [];
420 8         18 my $sql = "UPDATE $table SET ";
421 8         19 $sql = &_clause($sql, $set->[0], $map);
422 8         12 my $where_map_start = $#{$map};
  8         11  
423 8         25 $sql =~ s/ AND /\, /g;
424            
425 8 50       16 if ( $where ) {
426 8         31 $sql .= "WHERE\n";
427 8         17 $sql = &_clause($sql, $where->[0], $map);
428             } else {
429 0 0       0 return $sql if ( $RETURNSQL );
430 0         0 $sth = $dbh->prepare($sql);
431 0         0 $sth->execute( map { $set->[0]{$_} } sort(keys(%{$set->[0]})) );
  0         0  
  0         0  
432 0         0 $sth->finish();
433 0 0       0 $dbh->commit() unless ( $dbh->{'AutoCommit'} );
434             }
435            
436             # test for simpleness, so we can use the faster methods of execution
437 8         11 my $simple = 0;
438 8 50       36 $simple++ if ( $sql =~ /OR\n/ );
439 8 100       9 map { $simple++ if ( $_ ne 'VALUE' ) } @{$map};
  24         70  
  8         13  
440 8 50       17 warn $sql if ( $DEBUGSQL );
441 8 50       77 return $sql if ( $RETURNSQL );
442 0         0 eval { $sth = $dbh->prepare($sql); };
  0         0  
443 0 0       0 die(&cluck() . "\n" . $@) if ( $@ );
444            
445             # if singleset is present just create an array to bind against, instead of recalculating..
446 0 0       0 if ( $simple == 0 ) {
447 0         0 eval {
448 0 0       0 if ( $singleset ) {
449 0         0 my @set = map { $set->[0]{$_} } sort(keys(%{$set->[0]}));
  0         0  
  0         0  
450 0         0 foreach my $c ( 0..$#{$where} ) {
  0         0  
451 0         0 $sth->execute( @set, (map { $where->[$c]{$_} } sort(keys(%{$where->[$c]}))) );
  0         0  
  0         0  
452             #print Dumper( @set, (map { $where->[$c]{$_} } sort(keys(%{$where->[$c]}))) );
453             }
454             } else {
455 0         0 foreach my $c ( 0..$#{$where} ) {
  0         0  
456 0         0 $sth->execute( (map { $set->[$c]{$_} } sort(keys(%{$set->[$c]}))), (map { $where->[$c]{$_} } sort(keys(%{$where->[$c]}))) );
  0         0  
  0         0  
  0         0  
  0         0  
457             #print Dumper( (map { $set->[$c]{$_} } sort(keys(%{$set->[$c]}))), (map { $where->[$c]{$_} } sort(keys(%{$where->[$c]}))) );
458             }
459             }
460             };
461 0 0       0 die(&cluck() . "\n" . $@) if ( $@ );
462             } else {
463             # offset for where clause in the map
464             #eval {
465 0 0       0 if ( $singleset ) {
466             #my @set = map { $set->[0]{$_} } sort(keys(%{$set->[0]}));
467 0         0 foreach my $c ( 0..$#{$where} ) {
  0         0  
468 0         0 $sth->execute( (&_value($set->[0], $map)), (&_value($where->[$c], $map, \$where_map_start)) );
469             #print Dumper( (&_value($set->[0], $map)), (&_value($where->[$c], $map, \$where_map_start)) );
470             }
471             } else {
472 0         0 foreach my $c ( 0..$#{$where} ) {
  0         0  
473 0         0 $sth->execute( (&_value($set->[0], $map)), (&_value($where->[$c], $map, \$where_map_start)) );
474             #print Dumper( (&_value($set->[$c], $map)), (&_value($where->[$c], $map, \$where_map_start)) );
475             }
476             }
477             #};
478             #die(&cluck() . "\n" . $@) if ( $@ );
479             }
480 0         0 $sth->finish();
481 0 0       0 $dbh->commit() unless ( $dbh->{'AutoCommit'} );
482 0         0 return;
483             }
484            
485             =head1 Sql::Simple->insert
486            
487             =head2 provide an easy interface to insert row(s) into a table
488            
489             I use this routine quite a bit, so I tried to keep it any superfluous features far away from creeping in.
490             Since there are so many ways to pass things into this module, I'm just going to explain things in the examples.
491            
492             =item Examples
493            
494             create table users (
495             id int,
496             name varchar(20),
497             )
498            
499             create table visitors (
500             id int,
501             name varchar(20),
502             specialty varchar(10)
503             )
504            
505             # insert into users ( ?, ? )
506             # Executed with: 1, 'john'
507             Sql::Simple->insert($dbh, 'users', [ 'id', 'name' ], [ 1, 'john' ]);
508            
509             # insert into users ( ?, ? )
510             # Executed with: 2, 'jack'
511             # Executed with: 3, 'jim'
512             Sql::Simple->insert($dbh, 'users', [ 'id', 'name' ], [
513             [ 2, 'jack' ],
514             [ 3, 'jim' ],
515             ]);
516             Or, by using a hash directly.
517            
518             # insert into users ( ?, ? )
519             # Executed with: 1, 'john'
520             Sql::Simple->insert($dbh, 'users', { 'id' => 1, 'name' => 'john' });
521            
522             # insert into users ( ?, ? )
523             # Executed with: 2, 'jack'
524             # Executed with: 3, 'jim'
525             Sql::Simple->insert($dbh, 'users', [
526             { 'id' => 2, 'name' => 'jack' },
527             { 'id' => 3, 'name' => 'jim' },
528             ]);
529            
530             Lastly, a hash, but using a subquery
531            
532             # insert into users ( id, name )
533             # ( select id, name from visitors where specialty = ? )
534            
535             # Executed with: 'basketweaving'
536             Sql::Simple->insert($dbh, 'users',
537             [ qw(id name) ],
538             {
539             'columns' => [ qw(id name) ],
540             'table' => 'visitors',
541             'where' => { 'specialty' => 'basketweaving' }
542             }
543             );
544            
545             UPDATE 0.7: If you want to call a sql function, simply pass a scalar reference as a value for a column. (Example, to execute the "now()" function for a date..
546            
547             Sql::Simple->insert( $dbh, 'myTable', { 'columnA' => \'valueB' } );
548            
549             =cut
550            
551             sub insert {
552 5     5 1 240 my $class = shift;
553 5         6 my ( $columns, $values, $dbh );
554            
555 5 50       13 if ( ref($_[0]) eq 'DBI::db' ) {
556 0         0 $dbh = shift;
557             } else {
558 5         8 $dbh = $DBH;
559             }
560 5 0 33     12 croak("No database handle given!") if ( ! $RETURNSQL && ! ref($dbh) );
561            
562 5         9 my ( $table, $temp, $temp2 ) = @_;
563            
564 5 100       16 if ( ref($temp) eq 'ARRAY' ) {
    50          
565 4 100       15 if ( ref($temp->[0]) eq 'HASH' ) {
566 1         2 $values = $temp;
567 1         1 $columns = [ sort(keys(%{$temp->[0]})) ];
  1         5  
568             } else {
569 3         5 $columns = $temp;
570 3         4 $values = $temp2;
571             }
572             } elsif ( ref($temp) eq 'HASH' ) {
573 1         2 $values = [ map { $temp->{$_} } sort(keys(%{$temp})) ];
  2         6  
  1         6  
574 1         3 $columns = [ sort(keys(%{$temp})) ];
  1         3  
575             }
576            
577 5         6 my $simpleCheck;
578 5         7 my $map = [];
579 5         12 my $sql = "INSERT INTO $table\n( " . join(', ', @{$columns}) . " )\n";
  5         14  
580 5 100       15 if ( ref($values) eq 'ARRAY' ) {
    50          
581             # do a check to see if the values are hash refs..
582 4 100       11 if ( ref($values->[0]) eq 'HASH' ) {
583 1 50       4 if ( $values->[0]{'table'} ) {
584 0         0 $values->[0]{'return'} = 2;
585 0         0 my $tsql;
586 0         0 ( $tsql, $map ) = Sql::Simple->query(undef, %{$values->[0]});
  0         0  
587 0         0 $sql .= "( $tsql)";
588             } else {
589             # not simple if there are any values that are scalar references..
590 1 50       2 $simpleCheck = grep { 1 if ( ref($_) eq 'SCALAR' ) } @{$values};
  2         12  
  1         2  
591             # if it's a hash, see if any of it's memebers are scalars
592 1 100       5 $simpleCheck += grep { 1 if ( ref($values->[0]{$_}) eq 'SCALAR' ) } keys(%{$values->[0]}) if ( ref($values->[0]) eq 'HASH' );
  2 50       587  
  1         3  
593            
594 1 50       3 if ( $simpleCheck ) {
595 1         5 $sql .= &_insert($values->[0], $map);
596             } else {
597 0         0 $sql .= "VALUES\n( " . join(', ', ('?') x scalar(@{$columns}) ) . ' )';
  0         0  
598             }
599             }
600             } else {
601             # not simple if there are any values that are scalar references..
602 3 50 66     4 $simpleCheck = grep { 1 if ( ref($_) && ref($_) ne 'ARRAY' ) } @{$values};
  6         42  
  3         6  
603            
604 3 50       8 if ( $simpleCheck ) {
605 0         0 $sql .= &_insert($values, $map);
606             } else {
607 3         4 $sql .= "VALUES\n( " . join(', ', ('?') x scalar(@{$columns}) ) . ' )';
  3         14  
608             }
609             }
610             } elsif ( ref($values) eq 'HASH' ) {
611 1 50       3 if ( $values->{'table'} ) {
612 1         2 $values->{'return'} = 2;
613 1         1 my $tsql;
614 1         3 ( $tsql, $map ) = Sql::Simple->query(undef, %{$values});
  1         7  
615 1         5 $sql .= "( $tsql)";
616 1         2 $values = [ $values ];
617             }
618             }
619            
620 5 50       12 warn $sql if ( $DEBUGSQL );
621 5 50       27 return $sql if ( $RETURNSQL );
622            
623 0         0 my $sth = $dbh->prepare($sql);
624 0 0 0     0 if ( ref($map) eq 'ARRAY' && scalar(@{$map}) ) {
  0         0  
625 0 0       0 if ( ref($values->[0]) eq 'HASH' ) {
626 0 0       0 if ( defined($values->[0]{'where'}) ) {
627 0         0 foreach my $v ( @{$values} ) {
  0         0  
628 0         0 $sth->execute( &_value($v->{'where'}, $map));
629             }
630             } else {
631 0         0 foreach my $v ( @{$values} ) {
  0         0  
632 0         0 $sth->execute( &_value($v, $map) );
633             }
634             }
635             } else {
636 0         0 $sth->execute( &_value($values, $map));
637             }
638             } else {
639             # hmm. see if we have a single array, or an array of arrays
640 0 0       0 if ( ref($values->[0]) eq 'ARRAY' ) {
641 0         0 foreach my $v ( @{$values} ) {
  0         0  
642 0         0 eval {
643 0         0 $sth->execute(@{$v});
  0         0  
644             };
645 0 0       0 if ( $@ ) {
646 0 0       0 $dbh->rollback() unless ( $dbh->{'AutoCommit'} );
647 0         0 croak($@);
648             }
649             }
650             } else {
651 0 0       0 if ( ref($values->[0]) eq 'HASH' ) {
652 0 0       0 if ( $values->[0]{'table'} ) {
653 0 0       0 if ( ref($map) ) {
654 0         0 $sth->execute( &_value($values->[0]{'where'}, $map) );
655             } else {
656 0         0 $sth->execute();
657             }
658             } else {
659 0         0 map {
660 0         0 my $row = $_;
661 0         0 eval {
662 0         0 $sth->execute( map { $row->{$_} } sort(keys(%{$row})) );
  0         0  
  0         0  
663             };
664 0         0 $sth->finish();
665 0 0       0 if ( $@ ) {
666 0 0       0 $dbh->rollback() unless ( $dbh->{'AutoCommit'} );
667 0         0 croak($@);
668             }
669 0         0 } @{$values};
670             }
671             } else {
672 0         0 $sth->execute(@{$values});
  0         0  
673             }
674             }
675             }
676 0         0 $sth->finish();
677            
678 0 0       0 $dbh->commit() unless ( $dbh->{'AutoCommit'} );
679 0         0 return;
680             }
681            
682             =head1 Sql::Simple->query
683            
684             =head2 Retrieve information from a table
685            
686             Method invocation description.
687            
688             1. database handle (not required if "setdbh" used)
689             2. A datastructure depicting what columns you wish to query for.
690             The following formats are supported:
691             A. scalar of column (or columns, as it will simply interpolate
692             into the SQL to be executed)
693             B. arrayRef of column or columns (or computations, such as 2 + 2
694             as 'foo')
695             C. hashRef of columns (with the appropriate alias's as their values
696             D. hashRef of columns, with it's value an arrayRef
697             (the keys acting as a table prefix)
698            
699             Here's some examples of the "column" data structure
700            
701             'mycolumn'
702             # OR
703             [ qw(mycolumn mycolumn2 mycolumn3) ],
704             # OR
705             {
706             'mycolumn' => 'mc',
707             'mycolumn2' => 'm2',
708             'mycolumn3' => 'm3'
709             }
710             # OR
711             {
712             'mytable' => [ qw(mycolumn mycolumn2 mycolumn3) ]
713             }
714            
715             3. A datastructure depicting what tables you wish to query against
716             table => (scalar at the least is required)
717             A. scalar of the table you wish to query from.
718             B. hashRef of the relationships you are defining for this query..
719             ie: table1.column1 => table2.column1 ...
720             C. Array Reference of multiple tables
721            
722             4. A data structure depicting constraints in the "where" clause
723             (see complete documentation above)
724            
725             5. Options
726             order => (optional)
727             A. a scalar value with a single column to order by
728             B. an arrayRef of columns to order by, in the same alignment as given
729             col =>
730             A. a scalar value requesting that the result be handed as a complete
731             hash, courtesy of fetchall_hashref($col)
732            
733             =item Examples: (again, tables first)
734            
735             create table fruit (
736             id int,
737             name varchar(20),
738             cost float,
739             color varchar(20)
740             )
741            
742             create table plant (
743             species varchar(20),
744             produces int, # foreign key to fruit
745             )
746            
747             create table producer (
748             title varchar(20),
749             top_product int # foreign key to fruit
750             )
751            
752             # set the dbh for these transactions
753             Sql::Simple->setdbh($dbh);
754            
755             # select fruit_name, fruit_cost from fruit where fruit where fruit_color = ?
756             # Executed with: "red"
757             Sql::Simple->query(
758             [ 'name', 'cost' ],
759             'fruit',
760             { 'color' => 'red' }
761             );
762            
763             Simple table joins are fairly simple.
764            
765             # select fruit_name, plant_name
766             # from fruit inner join plant on fruit.fruit_id = plant.produces
767             Sql::Simple->query(
768             [ 'name', 'species' ],
769             { 'fruit.id' => 'plant.produces' }
770             );
771            
772             Complicated Table joins are only mildly more difficult (thanks to the standardization of ANSI-SQL 92)
773            
774             # select
775             # name, species, title
776             # from
777             # fruit
778             # inner join plant on fruit_id = plant.produces
779             # left outer join prodcuer on fruit.id = producer.top_product
780             # and producer.title ne 'Bad Fruit Company'
781             Sql::Simple->query(
782             [ 'name', 'species', 'title' ],
783             [
784             'fruit',
785             {
786             'table' => 'plant'
787             'on' => {
788             'fruit.id' => 'plant.produces'
789             }
790             },
791             {
792             'table' => 'producer',
793             'join' => 'left outer',
794             'on' => {
795             'fruit.id' => 'producer.top_product',
796             'producer.title' => {
797             'op' => 'ne',
798             'val' => 'Bad Fruit Company'
799             }
800             }
801             }
802             ]
803             );
804            
805             Ambiguity within table joins must be handled .. well, somewhat on your own. YMMV depending on your approach. This module B have your schema, so it's hard to figure out relationships. (next version might support something wacky like this). If you do not use a table prefix in your join, Sql::Simple will interpret it as a bind variable. The only way to get around this is by using a scalar reference. (example below)
806            
807             [
808             'produce',
809             {
810             'table' => 'shelf_life',
811             'on' => {
812             'produce_id' => 'sl_produce_id'
813             }
814             }
815             ]
816             # generates " FROM produce INNER JOIN shelf_life on produce_id = ? "
817             # intead of...
818             # " FROM produce INNER JOIN shelf_life on produce_id = sl_produce_id "
819            
820             Simple enough to get around..
821            
822             [
823             'produce',
824             {
825             'table' => 'shelf_life',
826             'on' => {
827             'produce_id' => \'sl_produce_id'
828             }
829             }
830             ]
831             # generates " FROM produce INNER JOIN shelf_life on produce_id = sl_produce_id "
832            
833             =item Return structure Format
834            
835             UPDATE 0.7: You can specify the type of structure you want returned from a query. The default is an array of hashref's, keyed by the column names. (that you may have chosen with aliases). You can get an array of arrays by calling the static method "asarray" and passing it a true or a false value. If you want the result set executed with using "fetchall_hashref()" simply pass the column you want after the where clause. Eventually, I'll probably move these other fetch styles as static methods like "asarray".
836            
837             =cut
838            
839             sub query {
840 35     35 1 2473 my $class = shift;
841 35         41 my ( $columns, $table, $where, $col, $return, $order, $distinct, $dbh );
842            
843 35 100       79 shift unless ( $_[0] );
844            
845 35 50 33     127 if ( ref($_[0]) eq 'DBI::db' ) {
    50          
846 0         0 $dbh = shift;
847             } elsif ( ref($class) && ref($class->{'dbh'}) ) {
848 0         0 $dbh = $class->{'dbh'};
849             } else {
850 35         45 $dbh = $DBH;
851             }
852            
853             # pass in arguments, or a hash.. we'll take care of the rest! (umm, I hope! ;-)
854 35 100       56 if ( grep { 1 if ( $_ eq 'table' ) } @_ ) {
  205 100       570  
855 23         65 my %temp = @_;
856 23         36 $table = $temp{'table'};
857 23         28 $where = $temp{'where'};
858 23         26 $columns = $temp{'columns'};
859 23         30 $col = $temp{'col'};
860 23         28 $return = $temp{'return'};
861 23         30 $order = $temp{'order'};
862 23         39 $distinct= $temp{'distinct'};
863             } else {
864             # some day I need to make the last argument after "WHERE" just be a hash of extra options
865 12         27 ( $columns, $table, $where, $col, $return, $order, $distinct ) = @_;
866             }
867 35 100       80 $return = 0 unless ( defined($return) );
868            
869 35 0 33     77 croak("No database handle given!") if ( ! $RETURNSQL && ! ref($dbh) && ! $return );
      0        
870            
871 35         43 my $sql = "SELECT ";
872 35 50       70 if ( $distinct ) {
873 0         0 $sql .= "distinct ";
874             }
875 35         47 my $map = [];
876 35         47 my $tables = {};
877            
878 35 100       97 if ( ref($table) eq 'ARRAY' ) {
    50          
    50          
879 6         16 &_columns($columns, \$sql);
880 6         14 &_from($table, \$sql, $map, $tables);
881             } elsif ( ref($table) eq 'HASH' ) {
882             # only allow a single key pair
883 0 0       0 die("Do not attempt to use a hash for more than one table join.") if ( scalar(keys(%{$table})) > 1 );
  0         0  
884 0         0 my $ft = join('', keys(%{$table}));
  0         0  
885 0 0       0 die("No complex joins in simple hash queries") if ( ref($table->{$ft}) );
886 0         0 &_columns($columns, \$sql);
887 0         0 $sql .= "FROM " . substr($ft, 0, index($ft, '.')) . " INNER JOIN " . substr($table->{$ft}, 0, index($table->{$ft}, '.')) . " ON $ft = $table->{$ft} ";
888             } elsif ( $table ) {
889 29         127 &_columns($columns, \$sql, $table);
890 29         57 $sql .= "FROM $table ";
891             } else {
892 0         0 &_columns($columns, \$sql, $table);
893             }
894            
895             # DAMN IT.. fix the code so it doesn't add a prefix onto columns that have parens
896            
897 35 100 100     83 unless ( $where || scalar(@{$map})) {
  8         14  
898 6 50       17 warn $sql if ( $DEBUGSQL );
899 6 50 66     30 return ( $sql ) if ( $return || $RETURNSQL );
900 0         0 my $sth = $dbh->prepare($sql);
901 0         0 $sth->execute();
902            
903             # save the statement handle and return it (if in an object, and state is on)
904 0 0 0     0 if ( ref($class) && $class->{'state'} ) {
905 0         0 $class->{'sth'} = $sth;
906 0         0 return $sth;
907             }
908            
909 0 0 0     0 if ( $col && ! ref($col) ) {
910 0         0 my $res = $sth->fetchall_hashref($col);
911 0         0 $sth->finish();
912 0         0 return($res);
913             } else {
914 0         0 my @outbound;
915 0         0 while ( my $row = $sth->fetchrow_hashref() ) {
916 0         0 push(@outbound, $row);
917             }
918            
919 0         0 $sth->finish();
920 0         0 return(\@outbound);
921             }
922             }
923             # the most important line is the _clause generation.. as it is possibly recursing
924             # multiple times, possibly to this function as well generating subqueries or whatnot.
925 29 100       62 if ( $where ) {
926 27         35 $sql .= 'WHERE ';
927 27         127 $sql = &_clause($sql, $where, $map);
928             }
929            
930 29 50       614 $sql .= " ORDER BY " . join(', ', @{$order}) if ( $order );
  0         0  
931             # I don't remember what this is for...
932 29 50       127 $sql .= join(' ', @{$col}) if ( ref($col) eq 'ARRAY' );
  0         0  
933            
934             # if the return variable is set.. return it..
935 29 100       83 return ( $sql, $map ) if ( $return == 2 );
936 18 50       31 warn $sql if ( $DEBUGSQL );
937 18 50 66     616 return ( $sql ) if ( $return == 1 || $RETURNSQL );
938            
939 0         0 my $sth;
940 0         0 $sth = $dbh->prepare($sql);
941            
942             # do the simple check thing
943 0         0 my $simple = 0;
944 0 0       0 $simple++ if ( $sql =~ /OR\n/ );
945 0 0       0 map { $simple++ if ( $_ ne 'VALUE' ) } @{$map};
  0         0  
  0         0  
946            
947             # if there is a join clause
948 0 0       0 if ( scalar(keys(%{$tables})) > 0 ) {
  0         0  
949            
950             # peel off the first table, since it is just a scalar
951 0         0 my $t = shift(@{$table});
  0         0  
952 0         0 my $d = -1;
953 0         0 my $c = \$d;
954             $sth->execute(
955             map {
956 0 0       0 if ( $_->{'on'} ) {
  0 0       0  
957 0         0 &_value($_->{'on'}, $map, $c);
958             } elsif ( $_ ) {
959 0         0 &_value($_, $map, $c);
960             }
961 0         0 } ( @{$table}, $where )
962             );
963 0         0 unshift(@{$table}, $t);
  0         0  
964             } else {
965 0 0       0 if ( $simple == 0 ) {
    0          
966             # if it's simple.. then key off the where hash
967 0         0 eval {
968 0         0 $sth->execute( map { $where->{$_} } sort(keys(%{$where})) );
  0         0  
  0         0  
969             };
970             } elsif ( ! $where ) {
971             # if there is no where clause (which seems to happen in every test script I write, just execute it flat)
972 0         0 eval {
973 0         0 $sth->execute();
974             };
975             } else {
976 0         0 eval {
977 0         0 $sth->execute( (&_value($where, $map)) );
978             };
979             }
980 0 0       0 if ( $@ ) {
981 0         0 die(cluck() . "\n" . $@);
982             }
983             }
984            
985             # save the statement handle and return it (if in an object, and state is on)
986 0 0 0     0 if ( ref($class) && $class->{'state'} ) {
987 0         0 $class->{'sth'} = $sth;
988 0         0 return $sth;
989             }
990            
991             # if there is a column defined for a fetchall
992 0 0 0     0 if ( $col && ! ref($col) ) {
993             # declare $res in the current scope
994 0         0 my $res = $sth->fetchall_hashref($col);
995 0         0 $sth->finish();
996 0         0 return($res);
997             } else {
998 0         0 my @outbound;
999 0         0 while ( my $row = $sth->fetchrow_hashref() ) {
1000 0         0 push(@outbound, $row);
1001             }
1002            
1003 0         0 $sth->finish();
1004 0         0 return(\@outbound);
1005             }
1006             }
1007            
1008             sub execute {
1009 0     0 0 0 my $class = shift;
1010 0         0 my $dbh;
1011            
1012 0 0 0     0 if ( ref($_[0]) eq 'DBI::db' ) {
    0          
1013 0         0 $dbh = shift;
1014             } elsif ( ref($class) && ref($class->{'dbh'}) ) {
1015 0         0 $dbh = $class->{'dbh'};
1016             } else {
1017 0         0 $dbh = $DBH;
1018             }
1019 0         0 my ( $sql, $struct, $col ) = @_;
1020            
1021 0 0       0 warn $sql if ( $DEBUGSQL );
1022            
1023 0         0 my $sth = $dbh->prepare($sql);
1024 0 0       0 if ( $sql =~ /^[\s\n]*select/i ) {
1025 0 0       0 if ( $struct ) {
1026             # ok, here we need to scan the structure and see if we need to do bindparams
1027 0         0 $sth->execute(@{$struct});
  0         0  
1028             } else {
1029 0         0 $sth->execute();
1030             }
1031 0 0       0 if ( ref($class) ) {
1032 0         0 $class->{'sth'} = $sth;
1033 0         0 return $sth;
1034             } else {
1035 0 0       0 if ( $ASARRAY ) {
    0          
1036 0         0 my $result = $sth->fetchall_arrayref();
1037 0         0 $sth->finish();
1038 0         0 return($result);
1039             } elsif ( $col ) {
1040 0         0 my $result = $sth->fetchall_hashref($col);
1041 0         0 $sth->finish();
1042 0         0 return($result);
1043             } else {
1044 0         0 my @outbound;
1045 0         0 while ( my $row = $sth->fetchrow_hashref() ) {
1046 0         0 push(@outbound, $row);
1047             }
1048 0         0 $sth->finish();
1049 0         0 return(\@outbound);
1050             }
1051             }
1052             } else {
1053 0 0       0 if ( ref($struct->[0]) eq 'ARRAY' ) {
1054 0         0 foreach my $s ( @{$struct} ) {
  0         0  
1055 0         0 $sth->execute(@{$s});
  0         0  
1056             }
1057             } else {
1058 0         0 $sth->execute(@{$struct});
  0         0  
1059             }
1060             }
1061 0         0 $sth->finish();
1062             }
1063            
1064             sub execute_procedure {
1065 0     0 0 0 my $class = shift;
1066 0         0 my ( $dbh, $sql );
1067            
1068 0 0 0     0 if ( ref($_[0]) eq 'DBI::db' ) {
    0          
1069 0         0 $dbh = shift;
1070             } elsif ( ref($class) && ref($class->{'dbh'}) ) {
1071 0         0 $dbh = $class->{'dbh'};
1072             } else {
1073 0         0 $dbh = $DBH;
1074             }
1075 0         0 my ( $procedure, $arguments, $types, $col ) = @_;
1076            
1077 0         0 $sql = $procedure;
1078             # generate the sql statement (for the procedure, hmm. not going to go down the bound parameter road)
1079 0 0 0     0 if ( ref($arguments) eq 'ARRAY' && scalar(@{$arguments}) > 0 ) {
  0         0  
1080            
1081 0         0 foreach my $a ( 0..$#{$arguments} ) {
  0         0  
1082 0 0 0     0 if ( defined($arguments->[$a]) && $types->{$a} eq 'number' ) { # they said it's a number
    0 0        
    0          
    0          
    0          
1083 0         0 $sql .= ' ' . $arguments->[$a] . ",";
1084             } elsif ( defined($arguments->[$a]) && $types->{$a} eq 'string' ) { # they said it's a string
1085 0         0 $sql .= " '" . $arguments->[$a] . "',";
1086             } elsif ( $arguments->[$a] =~ /^\-?\d+\.?\d*$/ ) { # it looks like a number
1087 0         0 $sql .= ' ' . $arguments->[$a] . ",";
1088             } elsif ( defined($arguments->[$a]) ) { # it's a string, quote it
1089 0         0 $sql .= " '" . $arguments->[$a] . "',";
1090             } elsif ( ! defined($arguments->[$a]) ) { # it's null
1091 0         0 $sql .= " NULL,";
1092             }
1093             }
1094 0         0 $sql =~ s/\,$//;
1095             }
1096            
1097 0 0       0 warn $sql if ( $DEBUGSQL );
1098 0 0       0 return $sql if ( $RETURNSQL );
1099             #warn $sql;
1100 0         0 my $sth = $dbh->prepare($sql);
1101 0         0 $sth->execute();
1102 0 0       0 if ( $col ) {
1103 0         0 my $result = $sth->fetchall_hashref($col);
1104 0         0 $sth->finish();
1105 0         0 return($result);
1106             } else {
1107 0         0 my @outbound;
1108 0         0 while ( my $row = $sth->fetchrow_hashref() ) {
1109 0         0 push(@outbound, $row);
1110             }
1111 0         0 $sth->finish();
1112 0         0 return(\@outbound);
1113             }
1114             }
1115            
1116             #######################################################################
1117             # internal functions
1118            
1119             sub _insert {
1120 1     1   2 my ( $values, $map ) = @_;
1121            
1122 1         2 my $sql .= "VALUES\n( ";
1123 1 50       4 if ( ref($values) eq 'ARRAY' ) {
1124 0         0 foreach my $c ( @{$values} ) {
  0         0  
1125 0 0       0 if ( ref($c) ) {
1126 0         0 $sql .= ${$c} . ", "; #$sql .= "'" . ${$c} . "', "; # by placing quotes around the escaped data, we don't provide support functions (maybe provide support for quoted strings as an "op"
  0         0  
1127 0         0 push(@{$map}, 'SCALAR');
  0         0  
1128             } else {
1129 0         0 $sql .= '?, ';
1130 0         0 push(@{$map}, 'VALUE');
  0         0  
1131             }
1132             }
1133             } else {
1134 1         1 foreach my $k ( sort(keys(%{$values})) ) {
  1         4  
1135 2 100       7 if ( ref($values->{$k}) ) {
1136 1         1 $sql .= ${$values->{$k}} . ', ';
  1         2  
1137 1         2 push(@{$map}, 'SCALAR');
  1         2  
1138             } else {
1139 1         1 $sql .= '?, ';
1140 1         2 push(@{$map}, 'VALUE');
  1         2  
1141             }
1142             }
1143             }
1144 1         7 $sql =~ s/, $/ \)/;
1145 1         3 return $sql;
1146             }
1147            
1148             sub _columns {
1149 35     35   56 my ( $columns, $sql, $tab ) = @_;
1150            
1151 35 100       79 $tab .= '.' if ( $tab );
1152            
1153 35 100       101 if ( ref($columns) eq 'HASH' ) {
    100          
1154             # columns as a hash are treated as an alias list
1155 6         10 foreach my $k ( sort(keys(%{$columns})) ) {
  6         29  
1156 12 100       22 if ( defined($columns->{$k}) ) {
1157 10 100       24 if ( $k =~ /[\s\.]/ ) {
1158 2 50       4 if ( ref($columns->{$k}) eq 'ARRAY' ) {
1159             # allow hashes for alias later on (within the array)
1160 0         0 ${$sql} .= join(' ', map{ $k . '.' . $_ . ',' } @{$columns->{$k}} ) . ' ';
  0         0  
  0         0  
  0         0  
1161             } else {
1162 2         3 ${$sql} .= "$k as $columns->{$k}, ";
  2         6  
1163             }
1164             } else {
1165 8 100       18 if ( ref($columns->{$k}) eq 'ARRAY' ) {
1166 4         5 ${$sql} .= join(' ', map{ $k . '.' . $_ . ',' } @{$columns->{$k}} ) . ' ';
  4         7  
  12         42  
  4         8  
1167             } else {
1168 4         4 ${$sql} .= "$tab$k as $columns->{$k}, ";
  4         21  
1169             }
1170             }
1171             } else {
1172 2 50       6 if ( $k =~ /[\s\.]/ ) {
1173 0         0 ${$sql} .= $k . ', ';
  0         0  
1174             } else {
1175 2         1 ${$sql} .= $tab . $k . ', ';
  2         6  
1176             }
1177             }
1178             }
1179             } elsif ( ref($columns) eq 'ARRAY' ) {
1180             # array references are simple concatenations
1181 23 50       22 ${$sql} .= join(', ', map { ( $_ =~ /[\s\.]/ ) ? $_ : $tab . $_; } @{$columns}) . ' ';
  23         602  
  42         177  
  23         36  
1182             } else {
1183             # if we have more than one column in the string..
1184 6 100       16 if ( $columns =~ /[\s\,]/ ) {
1185             # attach the table as a prefix if there is no prefix for that column
1186 2 50       3 ${$sql} .= join(', ', map { ( $_ =~ /\s\.]/ ) ? $_ : $tab . $_; } split(/, ?/, $columns)) . ' ';
  2         11  
  4         22  
1187             } else {
1188             # if no spaces or commas scalars are just loaded without any translation
1189 4         5 ${$sql} .= $columns . ' ';
  4         9  
1190             }
1191             }
1192 35         49 ${$sql} =~ s/\, $/ /;
  35         92  
1193             }
1194            
1195             # this will iterate through all the tables
1196             sub _from {
1197 6     6   10 my ( $struct, $sql, $map, $tables ) = @_;
1198            
1199 6         7 ${$sql} .= "FROM " . $struct->[0];
  6         13  
1200 6         13 $tables->{$struct->[0]} = 1;
1201            
1202 6         8 foreach my $s ( @{$struct} ) {
  6         10  
1203 14 100       26 if ( ref($s) eq 'HASH' ) {
1204             # get the table and the join type into the statement
1205 6 50       6 ${$sql} .= "\n" . ( ( $s->{'join'} ) ? uc($s->{'join'}) . ' JOIN ' : 'INNER JOIN ' ) . "$s->{'table'} ON ";
  6         25  
1206            
1207             # push the table into the table hash
1208 6         10 $tables->{$s->{'table'}} = 1;
1209            
1210 6 50       14 if ( ref($s->{'on'}) eq 'HASH' ) {
    0          
1211             # loop over all the relationships
1212 6         10 &_clause($sql, $s->{'on'}, $map, $tables);
1213             } elsif ( ref($s->{'on'}) eq 'ARRAY' ) {
1214 0         0 foreach my $a ( @{$s->{'on'}} ) {
  0         0  
1215 0         0 &_clause($sql, $a, $map, $tables);
1216             }
1217             }
1218             } else {
1219 8 100       21 ${$sql} .= ',' . $s . ' ' if ( $s ne $struct->[0] );
  2         21  
1220             }
1221             }
1222             }
1223            
1224             sub _clause {
1225 95     95   200 my ( $tsql, $where, $map, $from ) = @_;
1226             # Ok, we were passed an array, this is an or clause.. concatenate the individual pieces by calling
1227             # this function with the pieces.. after all the recursion it'll allow me to close the parens, and add
1228             # an OR clause at the end.. (after the last element, we'll trim it off)..
1229            
1230 95         94 my $sql;
1231 95 100       148 if ( ref($tsql) ) {
1232 20         21 $sql = $tsql;
1233             } else {
1234 75         93 $sql = \$tsql;
1235             }
1236            
1237 95 100       204 if ( ref($where) eq 'ARRAY' ) {
1238 6         7 foreach my $wa ( @{$where} ) {
  6         11  
1239 14         12 ${$sql} .= "( ";
  14         20  
1240 14 50       28 ${$sql} .= "\n" if ( ref($wa) eq 'ARRAY' );
  0         0  
1241 14         27 ${$sql} = &_clause($sql, $wa, $map);
  14         20  
1242 14         15 ${$sql} .= ") ";
  14         20  
1243 14 50       31 ${$sql} .= "\n" if ( ref($wa) eq 'ARRAY' );
  0         0  
1244 14         10 ${$sql} .= "OR\n";
  14         23  
1245             }
1246 6         8 ${$sql} =~ s/OR\n$//;
  6         19  
1247 6         7 ${$sql} =~ s/AND\n$//;
  6         9  
1248 6         6 return ${$sql};
  6         13  
1249             }
1250             # ok, wasn't called with an array ref, so it must be a hash, loop over the keys, and start mapping out the
1251             # "map" structure so mass calls won't be so damn painful when we're iterating through the execution loop
1252             # handling the different sub structures will alter the mapping appropriately (you can see that we call
1253             # the query function when subqueries kick in.. yay for indirection!)
1254 89         91 foreach my $w ( sort(keys(%{$where})) ) {
  89         266  
1255 119 100       228 if ( ref($where->{$w}) eq 'HASH' ) {
1256 18 100       41 if ( $where->{$w}{'table'} ) {
1257 10         32 $where->{$w}{'return'} = 2;
1258 10         11 my ( $msql, $mstack ) = Sql::Simple->query(undef, %{$where->{$w}});
  10         54  
1259 10         16 ${$sql} .= "$w in ( $msql ) AND ";
  10         29  
1260 10         14 my $tmp_map = [];
1261             # recursively process the underlying structure
1262 10         28 &_clause(undef, $where->{$w}{'where'}, $tmp_map);
1263             # then tie it back into the current structure
1264 10         10 push(@{$map}, $tmp_map);
  10         32  
1265             } else {
1266            
1267 8 50       16 if ( $where->{$w}{'allowNull'} ) {
1268 0         0 ${$sql} .= " ( ";
  0         0  
1269             }
1270            
1271             # if the value is an array
1272 8 50       32 if ( ref($where->{$w}{'val'}) eq 'ARRAY' ) {
    50          
    50          
1273 0         0 ${$sql} .= $w . ' ' . $where->{$w}{'op'} . ' ( ' . join(',', ('?') x scalar(@{$where->{$w}{'val'}}) ) . ' ) ';
  0         0  
  0         0  
1274 0         0 push(@{$map}, 'HASH-ARRAY' );
  0         0  
1275             } elsif ( ref($where->{$w}{'val'}) eq 'SCALAR') {
1276             # bloody hack.. I need to be able to throw an = sign if there is whitespace ie: IS NULL (vs. a scalar ref table join)
1277 0 0       0 ${$sql} .= $w . ( ( ${$where->{$w}} =~ /\s/ ) ? ' ' : ' = ' ) . ${$where->{$w}};
  0         0  
  0         0  
  0         0  
1278 0         0 push(@{$map}, 'SCALAR');
  0         0  
1279             } elsif ( defined($where->{$w}{'val2'}) ) {
1280 0         0 ${$sql} .= $w . ' ' . $where->{$w}{'op'} . ' ? AND ? ';
  0         0  
1281 0         0 push(@{$map}, 'VAL2');
  0         0  
1282             } else {
1283 8         8 ${$sql} .= $w . ' ' . $where->{$w}{'op'} . ' ? ';
  8         22  
1284 8         10 push(@{$map}, 'VAL');
  8         14  
1285             }
1286            
1287 8 50       19 if ( $where->{$w}{'allowNull'} ) {
1288 0         0 ${$sql} .= " OR $w is null ) ";
  0         0  
1289             }
1290 8         7 ${$sql} .= " AND ";
  8         18  
1291             }
1292             } else {
1293 101 100       242 if ( ref($where->{$w}) eq 'ARRAY' ) {
    100          
1294 8         8 ${$sql} .= $w . ' in (' . join(',', ('?') x scalar(@{$where->{$w}}) ) . ') AND ';
  8         35  
  8         31  
1295 8         8 push(@{$map}, 'ARRAY');
  8         20  
1296             } elsif ( ref($where->{$w}) eq 'SCALAR') {
1297 6 100       6 ${$sql} .= $w . ( ( ${$where->{$w}} =~ /\s/ ) ? ' ' : ' = ' ) . ${$where->{$w}} . ' AND ';
  6         7  
  6         23  
  6         12  
1298 6         8 push(@{$map}, 'SCALAR');
  6         23  
1299             } else {
1300             # from clause construct. sort of a hack. if we are in a from clause, and the from clause has a prefix
1301             # (with a dot as a seperator) as a table name, then we're dealing with a table join
1302 87 100 66     272 if ( $from && $from->{substr($w, 0, index($w, '.'))} && $from->{substr($where->{$w}, 0, index($where->{$w}, '.'))} ) {
      66        
1303 8         8 ${$sql} .= "$w = $where->{$w} AND ";
  8         18  
1304 8         9 push(@{$map}, 'SCALAR');
  8         18  
1305             } else {
1306 79         70 ${$sql} .= $w . ' = ? AND ';
  79         167  
1307 79         89 push(@{$map}, 'VALUE');
  79         192  
1308             }
1309             }
1310             }
1311             }
1312            
1313 89         126 ${$sql} =~ s/AND $//;
  89         349  
1314            
1315 89         110 return ${$sql};
  89         224  
1316             }
1317            
1318             # I guess I should explain having a seperate function for getting the values out of a structure for execution.
1319             # In my mind, the following function is a little less of an impact CPU wise than the above function.. Writing a "map"
1320             # of the structure, and simply using it to loop over the various elements has to be a little less stressful than
1321             # having to figure out the exact path of hoops to jump through to get the list of arguments to the execution list.
1322             # the "map" structure is just meant to help this module quickly figure out where the data is, it doesn't have to discover
1323             # it on every pass (using ref tests)..
1324            
1325             sub _value {
1326 0     0     my ( $value, $map, $c ) = @_;
1327            
1328 0           my @outbound;
1329 0 0         unless ( $c ) {
1330             # Hmmm.. since the map needs to increment every time, we'll start at -1..
1331             # notice that it's a ref to a scalar.. as this will allow me to maintain state
1332             # when dealing with multiple levels of nested "OR" clauses
1333 0           my $counter = -1;
1334 0           $c = \$counter;
1335             }
1336            
1337 0 0         if ( ref($value) eq 'HASH' ) {
    0          
1338 0           return map {
1339 0           ${$c}++;
  0            
1340            
1341 0 0         if ( $map->[${$c}] eq 'VALUE' ) {
  0 0          
  0 0          
    0          
    0          
    0          
    0          
1342 0           $value->{$_};
1343 0           } elsif ( $map->[${$c}] eq 'ARRAY' ) {
1344 0           @{$value->{$_}};
  0            
1345 0           } elsif ( $map->[${$c}] eq 'VAL2' ) {
1346 0           ( $value->{$_}{'val'}, $value->{$_}{'val2'} );
1347 0           } elsif ( $map->[${$c}] eq 'VAL' ) {
1348 0           $value->{$_}{'val'};
1349 0           } elsif ( $map->[${$c}] eq 'HASH-ARRAY' ) {
1350 0           @{$value->{$_}{'val'}};
  0            
1351            
1352 0           } elsif ( $map->[${$c}] eq 'SCALAR' ) {
1353             # do nothing ...
1354             } elsif ( ref($map->[${$c}]) ) {
1355             # a little recursion didn't hurt anyone.. (the current value in the map structures is obviously an array,
1356             # sooo, we'll be simply passing a reference to the current position, so this routine will think it's a totally
1357             # new structure to parse (phew!)
1358 0           &_value($value->{$_}{'where'}, $map->[${$c}])
  0            
1359             }
1360 0           } sort(keys(%{$value}));
1361             } elsif ( ref($value) eq 'ARRAY' ) {
1362             # map stays the same, but $c is simply a reference to the current entry within the map
1363 0 0         if ( ref($value->[0]) ne 'SCALAR' ) {
1364 0           return map {
1365 0           my @temp = &_value($_, $map, $c);
1366 0           @temp;
1367 0           } @{$value};
1368             } else {
1369             # insert is being called (with a scalar)
1370 0           return grep {
1371 0           ${$c}++;
  0            
1372 0 0         $_ if ( $map->[${$c}] eq 'VALUE' );
  0            
1373 0           } @{$value};
1374             }
1375             }
1376             }
1377            
1378             ####
1379             # easier "set" for DBH
1380             sub setdbh {
1381 0     0 0   my $class = shift;
1382 0           $DBH = shift;
1383             }
1384            
1385             ####
1386             # return the dbh
1387             sub getdbh {
1388 0     0 0   return $DBH;
1389             }
1390            
1391             ####
1392             # easier "set" for RETURNSQL
1393            
1394             sub setreturn {
1395 0     0 0   my $class = shift;
1396 0           $RETURNSQL = shift;
1397             }
1398            
1399             sub setReturn {
1400 0     0 0   my $class = shift;
1401 0           $RETURNSQL = shift;
1402             }
1403            
1404             sub setdebug {
1405 0     0 0   my $class = shift;
1406 0           $DEBUGSQL = shift;
1407             }
1408            
1409             sub setDebug {
1410 0     0 0   my $class = shift;
1411 0           $DEBUGSQL = shift;
1412             }
1413            
1414             sub returnsth {
1415 0     0 0   my $class = shift;
1416 0           $RETURNSTH = shift;
1417             }
1418            
1419             sub asarray {
1420 0     0 0   my $class = shift;
1421 0           $ASARRAY = shift;
1422             }
1423            
1424             sub asArray {
1425 0     0 0   my $class = shift;
1426 0           $ASARRAY = shift;
1427             }
1428            
1429             #################################################################################
1430             # object code
1431            
1432             sub new {
1433 0     0 0   my ( $class, $dbh, $state ) = @_;
1434            
1435 0   0       return bless( { 'dbh' => $dbh, 'state' => $state || 1 } );
1436             }
1437            
1438             =head1 state
1439            
1440             The state function controls how the stateful subroutines act in a OO environment. As class based functions, you are returned the entire result set as one large data structure. With larger data sets, you may wish to iterate through them one at a time, to avoid massive memory consumption. If this is set to "true", then any direct query will save the statement handle to the object. You can call all of the various DBI routines from here, or simply call the other helper methods.
1441            
1442             Here is a quick example of how you would deal with Sql::Simple through the OO interface.
1443            
1444             my $obj = new Sql::Simple( DBI->connect('dbi:ODBC:instance', 'username', 'password') );
1445             my $result = $s->query(qw(name density), 'minerals')->fetchall_arrayref();
1446            
1447             my $sth = $obj->query(qw(color taste durability), 'fruit');
1448             while ( my @row = $sth->fetchrow() ) {
1449             # do something...
1450             }
1451             $sth->finish();
1452             =cut
1453            
1454             =cut
1455            
1456             sub state {
1457             my ( $self, $state ) = @_;
1458             ( $state ) ? $self->{'state'} = $state : return $self->{'state'};
1459             }
1460            
1461             my @ofunctions = qw(column table where order);
1462            
1463             my @functions = qw(fetchall_arrayref fetchrow_arrayref fetchrow fetchall_hashref fetchrow_hashref fetchrow_array err rows bind_columns fetch dump_results);
1464            
1465             foreach my $field (@functions) {
1466             my $sub = q {
1467             sub [[field]] {
1468             my $self = shift;
1469             return $self->{'sth'}->[[field]]();
1470             }
1471             };
1472            
1473             $sub =~ s/\[\[field\]\]/$field/g;
1474             eval $sub;
1475            
1476             if ($@) {
1477             die $@;
1478             }
1479             }
1480            
1481             sub sth {
1482             my ( $self ) = @_;
1483             return $self->{'sth'};
1484             }
1485            
1486             sub finish {
1487             my ( $self ) = @_;
1488             $self->{'sth'}->finish() and delete($self->{'sth'}) if ( ref($self) && ref($self->{'sth'}) );
1489             }
1490            
1491             =head1 BUGS:
1492            
1493             I sure hope there are no bugs, feel free to drop me a line if you run into anything I need to be concerned with.
1494            
1495             =head1 Acknowledgements:
1496            
1497             The author of XML::Simple (use it all the time).
1498             Paul Lindner, Garth Webb, Kevin Moffatt, Chuck McLean, Intelligent Software Solutions (www.iswsolutions.com)
1499            
1500             =head1 TODO:
1501            
1502             1. Figure out a good way of handling prefix's for columns.. (ugh)
1503             2A. store pre-computed sql and map (in object or possibly global via mod_perl or serialized in mldbm or whatever)
1504             2B. Be able to pass in the precomputed information as arguments to functions.. (partially done, with the execute method)
1505            
1506             =head1 See also:
1507            
1508             DBI (manpage)
1509             Sql::* (lots of similar modules to this)
1510            
1511             Specifically, take a look at DBIx::Abstract and Sql::Abstract. I was rather astonished when I released this module today to find out there was another module that had such similar capabilities. Great minds must think alike ;-). After reviewing the modules, I can say that DBIx::Abstract and Sql::Simple have very little in common, but it does fill a niche that Sql::Simple does not. Sql::Abstract however, does have nearly identical syntax on a few of the method calls, and has support for some of the features that I tout in Sql::Simple. (I'm not apologizing for writing this module, I like what it has done for me,
1512            
1513             I'm not going to write a bullet background paper iterating over every feature this module has and doesn't have in comparison but I will cover the major differences.
1514            
1515             =item ANSI SQL 92 join support
1516            
1517             This feature, combined with the fact that the "clause" for the join is directly tied to the same code that generates a where clause is probably the biggest difference. This feature is available in all aspects of Sql::Simple, not just the query method (as any sub query made in insert, update, or delete simply recursively call the query method to build it's data set).
1518            
1519             =item Execution
1520            
1521             Sql::Abstract right now is better suited for a web environment where you would want to write your own custom handlers to handle errors. Once an OO interface is added to Sql::Simple, that may be reason enough to switch. Right now, Sql::Simple is capable of returning the completed Sql statement back to the user, not really all that different from Sql::Abstract.. ie:
1522            
1523             $Sql::Simple::RETURNSQL = 1;
1524             my $sth = $dbh->prepare(Sql::Simple->query('id', 'fruit', { 'name' => 'apple' }));
1525            
1526             Similar to.
1527            
1528             my $sql = SQL::Abstract->new;
1529             my ( $sth, @bind ) = $sql->select('fruit', ['id'], { 'name' => 'apple' });
1530            
1531             =item Mass Execution
1532            
1533             The main reason I wrote this module was to simplify the "I need to insert 10,000 records, but not use BCP, because I need it to hit the rules etc.". With that said, the ability to pass in an array ref of hash refs into the insert routine, is fairly nice (or an array ref of columns, and an arrayref of arrayrefs of values). Or be able to mass update quickly.
1534            
1535             =item Summary
1536            
1537             Umm, TMTOWTDI, or whatever. Use what suits you, the only real personal preference issue I have is that the variables are out of order in Sql::Abstract. I'd rather see it line up with an actual SQL query. IE: select COLUMNS from TABLE where CLAUSE, instead of TABLE, COLUMNS, WHERE
1538            
1539             =head1 COPYRIGHT:
1540            
1541             The Sql::Simple module is Copyright (c) 2004 Ryan Alan Dietrich. The Sql::Simple module is free software; you can redistribute it and/or modify it under the same terms as Perl itself with the exception that it cannot be placed on a CD-ROM or similar media for commercial distribution without the prior approval of the author.
1542            
1543             =head1 AUTHOR:
1544            
1545             Sql::Simple by Ryan Alan Dietrich
1546            
1547             Contributions (thanks!) from the following.
1548            
1549             Mark Stosberg, Miguel Manso, Tiago Almeida
1550            
1551             =cut
1552            
1553             1;