Added sub-queries support in SQL Report

This commit is contained in:
Len Kranendonk 2004-11-03 20:44:21 +00:00
parent edbe460fd1
commit d32ecc6251
3 changed files with 289 additions and 115 deletions

View file

@ -477,7 +477,7 @@ sub setDataByArrayRef {
#-------------------------------------------------------------------
=head2 setDataByQuery ( query [, dbh, unconditional ] )
=head2 setDataByQuery ( query [, dbh, unconditional, placeholders ] )
Retrieves a data set from a database and replaces whatever data set was passed in through the constructor.
@ -495,17 +495,21 @@ A DBI-style database handler. Defaults to the WebGUI site handler.
A boolean indicating that the query should be read unconditionally. Defaults to "0". If set to "1" and the unconditional read results in an error, the error will be returned by this method.
=head3 placeholders
An array reference containing a list of values to be used in the placeholders defined in the SQL statement.
=cut
sub setDataByQuery {
my ($sth, $rowCount, @row);
my ($self, $sql, $dbh, $unconditional) = @_;
my ($self, $sql, $dbh, $unconditional, $placeholders) = @_;
$dbh ||= WebGUI::SQL->getSlave;
if ($unconditional) {
$sth = WebGUI::SQL->unconditionalRead($sql,$dbh);
$sth = WebGUI::SQL->unconditionalRead($sql,$dbh,$placeholders);
return $sth->errorMessage if ($sth->errorCode > 0);
} else {
$sth = WebGUI::SQL->read($sql,$dbh);
$sth = WebGUI::SQL->read($sql,$dbh,$placeholders);
}
$self->{_totalRows} = $sth->rows;
$self->{_columnNames} = [ $sth->getColumnNames ];

View file

@ -266,20 +266,21 @@ sub errorMessage {
#-------------------------------------------------------------------
=head2 execute ( values )
=head2 execute ( [ placeholders ] )
Executes a prepared SQL statement.
=head3 values
=head3 placeholders
A list of values to be used in the placeholders defined in the prepared statement.
An array reference containing a list of values to be used in the placeholders defined in the SQL statement.
=cut
sub execute {
my $self = shift;
my $sql = shift;
$self->{_sth}->execute or WebGUI::ErrorHandler::fatalError("Couldn't execute prepared statement: $sql Root cause: ". DBI->errstr);
my $placeholders = shift || [];
my $sql = $self->{_sql};
$self->{_sth}->execute(@$placeholders) or WebGUI::ErrorHandler::fatalError("Couldn't execute prepared statement: $sql Root cause: ". DBI->errstr);
}
@ -306,7 +307,7 @@ Returns an array of column names. Use with a "read" method.
=cut
sub getColumnNames {
return @{$_[0]->{_sth}->{NAME}};
return @{$_[0]->{_sth}->{NAME}} if (ref $_[0]->{_sth}->{NAME} eq 'ARRAY');
}
@ -444,7 +445,7 @@ sub prepare {
push(@{$WebGUI::Session::session{SQLquery}},$sql);
}
my $sth = $dbh->prepare($sql) or WebGUI::ErrorHandler::fatalError("Couldn't prepare statement: ".$sql." : ". DBI->errstr);
bless ({_sth => $sth}, $class);
bless ({_sth => $sth, _sql => $sql}, $class);
}
@ -644,26 +645,31 @@ sub quoteAndJoin {
#-------------------------------------------------------------------
=head2 read ( sql [, dbh ] )
=head2 read ( sql [, dbh, placeholders ] )
Returns a statement handler. This is a utility method that runs both a prepare and execute all in one.
=head3 sql
An SQL query.
An SQL query. Can use the "?" placeholder for maximum performance on multiple statements with the execute method.
=head3 dbh
By default this method uses the WebGUI database handler. However, you may choose to pass in your own if you wish.
=head3 placeholders
An array reference containing a list of values to be used in the placeholders defined in the SQL statement.
=cut
sub read {
my $class = shift;
my $sql = shift;
my $dbh = shift;
my $placeholders = shift;
my $sth = WebGUI::SQL->prepare($sql, $dbh);
$sth->execute($sql);
$sth->execute($placeholders);
return $sth;
}
@ -745,7 +751,7 @@ sub setRow {
#-------------------------------------------------------------------
=head2 unconditionalRead ( sql [, dbh ] )
=head2 unconditionalRead ( sql [, dbh, placeholders ] )
An alias of the "read" method except that it will not cause a fatal error in WebGUI if the query is invalid. This is useful for user generated queries such as those in the SQL Report. Returns a statement handler.
@ -757,19 +763,24 @@ An SQL query.
By default this method uses the WebGUI database handler. However, you may choose to pass in your own if you wish.
=head3 placeholders
An array reference containing a list of values to be used in the placeholders defined in the SQL statement.
=cut
sub unconditionalRead {
my $class = shift;
my $sql = shift;
my $dbh = shift || _getDefaultDb();
my $placeholders = shift;
if ($WebGUI::Session::session{setting}{showDebug}) {
push(@{$WebGUI::Session::session{SQLquery}},$sql);
}
my $sth = $dbh->prepare($sql) or WebGUI::ErrorHandler::warn("Unconditional read failed: ".$sql." : ".DBI->errstr);
if ($sth) {
$sth->execute or WebGUI::ErrorHandler::warn("Unconditional read failed: ".$sql." : ".DBI->errstr);
bless ({_sth => $sth}, $class);
$sth->execute(@$placeholders) or WebGUI::ErrorHandler::warn("Unconditional read failed: ".$sql." : ".DBI->errstr);
bless ({_sth => $sth} , $class);
}
}

View file

@ -41,19 +41,52 @@ sub new {
my $self = WebGUI::Wobject->new(
-properties=>$property,
-extendedProperties=>{
dbQuery=>{},
databaseLinkId=>{
defaultValue=>0
},
paginateAfter=>{
defaultValue=>50
},
preprocessMacros=>{
dbQuery1=>{},
preprocessMacros1=>{
defaultValue=>0
},
placeholderParams1=>{},
databaseLinkId1=>{
defaultValue=>0
},
dbQuery2=>{},
preprocessMacros2=>{
defaultValue=>0
},
placeholderParams2=>{},
databaseLinkId2=>{
defaultValue=>0
},
dbQuery3=>{},
preprocessMacros3=>{
defaultValue=>0
},
placeholderParams3=>{},
databaseLinkId3=>{
defaultValue=>0
},
dbQuery4=>{},
preprocessMacros4=>{
defaultValue=>0
},
placeholderParams4=>{},
databaseLinkId4=>{
defaultValue=>0
},
dbQuery5=>{},
preprocessMacros5=>{
defaultValue=>0
},
placeholderParams5=>{},
databaseLinkId5=>{
defaultValue=>0
},
debugMode=>{
defaultValue=>0
}
},
},
-useTemplate=>1,
-useMetaData=>1
@ -71,24 +104,82 @@ sub www_edit {
my $privileges = WebGUI::HTMLForm->new;
my $layout = WebGUI::HTMLForm->new;
my $properties = WebGUI::HTMLForm->new;
$properties->yesNo(
-name=>"preprocessMacros",
-label=>WebGUI::International::get(15,$_[0]->get("namespace")),
-value=>$_[0]->getValue("preprocessMacros")
);
$properties->yesNo(
-name=>"debugMode",
-label=>WebGUI::International::get(16,$_[0]->get("namespace")),
-value=>$_[0]->getValue("debugMode")
);
$properties->textarea(
-name=>"dbQuery",
-label=>WebGUI::International::get(4,$_[0]->get("namespace")),
-value=>$_[0]->getValue("dbQuery")
);
$privileges->databaseLink(
-value=>$_[0]->getValue("databaseLinkId")
);
-name=>"debugMode",
-label=>WebGUI::International::get(16,$_[0]->get("namespace")),
-value=>$_[0]->getValue("debugMode")
);
# Add toggleQuery javascript
$properties->raw(qq|
<script language="javascript">
function toggleQuery(Id) {
queryClass = "query" + Id;
var tr = document.getElementsByTagName("tr");
if (tr == null) return;
for (i=0; i < tr.length; i++) {
if(tr[i].className == queryClass) {
if(tr[i].style.display == 'none') {
tr[i].style.display = '';
} else {
tr[i].style.display = 'none';
}
}
}
}
</script>
|);
for my $nr (1..5) {
# Set TR class for this query properties
$properties->trClass("query".$nr);
$properties->readOnly(
-value=>"<hr>",
-label=>"<b>query".$nr.":</b>",
);
$properties->yesNo(
-name=>"preprocessMacros".$nr,
-label=>WebGUI::International::get(15,$_[0]->get("namespace")),
-value=>$_[0]->getValue("preprocessMacros".$nr)
);
$properties->textarea(
-name=>"placeholderParams".$nr,
-label=>WebGUI::International::get('Placeholder Parameters',$_[0]->get("namespace")),
-value=>$_[0]->getValue("placeholderParams".$nr)
);
$properties->textarea(
-name=>"dbQuery".$nr,
-label=>WebGUI::International::get(4,$_[0]->get("namespace")),
-value=>$_[0]->getValue("dbQuery".$nr)
);
$properties->databaseLink(
-name=>"databaseLinkId".$nr,
-value=>$_[0]->getValue("databaseLinkId".$nr)
);
# Add a "Add another query" button
if ($nr < 5 and ($_[0]->get("dbQuery".($nr+1)) eq "" || ($_[0]->get("dbQuery".($nr)) eq "" and $_[0]->get("dbQuery".($nr+1)) ne ""))) {
$properties->button(
-value=>WebGUI::International::get('Add another query',$_[0]->get("namespace")),
-extras=>'onClick="toggleQuery(\''.($nr+1).'\'); this.style.display=\'none\';"',
-noWait=>1
);
}
# Make empty query blocks invisible
if ($nr > 1 && ($_[0]->get("dbQuery".$nr) eq "" || $_[0]->get("dbQuery".($nr-1)) eq "")) {
$properties->raw(qq|
<script language="javascript">
toggleQuery('$nr');
</script>
|);
}
}
# Undefine TR class
$properties->trClass();
$layout->integer(
-name=>"paginateAfter",
-label=>WebGUI::International::get(14,$_[0]->get("namespace")),
@ -103,88 +194,156 @@ sub www_edit {
);
}
#-------------------------------------------------------------------
sub www_view {
$_[0]->logView() if ($session{setting}{passiveProfilingEnabled});
my ($query, %var, @debug);
if ($_[0]->get("preprocessMacros")) {
$query = WebGUI::Macro::process($_[0]->get("dbQuery"));
} else {
$query = $_[0]->get("dbQuery");
}
push(@debug,{'debug.output'=>WebGUI::International::get(17,$_[0]->get("namespace")).$query});
my $dbLink = WebGUI::DatabaseLink->new($_[0]->get("databaseLinkId"));
my $dbh = $dbLink->dbh;
if (defined $dbh) {
if ($query =~ /^select/i || $query =~ /^show/i || $query =~ /^describe/i) {
my $url = WebGUI::URL::page('&wid='.$_[0]->get("wobjectId").'&func=view');
foreach (keys %{$session{form}}) {
unless ($_ eq "pn" || $_ eq "wid" || $_ eq "func" || $_ =~ /identifier/i || $_ =~ /password/i) {
$url = WebGUI::URL::append($url, WebGUI::URL::escape($_)
.'='.WebGUI::URL::escape($session{form}{$_}));
}
}
my $p = WebGUI::Paginator->new($url,$_[0]->get("paginateAfter"));
my $error = $p->setDataByQuery($query,$dbh,1);
if ($error ne "") {
WebGUI::ErrorHandler::warn("There was a problem with the query: ".$error);
push(@debug,{'debug.output'=>WebGUI::International::get(11,$_[0]->get("namespace"))." ".$error});
} else {
my $first = 1;
my @columns;
my @rows;
my $rownum = 1;
my $rowdata = $p->getPageData;
foreach my $data (@$rowdata) {
my %row;
my $colnum = 1;
my @fields;
foreach my $name ($p->getColumnNames) {
if ($first) {
push(@columns,{
'column.number'=>$colnum,
'column.name'=>$name
});
}
push(@fields,{
'field.number'=>$colnum,
'field.name'=>$name,
'field.value'=>$data->{$name}
});
$colnum++;
$row{'row.field.'.$name.'.value'} = $data->{$name};
}
$row{'row.number'} = $rownum;
$row{'row.field_loop'} = \@fields;
push(@rows,\%row);
$first = 0;
$rownum++;
}
$var{columns_loop} = \@columns;
$var{rows_loop} = \@rows;
$var{'rows.count'} = $p->getRowCount;
$var{'rows.count.isZero'} = ($p->getRowCount < 1);
$var{'rows.count.isZero.label'} = WebGUI::International::get(18,$_[0]->get("namespace"));
$p->appendTemplateVars(\%var);
}
} else {
push(@debug,{'debug.output'=>WebGUI::International::get(10,$_[0]->get("namespace"))});
WebGUI::ErrorHandler::warn("SQLReport [".$_[0]->get("wobjectId")."] The SQL query is improperly formatted.");
my $self = shift;
$self->logView() if ($session{setting}{passiveProfilingEnabled});
# Initiate an empty debug loop
$self->{_debug_loop} = [] ;
# Store queries in class
$self->_storeQueries();
# Process the quer(y|ies)
my $var = $self->_processQuery();
# Add debug loop to template vars
$var->{'debug_loop'} = $self->{_debug_loop};
#use Data::Dumper; return '<pre>'.Dumper($var).'</pre>';
return $self->processTemplate($self->get("templateId"),$var);
}
#-------------------------------------------------------------------
sub _storeQueries {
my $self = shift;
for my $nr (1..5) {
if($self->get("dbQuery".$nr)) {
$self->{_query}{$nr} = {
dbQuery => $self->get("dbQuery".$nr),
databaseLinkId => $self->get("databaseLinkId".$nr),
preprocessMacros => $self->get("preprocessMacros".$nr),
placeholderParams => $self->get("placeholderParams".$nr),
rowData => {},
};
}
$dbLink->disconnect;
} else {
push(@debug,{'debug.output'=>WebGUI::International::get(12,$_[0]->get("namespace"))});
WebGUI::ErrorHandler::warn("SQLReport [".$_[0]->get("wobjectId")."] Could not connect to database.");
}
$var{'debug_loop'} = \@debug;
return $_[0]->processTemplate($_[0]->get("templateId"),\%var);
}
}
#-------------------------------------------------------------------
sub _parsePlaceholderParams {
my $self = shift;
my $params = shift;
my @placeholderParams;
foreach my $param (split /\s*,\s*/, $params) {
if($param =~ /^form:/) {
$param = $session{form}{$'};
} elsif ($param =~ /^query(\d):/) {
$param = $self->{_query}{$1}{rowData}{$'};
}
push(@placeholderParams, $param);
}
return \@placeholderParams;
}
#-------------------------------------------------------------------
sub _processQuery {
my $self = shift;
my $nr = shift || 1;
my ($query, %var, $prefix);
if($nr > 1) {
$prefix = 'query'.$nr.'.';
}
# Parse placeholder parameters
my $placeholderParams = $self->_parsePlaceholderParams($self->{_query}{$nr}{placeholderParams});
# Preprocess macros
if ($self->{_query}{$nr}{preprocessMacros}) {
$query = WebGUI::Macro::process($self->{_query}{$nr}{dbQuery});
} else {
$query = $self->{_query}{$nr}{dbQuery};
}
push(@{$self->{_debug_loop}},{'debug.output'=>WebGUI::International::get(17,$self->get("namespace")).$query});
push(@{$self->{_debug_loop}},{'debug.output'=>WebGUI::International::get('debug placeholder parameters',$self->get("namespace")).join(",",@$placeholderParams)});
my $dbLink = WebGUI::DatabaseLink->new($self->{_query}{$nr}{databaseLinkId});
my $dbh = $dbLink->dbh;
if (defined $dbh) {
if ($query =~ /^select/i || $query =~ /^show/i || $query =~ /^describe/i) {
my $url = WebGUI::URL::page('&wid='.$self->get("wobjectId").'&func=view');
foreach (keys %{$session{form}}) {
unless ($_ eq "pn" || $_ eq "wid" || $_ eq "func" || $_ =~ /identifier/i || $_ =~ /password/i) {
$url = WebGUI::URL::append($url, WebGUI::URL::escape($_)
.'='.WebGUI::URL::escape($session{form}{$_}));
}
}
my $paginateAfter = $self->get("paginateAfter");
$paginateAfter = 1000 if($nr > 1);
my $p = WebGUI::Paginator->new($url,$paginateAfter);
my $error = $p->setDataByQuery($query,$dbh,1,$placeholderParams);
if ($error ne "") {
WebGUI::ErrorHandler::warn("There was a problem with the query: ".$error);
push(@{$self->{_debug_loop}},{'debug.output'=>WebGUI::International::get(11,$self->get("namespace"))." ".$error});
} else {
my $first = 1;
my @columns;
my @rows;
my $rownum = 1;
my $rowdata = $p->getPageData;
foreach my $data (@$rowdata) {
$self->{_query}{$nr}{rowData} = $data;
my %row;
my $colnum = 1;
my @fields;
foreach my $name ($p->getColumnNames) {
if ($first) {
push(@columns,{
'column.number'=>$colnum,
'column.name'=>$name
});
}
push(@fields,{
'field.number'=>$colnum,
'field.name'=>$name,
'field.value'=>$data->{$name}
});
$colnum++;
$row{$prefix.'row.field.'.$name.'.value'} = $data->{$name};
}
# Process nested query
if($self->{_query}{$nr + 1}{dbQuery}) {
my $nest = $self->_processQuery($nr+1);
%row = (%row , %$nest);
$row{$prefix.'hasNest'} = $nest->{'query'.($nr+1).'.rows.count'};
}
$row{$prefix.'row.number'} = $rownum;
$row{$prefix.'row.field_loop'} = \@fields;
push(@rows,\%row);
$first = 0;
$rownum++;
}
$var{$prefix.'columns_loop'} = \@columns;
$var{$prefix.'rows_loop'} = \@rows;
$var{$prefix.'columns.count'} = scalar(@columns);
$var{$prefix.'rows.count'} = $p->getRowCount;
$var{$prefix.'rows.count.isZero'} = ($p->getRowCount < 1);
$var{$prefix.'rows.count.isZero.label'} = WebGUI::International::get(18,$self->get("namespace"));
$p->appendTemplateVars(\%var) if ($nr == 1);
}
} else {
push(@{$self->{_debug_loop}},{'debug.output'=>WebGUI::International::get(10,$self->get("namespace"))});
WebGUI::ErrorHandler::warn("SQLReport [".$self->get("wobjectId")."] The SQL query is improperly formatted.");
}
$dbLink->disconnect;
} else {
push(@{$self->{_debug_loop}},{'debug.output'=>WebGUI::International::get(12,$self->get("namespace"))});
WebGUI::ErrorHandler::warn("SQLReport [".$self->get("wobjectId")."] Could not connect to database.");
}
return \%var;
}
1;