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=>"
'.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; +