DBIx-PgLink
view release on metacpan or search on metacpan
lib/DBIx/PgLink/Connector.pm view on Meta::CPAN
package DBIx::PgLink::Connector;
use Carp;
use Moose;
use MooseX::Method;
use DBIx::PgLink::Logger qw/trace_msg trace_level/;
use DBIx::PgLink::Local;
use DBIx::PgLink::Types;
use Scalar::Util qw/weaken/;
use Data::Dumper;
extends 'Moose::Object';
our $VERSION = '0.01';
has 'conn_name' => (
is => 'ro',
isa => 'Str',
required => 1,
);
has 'adapter' => (
is => 'rw',
isa => 'DBIx::PgLink::Adapter',
);
has 'credentials' => (
is => 'rw',
isa => 'HashRef',
);
with 'DBIx::PgLink::RoleInstaller';
# functions spread out to fixed roles
with 'DBIx::PgLink::TypeMapper';
with 'DBIx::PgLink::Accessor';
with 'DBIx::PgLink::RemoteAction';
sub BUILD {
my $self = shift;
my $self_attr = shift;
# load main connection record
my $conn = $self->load_connection;
# include additional paths
$self->use_libs( $conn->{use_libs} );
# apply Connector roles to myself
$self->load_roles('Connector', $self);
# check adapter class
my $adapter_class = $self->require_class($conn->{adapter_class}, "DBIx::PgLink::Adapter");
# load remote credentials
$self->credentials( $self->load_credentials($conn->{logon_mode}) )
or croak "Access to the " . $self->conn_name . " is denied because no login-mapping exists";
# load attributes
my $attr_href = $self->load_attributes;
# pass weak reference to self
$attr_href->{connector} = $self;
weaken $attr_href->{connector};
# create adapter
trace_msg('INFO', "Creating adapter '$adapter_class' for connection " . $self->conn_name)
if trace_level>=2;
$self->adapter( $adapter_class->new($attr_href) );
# remove applied attributes from hash
# the rest belongs to DBI or Adapter role
$self->apply_attributes_to_adapter($attr_href, 1);
# apply adapter roles
$self->load_roles('Adapter', $self->adapter);
# set role attributes
# the rest belongs to DBI (and DBI->connect ignore unknown attributes)
$self->apply_attributes_to_adapter($attr_href);
return if $self_attr->{no_connect}; # for debugging and connection uninstall
# connect to remote database
$self->adapter->connect(
$conn->{data_source},
$self->credentials->{remote_user},
$self->credentials->{remote_password},
$attr_href
);
trace_msg('NOTICE', "Connection " . $self->conn_name . " established to data source $conn->{data_source}"
. " as '" . $self->credentials->{remote_user} . "'"
) if trace_level>=1;
return;
}
sub load_connection {
my $self = shift;
my $conn = pg_dbh->selectrow_hashref(<<'END_OF_SQL',
SELECT *
FROM dbix_pglink.connections
WHERE conn_name = $1
END_OF_SQL
{
Slice=>{},
array=>[qw/use_libs/],
},
$self->conn_name,
);
confess "Connection named '" . $self->conn_name . "' not found" unless $conn;
return $conn;
}
sub use_libs {
my $self = shift;
my $libs = shift or return;
for my $lib (@{$libs}) {
eval q/use lib $lib/; # change global @INC, not scoped
}
}
sub load_roles {
my $self = shift;
my $role_kind = shift;
my $object = shift;
unless ($object->does('DBIx::PgLink::RoleInstaller')) {
trace_msg('WARNING', "Object $object cannot install roles");
return;
}
my $roles_aref = pg_dbh->selectall_arrayref(<<'END_OF_SQL', {Slice=>{}}, $self->conn_name, $role_kind);
SELECT role_name
FROM dbix_pglink.roles
WHERE conn_name = $1
and role_kind = $2
and (local_user = '' or local_user = session_user)
ORDER BY role_seq, local_user
END_OF_SQL
my %seen;
my @role_names = grep { ! $seen{$_}++ } map { $_->{role_name} } @{$roles_aref};
$object->install_roles(@role_names);
}
sub require_class {
my $self = shift;
my $class_name = shift;
my $class_prefix = shift;
$class_name = $class_prefix . "::" . $class_name unless $class_name =~ /::/;
eval "require $class_name";
confess "Cannot use class '$class_name' for connection " . $self->conn_name, $@ if $@;
return $class_name;
}
sub load_credentials {
my $self = shift;
my $logon_mode = shift;
my $cred_sth = pg_dbh->prepare_cached(<<'END_OF_SQL', {no_cursor=>1});
SELECT local_user, remote_user, remote_password
FROM dbix_pglink.users
WHERE conn_name = $1
and local_user = $2
END_OF_SQL
my $local_user = pg_dbh->pg_session_user; # session_user because here we in 'security definer' PL/Perl function
$cred_sth->execute( $self->conn_name, $local_user );
my $cred = $cred_sth->fetchrow_hashref;
# mapping exists
if ($cred) {
trace_msg('NOTICE', "Remote credentials: local user '$local_user' mapped to remote user '$cred->{remote_user}'")
if trace_level >= 2;
return $cred if defined $cred;
}
trace_msg('NOTICE', "Remote credentials: no remote user mapping found for local user '$local_user'")
if trace_level >= 2;
return if $logon_mode eq 'deny'; # connection refused
if ($logon_mode eq 'empty') {
# connect with empty user/password
return {
local_user => '',
remote_user => '',
remote_password => '',
};
} elsif ($logon_mode eq 'current') {
# connect as current user without password
trace_msg('NOTICE', "Remote credentials: with local user name '$local_user' without password")
if trace_level >= 2;
return {
local_user => $local_user,
remote_user => $local_user,
remote_password => '',
};
} elsif ($logon_mode eq 'default') {
# connect as default user
my $rc = $cred_sth->execute($self->conn_name, ''); # has empty string as 'local_user'
my $cred = $cred_sth->fetchrow_hashref;
if ($cred) {
trace_msg('NOTICE', "Remote credentials: as default user '$cred->{remote_user}' with default password")
if trace_level >= 2;
return $cred;
}
}
return; # connection refused
}
sub load_attributes {
my $self = shift;
# user value override global value
my $attr_aref = pg_dbh->selectall_arrayref(<<'END_OF_SQL', {Slice=>{}}, $self->conn_name);
SELECT attr_name, attr_value
FROM dbix_pglink.attributes
WHERE conn_name = $1
AND (local_user = '' or local_user = session_user)
ORDER BY local_user
END_OF_SQL
my %attr = map { $_->{attr_name} => $_->{attr_value} } @{$attr_aref};
return \%attr;
}
sub apply_attributes_to_adapter {
my $self = shift;
my $attr = shift;
my $skip = shift;
while (my ($a, $v) = each %{$attr}) {
# NOTE: run-time role damages $self->meta->has_attribute
next unless $self->adapter->can($a); # requires attr accessor
unless ($skip) {
$self->adapter->$a($v);
}
delete $attr->{$a}; # remove applied attribute from hash
trace_msg('INFO', "Applied attibute $a = $v")
if trace_level >= 3;
}
}
1;
__END__
=pod
=head1 NAME
DBIx::PgLink::Connector - glue between Adapter, Accessors and PL/Perl
=head1 SYNOPSIS
See L<DBIx::PgLink>
=head1 ATTRIBUTES
=over
( run in 0.617 second using v1.01-cache-2.11-cpan-5837b0d9d2c )