File Coverage

blib/lib/SQL/Abstract/Pg.pm
Criterion Covered Total %
statement 105 105 100.0
branch 47 48 97.9
condition 6 8 75.0
subroutine 24 24 100.0
pod 2 2 100.0
total 184 187 98.4


line stmt bran cond sub pod time code
1             package SQL::Abstract::Pg;
2              
3 1     1   69415 use strict;
  1         12  
  1         30  
4 1     1   6 use warnings;
  1         2  
  1         23  
5 1     1   5 use utf8;
  1         2  
  1         5  
6              
7 1     1   544 use parent 'SQL::Abstract';
  1         316  
  1         6  
8              
9             our $VERSION = '1.0';
10              
11 1     1   22358 BEGIN { *puke = \&SQL::Abstract::puke }
12              
13             sub insert {
14 9     9 1 34883 my ($self, $table, $data, $options) = @_;
15 9 100 100     67 local @{$options}{qw(returning _pg_returning)} = (1, 1) if exists $options->{on_conflict} && !$options->{returning};
  7         23  
16 9         36 return $self->SUPER::insert($table, $data, $options);
17             }
18              
19             sub new {
20 1     1 1 100 my $self = shift->SUPER::new(@_);
21              
22             # -json op
23 1         11 push @{$self->{unary_ops}}, {
24             regex => qr/^json$/,
25 2     2   4388 handler => sub { '?', {json => $_[2]} }
26 1         311 };
27              
28 1         4 return $self;
29             }
30              
31             sub _insert_returning {
32 8     8   7759 my ($self, $options) = @_;
33              
34 8 100       53 delete $options->{returning} if $options->{_pg_returning};
35              
36             # ON CONFLICT
37 8         20 my $sql = '';
38 8         13 my @bind;
39 8 50       19 if (exists $options->{on_conflict}) {
40 8         13 my $conflict = $options->{on_conflict};
41 8         14 my ($conflict_sql, @conflict_bind);
42             $self->_SWITCH_refkind(
43             $conflict => {
44             ARRAYREF => sub {
45 3     3   101 my ($target, $set) = @$conflict;
46 3 100       13 puke 'on_conflict value must be in the form [$target, \%set]' unless ref $set eq 'HASH';
47 2 100       5 $target = [$target] unless ref $target eq 'ARRAY';
48              
49 2         4 $conflict_sql = '(' . join(', ', map { $self->_quote($_) } @$target) . ')';
  3         29  
50 2         66 $conflict_sql .= $self->_sqlcase(' do update set ');
51 2         16 my ($set_sql, @set_bind) = $self->_update_set_values($set);
52 2         1267 $conflict_sql .= $set_sql;
53 2         7 push @conflict_bind, @set_bind;
54             },
55 1     1   39 ARRAYREFREF => sub { ($conflict_sql, @conflict_bind) = @$$conflict },
56 2     2   83 SCALARREF => sub { $conflict_sql = $$conflict },
57 1     1   24 UNDEF => sub { $conflict_sql = $self->_sqlcase('do nothing') }
58             }
59 8         100 );
60 6         70 $sql .= $self->_sqlcase(' on conflict ') . $conflict_sql;
61 6         39 push @bind, @conflict_bind;
62             }
63              
64 6 100       21 $sql .= $self->SUPER::_insert_returning($options) if $options->{returning};
65              
66 6         287 return $sql, @bind;
67             }
68              
69             sub _order_by {
70 13     13   3399 my ($self, $options) = @_;
71              
72             # Legacy
73 13 100 66     58 return $self->SUPER::_order_by($options) if ref $options ne 'HASH' or grep {/^-(?:desc|asc)/i} keys %$options;
  16         82  
74              
75             # GROUP BY
76 11         24 my $sql = '';
77 11         19 my @bind;
78 11 100       33 if (defined(my $group = $options->{group_by})) {
79 5         8 my $group_sql;
80             $self->_SWITCH_refkind(
81             $group => {
82             ARRAYREF => sub {
83 3     3   96 $group_sql = join ', ', map { $self->_quote($_) } @$group;
  4         33  
84             },
85 1     1   41 SCALARREF => sub { $group_sql = $$group }
86             }
87 5         42 );
88 4         89 $sql .= $self->_sqlcase(' group by ') . $group_sql;
89             }
90              
91             # HAVING
92 10 100       38 if (defined(my $having = $options->{having})) {
93 2         14 my ($having_sql, @having_bind) = $self->_recurse_where($having);
94 2         956 $sql .= $self->_sqlcase(' having ') . $having_sql;
95 2         12 push @bind, @having_bind;
96             }
97              
98             # ORDER BY
99 10 100       31 $sql .= $self->_order_by($options->{order_by}) if defined $options->{order_by};
100              
101             # LIMIT
102 10 100       417 if (defined $options->{limit}) {
103 1         4 $sql .= $self->_sqlcase(' limit ') . '?';
104 1         7 push @bind, $options->{limit};
105             }
106              
107             # OFFSET
108 10 100       23 if (defined $options->{offset}) {
109 1         4 $sql .= $self->_sqlcase(' offset ') . '?';
110 1         6 push @bind, $options->{offset};
111             }
112              
113             # FOR
114 10 100       24 if (defined(my $for = $options->{for})) {
115 4         7 my $for_sql;
116             $self->_SWITCH_refkind(
117             $for => {
118             SCALAR => sub {
119 2 100   2   60 puke qq{for value "$for" is not allowed} unless $for eq 'update';
120 1         4 $for_sql = $self->_sqlcase('UPDATE');
121             },
122 1     1   33 SCALARREF => sub { $for_sql .= $$for }
123             }
124 4         31 );
125 2         16 $sql .= $self->_sqlcase(' for ') . $for_sql;
126             }
127              
128 8         35 return $sql, @bind;
129             }
130              
131             sub _select_fields {
132 27     27   7912 my ($self, $fields) = @_;
133              
134 27 100       109 return $fields unless ref $fields eq 'ARRAY';
135              
136 4         6 my (@fields, @bind);
137 4         10 for my $field (@$fields) {
138             $self->_SWITCH_refkind(
139             $field => {
140             ARRAYREF => sub {
141 2 100   2   67 puke 'field alias must be in the form [$name => $alias]' if @$field < 2;
142 1         4 push @fields, $self->_quote($field->[0]) . $self->_sqlcase(' as ') . $self->_quote($field->[1]);
143             },
144             ARRAYREFREF => sub {
145 1     1   36 push @fields, shift @$$field;
146 1         8 push @bind, @$$field;
147             },
148 1     1   35 SCALARREF => sub { push @fields, $$field },
149 6     6   171 FALLBACK => sub { push @fields, $self->_quote($field) }
150             }
151 10         215 );
152             }
153              
154 3         113 return join(', ', @fields), @bind;
155             }
156              
157             sub _table {
158 28     28   131186 my ($self, $table) = @_;
159              
160 28 100       134 return $self->SUPER::_table($table) unless ref $table eq 'ARRAY';
161              
162 10         18 my (@table, @join);
163 10         23 for my $t (@$table) {
164 22 100       50 if (ref $t eq 'ARRAY') { push @join, $t }
  10         20  
165 12         27 else { push @table, $t }
166             }
167              
168 10         37 $table = $self->SUPER::_table(\@table);
169 10   50     2934 my $sep = $self->{name_sep} // '';
170 10         27 for my $join (@join) {
171 10 100       80 puke 'join must be in the form [$table, $fk => $pk]' if @$join < 3;
172 9 100       42 my ($type, $name, $fk, $pk, @morekeys) = @$join % 2 == 0 ? @$join : ('', @$join);
173 9 100       46 $table
174             .= $self->_sqlcase($type =~ /^-(.+)$/ ? " $1 join " : ' join ')
175             . $self->_quote($name)
176             . $self->_sqlcase(' on ') . '(';
177 9         271 do {
178 12 100       207 $table
    100          
    100          
179             .= $self->_quote(index($fk, $sep) > 0 ? $fk : "$name.$fk") . ' = '
180             . $self->_quote(index($pk, $sep) > 0 ? $pk : "$table[0].$pk")
181             . (@morekeys ? $self->_sqlcase(' and ') : ')');
182             } while ($fk, $pk, @morekeys) = @morekeys;
183             }
184              
185 9         444 return $table;
186             }
187              
188             1;
189              
190             =encoding utf8
191              
192             =head1 NAME
193              
194             SQL::Abstract::Pg - PostgreSQL features for SQL::Abstract
195              
196             =head1 SYNOPSIS
197              
198             use SQL::Abstract::Pg;
199              
200             my $abstract = SQL::Abstract::Pg->new;
201             say $abstract->select('some_table');
202              
203             =head1 DESCRIPTION
204              
205             L extends L with a few PostgreSQL features used by L.
206              
207             =head2 JSON
208              
209             In many places (as supported by L) you can use the C<-json> unary op to encode JSON from Perl data
210             structures.
211              
212             # "UPDATE some_table SET foo = '[1,2,3]' WHERE bar = 23"
213             $abstract->update('some_table', {foo => {-json => [1, 2, 3]}}, {bar => 23});
214              
215             # "SELECT * FROM some_table WHERE foo = '[1,2,3]'"
216             $abstract->select('some_table', '*', {foo => {'=' => {-json => [1, 2, 3]}}});
217              
218             =head1 INSERT
219              
220             $abstract->insert($table, \@values || \%fieldvals, \%options);
221              
222             =head2 ON CONFLICT
223              
224             The C option can be used to generate C queries with C clauses. So far, C to
225             pass C, array references to pass C with conflict targets and a C expression, scalar
226             references to pass literal SQL and array reference references to pass literal SQL with bind values are supported.
227              
228             # "INSERT INTO t (a) VALUES ('b') ON CONFLICT DO NOTHING"
229             $abstract->insert('t', {a => 'b'}, {on_conflict => undef});
230              
231             # "INSERT INTO t (a) VALUES ('b') ON CONFLICT DO NOTHING"
232             $abstract->insert('t', {a => 'b'}, {on_conflict => \'do nothing'});
233              
234             This includes operations commonly referred to as C.
235              
236             # "INSERT INTO t (a) VALUES ('b') ON CONFLICT (a) DO UPDATE SET a = 'c'"
237             $abstract->insert('t', {a => 'b'}, {on_conflict => [a => {a => 'c'}]});
238              
239             # "INSERT INTO t (a, b) VALUES ('c', 'd') ON CONFLICT (a, b) DO UPDATE SET a = 'e'"
240             $abstract->insert('t', {a => 'c', b => 'd'}, {on_conflict => [['a', 'b'] => {a => 'e'}]});
241              
242             # "INSERT INTO t (a) VALUES ('b') ON CONFLICT (a) DO UPDATE SET a = 'c'"
243             $abstract->insert('t', {a => 'b'}, {on_conflict => \['(a) do update set a = ?', 'c']});
244              
245             =head1 SELECT
246              
247             $abstract->select($source, $fields, $where, $order);
248             $abstract->select($source, $fields, $where, \%options);
249              
250             =head2 AS
251              
252             The C<$fields> argument now also accepts array references containing array references with field names and aliases, as
253             well as array references containing scalar references to pass literal SQL and array reference references to pass
254             literal SQL with bind values.
255              
256             # "SELECT foo AS bar FROM some_table"
257             $abstract->select('some_table', [[foo => 'bar']]);
258              
259             # "SELECT foo, bar AS baz, yada FROM some_table"
260             $abstract->select('some_table', ['foo', [bar => 'baz'], 'yada']);
261              
262             # "SELECT EXTRACT(EPOCH FROM foo) AS foo, bar FROM some_table"
263             $abstract->select('some_table', [\'extract(epoch from foo) AS foo', 'bar']);
264              
265             # "SELECT 'test' AS foo, bar FROM some_table"
266             $abstract->select('some_table', [\['? AS foo', 'test'], 'bar']);
267              
268             =head2 JOIN
269              
270             The C<$source> argument now also accepts array references containing not only table names, but also array references
271             with tables to generate C clauses for.
272              
273             # "SELECT * FROM foo JOIN bar ON (bar.foo_id = foo.id)"
274             $abstract->select(['foo', ['bar', foo_id => 'id']]);
275              
276             # "SELECT * FROM foo JOIN bar ON (foo.id = bar.foo_id)"
277             $abstract->select(['foo', ['bar', 'foo.id' => 'bar.foo_id']]);
278              
279             # "SELECT * FROM a JOIN b ON (b.a_id = a.id) JOIN c ON (c.a_id = a.id)"
280             $abstract->select(['a', ['b', a_id => 'id'], ['c', a_id => 'id']]);
281              
282             # "SELECT * FROM foo LEFT JOIN bar ON (bar.foo_id = foo.id)"
283             $abstract->select(['foo', [-left => 'bar', foo_id => 'id']]);
284              
285             # "SELECT * FROM a LEFT JOIN b ON (b.a_id = a.id AND b.a_id2 = a.id2)"
286             $abstract->select(['a', [-left => 'b', a_id => 'id', a_id2 => 'id2']]);
287              
288             =head2 ORDER BY
289              
290             Alternatively to the C<$order> argument accepted by L you can now also pass a hash reference with
291             various options. This includes C, which takes the same values as the C<$order> argument.
292              
293             # "SELECT * FROM some_table ORDER BY foo DESC"
294             $abstract->select('some_table', '*', undef, {order_by => {-desc => 'foo'}});
295              
296             =head2 LIMIT/OFFSET
297              
298             The C and C options can be used to generate C
299              
300             # "SELECT * FROM some_table LIMIT 10"
301             $abstract->select('some_table', '*', undef, {limit => 10});
302              
303             # "SELECT * FROM some_table OFFSET 5"
304             $abstract->select('some_table', '*', undef, {offset => 5});
305              
306             # "SELECT * FROM some_table LIMIT 10 OFFSET 5"
307             $abstract->select('some_table', '*', undef, {limit => 10, offset => 5});
308              
309             =head2 GROUP BY
310              
311             The C option can be used to generate C
312             pass a list of fields and scalar references to pass literal SQL are supported.
313              
314             # "SELECT * FROM some_table GROUP BY foo, bar"
315             $abstract->select('some_table', '*', undef, {group_by => ['foo', 'bar']});
316              
317             # "SELECT * FROM some_table GROUP BY foo, bar"
318             $abstract->select('some_table', '*', undef, {group_by => \'foo, bar'});
319              
320             =head2 HAVING
321              
322             The C option can be used to generate C
323             the C<$where> argument.
324              
325             # "SELECT * FROM t GROUP BY a HAVING b = 'c'"
326             $abstract->select('t', '*', undef, {group_by => ['a'], having => {b => 'c'}});
327              
328             =head2 FOR
329              
330             The C option can be used to generate C
331             pass C and scalar references to pass literal SQL are supported.
332              
333             # "SELECT * FROM some_table FOR UPDATE"
334             $abstract->select('some_table', '*', undef, {for => 'update'});
335              
336             # "SELECT * FROM some_table FOR UPDATE SKIP LOCKED"
337             $abstract->select('some_table', '*', undef, {for => \'update skip locked'});
338              
339             =head1 METHODS
340              
341             L inherits all methods from L.
342              
343             =head1 AUTHOR
344              
345             Sebastian Riedel, C.
346              
347             =head1 COPYRIGHT AND LICENSE
348              
349             Copyright (C) 2014-2021, Sebastian Riedel and others.
350              
351             This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version
352             2.0.
353              
354             =head1 SEE ALSO
355              
356             L, L, L.
357              
358             =cut