From d32ecc6251d55908fac79cc0f912b2dd6f2b3f14 Mon Sep 17 00:00:00 2001 From: Len Kranendonk Date: Wed, 3 Nov 2004 20:44:21 +0000 Subject: [PATCH] Added sub-queries support in SQL Report --- lib/WebGUI/Paginator.pm | 12 +- lib/WebGUI/SQL.pm | 37 ++-- lib/WebGUI/Wobject/SQLReport.pm | 355 +++++++++++++++++++++++--------- 3 files changed, 289 insertions(+), 115 deletions(-) diff --git a/lib/WebGUI/Paginator.pm b/lib/WebGUI/Paginator.pm index 5df621b9f..207cb99f2 100644 --- a/lib/WebGUI/Paginator.pm +++ b/lib/WebGUI/Paginator.pm @@ -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 ]; diff --git a/lib/WebGUI/SQL.pm b/lib/WebGUI/SQL.pm index 63ebb58dc..904605312 100644 --- a/lib/WebGUI/SQL.pm +++ b/lib/WebGUI/SQL.pm @@ -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); } } diff --git a/lib/WebGUI/Wobject/SQLReport.pm b/lib/WebGUI/Wobject/SQLReport.pm index ce146ad17..90ef8d4b1 100644 --- a/lib/WebGUI/Wobject/SQLReport.pm +++ b/lib/WebGUI/Wobject/SQLReport.pm @@ -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| + + |); + + for my $nr (1..5) { + # Set TR class for this query properties + $properties->trClass("query".$nr); + + $properties->readOnly( + -value=>"
", + -label=>"query".$nr.":", + ); + $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| + + |); + } + + } + # 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 '
'.Dumper($var).'
'; + 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; +