File Coverage

blib/lib/SQL/Tiny.pm
Criterion Covered Total %
statement 117 119 98.3
branch 25 26 96.1
condition 2 2 100.0
subroutine 10 10 100.0
pod 4 4 100.0
total 158 161 98.1


line stmt bran cond sub pod time code
1             package SQL::Tiny;
2              
3 5     5   340725 use 5.010001;
  5         56  
4 5     5   24 use strict;
  5         9  
  5         164  
5 5     5   42 use warnings;
  5         8  
  5         330  
6              
7             =head1 NAME
8              
9             SQL::Tiny - A very simple SQL-building library
10              
11             =head1 VERSION
12              
13             Version 0.04
14              
15             =cut
16              
17             our $VERSION = '0.04';
18              
19 5     5   2238 use parent 'Exporter';
  5         1450  
  5         27  
20              
21             our @EXPORT_OK = qw(
22             sql_select
23             sql_insert
24             sql_update
25             sql_delete
26             );
27              
28             our %EXPORT_TAGS = (
29             all => [@EXPORT_OK],
30             );
31              
32              
33             =head1 SYNOPSIS
34              
35             my ($sql,$binds) = sql_select( 'users', [ 'name', 'status' ], { status => [ 'Deleted', 'Inactive' ] }, { order_by => 'name' } );
36              
37             my ($sql,$binds) = sql_select( 'users', [ 'COUNT(*)' ], { status => [ 'Deleted', 'Inactive' ] }, { group_by => 'status' } );
38              
39             my ($sql,$binds) = sql_insert( 'users', { name => 'Dave', status => 'Active' } );
40              
41             my ($sql,$binds) = sql_update( 'users', { status => 'Inactive' }, { password => undef } );
42              
43             my ($sql,$binds) = sql_delete( 'users', { status => 'Inactive' } );
44              
45             =head1 DOCUMENTATION
46              
47             A very simple SQL-building library. It's not for all your SQL needs,
48             only the very simple ones.
49              
50             It doesn't handle JOINs. It doesn't handle subselects. It's only for simple SQL.
51              
52             In my test suites, I have a lot of ad hoc SQL queries, and it drives me
53             nuts to have so much SQL code lying around. SQL::Tiny is for generating
54             SQL code for simple cases.
55              
56             I'd far rather have:
57              
58             my ($sql,$binds) = sql_insert(
59             'users',
60             {
61             name => 'Dave',
62             salary => 50000,
63             status => 'Active',
64             dateadded => \'SYSDATE()',
65             qty => \[ 'ROUND(?)', 14.5 ],
66             }
67             );
68              
69             than hand-coding:
70              
71             my $sql = 'INSERT INTO users (name,salary,status,dateadded,qty) VALUES (:name,:status,:salary,SYSDATE(),ROUND(:qty))';
72             my $binds = {
73             ':name' => 'Dave',
74             ':salary' => 50000,
75             ':status' => 'Active',
76             ':dateadded' => \'SYSDATE()',
77             ':qty' => 14.5,
78             };
79              
80             or even the positional:
81              
82             my $sql = 'INSERT INTO users (name,salary,status,dateadded,qty) VALUES (?,?,?,SYSDATE(),ROUND(?))';
83             my $binds = [ 'Dave', 50000, 'Active', 14.5 ];
84              
85             The trade-off for that brevity of code is that SQL::Tiny has to make new
86             SQL and binds from the input every time. You can't cache the SQL that
87             comes back from SQL::Tiny because the placeholders could vary depending
88             on what the input data is. Therefore, you don't want to use SQL::Tiny
89             where speed is essential.
90              
91             The other trade-off is that SQL::Tiny handles only very simple code.
92             It won't handle JOINs of any kind.
93              
94             SQL::Tiny isn't meant for all of your SQL needs, only the simple ones
95             that you do over and over.
96              
97             =head1 EXPORT
98              
99             All subs can be exported, but none are by default. C<:all> exports all subs.
100              
101             =head1 SUBROUTINES/METHODS
102              
103             =head2 sql_select( $table, \@columns, \%where [, \%other ] )
104              
105             Creates simple SELECTs and binds.
106              
107             The C<%other> can contain C and C.
108              
109             Calling:
110              
111             my ($sql,$binds) = sql_select(
112             'users',
113             [qw( userid name )],
114             { status => 'X' ],
115             { order_by => 'name' },
116             );
117              
118             returns:
119              
120             $sql = 'SELECT userid,name FROM users WHERE status=? ORDER BY name';
121             $binds = [ 'X' ];
122              
123             =cut
124              
125             sub sql_select {
126 7     7 1 22803 my $table = shift;
127 7         11 my $columns = shift;
128 7         10 my $where = shift;
129 7   100     40 my $other = shift // {};
130              
131             my @parts = (
132 7         11 'SELECT ' . join( ',', @{$columns} ),
  7         30  
133             "FROM $table",
134             );
135              
136 7         11 my @binds;
137              
138 7         22 _build_where_section( \@parts, $where, \@binds );
139 7         24 _build_by_section( \@parts, 'GROUP BY', $other->{group_by} );
140 7         21 _build_by_section( \@parts, 'ORDER BY', $other->{order_by} );
141              
142 7         18 my $sql = join( ' ', @parts );
143              
144 7         26 return ( $sql, \@binds );
145             }
146              
147             =head2 sql_insert( $table, \%values )
148              
149             Creates simple INSERTs and binds.
150              
151             Calling:
152              
153             my ($sql,$binds) = sql_insert(
154             'users',
155             {
156             serialno => '12345',
157             name => 'Dave',
158             rank => 'Sergeant',
159             height => undef,
160             date_added => \'SYSDATE()',
161             }
162             );
163              
164             returns:
165              
166             $sql = 'INSERT INTO users (date_added,height,name,rank,serialno) VALUES (SYSDATE(),NULL,?,?,?)';
167             $binds = [ 'Dave', 'Sergeant', 12345 ]
168              
169             =cut
170              
171             sub sql_insert {
172 1     1 1 1997 my $table = shift;
173 1         3 my $values = shift;
174              
175 1         5 my @parts = (
176             "INSERT INTO $table"
177             );
178              
179 1         3 my @values;
180             my @binds;
181              
182 1         3 my @columns = sort keys %{$values};
  1         10  
183 1         3 for my $key ( @columns ) {
184 6         10 my $value = $values->{$key};
185              
186 6 100       21 if ( !defined($value) ) {
    100          
    100          
187 1         3 push @values, 'NULL';
188             }
189             elsif ( ref($value) eq 'SCALAR' ) {
190 1         2 push @values, ${$value};
  1         3  
191             }
192             elsif ( ref($value) eq 'REF' ) {
193 1         2 my $deepval = ${$value};
  1         12  
194              
195 1         4 my ($literal,$bind) = @{$deepval};
  1         4  
196 1         3 push @values, $literal;
197 1         3 push @binds, $bind;
198             }
199             else {
200 3         5 push @values, '?';
201 3         5 push @binds, $value;
202             }
203             }
204              
205 1         6 push @parts, '(' . join( ',', @columns ) . ')';
206 1         28 push @parts, 'VALUES (' . join( ',', @values ) . ')';
207 1         5 my $sql = join( ' ', @parts );
208              
209 1         7 return ( $sql, \@binds );
210             }
211              
212              
213             =head2 sql_update( $table, \%values, \%where )
214              
215             Creates simple UPDATE calls and binds.
216              
217             Calling:
218              
219             my ($sql,$binds) = sql_update(
220             'users',
221             {
222             status => 'X',
223             lockdate => undef,
224             },
225             {
226             orderdate => \'SYSDATE()',
227             },
228             );
229              
230             returns:
231              
232             $sql = 'UPDATE users SET lockdate=NULL, status=? WHERE orderdate=SYSDATE()'
233             $binds = [ 'X' ]
234              
235             =cut
236              
237             sub sql_update {
238 3     3 1 9481 my $table = shift;
239 3         6 my $values = shift;
240 3         5 my $where = shift;
241              
242 3         9 my @parts = (
243             "UPDATE $table"
244             );
245              
246 3         5 my @columns;
247             my @binds;
248              
249 3         5 for my $key ( sort keys %{$values} ) {
  3         15  
250 5         11 my $value = $values->{$key};
251              
252 5 100       18 if ( !defined($value) ) {
    50          
    100          
253 1         3 push @columns, "$key=NULL";
254             }
255             elsif ( ref($value) eq 'SCALAR' ) {
256 0         0 push @columns, "$key=${$value}";
  0         0  
257             }
258             elsif ( ref($value) eq 'REF' ) {
259 1         2 my $deepval = ${$value};
  1         2  
260              
261 1         11 my ($literal,$bind) = @{$deepval};
  1         5  
262 1         4 push @columns, "$key=$literal";
263 1         3 push @binds, $bind;
264             }
265             else {
266 3         22 push @columns, "$key=?";
267 3         8 push @binds, $value;
268             }
269             }
270 3         11 push @parts, 'SET ' . join( ', ', @columns );
271              
272 3         9 _build_where_section( \@parts, $where, \@binds );
273              
274 3         7 my $sql = join( ' ', @parts );
275              
276 3         13 return ( $sql, \@binds );
277             }
278              
279              
280             =head2 sql_delete( $table, \%where )
281              
282             Creates simple DELETE calls and binds.
283              
284             Calling:
285              
286             my ($sql,$binds) = sql_delete(
287             'users',
288             {
289             serialno => 12345,
290             height => undef,
291             date_added => \'SYSDATE()',
292             status => [qw( X Y Z )],
293             },
294             );
295              
296             returns:
297              
298             $sql = 'DELETE FROM users WHERE date_added = SYSDATE() AND height IS NULL AND serialno = ? AND status IN (?,?,?)'
299             $binds = [ 12345, 'X', 'Y', 'Z' ]
300              
301             =cut
302              
303             sub sql_delete {
304 3     3 1 9249 my $table = shift;
305 3         6 my $where = shift;
306              
307 3         9 my @parts = (
308             "DELETE FROM $table"
309             );
310              
311 3         5 my @binds;
312              
313 3         10 _build_where_section( \@parts, $where, \@binds );
314              
315 3         7 my $sql = join( ' ', @parts );
316              
317 3         12 return ( $sql, \@binds );
318             }
319              
320              
321             sub _build_where_section {
322 13     13   20 my $parts = shift;
323 13         38 my $where = shift;
324 13         32 my $binds = shift;
325              
326 13         18 my @conditions;
327 13         22 for my $key ( sort keys %{$where} ) {
  13         58  
328 19         31 my $value = $where->{$key};
329 19 100       96 if ( !defined($value) ) {
    100          
    100          
    100          
330 2         5 push @conditions, "$key IS NULL";
331             }
332             elsif ( ref($value) eq 'ARRAY' ) {
333 8         20 push @conditions, "$key IN (" . join( ',', ('?') x @{$value} ) . ')';
  8         29  
334 8         17 push @{$binds}, @{$value};
  8         11  
  8         21  
335             }
336             elsif ( ref($value) eq 'SCALAR' ) {
337 3         11 push @conditions, "$key=${$value}";
  3         9  
338             }
339             elsif ( ref($value) eq 'REF' ) {
340 3         6 my $deepval = ${$value};
  3         8  
341              
342 3         6 my ($literal,$bind) = @{$deepval};
  3         8  
343 3         11 push @conditions, "$key=$literal";
344 3         23 push @{$binds}, $bind;
  3         10  
345             }
346             else {
347 3         29 push @conditions, "$key=?";
348 3         6 push @{$binds}, $value;
  3         10  
349             }
350             }
351              
352 13 100       36 if ( @conditions ) {
353 9         13 push @{$parts}, 'WHERE ' . join( ' AND ', @conditions );
  9         28  
354             }
355              
356 13         28 return;
357             }
358              
359              
360             sub _build_by_section {
361 14     14   19 my $parts = shift;
362 14         22 my $section = shift;
363 14         20 my $columns = shift;
364              
365 14 100       27 if ( $columns ) {
366 5 100       13 if ( ref($columns) eq 'ARRAY' ) {
367 3         5 push @{$parts}, $section . ' ' . join( ',', @{$columns} );
  3         8  
  3         8  
368             }
369             else {
370 2         3 push @{$parts}, "$section $columns";
  2         5  
371             }
372             }
373              
374 14         22 return;
375             }
376              
377              
378             =head1 AUTHOR
379              
380             Andy Lester, C<< >>
381              
382             =head1 BUGS
383              
384             Please report any bugs or feature requests to
385             L, or email me directly.
386              
387             =head1 SUPPORT
388              
389             You can find documentation for this module with the perldoc command.
390              
391             perldoc SQL::Tiny
392              
393             You can also look for information at:
394              
395             =over 4
396              
397             =item * MetaCPAN
398              
399             L
400              
401             =item * GitHub issue tracker
402              
403             L
404              
405             =back
406              
407             =head1 ACKNOWLEDGEMENTS
408              
409             Thanks to the following folks for their contributions:
410             Mohammad S Anwar,
411             Tim Heaney.
412              
413             =head1 LICENSE AND COPYRIGHT
414              
415             Copyright 2019 Andy Lester.
416              
417             This program is free software; you can redistribute it and/or modify it
418             under the terms of the the Artistic License (2.0). You may obtain a
419             copy of the full license at:
420              
421             L
422              
423             =cut
424              
425             1; # End of SQL::Tiny