File Coverage

blib/lib/Pg/SQL/PrettyPrinter/Node/InsertStmt.pm
Criterion Covered Total %
statement 161 162 99.3
branch 43 48 89.5
condition 2 2 100.0
subroutine 20 20 100.0
pod 6 6 100.0
total 232 238 97.4


line stmt bran cond sub pod time code
1             package Pg::SQL::PrettyPrinter::Node::InsertStmt;
2              
3             # UTF8 boilerplace, per http://stackoverflow.com/questions/6162484/why-does-modern-perl-avoid-utf-8-by-default/
4 3     3   2125 use v5.26;
  3         21  
5 3     3   21 use strict;
  3         7  
  3         68  
6 3     3   13 use warnings;
  3         6  
  3         84  
7 3     3   14 use warnings qw( FATAL utf8 );
  3         6  
  3         104  
8 3     3   16 use utf8;
  3         6  
  3         17  
9 3     3   103 use open qw( :std :utf8 );
  3         7  
  3         26  
10 3     3   459 use Unicode::Normalize qw( NFC );
  3         7  
  3         213  
11 3     3   87 use Unicode::Collate;
  3         10  
  3         147  
12 3     3   22 use Encode qw( decode );
  3         7  
  3         208  
13              
14             if ( grep /\P{ASCII}/ => @ARGV ) {
15             @ARGV = map { decode( 'UTF-8', $_ ) } @ARGV;
16             }
17              
18             # If there is __DATA__,then uncomment next line:
19             # binmode( DATA, ':encoding(UTF-8)' );
20             # UTF8 boilerplace, per http://stackoverflow.com/questions/6162484/why-does-modern-perl-avoid-utf-8-by-default/
21              
22             # Useful common code
23 3     3   729 use autodie;
  3         8  
  3         20  
24 3     3   16675 use Carp qw( carp croak confess cluck );
  3         8  
  3         247  
25 3     3   22 use English qw( -no_match_vars );
  3         9  
  3         27  
26 3     3   1197 use Data::Dumper qw( Dumper );
  3         11  
  3         670  
27              
28             # give a full stack dump on any untrapped exceptions
29             local $SIG{ __DIE__ } = sub {
30             confess "Uncaught exception: @_" unless $^S;
31             };
32              
33             # now promote run-time warnings into stackdumped exceptions
34             # *unless* we're in an try block, in which
35             # case just generate a clucking stackdump instead
36             local $SIG{ __WARN__ } = sub {
37             if ( $^S ) { cluck "Trapped warning: @_" }
38             else { confess "Deadly warning: @_" }
39             };
40              
41             # Useful common code
42              
43 3     3   23 use parent qw( Pg::SQL::PrettyPrinter::Node );
  3         6  
  3         18  
44              
45             sub new {
46 16     16 1 559 my $class = shift;
47 16         74 my $self = $class->SUPER::new( @_ );
48 16         41 bless $self, $class;
49              
50 16         88 $self->objectify(
51             qw( selectStmt cols returningList ),
52             [ 'withClause', 'ctes' ],
53             );
54              
55 16 100       60 if ( exists $self->{ 'onConflictClause' } ) {
56 7 50       48 croak( 'Unsupported conflict action: ' . $self->{ 'onConflictClause' }->{ 'action' } ) unless $self->{ 'onConflictClause' }->{ 'action' } =~ m{\AONCONFLICT_(?:NOTHING|UPDATE)\z};
57 7         41 $self->objectify(
58             [ qw{ onConflictClause infer indexElems } ],
59             [ qw{ onConflictClause targetList } ],
60             [ qw{ onConflictClause whereClause } ],
61             );
62 7         34 $self->build_set_array();
63             }
64              
65 16         44 return $self;
66             }
67              
68             sub relname {
69 32     32 1 59 my $self = shift;
70 32 100       93 if ( !$self->{ '_relname' } ) {
71 16         36 my $R = $self->{ 'relation' };
72 17         83 my @elements = map { $self->quote_ident( $R->{ $_ } ) }
73 16         32 grep { exists $R->{ $_ } } qw{ catalogname schemaname relname };
  48         118  
74 16         65 $self->{ '_relname' } = join( '.', @elements );
75             }
76 32         100 return $self->{ '_relname' };
77             }
78              
79             sub as_text {
80 16     16 1 79 my $self = shift;
81 16         33 my @elements = ();
82 16         36 push @elements, 'INSERT INTO';
83 16         46 push @elements, $self->relname;
84 16 100       48 if ( exists $self->{ 'cols' } ) {
85 12         25 push @elements, '(';
86 12         28 push @elements, join( ', ', map { $_->as_text } @{ $self->{ 'cols' } } );
  40         98  
  12         30  
87 12         33 push @elements, ')';
88             }
89 16         70 push @elements, $self->{ 'selectStmt' }->as_text;
90 16         44 my $prefix = '';
91 16 100       43 if ( exists $self->{ 'withClause' } ) {
92 1         114 $prefix = 'WITH ';
93 1 50       7 $prefix .= 'RECURSIVE ' if $self->{ 'withClause' }->{ 'recursive' };
94 1         4 $prefix .= join( ', ', map { $_->as_text } @{ $self->{ 'withClause' }->{ 'ctes' } } ) . ' ';
  1         9  
  1         78  
95             }
96 16         61 push @elements, $self->conflict_handling();
97 16 100       45 if ( exists $self->{ 'returningList' } ) {
98 3         12 push @elements, 'RETURNING';
99 3         8 push @elements, join( ', ', map { $_->as_text } @{ $self->{ 'returningList' } } );
  5         17  
  3         8  
100             }
101 16         124 return $prefix . join( ' ', @elements );
102             }
103              
104             sub pretty_print {
105 16     16 1 10928 my $self = shift;
106 16         40 my @lines = ();
107 16         44 push @lines, 'INSERT INTO ' . $self->relname;
108 16 100       51 if ( exists $self->{ 'cols' } ) {
109 12         31 $lines[ -1 ] .= ' ( ';
110 12         24 $lines[ -1 ] .= join( ', ', map { $_->as_text } @{ $self->{ 'cols' } } );
  40         98  
  12         34  
111 12         32 $lines[ -1 ] .= ' )';
112             }
113 16         66 push @lines, $self->increase_indent( $self->{ 'selectStmt' }->pretty_print );
114              
115 16 100       52 if ( exists $self->{ 'withClause' } ) {
116              
117 1         3 my @cte_def = ();
118              
119 1         2 push @cte_def, map { $_->pretty_print . ',' } @{ $self->{ 'withClause' }->{ 'ctes' } };
  1         4  
  1         3  
120              
121             # Remove unnecessary trailing , in last element
122 1         5 $cte_def[ -1 ] =~ s/,\z//;
123 1 50       5 if ( $self->{ 'withClause' }->{ 'recursive' } ) {
124 0         0 $cte_def[ 0 ] = 'WITH RECURSIVE ' . $cte_def[ 0 ];
125             }
126             else {
127 1         4 $cte_def[ 0 ] = 'WITH ' . $cte_def[ 0 ];
128             }
129              
130 1         4 unshift @lines, join( ' ', @cte_def );
131             }
132              
133 16         48 push @lines, map { $self->increase_indent( $_ ) } $self->conflict_handling( 1 );
  18         43  
134              
135 16 100       46 if ( exists $self->{ 'returningList' } ) {
136 3         10 push @lines, $self->increase_indent( 'RETURNING ' );
137 3         9 $lines[ -1 ] .= join( ', ', map { $_->as_text } @{ $self->{ 'returningList' } } );
  5         15  
  3         9  
138             }
139              
140 16         80 return join( "\n", @lines );
141             }
142              
143             sub conflict_handling {
144 32     32 1 55 my $self = shift;
145 32   100     104 my $indent = shift // '';
146 32 100       89 return unless exists $self->{ 'onConflictClause' };
147              
148 14         29 my @lines = ();
149 14         23 my $C = $self->{ 'onConflictClause' };
150 14         30 my $A = $self->{ 'onConflictClause' }->{ 'action' };
151 14         24 push @lines, 'ON CONFLICT';
152 14 100       34 if ( $C->{ 'infer' } ) {
153 10         26 my $I = $C->{ 'infer' };
154 10 100       38 if ( exists $I->{ 'conname' } ) {
    50          
155 2         6 $lines[ -1 ] .= ' ON CONSTRAINT ' . $self->quote_ident( $I->{ 'conname' } );
156             }
157             elsif ( exists $I->{ 'indexElems' } ) {
158 8         17 $lines[ -1 ] .= sprintf ' ( %s )', join( ', ', map { $_->as_text } @{ $I->{ 'indexElems' } } );
  14         42  
  8         19  
159             }
160             }
161 14 100       48 if ( $A eq 'ONCONFLICT_NOTHING' ) {
    50          
162 8         18 $lines[ -1 ] .= ' DO NOTHING';
163             }
164             elsif ( $A eq 'ONCONFLICT_UPDATE' ) {
165 6         19 $lines[ -1 ] .= ' DO UPDATE';
166 6         14 push @lines, 'SET';
167 6         10 for my $item ( @{ $C->{ '_set' } } ) {
  6         18  
168 12 100       29 if ( exists $item->{ 'col' } ) {
169             push @lines, sprintf(
170             '%s = %s,',
171             $self->quote_ident( $item->{ 'col' } ),
172 10         29 $item->{ 'val' }->pretty_print
173             );
174             }
175             else {
176             push @lines, sprintf(
177             '( %s ) = %s,',
178 2         11 join( ', ', @{ $item->{ 'cols' } } ),
179 2         5 $item->{ 'val' }->pretty_print
180             );
181             }
182 12 100       50 $lines[ -1 ] = $self->increase_indent( $lines[ -1 ] ) if $indent;
183             }
184              
185             # Remove tailing ,
186 6         28 $lines[ -1 ] =~ s/,\z//;
187              
188 6 100       22 if ( exists $C->{ 'whereClause' } ) {
189 2         6 push @lines, 'WHERE';
190 2 100       8 if ( $indent ) {
191 1         5 push @lines, $self->increase_indent( $C->{ 'whereClause' }->pretty_print() );
192             }
193             else {
194 1         4 push @lines, $C->{ 'whereClause' }->as_text;
195             }
196             }
197             }
198 14         40 return @lines;
199             }
200              
201             sub build_set_array {
202 7     7 1 13 my $self = shift;
203 7         15 my $C = $self->{ 'onConflictClause' };
204 7 100       22 return unless exists $C->{ 'targetList' };
205              
206 3         7 my @set = ();
207 3         6 my $multi_join = 0;
208              
209 3         5 for my $item ( @{ $C->{ 'targetList' } } ) {
  3         9  
210 7         15 my $column = $item->{ 'name' };
211 7 100       15 if ( $multi_join > 0 ) {
212 1         3 push @{ $set[ -1 ]->{ 'cols' } }, $column;
  1         4  
213 1         3 $multi_join--;
214 1         2 next;
215             }
216 6         11 my $val = $item->{ 'val' };
217 6 100       15 if ( 'Pg::SQL::PrettyPrinter::Node::MultiAssignRef' eq ref $val ) {
218 1         10 push @set, { 'cols' => [ $column ], 'val' => $val->{ 'source' } };
219 1         3 $multi_join = $val->{ 'ncolumns' } - 1;
220             }
221             else {
222 5         19 push @set, { 'col' => $column, 'val' => $val };
223             }
224             }
225 3         11 $C->{ '_set' } = \@set;
226             }
227              
228             1;
229              
230             # vim: set ft=perl: