File Coverage

blib/lib/FU/SQL.pm
Criterion Covered Total %
statement 96 96 100.0
branch 35 36 97.2
condition 22 25 88.0
subroutine 26 26 100.0
pod 13 13 100.0
total 192 196 97.9


line stmt bran cond sub pod time code
1             package FU::SQL 1.4;
2 2     2   273424 use v5.36;
  2         17  
3 2     2   13 use Exporter 'import';
  2         5  
  2         89  
4 2     2   24 use Carp 'confess';
  2         4  
  2         124  
5 2     2   1048 use experimental 'builtin', 'for_list';
  2         9473  
  2         34  
6              
7             our @EXPORT = qw/
8             P RAW IDENT SQL
9             PARENS INTERSPERSE COMMA
10             AND OR WHERE
11             SET VALUES IN
12             /;
13              
14              
15 95     95   368 sub _obj { bless [@_], 'FU::SQL::val' }
16              
17 52     52 1 80316 sub P :prototype($) ($p) { bless \(my $x = $p), 'FU::SQL::p' }
  52         53  
  52         39  
  52         138  
18 11     11 1 30 sub RAW :prototype($) ($s) { _obj "$s" }
  11         20  
  11         13  
  11         25  
19 16     16 1 17 sub IDENT :prototype($) ($s) { bless \(my $x = "$s"), 'FU::SQL::i' }
  16         20  
  16         13  
  16         70  
20              
21             # These operate on $_ and must be called with &func syntax.
22             # The readonly check can be finicky.
23 243 100   243   838 sub _israw { builtin::created_as_string($_) && Internals::SvREADONLY($_) }
24 243 100   243   245 sub _tosql { &_israw ? "$_" : ref($_) =~ /^FU::SQL::/ ? $_ : P $_ }
    100          
25              
26 62     62 1 2368 sub SQL { _obj map &_tosql, @_ }
27 18     18 1 28 sub PARENS { SQL '(', @_, ')' }
28 22 100   22 1 38 sub INTERSPERSE { my @a = map &_tosql, @_; _obj map $_ > 1 ? ($a[0],$a[$_]) : $a[$_], 1..$#a }
  22         81  
29 12     12 1 19 sub COMMA { INTERSPERSE ',', @_ }
30              
31             sub _conditions {
32             @_ == 1 && ref $_[0] eq 'HASH'
33             ? map PARENS(IDENT $_,
34             !defined $_[0]{$_} ? ('IS NULL') :
35             ref($_[0]{$_}) eq 'FU::SQL::in' ? ($_[0]{$_})
36 8 100 66 8   44 : ('=', $_[0]{$_})
    100          
    100          
37             ), sort keys $_[0]->%*
38             : map PARENS($_), @_
39             }
40              
41 9 100 66 9 1 63 sub AND { !@_ || (@_ == 1 && ref $_[0] eq 'HASH' && keys $_[0]->%* == 0) ? RAW '1=1' : INTERSPERSE 'AND', _conditions @_ }
42 3 100 100 3 1 23 sub OR { !@_ || (@_ == 1 && ref $_[0] eq 'HASH' && keys $_[0]->%* == 0) ? RAW '1=0' : INTERSPERSE 'OR', _conditions @_ }
43 7     7 1 16 sub WHERE { SQL 'WHERE', AND @_ }
44              
45 2     2 1 3 sub SET($h) { SQL 'SET', COMMA map SQL(IDENT $_, '=', $h->{$_}), sort keys %$h }
  2         3  
  2         2  
  2         11  
46              
47             sub VALUES {
48             @_ == 1 && ref $_[0] eq 'HASH'
49 6 100 100 6 1 46 ? SQL '(', COMMA(map IDENT $_, sort keys $_[0]->%*), ') VALUES (', COMMA(map $_[0]{$_}, sort keys $_[0]->%*), ')'
    100 100        
50             : @_ == 1 && ref $_[0] eq 'ARRAY'
51             ? SQL 'VALUES (', COMMA($_[0]->@*), ')'
52             : SQL 'VALUES (', COMMA(@_), ')';
53             }
54              
55 4     4 1 6 sub IN :prototype($) ($a) {
  4         4  
  4         6  
56 4 50       10 confess "Expected arrayref" if ref $a ne 'ARRAY';
57 4         27 bless \$a, 'FU::SQL::in'
58             }
59              
60              
61              
62 94     94   103 sub FU::SQL::val::_compile($self, $opt, $sql, $params) {
  94         86  
  94         77  
  94         99  
  94         75  
  94         60  
63 94         129 for (@$self) {
64 259 100 100     635 $$sql .= ' ' if length $$sql && $$sql !~ /\s$/;
65 259 100       351 if (ref $_) { $_->_compile($opt, $sql, $params); }
  121         161  
66 138         171 else { $$sql .= $_; }
67             }
68             }
69              
70 61     61   61 sub FU::SQL::p::_compile($self, $opt, $sql, $params) {
  61         48  
  61         39  
  61         70  
  61         55  
  61         41  
71 61         78 push @$params, $$self;
72 61 100       1983 $$sql .= $opt->{placeholder_style} eq 'pg' ? '$'.@$params : '?';
73             }
74              
75 16     16   16 sub FU::SQL::i::_compile($self, $opt, $sql, $params) {
  16         14  
  16         15  
  16         11  
  16         15  
  16         12  
76 16 100       38 $$sql .= $opt->{quote_identifier} ? $opt->{quote_identifier}->($$self) : $$self;
77             }
78              
79 4     4   6 sub FU::SQL::in::_compile($self, $opt, $sql, $params) {
  4         4  
  4         5  
  4         3  
  4         5  
  4         6  
80 4 100       8 if ($opt->{in_style} eq 'pg') {
81 2         5 $$sql .= '= ANY(';
82 2         6 FU::SQL::p::_compile($self, $opt, $sql, $params);
83 2         4 $$sql .= ')';
84             } else {
85 2         4 $$sql .= 'IN(';
86 2         8 for my($i,$v) (builtin::indexed @$$self) {
87 7 100       11 $$sql .= ',' if $i;
88 7         9 FU::SQL::p::_compile(\$v, $opt, $sql, $params);
89             }
90 2         4 $$sql .= ')';
91             }
92             }
93              
94 46     46   306 sub FU::SQL::val::compile($self, %opt) {
  46         45  
  46         57  
  46         41  
95 46   66     358 !/^(placeholder_style|in_style|quote_identifier)$/ && confess "Unknown flag: $_" for keys %opt;
96 45   100     157 $opt{placeholder_style} ||= 'dbi';
97 45   100     121 $opt{in_style} ||= 'dbi';
98 45         85 my($sql, @params) = ('');
99 45         115 $self->_compile(\%opt, \$sql, \@params);
100 45         164 ($sql, \@params)
101             }
102              
103             *FU::SQL::p::compile = *FU::SQL::i::compile = *FU::SQL::in::compile = \*FU::SQL::val::compile;
104              
105             1;
106             __END__