Issue #1842226 by jhodgdon | spartlow: Fixed Search OR statements don't work if same keyword is used.

8.0.x
Alex Pott 2014-10-02 13:35:47 +02:00
parent 96dd466f8e
commit ca306bf3f1
2 changed files with 45 additions and 25 deletions

View File

@ -235,7 +235,7 @@ class SearchQuery extends SelectExtender {
}
// Classify tokens.
$or = FALSE;
$in_or = FALSE;
$limit_combinations = \Drupal::config('search.settings')->get('and_or_limit');
// The first search expression does not count as AND.
$and_count = -1;
@ -248,13 +248,14 @@ class SearchQuery extends SelectExtender {
break;
}
$phrase = FALSE;
// Strip off phrase quotes.
$phrase = FALSE;
if ($match[2]{0} == '"') {
$match[2] = substr($match[2], 1, -1);
$phrase = TRUE;
$this->simple = FALSE;
}
// Simplify keyword according to indexing rules and external
// preprocessors. Use same process as during search indexing, so it
// will match search index.
@ -275,7 +276,7 @@ class SearchQuery extends SelectExtender {
$last = array($last);
}
$this->keys['positive'][] = $last;
$or = TRUE;
$in_or = TRUE;
$or_count++;
continue;
}
@ -290,7 +291,7 @@ class SearchQuery extends SelectExtender {
// Lower-case "or" instead of "OR" is a warning condition.
$this->status |= SearchQuery::LOWER_CASE_OR;
}
if ($or) {
if ($in_or) {
// Add to last element (which is an array).
$this->keys['positive'][count($this->keys['positive']) - 1] = array_merge($this->keys['positive'][count($this->keys['positive']) - 1], $words);
}
@ -299,33 +300,38 @@ class SearchQuery extends SelectExtender {
$and_count++;
}
}
$or = FALSE;
$in_or = FALSE;
}
// Convert keywords into SQL statements.
$simple_and = FALSE;
$simple_or = FALSE;
$has_and = FALSE;
$has_or = FALSE;
// Positive matches.
foreach ($this->keys['positive'] as $key) {
// Group of ORed terms.
if (is_array($key) && count($key)) {
$simple_or = TRUE;
$any = FALSE;
// If we had already found one OR, this is another one AND-ed with the
// first, meaning it is not a simple query.
if ($has_or) {
$this->simple = FALSE;
}
$has_or = TRUE;
$has_new_scores = FALSE;
$queryor = db_or();
foreach ($key as $or) {
list($num_new_scores) = $this->parseWord($or);
$any |= $num_new_scores;
$has_new_scores |= $num_new_scores;
$queryor->condition('d.data', "% $or %", 'LIKE');
}
if (count($queryor)) {
$this->conditions->condition($queryor);
// A group of OR keywords only needs to match once.
$this->matches += ($any > 0);
$this->matches += ($has_new_scores > 0);
}
}
// Single ANDed term.
else {
$simple_and = TRUE;
$has_and = TRUE;
list($num_new_scores, $num_valid_words) = $this->parseWord($key);
$this->conditions->condition('d.data', "% $key %", 'LIKE');
if (!$num_valid_words) {
@ -335,9 +341,10 @@ class SearchQuery extends SelectExtender {
$this->matches += $num_new_scores;
}
}
if ($simple_and && $simple_or) {
if ($has_and && $has_or) {
$this->simple = FALSE;
}
// Negative matches.
foreach ($this->keys['negative'] as $key) {
$this->conditions->condition('d.data', "% $key %", 'NOT LIKE');
@ -409,8 +416,15 @@ class SearchQuery extends SelectExtender {
$this
->condition('i.type', $this->type)
->groupBy('i.type')
->groupBy('i.sid')
->having('COUNT(*) >= :matches', array(':matches' => $this->matches));
->groupBy('i.sid');
// If the query is simple, we should have calculated the number of
// matching words we need to find, so impose that criterion. For non-
// simple queries, this condition could lead to incorrectly deciding not
// to continue with the full query.
if ($this->simple) {
$this->having('COUNT(*) >= :matches', array(':matches' => $this->matches));
}
// Clone the query object to calculate normalization.
$normalize_query = clone $this->query;

View File

@ -88,15 +88,13 @@ class SearchMatchTest extends SearchTestBase {
* Run predefine queries looking for indexed terms.
*/
function _testQueries() {
/*
Note: OR queries that include short words in OR groups are only accepted
if the ORed terms are ANDed with at least one long word in the rest of the query.
e.g. enim dolore OR ut = enim (dolore OR ut) = (enim dolor) OR (enim ut) -> good
e.g. dolore OR ut = (dolore) OR (ut) -> bad
This is a design limitation to avoid full table scans.
*/
// Note: OR queries that include short words in OR groups are only accepted
// if the ORed terms are ANDed with at least one long word in the rest of
// the query. Examples:
// enim dolore OR ut = enim (dolore OR ut) = (enim dolor) OR (enim ut)
// is good, and
// dolore OR ut = (dolore) OR (ut)
// is bad. This is a design limitation to avoid full table scans.
$queries = array(
// Simple AND queries.
'ipsum' => array(1),
@ -109,7 +107,7 @@ class SearchMatchTest extends SearchTestBase {
'ut minim' => array(5),
'xx minim' => array(),
'enim veniam am minim ut' => array(5),
// Simple OR queries.
// Simple OR and AND/OR queries.
'dolore OR ipsum' => array(1, 2, 7),
'dolore OR xxxxx' => array(2, 7),
'dolore OR ipsum OR enim' => array(1, 2, 4, 5, 6, 7),
@ -119,6 +117,14 @@ class SearchMatchTest extends SearchTestBase {
'minim dolore OR ipsum OR enim' => array(5, 6, 7),
'dolore xx OR yy' => array(),
'xxxxx dolore OR ipsum' => array(),
// Sequence of OR queries.
'minim' => array(5, 6, 7),
'minim OR xxxx' => array(5, 6, 7),
'minim OR xxxx OR minim' => array(5, 6, 7),
'minim OR xxxx minim' => array(5, 6, 7),
'minim OR xxxx minim OR yyyy' => array(5, 6, 7),
'minim OR xxxx minim OR cillum' => array(6, 7, 5),
'minim OR xxxx minim OR xxxx' => array(5, 6, 7),
// Negative queries.
'dolore -sit' => array(7),
'dolore -eu' => array(2),