App-Framework
view release on metacpan or search on metacpan
lib/App/Framework/Feature/Sql.pm view on Meta::CPAN
my $dbh = $this->dbh() ;
my $trace_file = $this->trace_file() ;
# Update trace level
$this->_set_trace($dbh, $trace, $trace_file) ;
}
return $trace ;
}
#----------------------------------------------------------------------------
=item B<trace_file(@args)>
Change trace file
=cut
sub trace_file
{
my $this = shift ;
my (@args) = @_ ;
# Update value
## my $trace_file = $this->SUPER::trace_file(@args) ;
my $trace_file = $this->field_access('trace_file', @args) ;
if (@args)
{
my $dbh = $this->dbh() ;
my $trace = $this->trace() ;
# Update trace level
$this->_set_trace($dbh, $trace, $trace_file) ;
}
return $trace_file ;
}
#----------------------------------------------------------------------------
=item B<connect(%args)>
Connects to database. Either uses pre-set values for user/password/database,
or can use optionally specified args
=cut
sub connect
{
my $this = shift ;
my (%args) = @_ ;
$this->set(%args) ;
$this->_dbg_prt(["Sql::connect() => ",$this->database(),"\n"]) ;
$this->throw_fatal("SQL connect error: no database specified") unless $this->database() ;
$this->throw_fatal("SQL connect error: no host specified") unless $this->host() ;
my $dbh ;
eval
{
# Disconnect if already connected
$this->disconnect() ;
# Connect
$dbh = DBI->connect("DBI:mysql:database=".$this->database().
";host=".$this->host(),
$this->user(), $this->password(),
{'RaiseError' => 1}) or $this->throw_fatal( $DBI::errstr ) ;
$this->dbh($dbh) ;
};
if ($@)
{
$this->throw_fatal("SQL connect error: $@", 1000) ;
}
my $dbh_dbg = $dbh || "" ;
$this->_dbg_prt([" + connected dbh=$dbh_dbg : db=",$this->database()," user=",$this->user()," pass=",$this->password(),"\n"]) ;
return $dbh ;
}
#----------------------------------------------------------------------------
=item B<disconnect()>
Disconnect from database (if connected)
=cut
sub disconnect
{
my $this = shift ;
my $dbh = $this->dbh() ;
my $dbh_dbg = $dbh || "" ;
$this->_dbg_prt(["Sql::disconnect() => dbh=$dbh_dbg\n"]) ;
eval
{
if ($dbh)
{
$this->dbh(0) ;
}
};
if ($@)
{
$this->throw_fatal("SQL disconnect error: $@", 1000) ;
}
$this->_dbg_prt([" + disconnected\n"]) ;
}
#----------------------------------------------------------------------------
=item B<sth_create($name, $spec)>
Prepare a named SQL query & store it for later execution by query_sth()
Name is saved as $name. Certain names are 'special':
ins* - Create an 'insert' type command
upd* - Create an 'update' type command
sel* - Create a 'select' type command
check* - Create a 'select' type command
The $spec is either a SCALAR or HASH ref
If $spec is a SCALAR then it is in the form of sql. Note, when the query is executed the values
(if required) must be specified.
If $spec is a HASH ref then it can contain the following fields:
'cmd' => Command type: 'insert', 'update', 'select'
'vars' => ARRAY ref list of variable names (used for 'insert', 'update')
'vals' => Provides values to be used in the query (no extra values need to be specified). HASH ref or ARRAY ref.
HASH ref - the hash is used to look up the values using the 'vars' names
ARRAY ref - list of values (or refs to values)
NOTE: If insufficient values are provided for the query, then the remaining values must be specified in the query call
'sql' => Sql string.
NOTE: Depending on the command type, if the command is not specified then a default will be prepended to this string.
'table' => Overrides the object table setting for this query
'limit' => Sets the limit on the number of results
'group' => Specify group by string
'where' => Where clause. String or HASH ref.
String - specify sql for where clause (can omit 'WHERE' prefix)
HASH ref - specify where clause as HASH:
'sql' => Used to specify more complicated where clauses (e.g. '`pid`=? AND `channel`=?')
'vars' => ARRAY ref list of variable names (used for 'where'). If no 'sql' is specified, then the where clause
is created by ANDing the vars together (e.g. [qw/pid channel/] gives '`pid`=? AND `channel`=?')
'vals' => Provides values to be used in the query (no extra values need to be specified). HASH ref or ARRAY ref.
EXAMPLES
The following are all (almost) equivalent:
$sql->sth_create('check', {
'table' => '$table',
'limit' => 1,
'where' => {
'sql' => '`pid`=? AND `channel`=?',
'vars' => [qw/pid channel/],
'vals' => \%sql_vars
}) ;
$sql->sth_create('check2', {
'table' => '$table',
'limit' => 1,
'where' => '`pid`=? AND `channel`=?',# need to pass in extra params to query method
}}) ;
$sql->sth_create('check3', "SELECT * FROM `$table` WHERE `pid`=? AND `channel`=? LIMIT 1") ;
$sql->sth_create('select', "WHERE `pid`=? AND `channel`=? LIMIT 1") ;
They are then used as:
$sql->sth_query('check') ; # already given it's parameters
$sql->sth_query('check2', $pid, $channel) ;
$sql->sth_query('check3', $pid, $channel) ;
$sql->sth_query('select', $pid, $channel) ;
=cut
sub sth_create
{
my $this = shift ;
my ($name, $spec) = @_ ;
my @vals ;
## Set up vars
my %vars = $this->vars() ;
$vars{'sqlvar_select_varlist'} = '*' ;
$vars{'sqlvar_query'} = $CMD_SQL{'query'}{'format'} ;
$vars{'@sqlvar_query'} = $CMD_SQL{'query'}{'vals'} ;
# Default table name
$vars{'sqlvar_table'} = $vars{'table'} ;
$this->_dbg_prt(["sth_create($name)\n"], 2) ;
## Guess command based on name
my $cmd = $this->_sql_cmd($name) ;
$this->_dbg_prt([" + cmd=$cmd\n"], 2) ;
## Handle hash
if (ref($spec) eq 'HASH')
{
my %spec = (%{$spec}) ;
# Set table if specified
$vars{'sqlvar_table'} = delete $spec{'table'} if (exists($spec{'table'})) ;
# see if command specified
$cmd = delete $spec{'cmd'} if (exists($spec{'cmd'})) ;
$cmd = lc $cmd ;
# error check
$this->throw_fatal("No valid sql command") unless $cmd ;
# Process spec - set vars
$this->_sql_setvars($cmd, \%spec, \%vars) ;
}
elsif (!ref($spec))
{
# Process spec - set vars
$this->_sql_setvars($cmd || 'query', $spec, \%vars) ;
}
$this->_dbg_prt(["Vars=", \%vars], 2) ;
$this->_dbg_prt(["+ expand vars\n"], 2) ;
## Run through all vars and expand them
$this->_sql_expand_vars(\%vars) ;
## Run through all vars and expand arrays them
$this->_sql_expand_arrays(\%vars) ;
# query should now be in variable 'sqlvar_query'
my $sql = $vars{'sqlvar_query'} ;
# values should now be in variable '@sqlvar_query'
my $values_aref = $vars{'@sqlvar_query'} ;
if ($this->debug())
{
print "\n------------------------------------\n" ;
print "PREPARE SQL($name): $sql\n----------\n" ;
$this->prt_data("Values=", $values_aref) ;
}
#$this->prt_data("Values=", $values_aref, "\n--------------------\nVars=", \%vars) ;
## Use given/created command sql
my $dbh = $this->connect() ;
$this->throw_fatal("No database created", 1) unless $dbh ;
my $sth ;
eval
{
$sth = $dbh->prepare($sql) ;
};
$this->throw_fatal("STH prepare error $@\nQuery=$sql", 1) if $@ ;
my $sth_href = $this->_sth() ;
$sth_href->{$name} = {
'sth' => $sth,
'vals' => $values_aref,
'query' => $sql, # For debug
} ;
}
#----------------------------------------------------------------------------
=item B<sth_query($name, [@vals])>
Use a pre-prepared named sql query to return results. If the query has already been
given a set of values, then use them; otherwise use the values specified in this call
(or append the values to an insufficient list of values given when the sth was created)
=cut
sub sth_query
{
my $this = shift ;
my ($name, @vals) = @_ ;
my $sth_href = $this->_sth_record($name) ;
if ($sth_href)
{
my ($sth, $vals_aref, $query) = @$sth_href{qw/sth vals query/} ;
# TODO: expand vars?
my @args ;
foreach my $arg (@$vals_aref)
{
## process each value
if (ref($arg) eq 'SCALAR')
{
## Ref to scalar
push @args, $$arg ;
}
elsif (ref($arg) eq 'HASH')
{
## Special case handling where STH was created with an ARRAY ref or HASH ref
if ($arg->{'type'} eq 'HASH')
{
## get latest value from hash ref
push @args, $arg->{'hash'}{$arg->{'var'}} ;
}
elsif ($arg->{'type'} eq 'ARRAY')
{
## get latest value from array ref
push @args, $arg->{'array'}[$arg->{'index'}] ;
}
}
elsif (!ref($arg))
{
## Standard scalar
push @args, $arg ;
}
}
$this->_dbg_prt(["Sql::sth_query($query) : args=", \@args, "vals=", \@vals], 2) ;
# execute
eval
{
$sth->execute(@args, @vals) ;
};
if ($@)
{
my $vals = join(', ', @args, @vals) ;
$this->throw_fatal("STH \"$name\"execute error $@\nQuery=$query\nValues=$vals", 1) if $@ ;
}
}
return $this ;
}
#----------------------------------------------------------------------------
=item B<sth_query_all($name, [@vals])>
Use a pre-prepared named sql query to return results. Return all results in array.
=cut
sub sth_query_all
{
my $this = shift ;
my ($name, @vals) = @_ ;
my @results ;
$this->sth_query($name, @vals) ;
while(my $href = $this->next($name))
{
push @results, $href ;
}
return @results ;
}
#----------------------------------------------------------------------------
=item B<query($query [, @vals])>
Query database
=cut
sub query
{
my $this = shift ;
my ($query, @vals) = @_ ;
$this->sth_create($DEFAULT_STH_NAME, $query) ;
$this->sth_query($DEFAULT_STH_NAME, @vals) ;
return $this ;
}
#----------------------------------------------------------------------------
=item B<query_all($query)>
Query database - return array of complete results, each entry is a hash ref
=cut
sub query_all
{
my $this = shift ;
my ($query, @vals) = @_ ;
my @results ;
$this->query($query, @vals) ;
while(my $href = $this->next())
{
push @results, $href ;
}
return @results ;
}
#----------------------------------------------------------------------------
=item B<do($sql)>
Do sql command
=cut
sub do
{
my $this = shift ;
my ($sql) = @_ ;
my $dbh = $this->connect() ;
# Do query
eval
{
$dbh->do($sql) ;
};
if ($@)
{
$this->throw_fatal("SQL do error $@\nSql=$sql", 1) if $@ ;
}
return $this ;
}
#----------------------------------------------------------------------------
=item B<do_sql_text($sql_text)>
Process the SQL text, split it into one or more SQL command, then execute each of them
=cut
sub do_sql_text
{
my $this = shift ;
my ($sql_text) = @_ ;
while ($sql_text =~ /([^;]*);/gm)
{
$this->do($1) ;
}
return $this ;
}
#----------------------------------------------------------------------------
=item B<next([$name])>
Returns hash ref to next row (as a result of query). Uses prepared STH name $name
(as created by sth_create method), or default name (as created by query method)
=cut
sub next
{
my $this = shift ;
my ($name) = @_ ;
# Get STH and get next row
$name ||= $DEFAULT_STH_NAME ;
my $sth = $this->_sth_record_sth($name) ;
my $href = $sth->fetchrow_hashref() ;
$this->_dbg_prt(["Sql::next() => sth=",$sth, " : record=",$href,"\n"]) ;
return $href ;
}
#----------------------------------------------------------------------------
=item B<tables()>
Returns list of tables for this database
=cut
sub tables
{
lib/App/Framework/Feature/Sql.pm view on Meta::CPAN
{
$sqldate =~ s%-%/%g ;
my $date = ParseDate($sqldate) ;
$datestr = UnixDate($date, "%d-%m-%Y") ;
}
return $datestr ;
}
#----------------------------------------------------------------------------
=item B<sqldate_to_datemanip($sql_date)>
Convert SQL based date (YYYY-MM-DD) to a date string suitable for Date::Manip (d/M/YYYY)
=cut
sub sqldate_to_datemanip
{
my $this = shift ;
my ($sqldate) = @_ ;
my $datestr ;
if ($sqldate =~ m/(\d{4})\-(\d{2})\-(\d{2})/)
{
$datestr = "$3/$2/$1" ;
}
else
{
$sqldate =~ s%-%/%g ;
my $date = ParseDate($sqldate) ;
$datestr = UnixDate($date, "%d/%m/%Y") ;
}
return $datestr ;
}
#----------------------------------------------------------------------------
=item B<sql_from_data($name)>
NOTE: Only works when feature is registered with an application
Execute the (possible sequence of) command(s) stored in a named __DATA__ area in the application.
=cut
sub sql_from_data
{
my $this = shift ;
my ($name) = @_ ;
my $app = $this->app() ;
$this->throw_error("Unable to find DATA section since not associated with an application") unless $app ;
# Get named data
my $sql_text = $app->data($name) ;
if ($sql_text)
{
## process the data
$this->do_sql_text($sql_text) ;
}
else
{
$this->throw_error("Data section $name contains no SQL") ;
}
return $this ;
}
# ============================================================================================
# PRIVATE METHODS
# ============================================================================================
#----------------------------------------------------------------------------
=item B<_sql_cmd($name)>
Convert $name into a sql command if possible
=cut
sub _sql_cmd
{
my $this = shift ;
my ($name) = @_ ;
my $cmd ;
foreach my $match (keys %CMDS)
{
if ($name =~ m/^$match/i)
{
$cmd = $CMDS{$match} ;
last ;
}
}
return $cmd ;
}
#----------------------------------------------------------------------------
=item B<_sql_setvars($context, $spec, $vars_href)>
Set/add variables into the $vars_href HASH driven by the specification $spec (which may
be a sql string or a HASH specification). Creates the variables in the namespace defined by
the $context string (which is usually the lookup string into the %CMD_SQL table)
=cut
sub _sql_setvars
{
my $this = shift ;
my ($context, $spec, $vars_href) = @_ ;
$this->_dbg_prt([" > _sql_setvars($context)\n"], 2) ;
## Start by getting control info from %CMD_SQL if possible
my $var = "sqlvar_${context}" ;
my ($format, $prefix) ;
lib/App/Framework/Feature/Sql.pm view on Meta::CPAN
if ($vars_href->{$array})
{
# split on commas
my @arr_list = split(/[,\s+]/, $vars_href->{$array}) ;
# start array off
$vars_href->{$array} = [] ;
$this->_dbg_prt([" -- setting array\n"], 2) ;
# process them
foreach my $arr (@arr_list)
{
$this->_dbg_prt([" -- -- get $arr\n"], 2) ;
# if reference to another array, evaluate it
if ($arr =~ /^\@/)
{
$this->_dbg_prt([" -- -- -- expand $arr\n"], 2) ;
my $arr_aref = $this->_sql_expand_array($arr, $vars_href) ;
$this->_dbg_prt([" -- -- -- push array $arr=", $arr_aref, "\n"], 2) ;
# Add to list
push @{$vars_href->{$array}}, @$arr_aref if $arr_aref ;
}
else
{
$this->_dbg_prt([" -- -- -- push value ", $arr, "\n"], 2) ;
# Add to list
push @{$vars_href->{$array}}, $arr ;
}
}
}
}
$this->_dbg_prt(["ARRAY $array=", $vars_href->{$array}], 2) ;
$this->_dbg_prt(["_sql_expand_array($array) - END\n"], 2) ;
return ($vars_href->{$array}) ;
}
#----------------------------------------------------------------------------
=item B<_sth_record($name)>
Returns the saved sth information looked up from $name; returns undef otherwise
=cut
sub _sth_record
{
my $this = shift ;
my ($name) = @_ ;
# error check
if (!$name)
{
$this->dump_callstack() if $this->debug() ;
$this->throw_fatal("Attempting to find prepared statement but no name has been specified") unless $name ;
}
my $sth_href = $this->_sth() ;
if (exists($sth_href->{$name}))
{
$sth_href = $sth_href->{$name} ;
# error check
$this->throw_fatal("sth $name not created") unless $sth_href ;
}
else
{
# error
$this->throw_fatal("sth $name not created") ;
}
return $sth_href ;
}
#----------------------------------------------------------------------------
=item B<_sth_record_sth($name)>
Returns the saved sth looked up from $name; returns undef otherwise
=cut
sub _sth_record_sth
{
my $this = shift ;
my ($name) = @_ ;
my $sth ;
my $sth_href = $this->_sth_record($name) ;
if ($sth_href && exists($sth_href->{'sth'}))
{
$sth = $sth_href->{'sth'} ;
$this->throw_fatal("sth $name not created" ) unless $sth ;
}
else
{
$this->throw_fatal("sth $name not created" ) ;
}
return $sth ;
}
#----------------------------------------------------------------------------
=item B<_set_trace($dbh, $trace, $trace_file)>
Update trace level
=cut
sub _set_trace
{
my $this = shift ;
my ($dbh, $trace, $trace_file) = @_ ;
if ($dbh)
{
$dbh->trace($trace, $trace_file)
}
}
# ============================================================================================
# END OF PACKAGE
=back
=head1 DIAGNOSTICS
Setting the debug flag to level 1 prints out (to STDOUT) some debug messages, setting it to level 2 prints out more verbose messages.
=head1 AUTHOR
Steve Price C<< <sdprice at cpan.org> >>
=head1 BUGS
None that I know of!
NOTE: To avoid the common "Mysql server gone away" problem, everywhere that I get the database connection handle, I actually call
the connect() method to ensure the connection is working.
=cut
1;
__END__
( run in 0.660 second using v1.01-cache-2.11-cpan-39bf76dae61 )