File Coverage

blib/lib/Mojo/SQL/Statement.pm
Criterion Covered Total %
statement 83 83 100.0
branch 14 14 100.0
condition 2 3 66.6
subroutine 8 8 100.0
pod 6 6 100.0
total 113 114 99.1


line stmt bran cond sub pod time code
1             package Mojo::SQL::Statement;
2 2     2   13 use Mojo::Base -base, -signatures;
  2         4  
  2         15  
3              
4 2     2   578 use Scalar::Util qw(blessed);
  2         26  
  2         2096  
5              
6             has [qw(parts values)] => sub { [] };
7              
8 46     46 1 209174 sub parse ($self, $text, @values) {
  46         53  
  46         65  
  46         51  
  46         44  
9 46         41 my $escape = "\0";
10 46         103 $escape .= "\0" while index($text, $escape) >= 0;
11 46         79 $text =~ s/\?\?/$escape/g;
12              
13 46         93 my @text_parts = split /\?/, $text, -1;
14 46 100       73 @text_parts = ('') unless @text_parts;
15              
16 46         239 s/\Q$escape\E/?/g for @text_parts;
17              
18 46         51 my (@merged_parts, @merged_values);
19 46         44 my $merge_next = 0;
20 46         98 for my $i (0 .. $#text_parts) {
21 114 100       145 if ($merge_next) {
22 19         24 $merged_parts[-1] .= $text_parts[$i];
23 19         16 $merge_next = 0;
24             }
25 95         135 else { push @merged_parts, $text_parts[$i] }
26              
27 114 100       157 next if $i == $#text_parts;
28              
29 68         73 my $value = $values[$i];
30 68 100 66     138 if (blessed($value) && $value->isa('Mojo::SQL::Statement')) {
31 19         21 my @value_parts = @{$value->parts};
  19         28  
32 19         88 $merged_parts[-1] .= shift @value_parts;
33 19         27 push @merged_parts, @value_parts;
34 19         19 push @merged_values, @{$value->values};
  19         27  
35 19         58 $merge_next = 1;
36             }
37 49         62 else { push @merged_values, $value }
38             }
39              
40 46         68 $self->{parts} = \@merged_parts;
41 46         55 $self->{values} = \@merged_values;
42              
43 46         200 return $self;
44             }
45              
46 4     4 1 2163 sub parse_unsafe ($self, $text, @values) {
  4         5  
  4         4  
  4         5  
  4         4  
47 4         5 my $escape = "\0";
48 4         10 $escape .= "\0" while index($text, $escape) >= 0;
49 4         11 $text =~ s/\?\?/$escape/g;
50              
51 4         10 my @text_parts = split /\?/, $text, -1;
52 4         32 s/\Q$escape\E/?/g for @text_parts;
53              
54 4         5 my @merged;
55 4         17 for my $i (0 .. $#text_parts) {
56 10         15 push @merged, $text_parts[$i];
57 10 100       28 push @merged, $values[$i] if $i < $#text_parts;
58             }
59 4         14 $self->{parts} = [join '', @merged];
60 4         7 $self->{values} = [];
61              
62 4         13 return $self;
63             }
64              
65 25     25 1 33 sub to_query ($self, $options = {}) { {text => $self->to_string($options), values => $self->values} }
  25         25  
  25         25  
  25         20  
  25         39  
66              
67 12     12 1 11 sub to_array ($self, $options = {}) { [$self->to_string($options), @{$self->values}] }
  12         10  
  12         12  
  12         13  
  12         19  
  12         21  
68              
69 6     6 1 7 sub to_list ($self, $options = {}) { @{$self->to_array($options)} }
  6         6  
  6         8  
  6         5  
  6         9  
  6         10  
70              
71 37     37 1 31 sub to_string ($self, $options = {}) {
  37         34  
  37         36  
  37         32  
72 37         36 my @query;
73 37         41 my $placeholder = $options->{placeholder};
74 37         55 my $parts = $self->parts;
75 37         135 for my $i (1 .. scalar @$parts) {
76 98         129 push @query, $parts->[$i - 1];
77 98 100       172 push @query, defined $placeholder ? $placeholder : "\$$i" if defined $parts->[$i];
    100          
78             }
79              
80 37         112 return join '', @query;
81             }
82              
83             1;
84              
85             =encoding utf8
86              
87             =head1 NAME
88              
89             Mojo::SQL::Statement - SQL statement container
90              
91             =head1 SYNOPSIS
92              
93             use Mojo::SQL::Statement;
94              
95             my $stmt = Mojo::SQL::Statement->new->parse('SELECT * FROM users WHERE name = ?', 'sebastian');
96             my $query = $stmt->to_query;
97              
98             =head1 DESCRIPTION
99              
100             L is a container for an SQL statement and its bind values. Statements are composable by passing
101             one as a value to another, in which case its parts and values are spliced in recursively.
102              
103             =head1 ATTRIBUTES
104              
105             L implements the following attributes.
106              
107             =head2 parts
108              
109             my $parts = $stmt->parts;
110             $stmt = $stmt->parts(['SELECT * FROM users WHERE name = ', '']);
111              
112             The literal SQL fragments around each placeholder, as an array reference. There is always one more fragment than
113             placeholder.
114              
115             =head2 values
116              
117             my $values = $stmt->values;
118             $stmt = $stmt->values(['sebastian']);
119              
120             The bind values for each placeholder, as an array reference.
121              
122             =head1 METHODS
123              
124             L inherits all methods from L and implements the following new ones.
125              
126             =head2 parse
127              
128             $stmt = $stmt->parse('SELECT * FROM users WHERE name = ?', 'sebastian');
129              
130             Parse an SQL string with C placeholders and bind values into L and L. L
131             values are spliced in recursively, allowing partial statements to be composed. Literal question marks can be escaped
132             with C.
133              
134             =head2 parse_unsafe
135              
136             $stmt = $stmt->parse_unsafe("AND role = 'admin'");
137             $stmt = $stmt->parse_unsafe('AND ?', "role = 'admin'");
138              
139             Parse an SQL string where every C slot is replaced literally by the corresponding value. The result has no
140             placeholders or bind values; use with care, and make sure to escape values yourself with the appropriate escaping
141             functions for your database. Literal question marks can be escaped with C.
142              
143             =head2 to_array
144              
145             my $array = $stmt->to_array;
146             my $array = $stmt->to_array({placeholder => '?'});
147              
148             Render the statement to an array reference containing the SQL text and bind values, ready to be passed to a database
149             driver. Accepts the same options as L.
150              
151             =head2 to_list
152              
153             my @list = $stmt->to_list;
154             my @list = $stmt->to_list({placeholder => '?'});
155              
156             Same as L but returns a list.
157              
158             =head2 to_query
159              
160             my $query = $stmt->to_query;
161             my $query = $stmt->to_query({placeholder => '?'});
162              
163             Render the statement to a query hash reference with C and C keys, ready to be passed to a database
164             driver.
165              
166             These options are currently available:
167              
168             =over 2
169              
170             =item placeholder
171              
172             placeholder => '?'
173              
174             Placeholder character to use, defaults to numbered placeholders like C<$1> and C<$2>.
175              
176             =back
177              
178             =head2 to_string
179              
180             my $string = $stmt->to_string;
181             my $string = $stmt->to_string({placeholder => '?'});
182              
183             Render just the SQL string portion of the statement. Accepts the same options as L.
184              
185             =head1 SEE ALSO
186              
187             L, L, L.
188              
189             =cut