Revision 1 (by dpavlin, 2009/09/17 19:44:49) first version of SQL to RSS converted modeled after SQL2XML
#!/usr/bin/perl -T
use warnings;
use strict;

=head1 NAME

sql2rss.cgi - convert sql queries on file system to RSS feed

=head1 USAGE

Each file in current directory which ends in C<*.sql> will
be converted rss item. If you want to have specific order, you can
prefix filenames with numbers which will be striped when creating sheet
names.

Comments in sql files (lines beginning with C<-->) will be placed
in first line in bold.

To specify database on which SQL query is executed
C<\c database> syntax is supported.

=head1 INSTALLATION

If your server is configured to execute C<.cgi> files, you can
drop this script anywhere, but you can also add something like

   ScriptAlias /xls-reports /srv/SQL2XLS/sql2xls.cgi

in Apache's virtual host configuration to get nice URLs

To configure default database, user, password and other settings create
C<config.pl> file in same directory in which C<sql2xls.cgi> is with something
like this:

  $dsn      = 'DBI:mysql:dbname=';
  $database = 'database';
  $user     = 'user';
  $passwd   = 'password';

  $db_encoding     = 'utf-8';

  $debug = 1;

=head1 SECURITY

There is none. Use apache auth modules if you need it.

=head1 AUTHOR

Dobrica Pavlinusic, dpavlin@rot13.org, L<http://svn.rot13.org/index.cgi/SQL2RSS/>

=cut

use XML::FeedPP;
use DBI;
use CGI::Carp qw(fatalsToBrowser);
use Data::Dump qw/dump/;

warn dump( \%ENV );

our $dsn      = 'DBI:Pg:dbname=';
our $database = 'template1';
our $user     = 'dpavlin';
our $passwd   = '';

our $db_encoding;

our $debug = 1;


my $sql_dir = $ENV{SCRIPT_FILENAME} || '.';
$sql_dir =~ s,/[^/]+$,,;

my $reports_path = $ENV{PATH_INFO} || '';
$reports_path =~ s/\.\.//g; # some protection against path exploits
$reports_path ||= shift @ARGV; # for CLI invocation


sub require_config {
	my $config_path = $1 if "$sql_dir/config.pl" =~ m/^(.+)$/; # untaint
	warn "# using $config_path\n";
	require $config_path if -e $config_path;
}

require_config;


my @sql_files;

if ( -d "$sql_dir/$reports_path" ) {
	$sql_dir .= '/' . $reports_path;
	warn "SQL queries from $sql_dir\n";
	opendir(DIR, $sql_dir) || die "can't opendir $sql_dir: $!";
	@sql_files = sort grep { /\.sql$/i && -f "$sql_dir/$_" } readdir(DIR);
	closedir DIR;
} elsif ( -f "$sql_dir/$reports_path" ) {
	@sql_files = ( $reports_path );
}

my $url = "http://$ENV{HTTP_HOST}/$ENV{REQUEST_URI}";
print qq|Content-type: application/rss+xml\n\n|;

my $feed = XML::FeedPP::RSS->new();
$feed->title( $reports_path );
$feed->link( $url );
#$feed->pubDate( "Thu, 23 Feb 2006 14:43:43 +0900" );

our $dbh;
sub use_database {
	$dbh->disconnect if $dbh;
	my $database = shift || return;
	print STDERR "## connect to $database\n" if $debug;
	$dbh = DBI->connect($dsn . $database,$user,$passwd, { RaiseError => 1, AutoCommit => 0 }) || die $DBI::errstr;
	if ( 0 && $db_encoding ) { # FIXME
		if ( $dsn =~ m{Pg} ) {
			$dbh->do( qq{ set client_encoding = '$db_encoding'; } );
		} elsif ( $dsn =~ m{mysql} ) {
			$dbh->do( qq{ set names '$db_encoding'; } );
		} else {
			warn "Don't know how to set encoding to $db_encoding for $dsn";
		}
	}
}

use_database( $database );

sub table_row {
	my $type = shift @_;
	my $html = join('', map { qq|<$type>$_</$type>| } @_ );
	return qq|<tr>$html</tr>|;
}

foreach my $sql_file (@sql_files) {

	print STDERR "working on $sql_file\n" if ($debug);

	open(SQL,"$sql_dir/$sql_file") || die "can't open sql file '$sql_dir/$sql_file': $!";
	my $comment = '';
	my $full_sql = "";
	while(<SQL>) {
		chomp;
		if (/^\\c\s+(\S+)/) {
			use_database( $1 );
		} elsif (/^--(.+)/) {
			$comment.=$1;
		} else {
			$full_sql.= ' ' . $_;
		}
	}
	close(SQL);

	$full_sql =~ s/\s\s+/ /gs;
	$full_sql .= ';' unless $full_sql =~ m/;\s*/s;

	print STDERR "sql: $full_sql\ncomment: $comment\n" if ($debug);

	foreach my $sql ( split(/;/, $full_sql ) ) {

		warn "SQL: $sql\n";

		my $sth = $dbh->prepare($sql);
		$sth->execute();

		next unless $sth->{NAME} && $sth->rows > 0; # $sth->rows alone doesn't work for insert into with MySQL

		my @types = eval {
			map { $dbh->type_info($_) ? $dbh->type_info($_)->{TYPE_NAME} : '?' } @{ $sth->{TYPE} };
		};

		my $table = table_row( 'th' => @{ $sth->{NAME} } );

		while (my @row = $sth->fetchrow_array() ) {
			$table .= table_row( 'td' => @row );
		}

		my $item = $feed->add_item( "$url/$sql_file" );
		$item->title( $comment );
#		$item->pubDate( "2006-02-23T14:43:43+09:00" );
		$item->description( qq|
			<pre style="font-size: 70%">$sql</pre>
			<table border=1>$table</table>
		| );

	}
}

print $feed->to_string;

1;