Commit 0beb5251 authored by webchick's avatar webchick

Issue #2195907 by jhodgdon: Search ranking for number of views fails in PostgreSQL.

parent 125cfd58
......@@ -1034,7 +1034,7 @@ function comment_node_update_index(EntityInterface $node, $langcode) {
function comment_cron() {
// Store the maximum possible comments per thread (used for node search
// ranking by reply count).
\Drupal::state()->set('comment.node_comment_statistics_scale', 1.0 / max(1, db_query('SELECT MAX(comment_count) FROM {comment_entity_statistics}')->fetchField()));
\Drupal::state()->set('comment.node_comment_statistics_scale', 1.0 / max(1.0, db_query('SELECT MAX(comment_count) FROM {comment_entity_statistics}')->fetchField()));
}
/**
......@@ -1547,9 +1547,12 @@ function comment_ranking() {
// nodes.
'on' => "ces.entity_id = i.sid AND ces.entity_type = 'node' AND ces.field_id = 'node__comment'",
),
// Inverse law that maps the highest reply count on the site to 1 and 0 to 0.
'score' => '2.0 - 2.0 / (1.0 + ces.comment_count * :scale)',
'arguments' => array(':scale' => (float) \Drupal::state()->get('comment.node_comment_statistics_scale') ?: 0),
// Inverse law that maps the highest reply count on the site to 1 and 0
// to 0. Note that the CAST here is necessary for PostgreSQL, because the
// PostgreSQL PDO driver sometimes puts values in as strings instead of
// numbers in complex expressions like this.
'score' => '2.0 - 2.0 / (1.0 + ces.comment_count * (CAST (:comment_scale AS DECIMAL(10, 4))))',
'arguments' => array(':comment_scale' => \Drupal::state()->get('comment.node_comment_statistics_scale') ?: 0),
),
);
}
......
......@@ -131,6 +131,13 @@ class SearchQuery extends SelectExtender {
*/
protected $multiply = array();
/**
* The number of 'i.relevance' occurrences in score expressions.
*
* @var int
*/
protected $relevance_count = 0;
/**
* Whether or not search expressions were ignored.
*
......@@ -384,26 +391,25 @@ public function executeFirstPass() {
* expression can reference 'calculated_score', which will be the total
* calculated score value.
*
* @param $score
* @param string $score
* The score expression, which should evaluate to a number between 0 and 1.
* The string 'i.relevance' in a score expression will be replaced by a
* measure of keyword relevance between 0 and 1.
* @param $arguments
* @param array $arguments
* Query arguments needed to provide values to the score expression.
* @param $multiply
* @param float $multiply
* If set, the score is multiplied with this value. However, all scores
* with multipliers are then divided by the total of all multipliers, so
* that overall, the normalization is maintained.
*
* @return object
* The updated query object.
* @return $this
*/
public function addScore($score, $arguments = array(), $multiply = FALSE) {
if ($multiply) {
$i = count($this->multiply);
// Modify the score expression so it is multiplied by the multiplier,
// with a divisor to renormalize.
$score = "CAST(:multiply_$i AS DECIMAL) * COALESCE(( " . $score . "), 0) / CAST(:total_$i AS DECIMAL)";
$score = "(CAST (:multiply_$i AS DECIMAL(10,4))) * COALESCE(($score), 0) / (CAST (:total_$i AS DECIMAL(10,4)))";
// Add an argument for the multiplier. The :total_$i argument is taken
// care of in the execute() method, which is when the total divisor is
// calculated.
......@@ -411,6 +417,17 @@ public function addScore($score, $arguments = array(), $multiply = FALSE) {
$this->multiply[] = $multiply;
}
// Search scoring needs a way to include a keyword relevance in the score.
// For historical reasons, this is done by putting 'i.relevance' into the
// search expression. So, use string replacement to change this to a
// calculated query expression, counting the number of occurrences so
// in the execute() method we can add arguments.
while (($pos = strpos($score, 'i.relevance')) !== FALSE) {
$pieces = explode('i.relevance', $score, 2);
$score = implode('((CAST (:normalization_' . $this->relevance_count . ' AS DECIMAL(10,4))) * i.score * t.count)', $pieces);
$this->relevance_count++;
}
$this->scores[] = $score;
$this->scoresArguments += $arguments;
......@@ -430,7 +447,6 @@ public function addScore($score, $arguments = array(), $multiply = FALSE) {
*/
public function execute()
{
if (!$this->executedFirstPass) {
$this->executeFirstPass();
}
......@@ -457,13 +473,11 @@ public function execute()
}
}
// Replace the pseudo-expression 'i.relevance' with a measure of keyword
// relevance in all score expressions, using string replacement. Careful
// though! If you just print out a float, some locales use ',' as the
// decimal separator in PHP, while SQL always uses '.'. So, make sure to
// set the number format correctly.
$relevance = number_format((1.0 / $this->normalize), 10, '.', '');
$this->scores = str_replace('i.relevance', '(' . $relevance . ' * i.score * t.count)', $this->scores);
// Add arguments for the keyword relevance normalization number.
$normalization = 1.0 / $this->normalize;
for ($i = 0; $i < $this->relevance_count; $i++ ) {
$this->scoresArguments[':normalization_' . $i] = $normalization;
}
// Add all scores together to form a query field.
$this->addExpression('SUM(' . implode(' + ', $this->scores) . ')', 'calculated_score', $this->scoresArguments);
......
......@@ -107,7 +107,7 @@ function statistics_cron() {
}
// Calculate the maximum of node views, for node search ranking.
\Drupal::state()->set('statistics.node_counter_scale', 1.0 / max(1, db_query('SELECT MAX(totalcount) FROM {node_counter}')->fetchField()));
\Drupal::state()->set('statistics.node_counter_scale', 1.0 / max(1.0, db_query('SELECT MAX(totalcount) FROM {node_counter}')->fetchField()));
}
/**
......@@ -230,9 +230,12 @@ function statistics_ranking() {
'alias' => 'node_counter',
'on' => 'node_counter.nid = i.sid',
),
// Inverse law that maps the highest view count on the site to 1 and 0 to 0.
'score' => '2.0 - 2.0 / (1.0 + node_counter.totalcount * :scale)',
'arguments' => array(':scale' => (float) \Drupal::state()->get('statistics.node_counter_scale') ?: 0),
// Inverse law that maps the highest view count on the site to 1 and 0
// to 0. Note that the CAST here is necessary for PostgreSQL, because
// the PostgreSQL PDO driver sometimes puts values in as strings
// instead of numbers in complex expressions like this.
'score' => '2.0 - 2.0 / (1.0 + node_counter.totalcount * (CAST (:statistics_scale AS DECIMAL(10,4))))',
'arguments' => array(':statistics_scale' => \Drupal::state()->get('statistics.node_counter_scale') ?: 0),
),
);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment