File Coverage

blib/lib/Pg/SQL/PrettyPrinter/Node/FuncCall.pm
Criterion Covered Total %
statement 162 169 95.8
branch 69 78 88.4
condition 7 7 100.0
subroutine 21 21 100.0
pod 7 7 100.0
total 266 282 94.3


line stmt bran cond sub pod time code
1             package Pg::SQL::PrettyPrinter::Node::FuncCall;
2              
3             # UTF8 boilerplace, per http://stackoverflow.com/questions/6162484/why-does-modern-perl-avoid-utf-8-by-default/
4 3     3   2615 use v5.26;
  3         22  
5 3     3   128 use strict;
  3         17  
  3         162  
6 3     3   21 use warnings;
  3         7  
  3         112  
7 3     3   27 use warnings qw( FATAL utf8 );
  3         7  
  3         117  
8 3     3   15 use utf8;
  3         6  
  3         27  
9 3     3   74 use open qw( :std :utf8 );
  3         7  
  3         22  
10 3     3   463 use Unicode::Normalize qw( NFC );
  3         8  
  3         194  
11 3     3   27 use Unicode::Collate;
  3         8  
  3         95  
12 3     3   17 use Encode qw( decode );
  3         6  
  3         220  
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   756 use autodie;
  3         7  
  3         23  
24 3     3   17080 use Carp qw( carp croak confess cluck );
  3         27  
  3         264  
25 3     3   22 use English qw( -no_match_vars );
  3         11  
  3         29  
26 3     3   1272 use Data::Dumper qw( Dumper );
  3         7  
  3         770  
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   22 use parent qw( Pg::SQL::PrettyPrinter::Node );
  3         7  
  3         33  
44              
45             # Taken from PostgreSQL sources, from src/include/nodes/parsenodes.h
46             our $FRAMEOPTION_NONDEFAULT = 0x00001; # any specified?
47             our $FRAMEOPTION_RANGE = 0x00002; # RANGE behavior
48             our $FRAMEOPTION_ROWS = 0x00004; # ROWS behavior
49             our $FRAMEOPTION_GROUPS = 0x00008; # GROUPS behavior
50             our $FRAMEOPTION_BETWEEN = 0x00010; # BETWEEN given?
51             our $FRAMEOPTION_START_UNBOUNDED_PRECEDING = 0x00020; # start is U. P.
52             our $FRAMEOPTION_END_UNBOUNDED_PRECEDING = 0x00040; # (disallowed)
53             our $FRAMEOPTION_START_UNBOUNDED_FOLLOWING = 0x00080; # (disallowed)
54             our $FRAMEOPTION_END_UNBOUNDED_FOLLOWING = 0x00100; # end is U. F.
55             our $FRAMEOPTION_START_CURRENT_ROW = 0x00200; # start is C. R.
56             our $FRAMEOPTION_END_CURRENT_ROW = 0x00400; # end is C. R.
57             our $FRAMEOPTION_START_OFFSET_PRECEDING = 0x00800; # start is O. P.
58             our $FRAMEOPTION_END_OFFSET_PRECEDING = 0x01000; # end is O. P.
59             our $FRAMEOPTION_START_OFFSET_FOLLOWING = 0x02000; # start is O. F.
60             our $FRAMEOPTION_END_OFFSET_FOLLOWING = 0x04000; # end is O. F.
61             our $FRAMEOPTION_EXCLUDE_CURRENT_ROW = 0x08000; # omit C.R.
62             our $FRAMEOPTION_EXCLUDE_GROUP = 0x10000; # omit C.R. & peers
63             our $FRAMEOPTION_EXCLUDE_TIES = 0x20000; # omit C.R.'s peers
64              
65             sub new {
66 47     47 1 1579 my $class = shift;
67 47         172 my $self = $class->SUPER::new( @_ );
68 47         111 bless $self, $class;
69              
70             $self->objectify(
71             'funcname',
72             'args',
73             'agg_filter',
74 47         110 map { [ 'over', $_ ] } qw( orderClause partitionClause startOffset endOffset )
  188         546  
75             );
76              
77 47 100       293 if ( $self->{ 'func_variadic' } ) {
78 4         43 my $last_type = ref $self->{ 'args' }->[ -1 ];
79 4         18 $last_type =~ s/^Pg::SQL::PrettyPrinter::Node:://;
80 4 50       27 croak( "Function is variadic, but last arg is not an array/subquery: ${last_type}" ) unless $last_type =~ m{\A(?:A_ArrayExpr|SubLink)\z};
81             }
82              
83 47         124 return $self;
84             }
85              
86             sub func_name {
87 102     102 1 321 my $self = shift;
88 102 100       289 unless ( exists $self->{ '_funcname' } ) {
89 47         79 $self->{ '_funcname' } = join '.', map { $_->as_ident } @{ $self->{ 'funcname' } };
  48         126  
  47         97  
90             }
91 102         474 return $self->{ '_funcname' };
92             }
93              
94             sub over_clause_as_text {
95 58     58 1 94 my $self = shift;
96 58 100       236 return unless exists $self->{ 'over' };
97              
98             # shortcut
99 11         16 my $O = $self->{ 'over' };
100              
101             # Build the clause from parts, as it's simpler that way.
102 11         20 my @parts = ();
103              
104 11 100       29 if ( exists $O->{ 'partitionClause' } ) {
105 9         16 push @parts, 'PARTITION BY ' . join( ', ', map { $_->as_text } @{ $O->{ 'partitionClause' } } );
  11         26  
  9         17  
106             }
107 11 100       35 if ( exists $O->{ 'orderClause' } ) {
108 9         13 push @parts, 'ORDER BY ' . join( ', ', map { $_->as_text } @{ $O->{ 'orderClause' } } );
  10         29  
  9         19  
109             }
110              
111             # If there is no frame clause it will be empty array, so nothing will get pushed.
112 11         30 push @parts, $self->frame_clause();
113              
114             # Shortcut for over without clauses
115 11 100       32 return ' OVER ()' if 0 == scalar @parts;
116              
117 10         47 return sprintf( ' OVER ( %s )', join( ' ', @parts ) );
118             }
119              
120             sub over_clause_pretty {
121 42     42 1 71 my $self = shift;
122 42 100       165 return unless exists $self->{ 'over' };
123              
124             # shortcut
125 11         19 my $O = $self->{ 'over' };
126              
127             # Build the clause from parts, as it's simpler that way.
128 11         21 my @parts = ();
129              
130 11 100       25 if ( exists $O->{ 'partitionClause' } ) {
131 9         16 push @parts, 'PARTITION BY ' . join( ', ', map { $_->pretty_print } @{ $O->{ 'partitionClause' } } );
  11         30  
  9         18  
132             }
133 11 100       30 if ( exists $O->{ 'orderClause' } ) {
134 9         17 push @parts, 'ORDER BY ' . join( ', ', map { $_->pretty_print } @{ $O->{ 'orderClause' } } );
  10         24  
  9         21  
135             }
136              
137             # If there is no frame clause it will be empty array, so nothing will get pushed.
138 11         32 push @parts, $self->frame_clause();
139              
140             # Shortcut for over without clauses
141 11 100       28 return ' OVER ()' if 0 == scalar @parts;
142              
143             # Shortcut for over with just 1 clause
144 10 100       31 return sprintf( ' OVER ( %s )', $parts[ 0 ] ) if 1 == scalar @parts;
145              
146 8         16 my @lines = ();
147 8         12 push @lines, ' OVER (';
148 8         17 push @lines, map { $self->increase_indent( $_ ) } @parts;
  23         53  
149 8         16 push @lines, ')';
150 8         30 return join( "\n", @lines );
151             }
152              
153             sub frame_clause {
154 22     22 1 30 my $self = shift;
155              
156             # shortcuts
157 22         42 my $O = $self->{ 'over' };
158 22         33 my $FO = $O->{ 'frameOptions' };
159              
160             # Make sure it's called for FuncCalls with some frameOptions.
161 22 50       51 return unless defined $FO;
162              
163             # Make sure the frameOptions are not default.
164 22 100       55 return unless $FO & $FRAMEOPTION_NONDEFAULT;
165              
166 14         21 my @elements = ();
167              
168             # Frame based off what? range? rows? groups?
169 14 100       38 if ( $FO & $FRAMEOPTION_RANGE ) {
    100          
    50          
170 4         10 push @elements, 'RANGE';
171             }
172             elsif ( $FO & $FRAMEOPTION_ROWS ) {
173 8         17 push @elements, 'ROWS';
174             }
175             elsif ( $FO & $FRAMEOPTION_GROUPS ) {
176 2         4 push @elements, 'GROUPS';
177             }
178             else {
179 0         0 croak( "Bad (#1) frameOptions: $FO" );
180             }
181              
182             # Calculate start clause, as it's used in both between and just-start frames
183 14         22 my $start_clause;
184 14 100       42 if ( $FO & $FRAMEOPTION_START_UNBOUNDED_PRECEDING ) {
    100          
    100          
    50          
185 4         10 $start_clause = 'UNBOUNDED PRECEDING';
186             }
187             elsif ( $FO & $FRAMEOPTION_START_CURRENT_ROW ) {
188 2         4 $start_clause = 'CURRENT ROW';
189             }
190             elsif ( $FO & $FRAMEOPTION_START_OFFSET_PRECEDING ) {
191 6         20 $start_clause = $self->{ 'over' }->{ 'startOffset' }->as_text . ' PRECEDING';
192             }
193             elsif ( $FO & $FRAMEOPTION_START_OFFSET_FOLLOWING ) {
194 2         7 $start_clause = $self->{ 'over' }->{ 'startOffset' }->as_text . ' FOLLOWING';
195             }
196             else {
197 0         0 croak( "Bad (#2) frameOptions: $FO" );
198             }
199              
200 14 100       29 if ( $FO & $FRAMEOPTION_BETWEEN ) {
201              
202             # It's frame with BETWEEN operation. It needs end_clause and proper format ...
203 4         9 my $end_clause = '';
204 4 100       16 if ( $FO & $FRAMEOPTION_END_UNBOUNDED_FOLLOWING ) {
    50          
    50          
    50          
205 2         3 $end_clause = 'UNBOUNDED FOLLOWING';
206             }
207             elsif ( $FO & $FRAMEOPTION_END_CURRENT_ROW ) {
208 0         0 $end_clause = 'CURRENT ROW';
209             }
210             elsif ( $FO & $FRAMEOPTION_END_OFFSET_PRECEDING ) {
211 0         0 $end_clause = $self->{ 'over' }->{ 'endOffset' }->as_text . ' PRECEDING';
212             }
213             elsif ( $FO & $FRAMEOPTION_END_OFFSET_FOLLOWING ) {
214 2         7 $end_clause = $self->{ 'over' }->{ 'endOffset' }->as_text . ' FOLLOWING';
215             }
216             else {
217 0         0 croak( "Bad (#3) frameOptions: $FO" );
218             }
219              
220             # Put the elements of between clause together.
221 4         42 push @elements, 'BETWEEN';
222 4         8 push @elements, $start_clause;
223 4         9 push @elements, 'AND';
224 4         6 push @elements, $end_clause;
225             }
226             else {
227             # If it's not BETWEEN frame, just put start clause to output
228 10         19 push @elements, $start_clause;
229             }
230              
231             # Handle excludes in the frame.
232 14 100       44 if ( $FO & $FRAMEOPTION_EXCLUDE_CURRENT_ROW ) {
    100          
    100          
233 2         4 push @elements, 'EXCLUDE CURRENT ROW';
234             }
235             elsif ( $FO & $FRAMEOPTION_EXCLUDE_GROUP ) {
236 2         4 push @elements, 'EXCLUDE GROUP';
237             }
238             elsif ( $FO & $FRAMEOPTION_EXCLUDE_TIES ) {
239 2         5 push @elements, 'EXCLUDE TIES';
240             }
241              
242 14         46 return join( ' ', @elements );
243             }
244              
245             sub as_text {
246 58     58 1 138 my $self = shift;
247              
248 58   100     164 my $suffix = $self->over_clause_as_text // '';
249 58 50       177 if ( exists $self->{ 'agg_filter' } ) {
250 0         0 $suffix .= ' FILTER ( WHERE ' . $self->{ 'agg_filter' }->as_text . ' )';
251             }
252              
253 58 100       293 return $self->func_name . '(*)' . $suffix if $self->{ 'agg_star' };
254 50 100       129 return $self->func_name . '()' . $suffix unless exists $self->{ 'args' };
255              
256 47         97 my @args_as_text = map { $_->as_text } @{ $self->{ 'args' } };
  74         206  
  47         95  
257 47 100       140 if ( $self->{ 'func_variadic' } ) {
258 4         40 $args_as_text[ -1 ] = 'VARIADIC ' . $args_as_text[ -1 ];
259             }
260 47         140 my $args_str = join( ', ', @args_as_text );
261 47         105 return $self->func_name . '( ' . $args_str . ' )' . $suffix;
262             }
263              
264             sub pretty_print {
265 42     42 1 77 my $self = shift;
266              
267 42   100     102 my $suffix = $self->over_clause_pretty // '';
268 42 50       113 if ( exists $self->{ 'agg_filter' } ) {
269 0         0 $suffix .= ' FILTER ( WHERE ' . $self->{ 'agg_filter' }->as_text . ' )';
270             }
271              
272 42 100       131 return $self->func_name . '(*)' . $suffix if $self->{ 'agg_star' };
273 35 100       91 return $self->func_name . '()' . $suffix unless exists $self->{ 'args' };
274              
275 32         60 my @args_as_text = map { $_->as_text } @{ $self->{ 'args' } };
  50         144  
  32         65  
276 32 100       109 if ( $self->{ 'func_variadic' } ) {
277 4         42 $args_as_text[ -1 ] = 'VARIADIC ' . $args_as_text[ -1 ];
278             }
279 32         82 my $args_str = join( ', ', @args_as_text );
280 32 100 100     51 if ( ( 1 == scalar @{ $self->{ 'args' } } )
  32         145  
281             && ( 40 > length( $args_str ) ) )
282             {
283 19         46 return $self->func_name . '( ' . $args_str . ' )' . $suffix;
284             }
285 13         29 my @lines = ();
286 13         33 push @lines, $self->func_name . '(';
287 13         26 my @args_pp = map { $_->pretty_print } @{ $self->{ 'args' } };
  31         91  
  13         30  
288 13 100       48 if ( $self->{ 'func_variadic' } ) {
289 3         31 $args_pp[ -1 ] = 'VARIADIC ' . $args_pp[ -1 ];
290             }
291 13         31 push @lines, map { $self->increase_indent( $_ ) . ',' } @args_pp;
  31         82  
292 13         62 $lines[ -1 ] =~ s/,\z//;
293 13         40 push @lines, ')' . $suffix;
294 13         59 return join( "\n", @lines );
295             }
296              
297             1;