File Coverage

blib/lib/PGObject/Util/PGConfig.pm
Criterion Covered Total %
statement 52 90 57.7
branch 7 14 50.0
condition n/a
subroutine 14 19 73.6
pod 13 13 100.0
total 86 136 63.2


line stmt bran cond sub pod time code
1             package PGObject::Util::PGConfig;
2              
3 5     5   277286 use 5.006;
  5         20  
4 5     5   30 use strict;
  5         12  
  5         426  
5 5     5   30 use warnings;
  5         15  
  5         248  
6 5     5   33 use Carp;
  5         12  
  5         6581  
7              
8             =head1 NAME
9              
10             PGObject::Util::PGConfig - Postgres Configuration Management
11              
12             =head1 VERSION
13              
14             Version 0.20
15              
16             =cut
17              
18             our $VERSION = 0.020000;
19              
20              
21             =head1 SYNOPSIS
22              
23             use PGObject::Util::PGConfig;
24              
25             my $config = PGObject::Util::PGConfig->new();
26             # setting values in the internal store
27             $config->set('statement_timeout', 3600); # set the desired state
28             $config->set('datestyle', 'ISO');
29              
30             # session configuration management
31             $config->list($dbh);
32             $config->fetch($dbh, 'statement_timeout'); # get the session statement timeout
33             # We can now sync with Pg for the dbh session
34             $config->sync_session($dbh, qw(statement_timeout datestyle));
35              
36             # or we can get from a file
37             $config->fromfile('path/to/file');
38             # or from file contents
39             $config->fromcontents($string);
40             # and can return current state as file contents
41             $config->filecontents();
42             # or write to a file
43             $config->tofile('path/to/new.conf');
44              
45             =head1 DESCRIPTION
46              
47             The current config module provides an abstraction around the PostgreSQL GUC
48             (configuration system). This includes parsing config files (postgresql.conf,
49             recovery.conf) and retrieve current settings from a database configuration.
50              
51             The module does not depend on a database configuration so it can be used to
52             aggregate configuration data from different sources.
53              
54             Session update guarantees that only appropriate session variables are
55             updated.
56              
57             =head1 Methods
58              
59             =head2 Constructor
60              
61             =head3 new
62              
63             The constructor takes no arguments and initializes an empty store. The store
64             is implemented as a hashref similar to what you would expect from a Moo/Moose
65             object but it is recommended that you do not inspect directly because this
66             behavior is not guaranteed for subclasses.
67              
68             If a subclass overwrites the storage approach, it MUST override this method
69             as well.
70              
71             =cut
72              
73             sub new {
74 3     3 1 166 my ($pkg) = @_;
75 3         10 my $self = {};
76 3         22 bless $self, $pkg;
77             }
78              
79             =head2 Internal store
80              
81             There are several things which are not the responsibility of the internal
82             store. These include checking validity of variable names as these could
83             vary between major versions of PostgreSQL. Subclasses MAY override these
84             methods safely and provide a different storage mechanism.
85              
86             =head3 set($key, $value)
87              
88             Sets a current GUC variable to a particular value.
89              
90             =cut
91              
92             sub set {
93 14     14 1 31 my ($self, $key, $value) = @_;
94 14 50       31 croak 'References unsupported' if ref $value;
95 14         39 $self->{$key} = $value;
96             }
97              
98             =head3 forget($key)
99              
100             Deletes a key from the store
101              
102             =cut
103              
104             sub forget {
105 0     0 1 0 my ($self, $key) = @_;
106 0         0 delete $self->{$key};
107             }
108              
109             =head3 known_keys()
110              
111             Returns a list of keys from the store.
112              
113             =cut
114              
115             sub known_keys {
116 8     8 1 18 my ($self) = @_;
117 8         72 return keys %$self;
118             }
119              
120             =head3 get_value($key)
121              
122             Returns a value from the key in the store.
123              
124             =cut
125              
126             sub get_value {
127 30     30 1 57 my ($self, $key) = @_;
128 30         108 return $self->{$key};
129             }
130              
131             =head2 DB Session
132              
133             The methods in this session integrate with a database session and pull
134             data from these. The module itself does not depend on the database
135             session for general use.
136              
137             =head3 fetch($dbh, $key)
138              
139             Retrieves a setting from the session and saves it to the store.
140              
141             Returns the stored value.
142              
143             =cut
144              
145             sub fetch {
146 0     0 1 0 my ($self, $dbh, $key) = @_;
147 0         0 my $sth = $dbh->prepare("SELECT current_setting(?)");
148 0         0 $sth->execute($key);
149 0         0 $self->set($key, $sth->fetchrow_array);
150 0         0 return $self->get_value($key);
151             }
152              
153             =head3 list($dbh)
154              
155             Returns a list of all GUC variables set for the database session at $dbh
156              
157             Does not affect store.
158              
159             =cut
160              
161             sub list {
162 0     0 1 0 my ($self, $dbh) = @_;
163 0         0 my $sth = $dbh->prepare('SELECT name FROM pg_settings ORDER BY name');
164 0         0 $sth->execute;
165 0         0 my @keys;
166 0         0 while (my ($key) = $sth->fetchrow_array){
167 0         0 push @keys, $key;
168             }
169 0         0 return @keys;
170             }
171              
172             =head3 sync_session($dbh)
173              
174             Synchronizes all stored variables into the current session if applicable.
175              
176             =cut
177              
178             sub sync_session{
179 0     0 1 0 my ($self, $dbh) = @_;
180 0         0 my $query = "
181             SELECT s.name FROM pg_settings s
182             JOIN pg_roles r ON rolname = session_user
183             WHERE name = any(?)
184             AND (s.context = 'user'
185             OR s.context = 'superuser' AND r.rolsuper)
186             ";
187 0         0 my $sth = $dbh->prepare($query);
188 0         0 $sth->execute([$self->known_keys]);
189 0         0 my $setsth = $dbh->prepare(
190             "SELECT set_config(?, ?, false)");
191 0         0 while (my ($setname) = $sth->fetchrow_array){
192 0         0 $setsth->execute($setname, $self->get_value($setname));
193             }
194             }
195              
196             =head2 apply_system($dbh)
197              
198             Requires superuser access. Runs ALTER SYSTEM commands for all appropriate
199             keys.
200              
201             Returns a list of keys applied. This can then be used to check against
202             expected behavior.
203              
204             =cut
205              
206             sub apply_system {
207 0     0 1 0 my ($self, $dbh) = @_;
208 0 0       0 croak 'Need a database handle' unless ref $dbh;
209 0         0 my $sth = $dbh->prepare('
210             select rolsuper from pg_roles where rolname = session_user
211             ');
212 0         0 $sth->execute();
213 0 0       0 if (! ($sth->fetchrow_array)[0]){
214 0         0 croak 'DB connection for apply_system must be for a superuser';
215             }
216 0         0 my $setting_sth = $dbh->prepare("
217             select name from pg_settings where name = any(?)
218             and context <> 'internal'
219             ");
220 0         0 $setting_sth->execute([$self->known_keys]);
221 0         0 my @settings;
222 0         0 while (my ($setting) = $setting_sth->fetchrow_array){
223 0         0 push @settings, $setting;
224             }
225 0         0 $setting_sth->finish;
226 0         0 for (@settings){
227 0         0 my $key = $dbh->quote_identifier($_);
228 0         0 my $value = $dbh->quote($self->get_value($_));
229 0         0 $dbh->do("ALTER SYSTEM SET $key TO $value");
230             }
231 0         0 return @settings;
232            
233             }
234              
235             =head2 File and Contents
236              
237             This module is also capable of reading to and writing to files
238             and generating file content in the format expected. This means that the
239             general whitespace rules and escaping approach PostgreSQL expects are met.
240              
241             =head3 fromfile($path)
242              
243             Reads the contents from a file. Loads the whole file into memory.
244              
245             =cut
246              
247             sub fromfile {
248 1     1 1 6 my ($self, $file) = @_;
249 1         1 my $fh;
250 1         48 open $fh, '<', $file;
251 1         32 $self->fromcontents(join("", <$fh>));
252 1         10 close $fh;
253             }
254              
255             =head3 fromcontents($contents)
256              
257             Parses file content and sets the internal store accordingly.
258              
259             =cut
260              
261             sub _unescape {
262 11     11   21 my ($val) = @_;
263 11 50       21 return unless defined $val;
264 11         23 $val =~ s/''/'/g;
265 11         34 $val =~ s/(^'|'$)//g;
266 11         33 return $val;
267             }
268              
269             sub _escape {
270 17     17   24 my ($val) = @_;
271 17         33 $val =~ s/'/''/g;
272 17         56 return $val;
273             }
274              
275             sub fromcontents {
276 3     3 1 9 my ($self, $contents) = @_;
277 3         34 for my $line (split(/(\r|\n)/, $contents)){
278 29         66 $line =~ s/\#.*//;
279 29         156 $line =~ s/(^\s*|\s*$)//g;
280 29 100       63 next unless $line;
281              
282 11         16 my ($key, $value);
283 11 100       23 if ($line =~ /=/){
284 8         35 ($key, $value) = split(/\s*=\s*/, $line, 2);
285             } else {
286 3         10 ($key, $value) = split(/\s/, $line, 2);
287             }
288            
289 11         25 $self->set($key, _unescape($value));
290             }
291             }
292              
293             =head3 filecontents()
294              
295             Returns file contents. Variables are set in alphabetical order
296              
297             =cut
298              
299             sub filecontents{
300 4     4 1 11 my ($self) = @_;
301             return join "\n",
302 4         11 (map {"$_ = '" . _escape($self->get_value($_)) . "'" }
  17         30  
303             sort $self->known_keys);
304             }
305              
306             =head3 tofile($path)
307              
308             Writes the contents, per filecontents above, to $path
309              
310             =cut
311              
312             sub tofile {
313 1     1 1 4 my ($self, $path) = @_;
314 1         3 my $fh;
315 1 50       82 open($fh, '>', $path) or die $!;
316 1         4 print $fh $self->filecontents;
317 1         43 close $fh;
318             }
319              
320             =head2 Future Versions
321              
322             =head3 sync_system($dbh)
323              
324             This command will use ALTER SYSTEM statements to set defaults to be used on
325             next PostgreSQL restart or reload. Not yet supported.
326              
327             =head1 AUTHOR
328              
329             Chris Travers, C<< >>
330              
331             =head1 BUGS
332              
333             Please report any bugs or feature requests to C, or through
334             the web interface at L. I will be notified, and then you'll
335             automatically be notified of progress on your bug as I make changes.
336              
337              
338              
339              
340             =head1 SUPPORT
341              
342             You can find documentation for this module with the perldoc command.
343              
344             perldoc PGObject::Util::PGConfig
345              
346              
347             You can also look for information at:
348              
349             =over 4
350              
351             =item * RT: CPAN's request tracker (report bugs here)
352              
353             L
354              
355             =item * AnnoCPAN: Annotated CPAN documentation
356              
357             L
358              
359             =item * CPAN Ratings
360              
361             L
362              
363             =item * Search CPAN
364              
365             L
366              
367             =back
368              
369              
370             =head1 ACKNOWLEDGEMENTS
371              
372              
373             =head1 LICENSE AND COPYRIGHT
374              
375             Copyright 2017 Adjust.com
376              
377             This program is distributed under the (Revised) BSD License:
378             L
379              
380             Redistribution and use in source and binary forms, with or without
381             modification, are permitted provided that the following conditions
382             are met:
383              
384             * Redistributions of source code must retain the above copyright
385             notice, this list of conditions and the following disclaimer.
386              
387             * Redistributions in binary form must reproduce the above copyright
388             notice, this list of conditions and the following disclaimer in the
389             documentation and/or other materials provided with the distribution.
390              
391             * Neither the name of Adjust.com
392             nor the names of its contributors may be used to endorse or promote
393             products derived from this software without specific prior written
394             permission.
395              
396             THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
397             "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
398             LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
399             A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
400             OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
401             SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
402             LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
403             DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
404             THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
405             (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
406             OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
407              
408              
409             =cut
410              
411             1; # End of PGObject::Util::PGConfig