Mojar-Mysql
view release on metacpan or search on metacpan
lib/Mojar/Mysql/Connector.pm view on Meta::CPAN
use Carp 'croak';
use File::Spec::Functions 'catfile';
use Mojar::ClassShare 'have';
sub import {
my ($pkg, %param) = @_;
my $caller = caller;
# Helpers
$param{-connector} //= 1 if exists $param{-dbh} and $param{-dbh};
if (exists $param{-connector} and my $cname = delete $param{-connector}) {
$cname = 'connector' if "$cname" eq '1';
no strict 'refs';
*{"${caller}::$cname"} = sub {
my $self = shift;
if (@_) {
$self->{$cname} = (@_ > 1) ? Mojar::Mysql::Connector->new(@_) : shift;
return $self;
}
return $self->{$cname} //= Mojar::Mysql::Connector->new;
};
if (exists $param{-dbh} and my $hname = delete $param{-dbh}) {
$hname = 'dbh' if "$hname" eq '1';
*{"${caller}::$hname"} = sub {
my $self = shift;
if (@_) {
$self->{$hname} = (@_ > 1) ? $self->$cname->connect(@_) : shift;
return $self;
}
return $self->{$hname}
if defined $self->{$hname} and $self->{$hname}->ping;
return $self->{$hname} = $self->$cname->connect;
};
}
}
# Global defaults
if (%param and %{$pkg->Defaults}) {
# Already have defaults => check unchanged
# Not interested in defaults of Defaults => use hash not methods
my $ps = join ':', map +($_ .':'. ($param{$_} // 'undef')),
sort keys %param;
my $ds = join ':', map +($_ .':'. ($pkg->Defaults->{$_} // 'undef')),
sort keys %{$pkg->Defaults};
die "Redefining class defaults for $pkg" unless $ps eq $ds;
}
@{$pkg->Defaults}{keys %param} = values %param if %param;
# Debugging
$pkg->trace($param{TraceLevel})
if exists $param{TraceLevel} and defined $param{TraceLevel};
}
# Class attribute
# Use a singleton object for holding use-time class defaults
have Defaults => sub { bless {} => ref $_[0] || $_[0] };
# Attributes
has quiesce_timeout => 500;
my @DbdFields = qw(RaiseError PrintError PrintWarn AutoCommit TraceLevel
mysql_auto_reconnect mysql_enable_utf8);
has RaiseError => 1;
has PrintError => 0;
has PrintWarn => 0;
has AutoCommit => 1;
has TraceLevel => 0;
has mysql_auto_reconnect => 0;
has mysql_enable_utf8 => 1;
my @ConFields = qw(label cnfdir cnf cnfgroup);
has 'label';
has cnfdir => '.';
has 'cnf';
has 'cnfgroup';
my @DbiFields = qw(driver host port schema user password);
has driver => 'mysql';
has 'host'; # eg 'localhost'
has 'port'; # eg 3306
has 'schema'; # eg 'test';
has 'user';
has 'password';
# Public methods
sub new {
my ($proto, %param) = @_;
# $proto may contain defaults to be cloned
# %param may contain defaults for overriding
my %defaults = ref $proto ? ( %{ ref($proto)->Defaults }, %$proto )
: %{$proto->Defaults};
delete $defaults{$_} for grep { ref $proto and /^dbh\./ } keys %defaults;
return Mojo::Base::new($proto, %defaults, %param);
}
sub connect {
my ($proto, @args) = @_;
my $class = ref $proto || $proto;
@args = $proto->dsn(@args) unless @args and $args[0] =~ /^DBI:/i;
my $dbh;
eval { $dbh = $class->SUPER::connect(@args) }
or do {
my $e = $@;
croak sprintf "Connection error\n%s\n%s", $proto->dsn_to_dump(@args), $e;
};
return $dbh;
}
sub connection {
my ($self, $tag) = @_; $tag //= 'connection';
return $self->{"dbh.$tag"} if ($self->{"dbh.$tag"} //= $self->connect)->ping;
return $self->{"dbh.$tag"} = $self->connect;
}
sub dsn {
my ($proto, %param) = @_;
my $param = $proto->new(%param);
my $cnf_txt = '';
if (my $cnf = $param->cnf) {
# MySQL .cnf file
$cnf .= '.cnf' unless $cnf =~ /\.cnf$/;
$cnf = catfile $param->cnfdir, $cnf if ! -r $cnf and defined $param->cnfdir;
croak "Failed to find/read .cnf file ($cnf)" unless -f $cnf and -r $cnf;
$cnf_txt = ';mysql_read_default_file='. $cnf;
lib/Mojar/Mysql/Connector.pm view on Meta::CPAN
security, and error handling. Supports easy use of credential (cnf) files, akin
to
mysql --defaults-file=credentials.cnf
It aims to reduce boilerplate, verbosity, mistakes, and parameter overload, but
above all it tries to make it quick and easy to Do The Right Thing.
As the name implies, the class provides connector objects -- containers for
storing and updating your connection parameters. When you call C<connect>, the
connector returns a handle created using its retained parameters plus any
call-time parameters passed. You don't however have to use connectors; for
simple usage it can be easier to use C<connect> directly from the class.
You can use a DSN tuple if you want to, but it's more readable and less
error-prone to specify your parameters either as a hash or by setting individual
attributes. Each call to C<connect> will then construct the DSN for you.
You can optionally import a helper method, called C<dbh> (or whatever name you
choose) so that you can focus even less on the connector/connection and more on
your code. The helper will cache your database handle and create a new one
automatically if the old one is destroyed or goes stale.
The fourth layer of convenience is provided by the added database handle
methods. Changing session variables (C<session_var>) is easy, listing only
genuine tables (C<real_tables>) is easy, and there's
L<more|/"DATABASE HANDLE METHODS">.
=head1 CLASS METHODS
=head2 C<new>
Mojar::Mysql::Connector->new(label => 'cache', cnf => 'myuser_localhost');
Constructor for a connector, based on class defaults. Takes a (possibly empty)
list of parameters. Returns a connector (Mojar::Mysql::Connector object) the
defaults of which are those of the class overlaid with those passed to the
constructor.
=head2 C<connect>
$dbh1 = Mojar::Mysql::Connector->connect(
'DBI:mysql:test;host=localhost', 'admin', 's3cr3t', {}
);
$dbh2 = Mojar::Mysql::Connector->connect(
schema => 'test',
host => 'localhost',
user => 'admin',
password => 's3cr3t'
);
$dbh3 = Mojar::Mysql::Connector->connect;
Constructor for a connection (db handle). If the first element passed has
prefix C<DBI:> then it is a DSN string (the traditional route) and so is passed
straight to C<DBI::connect> (L<DBI/"DBI Class Methods">). Otherwise a DSN is
first constructed. (The DSN tuple does not persist and is constructed fresh on
each call to C<connect>.)
In the examples above, $dbh1 and $dbh2 are not equivalent because the second
connector would also incorporate module defaults and use-time parameters, in
addition to the passed parameters. So, for instance, mysql_enable_utf8 might be
included in the second connector.
=head2 C<dsn>
@dbi_args = Mojar::Mysql::Connector->dsn(
cnf => 'myuser_localhost', schema => 'test'
);
A convenience method used internally by connect. Takes a (possibly empty)
parameter hash. Returns a four-element array to pass to C<DBI::connect>,
constructed from the default values of the constructing class overlaid with any
additional parameters passed. The main reason for using this method is when you
want to use L<DBI> (or another DSN-consumer) directly but want to avoid the
inconvenience of assembling sensible parameters yourself.
use DBI;
use Mojar::Mysql::Connector (
cnfdir => '/srv/myapp/cfg',
cnf => 'myuser_localhost'
);
my $dbh = DBI->connect(
Mojar::Mysql::Connector->dsn(schema => 'foo', AutoCommit => 0)
);
=head2 C<dsn_to_dump>
warn(Mojar::Mysql::Connector->dsn_to_dump(@dsn));
A convenience method used internally to chop up the four-element array
(particularly the fourth element, the hash ref) into something more readable,
for error reporting and debugging. Tries to occlude any password within.
=head2 C<Defaults>
say Mojar::Util::dumper(Mojar::Mysql::Connector->Defaults);
Provides access to the class defaults in order to help debugging.
=head1 OBJECT METHODS
=head2 C<new>
$connector->new(label => 'transaction', AutoCommit => 0);
Constructor for a connector based on an existing connector. Takes a
(possibly empty) parameter hash. Returns a connector (Mojar::Mysql::Connector
object) the defaults of which are those of the given connector overlaid with
any arguments passed in.
=head2 C<connect>
$dbh = $connector->connect(
'DBI:mysql:test;host=localhost', 'admin', 's3cr3t', {});
$dbh = $connector->connect(AutoCommit => 0);
$dbh = $connector->connect;
Constructor for a connection (db handle). If the first element passed has
prefix C<DBI:> then it is a DSN string (the traditional route) and so is passed
straight to C<DBI::connect> (L<DBI/"DBI Class Methods">) without consideration
of the connector's existing parameters. Otherwise a DSN is first constructed.
(The DSN tuple does not persist and is constructed fresh on each call to
C<connect>.)
=head2 Attributes
All connector parameters are implemented as attributes with exactly the same
spelling. So for example you can
$connector->RaiseError(undef); # disable RaiseError
$connector->mysql_enable_utf8(0); # disable mysql_enable_utf8
The attributes, with their coded defaults, are
RaiseError => 1
PrintError => 0
PrintWarn => 0
AutoCommit => 1
TraceLevel => 0
mysql_auto_reconnect => 0
mysql_enable_utf8 => 1
label
cnfdir => '.'
cnf
cnfgroup
driver => 'mysql'
host
port
schema
user
password
In addition, any L<DBD::mysql> attributes (beginning "mysql_") are passed
through to the driver.
$dbh = $connector->connect(mysql_skip_secure_auth => 1);
=head1 DATABASE HANDLE METHODS
=head2 C<selectall_arrayref_hashrefs>
$_->{Command} ne 'Sleep' and say $_->{User}
for $dbh->selectall_arrayref_hashrefs(q{SHOW FULL PROCESSLIST});
printf '%s can select: %s', $_->{User}, $_->{Select_priv}
for $dbh->selectall_arrayref_hashrefs(q{SELECT * FROM mysql.user});
Returns an arrayref of hashrefs, each hashref being a record of the resultset.
The keys of the hashref are the column/field names of the record. This is
simply minimal sugar on the selectall_arrayref method provided by DBI; it saves
the little boilerplate of "Slice => {}". If you want to pass bound values then
you need undef as the second argument.
printf '%s can select: %s', $_->{User}, $_->{Select_priv}
for $dbh->selectall_arrayref_hashrefs(
q{SELECT * FROM mysql.user WHERE User != ?}, undef, 'root'
);
=head2 C<mysqld_version>
if ($dbh->mysqld_version =~ /^5.0/) {...}
Returns the version of the db server connected to; the version part of
mysqld --version
=head2 C<thread_id>
$tmp_table_name = q{ConcurrencySafe_}. $dbh->thread_id;
Utility method to get the connection's thread identifier (unique on that db
server at that point in time).
=head2 C<current_schema>
$schema_name = $dbh->current_schema;
The same string as given by
lib/Mojar/Mysql/Connector.pm view on Meta::CPAN
=head2 C<enable_quotes>
The inverse of C<disable_quotes>.
=head2 C<disable_fk_checks>
$dbh->disable_fk_checks->do(q{DROP TABLE ...});
Disable foreign key checks. Lasts the lifetime of the connection.
=head2 C<enable_fk_checks>
The inverse of C<disable_fk_checks>.
=head2 C<schemata>
for my $schema (@{$dbh->schemata}) {...}
Returns an arrayref of schema names, similar to
SHOW DATABASES
but does not get fooled by C<lost+found>.
=head2 C<tables_and_views>
foreach my $table ($dbh->tables_and_views) {...}
Returns a hashref of table and view names, similar to
SHOW TABLES
See also L<DBI/tables>.
=head2 C<real_tables>
for my $table (@{$dbh->real_tables}) {...}
Returns a arrayref of real table names, similar to
SHOW TABLES
but excluding views.
=head2 C<views>
for my $view (@{$dbh->views}) {...}
Returns a arrayref of view names, similar to
SHOW TABLES
but excluding real tables.
=head1 CHARACTER ENCODINGS
To read/store characters encoded as non-ASCII, non-UTF8, you must disable
handling of UTF-8.
$connector = Mojar::Mysql::Connector->new(mysql_enable_utf8 => 0);
This is essential, for example, when fetching high-latin (eg non-ASCII 8859-1)
characters.
=head1 DEBUGGING
You can enable DBI trace logging at use-time:
use Mojar::Mysql::Connector (TraceLevel => '3|CON');
and since you have access to all of L<DBI>, you can set tracing using the method
$dbh->trace('3|CON');
...
$dbh->trace(0);
or by using the attribute
{
local $dbh->{TraceLevel} = '3|CON';
...
}
To set tracing for all handles, use the class method instead. See
L<DBI/TRACING>.
=head1 SUPPORT
=head2 Homepage
L<http://niczero.github.com/mojar-mysql>
=head2 Wiki
L<http://github.com/niczero/mojar/wiki>
=head1 RATIONALE
This class was first used in production in 2002. Before then, connecting to
databases was ugly and annoying. Setting C<RaiseError> upon every connect was
clumsy and irritating. In development teams it was tricky checking that all
code was using sensible parameters and awkward ensuring use of risky parameters
(eg C<disable_fk_checks>) was kept local. As use of this class spread, it had
to be useful in persistent high performance applications as well as many small
scripts and the occasional commandline. More recently I discovered the Joy of
L<Mojolicious> and employed L<Mojo::Base> to remove unwanted complexity and
eliminate a long-standing bug. The ensuing fun motivated an extensive rewrite,
fixing broken documentation, improved the tests (thank you travis), and we have,
finally, its public release. As noted below there are now quite a few smart
alternatives out there but I'm still surprised how little support there is for
keeping passwords out of your codebase and helping you manage multiple
connections.
=head1 COPYRIGHT AND LICENCE
Copyright (C) 2002--2017, Nic Sandfield.
This program is free software, you can redistribute it and/or modify it under
the terms of the Artistic License version 2.0.
( run in 1.628 second using v1.01-cache-2.11-cpan-6aa56a78535 )