/** * @param nc_search_query_expression_phrase $expression * @return string */ protected function translate_phrase(nc_search_query_expression_phrase $expression) { $this->stack[] = "phrase"; // Given a phrase "one two", where "two" has forms of "two1" and "two2": // - added to the main FTS condition: ((+one +two1) (+one +two2)) // (this query is used to fetch rows which contain these words using a FTS index) // - added to the query: REGEXP condition(s) to fetch only rows where these terms // come in sequence (REGEXP is used because there could be several // variants of the same term in the index in the form "one two1|two2") $term_combinations = array(); $regexps = array(); /** @var $item nc_search_query_expression_term */ foreach ($expression->get_items() as $item) { // "for each term in the phrase" $item_codes = $this->process_term($item); if ($item_codes[0]) { // not a stop word $brackets = $this->get_brackets(count($item_codes) > 1); $regexps[] = $brackets[0] . join("|", array_filter($item_codes)) . $brackets[1]; } if (count($term_combinations) == 0) { // it's a first (meaningful) term $term_combinations = $item_codes; } else { // produce all possible combinations $previous_state = $term_combinations; $term_combinations = array(); foreach ($previous_state as $p) { foreach ($item_codes as $c) { $not_empty = strlen($c) > 0; // might be a stop word $term_combinations[] = $p . ($not_empty ? " {$c}" : ""); } } } } // there can be a space in the beginning if the first term is stop word if ($term_combinations && $term_combinations[0][0] == " ") { $term_combinations = array_map('trim', $term_combinations); } // all terms in the phrase are stop words?! if (!$term_combinations || !$term_combinations[0]) { return ""; } // make FTS query to pre-filter results before running regexp match $fts_query = ""; if (!$expression->is_excluded()) { $brackets = $this->get_brackets(count($term_combinations) > 1); $fts_query = "{$brackets['0']}(+" . join(")(+", $term_combinations) . "){$brackets['1']}"; $fts_query = strtr($fts_query, array(" " => " +", ")(" => ") (")); } // add exact sequence condition $table_names = $this->get_table_names($expression); // [.vertical-line.] is not redundant: it prevents from matching a partial code $term_boundary = "([[.vertical-line.]][^[.space.][.slash.]]+)?[[.space.]]"; $skip_regexps = false; $num_regexps = count($regexps); if ($expression->get_distance()) { // it's a "proximity search" if ($num_regexps < 2 || $num_regexps > (int) nc_search::get_setting("DatabaseIndex_MaxProximityTerms")) { // the proximity phrase is too long (or too short)! $skip_regexps = true; } else { // get all possible permutations $skipped_term = "[^[.space.][.slash.]]+[[.space.]]"; $distance = min((int) nc_search::get_setting("DatabaseIndex_MaxProximityDistance"), $expression->get_distance()); $gaps = $distance == 1 ? $term_boundary : "{$term_boundary}({$skipped_term}){0,{$distance}}"; $regexps = $this->permute($regexps, $gaps); } } else { // ordinary phrase, distance=0 if ($num_regexps > 1) { $regexps = join($term_boundary, $regexps); } else { $skip_regexps = true; } } if (!$skip_regexps) { $this->query_builder->add_field_regexps($table_names, $regexps, $expression->is_excluded()); } elseif ($expression->get_field()) { $this->add_field_matches($expression, $fts_query); } // remove "phrase" from the stack array_pop($this->stack); // done return $fts_query; }
/** * @param nc_search_query_expression_phrase $expression * @return void */ protected function translate_phrase(nc_search_query_expression_phrase $expression) { $terms = array(); foreach ($expression->get_items() as $term) { $terms[] = $term->get_value(); } $string = 'PHRASE "' . join(" ", $terms) . '"' . $this->get_modifiers($expression); $this->print_line($string); }