File Coverage

blib/lib/DBIx/PgLink/Connector.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package DBIx::PgLink::Connector;
2              
3 1     1   2220 use Carp;
  1         2  
  1         68  
4 1     1   430 use Moose;
  0            
  0            
5             use MooseX::Method;
6             use DBIx::PgLink::Logger qw/trace_msg trace_level/;
7             use DBIx::PgLink::Local;
8             use DBIx::PgLink::Types;
9             use Scalar::Util qw/weaken/;
10             use Data::Dumper;
11              
12             extends 'Moose::Object';
13              
14             our $VERSION = '0.01';
15              
16             has 'conn_name' => (
17             is => 'ro',
18             isa => 'Str',
19             required => 1,
20             );
21              
22             has 'adapter' => (
23             is => 'rw',
24             isa => 'DBIx::PgLink::Adapter',
25             );
26              
27             has 'credentials' => (
28             is => 'rw',
29             isa => 'HashRef',
30             );
31              
32             with 'DBIx::PgLink::RoleInstaller';
33              
34             # functions spread out to fixed roles
35             with 'DBIx::PgLink::TypeMapper';
36             with 'DBIx::PgLink::Accessor';
37             with 'DBIx::PgLink::RemoteAction';
38              
39              
40             sub BUILD {
41             my $self = shift;
42             my $self_attr = shift;
43              
44             # load main connection record
45             my $conn = $self->load_connection;
46              
47             # include additional paths
48             $self->use_libs( $conn->{use_libs} );
49              
50             # apply Connector roles to myself
51             $self->load_roles('Connector', $self);
52              
53             # check adapter class
54             my $adapter_class = $self->require_class($conn->{adapter_class}, "DBIx::PgLink::Adapter");
55              
56             # load remote credentials
57             $self->credentials( $self->load_credentials($conn->{logon_mode}) )
58             or croak "Access to the " . $self->conn_name . " is denied because no login-mapping exists";
59              
60             # load attributes
61             my $attr_href = $self->load_attributes;
62              
63             # pass weak reference to self
64             $attr_href->{connector} = $self;
65             weaken $attr_href->{connector};
66              
67             # create adapter
68             trace_msg('INFO', "Creating adapter '$adapter_class' for connection " . $self->conn_name)
69             if trace_level>=2;
70             $self->adapter( $adapter_class->new($attr_href) );
71              
72             # remove applied attributes from hash
73             # the rest belongs to DBI or Adapter role
74             $self->apply_attributes_to_adapter($attr_href, 1);
75              
76             # apply adapter roles
77             $self->load_roles('Adapter', $self->adapter);
78              
79             # set role attributes
80             # the rest belongs to DBI (and DBI->connect ignore unknown attributes)
81             $self->apply_attributes_to_adapter($attr_href);
82              
83              
84             return if $self_attr->{no_connect}; # for debugging and connection uninstall
85              
86             # connect to remote database
87             $self->adapter->connect(
88             $conn->{data_source},
89             $self->credentials->{remote_user},
90             $self->credentials->{remote_password},
91             $attr_href
92             );
93             trace_msg('NOTICE', "Connection " . $self->conn_name . " established to data source $conn->{data_source}"
94             . " as '" . $self->credentials->{remote_user} . "'"
95             ) if trace_level>=1;
96              
97             return;
98             }
99              
100              
101             sub load_connection {
102             my $self = shift;
103             my $conn = pg_dbh->selectrow_hashref(<<'END_OF_SQL',
104             SELECT *
105             FROM dbix_pglink.connections
106             WHERE conn_name = $1
107             END_OF_SQL
108             {
109             Slice=>{},
110             array=>[qw/use_libs/],
111             },
112             $self->conn_name,
113             );
114             confess "Connection named '" . $self->conn_name . "' not found" unless $conn;
115             return $conn;
116             }
117              
118              
119             sub use_libs {
120             my $self = shift;
121             my $libs = shift or return;
122              
123             for my $lib (@{$libs}) {
124             eval q/use lib $lib/; # change global @INC, not scoped
125             }
126             }
127              
128              
129             sub load_roles {
130             my $self = shift;
131             my $role_kind = shift;
132             my $object = shift;
133              
134             unless ($object->does('DBIx::PgLink::RoleInstaller')) {
135             trace_msg('WARNING', "Object $object cannot install roles");
136             return;
137             }
138              
139             my $roles_aref = pg_dbh->selectall_arrayref(<<'END_OF_SQL', {Slice=>{}}, $self->conn_name, $role_kind);
140             SELECT role_name
141             FROM dbix_pglink.roles
142             WHERE conn_name = $1
143             and role_kind = $2
144             and (local_user = '' or local_user = session_user)
145             ORDER BY role_seq, local_user
146             END_OF_SQL
147             my %seen;
148             my @role_names = grep { ! $seen{$_}++ } map { $_->{role_name} } @{$roles_aref};
149             $object->install_roles(@role_names);
150             }
151              
152              
153             sub require_class {
154             my $self = shift;
155             my $class_name = shift;
156             my $class_prefix = shift;
157              
158             $class_name = $class_prefix . "::" . $class_name unless $class_name =~ /::/;
159             eval "require $class_name";
160             confess "Cannot use class '$class_name' for connection " . $self->conn_name, $@ if $@;
161              
162             return $class_name;
163             }
164              
165              
166             sub load_credentials {
167             my $self = shift;
168             my $logon_mode = shift;
169              
170             my $cred_sth = pg_dbh->prepare_cached(<<'END_OF_SQL', {no_cursor=>1});
171             SELECT local_user, remote_user, remote_password
172             FROM dbix_pglink.users
173             WHERE conn_name = $1
174             and local_user = $2
175             END_OF_SQL
176              
177             my $local_user = pg_dbh->pg_session_user; # session_user because here we in 'security definer' PL/Perl function
178             $cred_sth->execute( $self->conn_name, $local_user );
179             my $cred = $cred_sth->fetchrow_hashref;
180              
181             # mapping exists
182             if ($cred) {
183             trace_msg('NOTICE', "Remote credentials: local user '$local_user' mapped to remote user '$cred->{remote_user}'")
184             if trace_level >= 2;
185             return $cred if defined $cred;
186             }
187             trace_msg('NOTICE', "Remote credentials: no remote user mapping found for local user '$local_user'")
188             if trace_level >= 2;
189              
190             return if $logon_mode eq 'deny'; # connection refused
191              
192             if ($logon_mode eq 'empty') {
193             # connect with empty user/password
194             return {
195             local_user => '',
196             remote_user => '',
197             remote_password => '',
198             };
199             } elsif ($logon_mode eq 'current') {
200             # connect as current user without password
201             trace_msg('NOTICE', "Remote credentials: with local user name '$local_user' without password")
202             if trace_level >= 2;
203             return {
204             local_user => $local_user,
205             remote_user => $local_user,
206             remote_password => '',
207             };
208             } elsif ($logon_mode eq 'default') {
209             # connect as default user
210             my $rc = $cred_sth->execute($self->conn_name, ''); # has empty string as 'local_user'
211             my $cred = $cred_sth->fetchrow_hashref;
212             if ($cred) {
213             trace_msg('NOTICE', "Remote credentials: as default user '$cred->{remote_user}' with default password")
214             if trace_level >= 2;
215             return $cred;
216             }
217             }
218              
219             return; # connection refused
220             }
221              
222              
223             sub load_attributes {
224             my $self = shift;
225              
226             # user value override global value
227             my $attr_aref = pg_dbh->selectall_arrayref(<<'END_OF_SQL', {Slice=>{}}, $self->conn_name);
228             SELECT attr_name, attr_value
229             FROM dbix_pglink.attributes
230             WHERE conn_name = $1
231             AND (local_user = '' or local_user = session_user)
232             ORDER BY local_user
233             END_OF_SQL
234             my %attr = map { $_->{attr_name} => $_->{attr_value} } @{$attr_aref};
235             return \%attr;
236             }
237              
238              
239             sub apply_attributes_to_adapter {
240             my $self = shift;
241             my $attr = shift;
242             my $skip = shift;
243             while (my ($a, $v) = each %{$attr}) {
244             # NOTE: run-time role damages $self->meta->has_attribute
245             next unless $self->adapter->can($a); # requires attr accessor
246             unless ($skip) {
247             $self->adapter->$a($v);
248             }
249             delete $attr->{$a}; # remove applied attribute from hash
250             trace_msg('INFO', "Applied attibute $a = $v")
251             if trace_level >= 3;
252             }
253             }
254              
255              
256             1;
257              
258              
259             __END__
260              
261             =pod
262              
263             =head1 NAME
264              
265             DBIx::PgLink::Connector - glue between Adapter, Accessors and PL/Perl
266              
267             =head1 SYNOPSIS
268              
269             See L<DBIx::PgLink>
270              
271             =head1 ATTRIBUTES
272              
273             =over
274              
275             =item conn_name
276              
277             Connection name (I<dbix_pglink.connections.conn_name>).
278              
279             =item adapter
280              
281             Instance of L<DBIx::PgLink::Adapter> class.
282              
283             =back
284              
285             =head1 METHODS
286              
287             =over
288              
289             =item C<new>
290              
291             Create new instance of Adapter class, load settings from PostgreSQL tables in I<dbix_pglink> schema,
292             and immediately connect to datasource.
293              
294             =item C<build_accessors>
295              
296             $connector->build_accessors(
297             local_schema => $local_schema,
298             remote_catalog => $remote_catalog,
299             remote_schema => $remote_schema,
300             remote_object => $remote_object,
301             remote_object_type => \@types,
302             );
303              
304             Enumerates database objects of remote database,
305             and build local accessors in specified C<local_schema>.
306              
307             Can accept like-pattern of remote catalog, schema and object names.
308              
309             Local schema created automatically if not exists.
310             Building methods must be reenterable and must drop old object before creating new one.
311              
312             Implemented with C<DBIx::PgLink::Accessor> role.
313              
314             =item C<remote_query>
315              
316             I<In PL/Perl function>
317             $connector->remote_query($query);
318             $connector->remote_query($query, $param_values);
319             $connector->remote_query($query, $param_values, $param_types);
320              
321             Execute set-returning SQL $query in remote database and returns dataset as result of PL/Perl PostgreSQL function.
322             Query can by parametrized and $param_values binded.
323             Parameter values and types is function input parameters of TEXT[] type.
324             Parameter type can be specified as 'SQL_FLOAT' or 'FLOAT' or integer type code. See L<DBI/"DBI Constants">.
325              
326             Implemented with C<DBIx::PgLink::RemoteAction> role.
327              
328             =item C<remote_exec>
329              
330             I<In PL/Perl function>
331             $connector->remote_exec($query);
332             $connector->remote_exec($query, $param_values);
333             $connector->remote_exec($query, $param_values, $param_types);
334              
335             The same as C<remote_query> but returns only number of proceeded rows.
336             Ignore any resultset returned.
337              
338             Implemented with C<DBIx::PgLink::RemoteAction> role.
339              
340             =back
341              
342             =head1 SEE ALSO
343              
344             L<DBI>,
345             L<DBIx::PgLink>
346             L<DBIx::PgLink::Adapter>
347             L<DBIx::PgLink::Accessor::Tables>
348              
349             =head1 AUTHOR
350              
351             Alexey Sharafutdinov E<lt>alexey.s.v.br@gmail.comE<gt>
352              
353             =head1 COPYRIGHT AND LICENSE
354              
355             This library is free software; you can redistribute it and/or modify
356             it under the same terms as Perl itself, either Perl version 5.8.8 or,
357             at your option, any later version of Perl 5 you may have available.
358              
359             =cut