diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index 0cbe24c..a9be5ed 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -1057,7 +1057,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())); } /** @@ -1569,9 +1569,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), ), ); } diff --git a/core/modules/search/lib/Drupal/search/SearchQuery.php b/core/modules/search/lib/Drupal/search/SearchQuery.php index b9b41c6..cd95ff5 100644 --- a/core/modules/search/lib/Drupal/search/SearchQuery.php +++ b/core/modules/search/lib/Drupal/search/SearchQuery.php @@ -132,6 +132,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. * * The maximum number of AND/OR combinations exceeded can be configured to @@ -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); diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module index 5f3a4af..37ff948 100644 --- a/core/modules/statistics/statistics.module +++ b/core/modules/statistics/statistics.module @@ -120,7 +120,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())); } /** @@ -243,9 +243,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), ), ); }