Anansi-DatabaseComponent

 view release on metacpan or  search on metacpan

lib/Anansi/DatabaseComponent.pm  view on Meta::CPAN

package Anansi::DatabaseComponent;


=head1 NAME

Anansi::DatabaseComponent - A manager template for database drivers.

=head1 SYNOPSIS

    package Anansi::Database::Example;

    use base qw(Anansi::DatabaseComponent);

    sub connect {
        my ($self, $channel, %parameters) = @_;
        return $self->SUPER::connect(
            undef,
            INPUT => [
                'some text',
                {
                    NAME => 'someParameter',
                }, {
                    INPUT => [
                        'more text',
                        {
                            NAME => 'anotherParameter',
                        },
                        'yet more text',
                    ]
                }, {
                    DEFAULT => 'abc',
                    NAME => 'yetAnotherParameter',
                },
            ],
            (%parameters),
        );
    }

    sub validate {
        my ($self, $channel, %parameters) = @_;
        $parameters{DRIVER} = 'Example';
        return Anansi::DatabaseComponent::validate(undef, %parameters);
    }

    Anansi::DatabaseComponent::addChannel('Anansi::Database::Example', 'AUTOCOMMIT' => 'Anansi::DatabaseComponent::autocommit');
    Anansi::DatabaseComponent::addChannel('Anansi::Database::Example', 'COMMIT' => 'Anansi::DatabaseComponent::commit');
    Anansi::DatabaseComponent::addChannel('Anansi::Database::Example', 'CONNECT' => 'connect');
    Anansi::DatabaseComponent::addChannel('Anansi::Database::Example', 'DISCONNECT' => 'Anansi::DatabaseComponent::disconnect');
    Anansi::DatabaseComponent::addChannel('Anansi::Database::Example', 'FINISH' => 'Anansi::DatabaseComponent::finish');
    Anansi::DatabaseComponent::addChannel('Anansi::Database::Example', 'HANDLE' => 'Anansi::DatabaseComponent::handle');
    Anansi::DatabaseComponent::addChannel('Anansi::Database::Example', 'PREPARE' => 'Anansi::DatabaseComponent::prepare');
    Anansi::DatabaseComponent::addChannel('Anansi::Database::Example', 'ROLLBACK' => 'Anansi::DatabaseComponent::rollback');
    Anansi::DatabaseComponent::addChannel('Anansi::Database::Example', 'STATEMENT' => 'Anansi::DatabaseComponent::statement');
    Anansi::DatabaseComponent::addChannel('Anansi::Database::Example', 'VALIDATE_AS_APPROPRIATE' => 'validate'); 

    1;

    package main;

    use Anansi::Database;

    my $database = Anansi::Database->new();
    my $component = $database->addComponent(undef,
        DRIVER => 'Example',
    );
    if(defined($component)) {
        if($database->connect(
            undef,
            $component,
            someParameter => 'some data',
            anotherParameter => 'more data',
            yetAnotherParameter => 'further data',
        )) {
            my $result = $database->statement(
                undef,
                $component,
                SQL => 'SELECT someThing FROM someTable where modified = ?;',
                INPUT => [
                    {
                        NAME => 'modified',
                    },
                ],
                modified => '2011-02-22 00:21:46',
            );
            if(!defined($result)) {
            } elsif(ref($result) =~ /^ARRAY$/i) {
                foreach my $record (@{$result}) {
                    next if(ref($record) !~ /^HASH$/i);
                    print 'someThing: "'.${$record}{someThing}.'"'."\n";
                }
            }
        }
    }

    1;

=head1 DESCRIPTION

Manages a database connection providing generic processes to allow it's opening,
closing and various SQL interactions.  Uses L<Anansi::Actor>.

=cut


our $VERSION = '0.05';

use base qw(Anansi::Component);

use Anansi::Actor;


=head1 METHODS

=cut

lib/Anansi/DatabaseComponent.pm  view on Meta::CPAN

Attempts to use the supplied parameters to assign values to a SQL statement that
has already been prepared to accept them.  Returns B<0> I<(zero)> on failure and
the database statement handle on success.

=cut


sub bind {
    my ($self, %parameters) = @_;
    return 0 if(!defined($parameters{HANDLE}));
    return 0 if(!defined($parameters{INPUT}));
    return 0 if(ref($parameters{INPUT}) !~ /^ARRAY$/i);
    return 0 if(!defined($parameters{VALUE}));
    return 0 if(ref($parameters{VALUE}) !~ /^HASH$/i);
    my $index = 1;
    foreach my $input (@{$parameters{INPUT}}) {
        if(defined(${$parameters{VALUE}}{${$input}{NAME}})) {
            if(defined(${$input}{TYPE})) {
                $parameters{HANDLE}->bind_param($index, ${$parameters{VALUE}}{${$input}{NAME}}, ${$input}{TYPE});
            } else {
                $parameters{HANDLE}->bind_param($index, ${$parameters{VALUE}}{${$input}{NAME}});
            }
        } elsif(defined(${$input}{DEFAULT})) {
            if(defined(${$input}{TYPE})) {
                $parameters{HANDLE}->bind_param($index, ${$input}{DEFAULT}, ${$input}{TYPE});
            } else {
                $parameters{HANDLE}->bind_param($index, ${$input}{DEFAULT});
            }
        } elsif(defined(${$input}{TYPE})) {
            $parameters{HANDLE}->bind_param($index, '', ${$input}{TYPE});
        } else {
            $parameters{HANDLE}->bind_param($index, '');
        }
        $index++;
    }
    return $parameters{HANDLE};
}


=head2 binding

    if(1 == Anansi::DatabaseComponent::binding($OBJECT));

    if(1 == $OBJECT->binding());

=over 4

=item self I<(Blessed Hash B<or> String, Required)>

Either an object or a string of this namespace.

=item parameters I<(Array, Optional)>

An array of hashes.  Each hash should contain a I<NAME> key with a string value.

=back

Verifies that the supplied parameters are all hashes and that they each contain
a I<NAME> key with a string value.  Returns B<1> I<(one)> when validity is
confirmed and B<0> I<(zero)> when an invalid structure is determined.  Used to
validate the I<INPUT> parameter of the B<bind> method.

=cut


sub binding {
    my ($self, @parameters) = @_;
    foreach my $parameter (@parameters) {
        return 0 if(ref($parameter) !~ /^HASH$/i);
        return 0 if(!defined(${$parameter}{NAME}));
        return 0 if(ref(${$parameter}{NAME}) !~ /^$/);
        return 0 if(${$parameter}{NAME} !~ /^[a-zA-Z_]+(\s*[a-zA-Z0-9_]+)*$/);
    }
    return 1;
}


=head2 commit

    if(1 == Anansi::DatabaseComponent::commit($OBJECT, undef));

    if(1 == Anansi::DatabaseComponent::channel($OBJECT, 'COMMIT'));

    if(1 == $OBJECT->commit(undef));

    if(1 == $OBJECT->channel('COMMIT'));

=over 4

=item self I<(Blessed Hash B<or> String, Required)>

Either an object or a string of this namespace.

=item channel I<(String, Required)>

The abstract identifier of a subroutine.

=item parameters I<(Hash, Optional)>

Named parameters.

=back

Attempts to perform a database commit.  Returns B<1> I<(one)> on success and
B<0> I<(zero)> on failure.

=cut


sub commit {
    my ($self, $channel, %parameters) = @_;
    return 0 if(ref($self) =~ /^(|ARRAY|CODE|FORMAT|GLOB|HASH|IO|LVALUE|REF|Regexp|SCALAR|VSTRING)$/i);
    return 0 if(!defined($self->{HANDLE}));
    return 1 if($self->autocommit());
    my $commit;
    eval {
        $commit = $self->{HANDLE}->commit();
        1;
    } or do {
        $self->rollback();
        return 0;

lib/Anansi/DatabaseComponent.pm  view on Meta::CPAN

=over 4

=item self I<(Blessed Hash, Required)>

An object of this namespace.

=item channel I<(String, Required)>

The abstract identifier of a subroutine.

=item parameters I<(Hash, Required)>

Named parameters.

=over 4

=item HANDLE I<(DBI::db, Optional)>

The database handle of an existing database connection.  Used in preference to
the I<INPUT> parameter.

=item INPUT I<(Array B<or> Scalar, Optional)>

An array or single value containing a description of each parameter in the order
that it is passed to the database driver's I<connect> method.  Used when the
I<HANDLE> parameter does not exist.

=over 4

=item I<(Non-Hash)>

An element that does not contain a hash value will be used as the corresponding
I<connect> method's parameter value.

=item I<(Hash)>

An element that contains a hash value is assumed to be a description of how to
generate the corresponding I<connect> method's parameter value.  when a value
can not be generated, an B<undef> value will be used.

=over 4

=item DEFAULT I<(Optional)>

The value to use if no other value can be determined.

=item INPUT I<(Array B<or> Scalar, Optional)>

Contains a structure like that given in I<INPUT> above with the exception that
any further I<INPUT> keys will be ignored.  As this key is only valid when
I<NAME> is undefined and I<REF> either specifies a string or a hash, it's value
will be either a concatenation of all the calculated strings or a hash
containing all of the specified keys and values.

=item NAME I<(String, Optional)>

The name of the parameter that contains the value to use.

=item REF I<(Array B<or> String, Optional)>

The data types used to validate the value to use.

=back

=back

=back

=back

Either uses an existing database connection or attempts to perform a database
connection using the supplied parameters.  Returns B<1> I<(one)> on success and
B<0> I<(zero)> on failure.

=cut


sub connect {
    my ($self, $channel, %parameters) = @_;
    return 0 if(ref($self) =~ /^(|ARRAY|CODE|FORMAT|GLOB|HASH|IO|LVALUE|REF|Regexp|SCALAR|VSTRING)$/i);
    $self->disconnect();
    if(defined($parameters{HANDLE})) {
        return 0 if(ref($parameters{HANDLE}) !~ /^DBI::db$/);
        $self->{HANDLE} = $parameters{HANDLE};
        $self->{MANAGE_HANDLE} = 0;
    } elsif(!defined($parameters{INPUT})) {
        return 0;
    } elsif(ref($parameters{INPUT}) !~ /^ARRAY$/i) {
        return 0;
    } else {
        my @inputs;
        foreach my $input (@{$parameters{INPUT}}) {
            if(ref($input) !~ /^HASH$/i) {
                push(@inputs, $input);
                next;
            }
            my $value = undef;
            $value = ${$input}{DEFAULT} if(defined(${$input}{DEFAULT}));
            if(!defined(${$input}{NAME})) {
                if(!defined(${$input}{INPUT})) {
                } elsif(ref(${$input}{INPUT}) !~ /^ARRAY$/i) {
                } elsif(!defined(${$input}{REF})) {
                } elsif(ref(${$input}{REF}) !~ /^$/i) {
                } elsif('' eq ${$input}{REF}) {
                    my @subInputs;
                    for(my $index = 0; $index < scalar(@{${$input}{INPUT}}); $index++) {
                        if(ref(${${$input}{INPUT}}[$index]) =~ /^$/i) {
                            push(@subInputs, ${${$input}{INPUT}}[$index]);
                            next;
                        } elsif(ref(${${$input}{INPUT}}[$index]) !~ /^HASH$/) {
                            next;
                        }
                        my $subValue = '';
                        $subValue = ${${${$input}{INPUT}}[$index]}{DEFAULT} if(defined(${${${$input}{INPUT}}[$index]}{DEFAULT}));
                        if(!defined(${${${$input}{INPUT}}[$index]}{NAME})) {
                        } elsif(ref(${${${$input}{INPUT}}[$index]}{NAME}) !~ /^$/) {
                        } elsif(defined($parameters{${${${$input}{INPUT}}[$index]}{NAME}})) {
                            if(!defined(${${${$input}{INPUT}}[$index]}{REF})) {
                                $subValue = $parameters{${${${$input}{INPUT}}[$index]}{NAME}} if('' eq ref($parameters{${${${$input}{INPUT}}[$index]}{NAME}}));
                            } elsif(ref(${${${$input}{INPUT}}[$index]}{REF}) !~ /^$/) {
                            } elsif('' ne ${${${$input}{INPUT}}[$index]}{REF}) {

lib/Anansi/DatabaseComponent.pm  view on Meta::CPAN

    } else {
        eval {
            $handle = $self->{HANDLE}->prepare($parameters{SQL});
            1;
        } or do {
            $self->rollback();
            return 0;
        };
        my $questionMarks = $parameters{SQL};
        my $questionMarks = $questionMarks =~ s/\?/$1/sg;
        if(0 == $questionMarks) {
            if(defined($parameters{INPUT})) {
                $self->rollback();
                return 0;
            }
        } elsif(!defined($parameters{INPUT})) {
            $self->rollback();
            return 0;
        } elsif(ref($parameters{INPUT}) !~ /^ARRAY$/i) {
            $self->rollback();
            return 0;
        } elsif(scalar(@{$parameters{INPUT}}) != $questionMarks) {
            $self->rollback();
            return 0;
        } else {
            if(!$self->bind(
                HANDLE => $handle,
                INPUT => $parameters{INPUT},
                VALUE => \%parameters,
            )) {
                $self->rollback();
                return 0;
            }
        }
    }
    eval {
        $handle->execute();
        1;
    } or do {
        $handle->rollback();
        return 0;
    };
    if(!defined($handle->{NUM_OF_FIELDS})) {
        return 1;
    } elsif(undef == $handle->{NUM_OF_FIELDS}) {
        return 1;
    } elsif(0 == $handle->{NUM_OF_FIELDS}) {
        return 1;
    }
    my $result = [];
    while(my $row = $handle->fetchrow_hashref()) {
        push(@{$result}, $row);
    }
    $handle->finish() if(!$prepared);
    return $result;
}

Anansi::DatabaseComponent::addChannel('Anansi::DatabaseComponent', 'STATEMENT' => 'statement');


=head2 validate

    if(1 == Anansi::DatabaseComponent::validate($OBJECT, undef));

    if(1 == Anansi::DatabaseComponent::channel($OBJECT, 'VALIDATE_AS_APPROPRIATE'));

    if(1 == Anansi::DatabaseComponent->validate(undef, DRIVERS => ['some::driver::module', 'anotherDriver']));

    if(1 == Anansi::DatabaseComponent->channel('VALIDATE_AS_APPROPRIATE'));

    if(1 == $OBJECT->validate(undef, DRIVER => 'Example'));

    if(1 == $OBJECT->channel('VALIDATE_AS_APPROPRIATE', DRIVER => 'Example'));

    if(1 == Anansi::DatabaseComponent->validate(undef, DRIVER => 'Example', DRIVERS => 'some::driver'));

    if(1 == Anansi::DatabaseComponent->channel('VALIDATE_AS_APPROPRIATE', DRIVER => 'Example'));

=over 4

=item self I<(Blessed Hash B<or> String, Required)>

Either an object or a string of this namespace.

=item channel I<(String, Required)>

The abstract identifier of a subroutine.

=item parameters I<(Hash, Optional)>

Named parameters.

=over 4

=item DRIVER I<(String, Optional)>

Either the namespace of a database driver or the name of a database driver that
should be used.

=item DRIVERS I<(Array B<or> String, Optional)>

An array of strings or a single string containing either the namespace of a
valid database driver or the name of a database driver that should be looked for
among the installed modules.

=item HANDLE I<DBI::db, Optional>

An existing database connection handle.

=back

=back

Generic validation for whether a database should be handled by a component.  If
the driver name is supplied then an attempt will be made to use that driver as
long as it matches any of the acceptable B<DRIVERS>, otherwise one of the
acceptable B<DRIVERS> will be tried or a generic driver if none have been
supplied.  Returns B<1> I<(one)> for valid and B<0> I<(zero)> for invalid.

=cut


sub validate {
    my ($self, $channel, %parameters) = @_;
    my $package = $self;
    $package = ref($self) if(ref($self) !~ /^$/);
    my %modules = Anansi::Actor->modules();
    return 0 if(!defined($modules{'Bundle::DBI'}));
    my $HANDLE_DRIVER;
    if(defined($parameters{HANDLE})) {
        return 0 if(ref($parameters{HANDLE}) !~ /^DBI::db$/);
        my $driver = $parameters{HANDLE}->get_info(17);
        return 0 if(!defined($driver));
        $HANDLE_DRIVER = $driver;
    }
    if(!defined($parameters{DRIVER})) {
        if(defined($parameters{DRIVERS})) {
            $parameters{DRIVERS} = [( $parameters{DRIVERS} )] if(ref($parameters{DRIVERS}) =~ /^$/);
            return 0 if(ref($parameters{DRIVERS}) !~ /^ARRAY$/i);
            if(defined($HANDLE_DRIVER)) {
                foreach my $DRIVER (@{$parameters{DRIVERS}}) {
                    return 0 if(ref($DRIVER) !~ /^$/);
                    return 1 if($DRIVER eq $HANDLE_DRIVER);
                }
            } else {
                my %reduced = map { lc($_) => $modules{$_} } (keys(%modules));
                foreach my $DRIVER (@{$parameters{DRIVERS}}) {
                    return 0 if(ref($DRIVER) !~ /^$/);
                    return 1 if(defined($modules{$DRIVER}));
                    return 1 if(defined($modules{'DBD::'.$DRIVER}));
                    return 1 if(defined($modules{'Bundle::DBD::'.$DRIVER}));
                    return 1 if(defined($reduced{lc($DRIVER)}));
                    return 1 if(defined($reduced{lc('DBD::'.$DRIVER)}));
                    return 1 if(defined($reduced{lc('Bundle::DBD::'.$DRIVER)}));
                }
            }
            return 0;
        } elsif(defined($HANDLE_DRIVER)) {
        } elsif(!defined($modules{'Bundle::DBD'})) {
            return 0;
        }
    } elsif(ref($parameters{DRIVER}) !~ /^$/) {
        return 0;
    } elsif(defined($parameters{DRIVERS})) {
        $parameters{DRIVERS} = [( $parameters{DRIVERS} )] if(ref($parameters{DRIVERS}) =~ /^$/);
        return 0 if(ref($parameters{DRIVERS}) !~ /^ARRAY$/i);
        my %DRIVERS;
        $DRIVERS{$parameters{DRIVER}} = 1;
        $DRIVERS{'DBD::'.$parameters{DRIVER}} = 1;
        $DRIVERS{'Bundle::DBD::'.$parameters{DRIVER}} = 1;
        $DRIVERS{lc($parameters{DRIVER})} = 1;
        $DRIVERS{lc('DBD::'.$parameters{DRIVER})} = 1;
        $DRIVERS{lc('Bundle::DBD::'.$parameters{DRIVER})} = 1;
        my $found = 0;
        foreach my $DRIVER (@{$parameters{DRIVERS}}) {
            return 0 if(ref($DRIVER) !~ /^$/);
            $found = 1;
            last if(defined($DRIVERS{$DRIVER}));
            last if(defined($DRIVERS{'DBD::'.$DRIVER}));
            last if(defined($DRIVERS{'Bundle::DBD::'.$DRIVER}));
            last if(defined($DRIVERS{lc($DRIVER)}));
            last if(defined($DRIVERS{lc('DBD::'.$DRIVER)}));
            last if(defined($DRIVERS{lc('Bundle::DBD::'.$DRIVER)}));
            $found = 0;
        }
        return 0 if(!$found);
        if(defined($HANDLE_DRIVER)) {
            foreach my $DRIVER (@{$parameters{DRIVERS}}) {
                return 0 if(ref($DRIVER) !~ /^$/);
                return 1 if($DRIVER eq $HANDLE_DRIVER);
            }
        } else {
            my %reduced = map { lc($_) => $modules{$_} } (keys(%modules));
            foreach my $DRIVER (@{$parameters{DRIVERS}}) {
                return 1 if(defined($modules{$DRIVER}));
                return 1 if(defined($modules{'DBD::'.$DRIVER}));
                return 1 if(defined($modules{'Bundle::DBD::'.$DRIVER}));
                return 1 if(defined($reduced{lc($DRIVER)}));
                return 1 if(defined($reduced{lc('DBD::'.$DRIVER)}));
                return 1 if(defined($reduced{lc('Bundle::DBD::'.$DRIVER)}));
            }
        }
        return 0;
    } elsif(defined($modules{$parameters{DRIVER}})) {
        if(defined($HANDLE_DRIVER)) {
            return 0 if($parameters{DRIVER} ne $HANDLE_DRIVER);
        }
    } elsif(defined($modules{'DBD::'.$parameters{DRIVER}})) {
        if(defined($HANDLE_DRIVER)) {
            return 0 if('DBD::'.$parameters{DRIVER} ne $HANDLE_DRIVER);
        }
    } elsif(!defined($modules{'Bundle::DBD::'.$parameters{DRIVER}})) {
        my %reduced = map { lc($_) => $modules{$_} } (keys(%modules));
        if(defined($reduced{lc($parameters{DRIVER})})) {
            if(defined($HANDLE_DRIVER)) {
                return 0 if(lc($parameters{DRIVER}) ne lc($HANDLE_DRIVER));
            }
        } elsif(defined($reduced{lc('DBD::'.$parameters{DRIVER})})) {
            if(defined($HANDLE_DRIVER)) {
                return 0 if(lc('DBD::'.$parameters{DRIVER}) ne lc($HANDLE_DRIVER));
            }
        } elsif(defined($reduced{lc('Bundle::DBD::'.$parameters{DRIVER})})) {
            if(defined($HANDLE_DRIVER)) {
                return 0 if(lc('Bundle::DBD::'.$parameters{DRIVER}) ne lc($HANDLE_DRIVER));
            }
        } else {
            return 0;
        }
    }
    return 1;
}

Anansi::DatabaseComponent::addChannel('Anansi::DatabaseComponent', 'VALIDATE_AS_APPROPRIATE' => 'validate');


=head1 NOTES

This module is designed to make it simple, easy and quite fast to code your
design in perl.  If for any reason you feel that it doesn't achieve these goals
then please let me know.  I am here to help.  All constructive criticisms are
also welcomed.

=cut


=head1 AUTHOR

Kevin Treleaven <kevin I<AT> treleaven I<DOT> net>

=cut


1;



( run in 0.836 second using v1.01-cache-2.11-cpan-e93a5daba3e )