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. 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. 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 =cut
sub setDataByQuery { sub setDataByQuery {
my ($sth, $rowCount, @row); my ($sth, $rowCount, @row);
my ($self, $sql, $dbh, $unconditional) = @_; my ($self, $sql, $dbh, $unconditional, $placeholders) = @_;
$dbh ||= WebGUI::SQL->getSlave; $dbh ||= WebGUI::SQL->getSlave;
if ($unconditional) { if ($unconditional) {
$sth = WebGUI::SQL->unconditionalRead($sql,$dbh); $sth = WebGUI::SQL->unconditionalRead($sql,$dbh,$placeholders);
return $sth->errorMessage if ($sth->errorCode > 0); return $sth->errorMessage if ($sth->errorCode > 0);
} else { } else {
$sth = WebGUI::SQL->read($sql,$dbh); $sth = WebGUI::SQL->read($sql,$dbh,$placeholders);
} }
$self->{_totalRows} = $sth->rows; $self->{_totalRows} = $sth->rows;
$self->{_columnNames} = [ $sth->getColumnNames ]; $self->{_columnNames} = [ $sth->getColumnNames ];

View file

@ -266,20 +266,21 @@ sub errorMessage {
#------------------------------------------------------------------- #-------------------------------------------------------------------
=head2 execute ( values ) =head2 execute ( [ placeholders ] )
Executes a prepared SQL statement. 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 =cut
sub execute { sub execute {
my $self = shift; my $self = shift;
my $sql = shift; my $placeholders = shift || [];
$self->{_sth}->execute or WebGUI::ErrorHandler::fatalError("Couldn't execute prepared statement: $sql Root cause: ". DBI->errstr); 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 =cut
sub getColumnNames { 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); push(@{$WebGUI::Session::session{SQLquery}},$sql);
} }
my $sth = $dbh->prepare($sql) or WebGUI::ErrorHandler::fatalError("Couldn't prepare statement: ".$sql." : ". DBI->errstr); 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. Returns a statement handler. This is a utility method that runs both a prepare and execute all in one.
=head3 sql =head3 sql
An SQL query. An SQL query. Can use the "?" placeholder for maximum performance on multiple statements with the execute method.
=head3 dbh =head3 dbh
By default this method uses the WebGUI database handler. However, you may choose to pass in your own if you wish. 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 =cut
sub read { sub read {
my $class = shift; my $class = shift;
my $sql = shift; my $sql = shift;
my $dbh = shift; my $dbh = shift;
my $placeholders = shift;
my $sth = WebGUI::SQL->prepare($sql, $dbh); my $sth = WebGUI::SQL->prepare($sql, $dbh);
$sth->execute($sql); $sth->execute($placeholders);
return $sth; 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. 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. 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 =cut
sub unconditionalRead { sub unconditionalRead {
my $class = shift; my $class = shift;
my $sql = shift; my $sql = shift;
my $dbh = shift || _getDefaultDb(); my $dbh = shift || _getDefaultDb();
my $placeholders = shift;
if ($WebGUI::Session::session{setting}{showDebug}) { if ($WebGUI::Session::session{setting}{showDebug}) {
push(@{$WebGUI::Session::session{SQLquery}},$sql); push(@{$WebGUI::Session::session{SQLquery}},$sql);
} }
my $sth = $dbh->prepare($sql) or WebGUI::ErrorHandler::warn("Unconditional read failed: ".$sql." : ".DBI->errstr); my $sth = $dbh->prepare($sql) or WebGUI::ErrorHandler::warn("Unconditional read failed: ".$sql." : ".DBI->errstr);
if ($sth) { if ($sth) {
$sth->execute or WebGUI::ErrorHandler::warn("Unconditional read failed: ".$sql." : ".DBI->errstr); $sth->execute(@$placeholders) or WebGUI::ErrorHandler::warn("Unconditional read failed: ".$sql." : ".DBI->errstr);
bless ({_sth => $sth}, $class); bless ({_sth => $sth} , $class);
} }
} }

View file

@ -41,19 +41,52 @@ sub new {
my $self = WebGUI::Wobject->new( my $self = WebGUI::Wobject->new(
-properties=>$property, -properties=>$property,
-extendedProperties=>{ -extendedProperties=>{
dbQuery=>{},
databaseLinkId=>{
defaultValue=>0
},
paginateAfter=>{ paginateAfter=>{
defaultValue=>50 defaultValue=>50
}, },
preprocessMacros=>{ dbQuery1=>{},
preprocessMacros1=>{
defaultValue=>0 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=>{ debugMode=>{
defaultValue=>0 defaultValue=>0
} },
}, },
-useTemplate=>1, -useTemplate=>1,
-useMetaData=>1 -useMetaData=>1
@ -71,24 +104,82 @@ sub www_edit {
my $privileges = WebGUI::HTMLForm->new; my $privileges = WebGUI::HTMLForm->new;
my $layout = WebGUI::HTMLForm->new; my $layout = WebGUI::HTMLForm->new;
my $properties = 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( $properties->yesNo(
-name=>"debugMode", -name=>"debugMode",
-label=>WebGUI::International::get(16,$_[0]->get("namespace")), -label=>WebGUI::International::get(16,$_[0]->get("namespace")),
-value=>$_[0]->getValue("debugMode") -value=>$_[0]->getValue("debugMode")
); );
$properties->textarea(
-name=>"dbQuery", # Add toggleQuery javascript
-label=>WebGUI::International::get(4,$_[0]->get("namespace")), $properties->raw(qq|
-value=>$_[0]->getValue("dbQuery") <script language="javascript">
); function toggleQuery(Id) {
$privileges->databaseLink( queryClass = "query" + Id;
-value=>$_[0]->getValue("databaseLinkId") 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( $layout->integer(
-name=>"paginateAfter", -name=>"paginateAfter",
-label=>WebGUI::International::get(14,$_[0]->get("namespace")), -label=>WebGUI::International::get(14,$_[0]->get("namespace")),
@ -103,88 +194,156 @@ sub www_edit {
); );
} }
#------------------------------------------------------------------- #-------------------------------------------------------------------
sub www_view { sub www_view {
$_[0]->logView() if ($session{setting}{passiveProfilingEnabled}); my $self = shift;
my ($query, %var, @debug); $self->logView() if ($session{setting}{passiveProfilingEnabled});
if ($_[0]->get("preprocessMacros")) {
$query = WebGUI::Macro::process($_[0]->get("dbQuery")); # Initiate an empty debug loop
} else { $self->{_debug_loop} = [] ;
$query = $_[0]->get("dbQuery");
} # Store queries in class
push(@debug,{'debug.output'=>WebGUI::International::get(17,$_[0]->get("namespace")).$query}); $self->_storeQueries();
my $dbLink = WebGUI::DatabaseLink->new($_[0]->get("databaseLinkId"));
my $dbh = $dbLink->dbh; # Process the quer(y|ies)
if (defined $dbh) { my $var = $self->_processQuery();
if ($query =~ /^select/i || $query =~ /^show/i || $query =~ /^describe/i) {
my $url = WebGUI::URL::page('&wid='.$_[0]->get("wobjectId").'&func=view'); # Add debug loop to template vars
foreach (keys %{$session{form}}) { $var->{'debug_loop'} = $self->{_debug_loop};
unless ($_ eq "pn" || $_ eq "wid" || $_ eq "func" || $_ =~ /identifier/i || $_ =~ /password/i) { #use Data::Dumper; return '<pre>'.Dumper($var).'</pre>';
$url = WebGUI::URL::append($url, WebGUI::URL::escape($_) return $self->processTemplate($self->get("templateId"),$var);
.'='.WebGUI::URL::escape($session{form}{$_})); }
}
} #-------------------------------------------------------------------
my $p = WebGUI::Paginator->new($url,$_[0]->get("paginateAfter")); sub _storeQueries {
my $error = $p->setDataByQuery($query,$dbh,1); my $self = shift;
if ($error ne "") { for my $nr (1..5) {
WebGUI::ErrorHandler::warn("There was a problem with the query: ".$error); if($self->get("dbQuery".$nr)) {
push(@debug,{'debug.output'=>WebGUI::International::get(11,$_[0]->get("namespace"))." ".$error}); $self->{_query}{$nr} = {
} else { dbQuery => $self->get("dbQuery".$nr),
my $first = 1; databaseLinkId => $self->get("databaseLinkId".$nr),
my @columns; preprocessMacros => $self->get("preprocessMacros".$nr),
my @rows; placeholderParams => $self->get("placeholderParams".$nr),
my $rownum = 1; rowData => {},
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.");
} }
$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."); #-------------------------------------------------------------------
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);
} }
$var{'debug_loop'} = \@debug; return \@placeholderParams;
return $_[0]->processTemplate($_[0]->get("templateId"),\%var);
} }
#-------------------------------------------------------------------
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; 1;