Can DBIx::Class be used with stored procedures instead of tables? - perl

The access to read from the db has been given to me via mssql stored procedures that return result sets rather than tables or views. But I want to be able to read the data using ORM.
I tried to use DBIx::Class::ResultSource::View to do the procedure call (e.g. EXEC my_stored_proc ?) as a custom query but this didn't work because it tried to convert the procedure call into a select statement.
Does anyone have another suggestion?

No, there is no reasonable way to execute a stored procedure in the context of DBIx::Class.
As far as I can tell, the closest thing to a workaround is "using the ORM" to get a database handle, which is weak soup:
my #results = $schema->storage->dbh_do(sub{
my ($storage, $dbh, #args) = #_;
my $sth = $dbh->prepare('call storedProcNameFooBar()');
my #data;
$sth->execute();
while( my $row = $sth->fetchrow_hashref){
push #data, $row;
}
return #data;
},());
[ see details at
http://metacpan.org/pod/DBIx::Class::Storage::DBI#dbh_do ]
...as you get none of the benefits of an ORM for your trouble.

You can use register_source
package My::Schema::User;
use base qw/DBIx::Class/;
# ->load_components, ->table, ->add_columns, etc.
# Make a new ResultSource based on the User class
my $source = __PACKAGE__->result_source_instance();
my $new_source = $source->new( $source );
$new_source->source_name( 'UserFriendsComplex' );
# Hand in your query as a scalar reference
# It will be added as a sub-select after FROM,
# so pay attention to the surrounding brackets!
$new_source->name( \<<SQL );
( SELECT u.* FROM user u
INNER JOIN user_friends f ON u.id = f.user_id
WHERE f.friend_user_id = ?
UNION
SELECT u.* FROM user u
INNER JOIN user_friends f ON u.id = f.friend_user_id
WHERE f.user_id = ? )
SQL
# Finally, register your new ResultSource with your Schema
My::Schema->register_source( 'UserFriendsComplex' => $new_source );
To call with parameters do the following
my $friends = [ $schema->resultset( 'UserFriendsComplex' )->search( {
+},
{
bind => [ 12345, 12345 ]
}
) ];

Related

DBI: selectall_arrayref and columnnames

When I fetch the data this way is it possible then to access the column names and the column types or do I need an explicit prepare to reach this?
use DBI;
my $dbh = DBI->connect( ... );
my $select = "...";
my #arguments = ( ... );
my $ref = $dbh->selectall_arrayref( $select, {}, #arguments, );
Update:
With prepare I would do it this way:
my $sth = $dbh->prepare( $select );
$sth->execute( #arguments );
my $col_names = $sth->{NAME};
my $col_types = $sth->{TYPE};
my $ref = $sth->fetchall_arrayref;
unshift #$ref, $col_names;
The best solution is to use prepare to get a statement handle, as you describe in the second part of your question. If you use selectall_hashref or selectall_arrayref, you don't get a statement handle, and have to query the column type information yourself via $dbh->column_info (docs):
my $sth = $dbh->column_info('','',$table,$column); # or $column='' for all
my $info = $sth->fetchall_arrayref({});
use Data::Dumper; print Dumper($info);
(specifically, the COLUMN_NAME and TYPE_NAME attributes).
However, this introduces a race condition if the table changes schema between the two queries.
Also, you may use selectall_arrayref with the Slice parameter to fetch all the columns into a hash ref, it needs no prepared statement and will return an array ref of the result set rows, with each rows columns the key's to a hash and the values are the column values. ie:
my $result = $dbh->selectall_arrayref( qq{
SELECT * FROM table WHERE condition = value
}, { Slice => {} }) or die "Error: ".$dbh->errstr;
$result = [
[0] = { column1 => 'column1Value', column2 => 'column2Value', etc...},
[1] = { column1 => 'column1Value', column2 => 'column2Value', etc...},
];
Making it easy to iterate over results.. ie:
for my $row ( #$results ){
print "$row->{column1Value}, $row->{column2Value}\n";
}
You can also specify which columns to extract but it's pretty useless due to the fact it's more efficient to do that in your SQL query syntax.
{ Slice => { column1Name => 1, column2Name => 1 } }
That would only return the values for column1Name and column2Name just like saying in your SQL:
SELECT column1Name, column2Name FROM table...

Perl DBI insert and select

I want to copy a single record from a table, modify some of the fields in the record and insert into the same table. The table has 90 columns.
Thought of using insert..select in one statement but there are 90 columns and i need to tell the column name in the select query. How can i do it in a better way in perldbi. Pls provide me an example.
Fetch and cache the column names for your table using the NAME statement attribute
my $sth = $dbh->prepare('SELECT * FROM my_table where 1=0');
$sth->execute;
my $cols = $sth->{NAME};
then use $cols to construct your insert...select using some replacement function to inject your modifications in the select.
my %mods_for_column = ( 'foo' => 'foo+10', 'bar' => 'trim(bar)' );
my $inscols = join(',', #$cols);
my $selcols = join(',',
map { exists($mods_for_column($_)) ? $mods_for_column($_) : $_ } #$cols
);
my $sql = "insert into my_table ($inscols) select $selcols from my_table where mumble...";

Best way to use Perl's DBI to UPDATE a row, and print the response to the cmd-line?

Have some Perl code which is using the DBI module - (the code is at work, I can post it in the morning if needed) - but mainly trying to get a sense of what DBI needs to do an update to a row -- and get either errors back, or confirmation that the UPDATE was executed.
(Below is just a basic example, feel free to give your own example and sample DDL if you want... just want some code that I know works. I've run my code via the Perl PtkDB debugger, and can "see" the SQL it generating and executing -- even paste in in the MySQL consol and execute it... but it's doing nothing in the Perl, even thought the select statements are working. Mainly just want a better idea of how DBI is handling UPDATE to MySQL, and if there's any built in feature in DBI that would make debugging this more simple. Thanks!)
So, please supply one full Perl script that:
Sets the connection (MySQL)
SELECT row two based on ID and get the first and last name
Lowercase the names
UPDATE the table
disconnect
Sample TABLE
<COL01>Id <COL02>FirstName <COL03>LastName
<ROW01-COL01>1 <ROW01-COL02>John <ROW01-COL03>Smith
<ROW02-COL01>2 <ROW02-COL02>Jane <ROW02-COL03>Doe
UPDATE (1): Code in question is below. The ONLY thing I've changed is remove code not related to the issue and the config info (eg database name, user, password, etc.) and made the value production for the variables super simple. This code was created by someone else and in a legacy code base.
use strict;
use warnings;
use DBI;
sub dbOpen {
my $dsn;
my $dbh;
$dsn = "DBI:mysql:database=databasename;host=localhost;port=3306";
$dbh = DBI->connect( $dsn, "root", "password" ) ||
print STDERR "FATAL: Could not connect to database.\n$DBI::errstr\n";
$dbh->{ AutoCommit } = 0;
return($dbh);
} # END sub dbOpen
my $Data;
$Data = &dbOpen();
my ($sql,$rs,$sql_update_result);
my $column2,
my $column3;
my $id;
$column2 = 2,
$column3 = 3;
$id = 1;
$sql = "UPDATE table SET column1 = NULL, column2 = ".$column2.", column3 = ".$column3." WHERE id = ".$id.";";
$rs = $Data->prepare( $sql );
$rs->execute() || &die_clean("Couldn't execute\n$sql\n".$Data->errstr."\n" );
($sql_update_result) = $rs->fetchrow;
$Data->disconnect();
DDL for MySQL -- if needed, just comment and I'll post one.
UPDATE (2):
Final found one complete example, though it's only for a select statement and not even inserting any VARs into the SQL: http://search.cpan.org/~timb/DBI/DBI.pm#Simple_Examples
Almost copy and paste from DBI Synopsis:
use DBI;
$dbh = DBI->connect($data_source, $username, $auth, \%attr);
$statement = "UPDATE some_table SET som_col = ? WHERE id = ?";
$rv = $dbh->do($statement, undef, $som_val, $id);
$DBI::err && die $DBI::errstr;
$rc = $dbh->disconnect;
I prefer to use do when updating or deleting since these operations doesn't return any row.
So, in order to have a little debug, i would modify your code like this:
my $sql = "UPDATE table SET column1=NULL, column2=$column2, column3=$column3 WHERE id=$id";
print STDERR "SQL: $sql\n"
my $numrows = $Data->do($sql);
if (not defined $numrows) {
print STDERR "ERROR: $DBI::errstr";
} else {
print STDERR "INFO: $numrows rows updated";
}
You can measure query response times from within your perl code, but since it is a database thing, i recommend you using any Mysql specialized tool (i don't use MySQL, sorry).
Have you considered something a bit higher level - like DBIx::Class?
You don't need to return the values, lowercase them in Perl, then update the rows. Just do that in one SQL statement:
my $sql = "UPDATE table SET column2=lower(column2) WHERE id = ?";
$sth = $dbh->prepare($sql);
foreach my $id (#ids) {
$sth->execute($id);
}
You also want to use placeholders to prevent Bobby Tables from visiting.

Can I get the table names from an SQL query with Perl's DBI?

I am writing small snippets in Perl and DBI (SQLite yay!)
I would like to log some specific queries to text files having the same filename as that of the table name(s) on which the query is run.
Here is the code I use to dump results to a text file :
sub dumpResultsToFile {
my ( $query ) = #_;
# Prepare and execute the query
my $sth = $dbh->prepare( $query );
$sth->execute();
# Open the output file
open FILE, ">results.txt" or die "Can't open results output file: $!";
# Dump the formatted results to the file
$sth->dump_results( 80, "\n", ", ", \*FILE );
# Close the output file
close FILE or die "Error closing result file: $!\n";
}
Here is how I can call this :
dumpResultsToFile ( <<" END_SQL" );
SELECT TADA.fileName, TADA.labelName
FROM TADA
END_SQL
What I effectively want is, instead of stuff going to "results.txt" ( that is hardcoded above ), it should now go to "TADA.txt".
Had this been a join between tables "HAI" and "LOL", then the resultset should be written to "HAI.LOL.txt"
Is what I am saying even possible using some magic in DBI?
I would rather do without parsing the SQL query for tables, but if there is a widely used and debugged function to grab source table names in a SQL query, that would work for me too.
What I want is just to have a filename
that gives some hint as to what query
output it holds. Seggregating based on
table name seems a nice way for now.
Probably not. Your SQL generation code takes the wrong approach. You are hiding too much information from your program. At some point, your program knows which table to select from. Instead of throwing that information away and embedding it inside an opaque SQL command, you should keep it around. Then your logger function doesn't have to guess where the log data should go; it knows.
Maybe this is clearer with some code. Your code looks like:
sub make_query {
my ($table, $columns, $conditions) = #_;
return "SELECT $columns FROM $table WHERE $conditions";
}
sub run_query {
my ($query) = #_;
$dbh->prepare($query);
...
}
run_query( make_query( 'foo', '*', '1=1' ) );
This doesn't let you do what you want to do. So you should structure
your program to do something like:
sub make_query {
my ($table, $columns, $conditions) = #_;
return +{
query => "SELECT $columns FROM $table WHERE $conditions",
table => $table,
} # an object might not be a bad idea
}
sub run_query {
my ($query) = #_;
$dbh->prepare($query->{query});
log_to_file( $query->{table}.'.log', ... );
...
}
run_query( make_query( 'foo', '*', '1=1' ) );
The API is the same, but now you have the information you need to log
the way you want.
Also, consider SQL::Abstract for dynamic SQL generation. My code
above is just an example.
Edit: OK, so you say you're using SQLite. It has an EXPLAIN command
which you could parse the output of:
sqlite> explain select * from test;
0|Trace|0|0|0|explain select * from test;|00|
1|Goto|0|11|0||00|
2|SetNumColumns|0|2|0||00|
3|OpenRead|0|2|0||00|
4|Rewind|0|9|0||00|
5|Column|0|0|1||00|
6|Column|0|1|2||00|
7|ResultRow|1|2|0||00|
8|Next|0|5|0||00|
9|Close|0|0|0||00|
10|Halt|0|0|0||00|
11|Transaction|0|0|0||00|
12|VerifyCookie|0|1|0||00|
13|TableLock|0|2|0|test|00|
14|Goto|0|2|0||00|
Looks like TableLock is what you would want to look for. YMMV, this
is a bad idea.
In general, in SQL, you cannot reliably deduce table names from result set, both for theoretical reasons (the result set may only consist of computed columns) and practical (the result set never includes table names - only column names - in its data).
So the only way to figure out the tables used is to stored them with (or deduce them from) the original query.
I've heard good things about the parsing ability of SQL::Statement but never used it before now myself.
use SQL::Statement;
use strict;
use warnings;
my $sql = <<" END_SQL";
SELECT TADA.fileName, TADA.labelName
FROM TADA
END_SQL
my $parser = SQL::Parser->new();
$parser->{RaiseError} = 1;
$parser->{PrintError} = 0;
my $stmt = eval { SQL::Statement->new($sql, $parser) }
or die "parse error: $#";
print join',',map{$_->name}$stmt->tables;

How can I write a subroutine for DBI updates with varying number of arguments?

I'm writing a subroutine for DBI updates, and are having some trouble figuring out how to add placeholder and stuff...
I have this:
sub row_update {
my $table = shift;
my %updates = #_;
my $placeholders = ...
$dbh->do("UPDATE $table SET (foo) WHERE (bar)") etc...
}
Any ideas?
I just want a simple update function where I can send in x number of arguments (as a hash).
Thanks in advance!
Something like this might be Good Enough:
sub update {
my ($dbh, $args) = #_;
my $table = $args->{table} || die 'need table';
my $updates = $args->{updates} || die 'need updates';
my #cols = keys %$updates;
my $query = 'UPDATE $table SET '.
(join ', ', map { '$_ = ?' } #cols)
($args->{where} ? ' WHERE '. $args->{where} : '');
my $sth = $dbh->prepare($query);
$sth->execute(map { $updates->{$_} } #cols);
return $sth;
}
Use it like:
my $sth = update $dbh, {
table => 'foo',
updates => {
col1 => 'new_value',
col2 => 'another_value',
},
where => 'id=42',
};
Really, though, you want to look into using an ORM like
DBIx::Class. It will do a much better job of building queries than
string manipulation like this will.
(Rewriting the where clause to be parameterized is left as an exercise to the reader. You also need to quote the update keys and table name. See why people use ORMs?)
Edit: Thinking about this a bit more, you might like DBIx::Simple combined with SQL::Abstract. This will take less configuration effort than an ORM, but still give you many of the benefits.
If I understand the question correctly, it sounds like you're after SQL::Abstract. First, we create an SQL::Abstract object:
use SQL::Abstract;
my $sql = SQL::Abstract->new;
Now, as an example, we'll use it to insert some data into a table:
my %record = (
FirstName => 'Buffy',
LastName => 'Summers',
Address => '1630 Revello Drive',
City => 'Sunnydale',
State => 'California',
Occupation => 'Student',
Health => 'Alive',
);
my ($stmt, #bind) = $sql->insert(’staff’,\%record);
This results in:
$stmt = "INSERT INTO staff
(FirstName, LastName, Address, City,
State, Occupation, Health)
VALUES (?, ?, ?, ?, ?, ?, ?)";
#bind = ('Buffy','Summers','1630 Revello Drive',
'Sunnydale',’California','Student','Alive');
The nice thing about this is we can pass it directly to DBI:
$dbh->do($stmt, undef, #bind);
Of course, you want to be updating records, not just inserting them. Luckily, this is also quite easy:
my $table = 'People';
my %new_fields = (
Occupation => 'Slayer',
Health => 'Dead',
);
my %where = (
FirstName => 'Buffy',
LastName => 'Summers',
);
my ($stmt, #bind) = $sql->update($table, \%new_fields, \%where);
$dbh->do($stmt, undef, #bind);
This produces:
$stmt = 'UPDATE People SET Health = ?, Occupation = ?
WHERE ( FirstName = ? AND LastName = ? )';
#bind = ('Dead', 'Slayer', 'Buffy', 'Summers');
If you're after more information about SQL::Abstract, I recommend you look at its CPAN page. There's also a chapter in Perl Training Australia's Database Programming with Perl manual, which are freely available from our course notes page.
All the best,
Paul
Disclaimer: I'm managing director of Perl Training Australia, and therefore think that our course notes are pretty good.
Others suggested the usual "build a query with the right number of '?'s" approach.
For most queries like this the DBI->quote method is forgotten, but it can keep the code simpler and in most cases it's not any slower than the "proper" placeholder approach.
1) Use DBI->quote instead of place holders to build the query. For example for a simple select:
my $sql = "select foo from bar where baz in ("
. join(",", map { DBI->quote($_) } #bazs)
. ")";
my $data = $dbh->selectall_arrayref($sql);
2) As jrockway suggested - use an ORM to do this sort of low level stuff for you. DBIx::Class or Rose::DB::Object, for example.
The NUM_OF_PARAMS attribute may be of assistance:
"NUM_OF_PARAMS" (integer, read-only)
The number of parameters (placeholders) in the prepared
statement.
So for instance, I've got a script to run arbitrary SQL from the command line that uses this code:
my $s = $h->prepare($_);
for my $i (1..$s->{NUM_OF_PARAMS}){
my $param = shift #ARGV;
$s->bind_param($i, $param);
print LOG "Bind param $i using $param.\n"
or die "can't append to $opt{log}: $!";
}
$s->execute();
I can't say I've used the other modules that have been suggested, so they may do a better job.