/** * */ public function get_query_string() { if (!$this->query) { return null; } return $this->query->get('query_string'); }
/** * @param nc_search_query $query * @return string|null */ public function translate(nc_search_query $query) { $root = $query->parse(); // empty query? if ($root instanceof nc_search_query_expression_empty) { return null; } // queries with only excluded terms are forbidden if ($root->is_excluded() || $root instanceof nc_search_query_expression_not) { return null; } // initialize variables used for a translation of the query $this->query_builder = $builder = new nc_search_provider_index_querybuilder($query, $this); $this->stack = array(); $this->unknown_required_terms = array(); $this->can_skip_fts_query = false; $this->implicit_field_match = $this->expression_requires_implicit_match($root); // get language filters chain to use in this.translate_term() etc. $language = $query->get('language'); if (!$language) { $language = nc_Core::get_object()->lang->detect_lang(); } $query_context = new nc_search_context(array("search_provider" => get_class($this->provider), "language" => $language, "action" => "searching")); $this->text_filters = nc_search_extension_manager::get('nc_search_language_filter', $query_context)->stop_on(array()); // Ready to go! // translate the expression tree $index_query = $this->dispatch_translate($root); if (!$this->can_skip_fts_query) { // interval search at the root for a numeric field // index query is required almost always if (!strlen($index_query)) { return null; } // e.g. if query string consists of stop words if ($index_query == "____" || $index_query == "(____)") { // empty query return null; } } // set query for the combined index in the query builder if ($index_query && !$root->get_field()) { $builder->set_index_match($index_query); } // there are required terms that are not in the index, so don’t make a query if ($this->unknown_required_terms) { return null; } // использовать временную таблицу для уточняющих запросов при поиске фраз? $use_temp_table = !nc_search::get_setting('DatabaseIndex_AlwaysGetTotalCount') && $this->expression_has_phrase($root); // return SQL query string $result = $builder->get_sql_query($use_temp_table); return $result; }
/** * Выполнение запроса * @param nc_search_query $query * @param boolean $should_highlight * @return nc_search_result */ public function find(nc_search_query $query, $should_highlight = true) { $db = $this->get_db(); $id_list = "0"; $translator = new nc_search_provider_index_translator($this); // get IDs $select_ids_query = $query->translate($translator); $limit = (int) $query->get('limit'); $offset = (int) $query->get('offset'); $total_hits_unknown = false; if ($select_ids_query) { // neither null nor empty string $db->last_error = null; if (is_array($select_ids_query)) { // will need to create a temporary table; no final row count $prefilter_query = $select_ids_query["prefilter"]; $refinement_query = $select_ids_query["refinement"]; $query_hash = sha1($prefilter_query); // (1) create temporary table $db->query("SET @rank=0"); $db->query("CREATE TEMPORARY TABLE `{$select_ids_query['temp_table']}` " . "(INDEX (`Rank`,`Document_ID`)) " . "SELECT filtered.`Document_ID`, @rank := @rank+1 AS `Rank` " . "FROM ({$prefilter_query}) AS filtered"); // (2) check for cached offset values so we won't need to check from the beginning of the pre-filtered list $rank_table = $this->last_rank_table_name; $db->query("DELETE FROM `{$rank_table}` WHERE `Time` < NOW() - INTERVAL {$this->rank_cache_interval}"); $cached_rank = $offset ? $db->get_row("SELECT `Offset`, `Rank` FROM `{$rank_table}`\n" . "WHERE `QueryHash` = '{$query_hash}' AND `Offset` <= {$offset}\n" . "ORDER BY `Offset` DESC\nLIMIT 1", ARRAY_A) : null; if ($cached_rank) { $refinement_offset = $offset - $cached_rank["Offset"]; $refinement_rank_value = $cached_rank["Rank"]; } else { $refinement_offset = $offset; $refinement_rank_value = 0; } $db->query("SET @rank_value = {$refinement_rank_value}"); // (3) select IDs $db->query("{$refinement_query}\n" . "LIMIT " . ($limit + 1) . " OFFSET {$refinement_offset}"); $ids = $db->get_col(); $id_list = join(', ', $ids); if (count($ids) > $limit) { // has next page // total result count is set to (current page + 1 page) // so there will be a paginator $total_hits = $offset + 2 * $limit; $total_hits_unknown = true; // save rank for the next page $last_rank = $db->get_var(null, 1, $limit); $db->query("REPLACE INTO `{$this->last_rank_table_name}`\n SET `Time` = NOW(),\n `QueryHash` = '{$query_hash}',\n `Offset` = " . ($offset + $limit) . ",\n `Rank` = {$last_rank}"); } else { $total_hits = $offset + count($ids); } } else { // select IDs in a single query $id_list = join(', ', $db->get_col($select_ids_query)); $total_hits = $db->last_error ? 0 : $db->get_var("SELECT FOUND_ROWS()"); } } else { // Translator thinks there won't be any results (terms in the query are not in the index) // we could get $translator->get_unknown_terms(), but it is of no use for the correctors... :( $total_hits = 0; } // make 'result' object $result = new nc_search_result(array(), $total_hits); $result->set_query($query); /* @todo use $total_hits_unknown to mark that total count is not known */ if (!$should_highlight) { $result->disable_highlighting(); } if ($total_hits && $id_list) { // get field list $doc = new nc_search_result_document(); $fields = $doc->get_column_names($query->get('options_to_fetch')); // get document data $doc_query = "SELECT {$fields} FROM `%t%` WHERE `Document_ID` IN ({$id_list}) " . "ORDER BY FIELD(`Document_ID`, {$id_list}) " . "LIMIT {$limit}"; $result->select_from_database($doc_query); } return $result; }
/** * @param bool $use_temp_table * @return string|array */ public function get_sql_query($use_temp_table) { $index = $this->index_table; $has_conditions = count($this->condition_stack[0]) > 0; $create_temp_table = $use_temp_table && $has_conditions; if (!$create_temp_table) { foreach ($this->condition_joins as $table_name => $join_condition) { $this->add_left_join($table_name); } } // prepare ORDER BY and value for the `Score` column $ranking_select = "1 AS `Score` "; $order_by = ""; $sort_field_name = $this->query->get('sort_by'); if ($sort_field_name == "last_modified") { $this->add_join($this->document_table); $order_by = "ORDER BY `{$this->document_table}`.`LastModified` DESC"; } elseif ($sort_field_name) { // some other field /** @var $sort_fields nc_search_provider_index_field_manager */ $sort_fields = $this->translator->get_fields('name', $sort_field_name)->where('is_sortable', true); if (count($sort_fields)) { $order_by = array(); /** @var $f nc_search_provider_index_field */ foreach ($sort_fields as $f) { $field_table_name = $f->get_field_table_name(); $this->add_left_join($field_table_name); $order_by[] = "`{$field_table_name}`.`RawData`"; } $order_by = "ORDER BY IFNULL(" . join(", ", $order_by) . ", 0) " . ($this->query->get('sort_direction') == SORT_DESC ? "DESC" : "ASC"); } } if (!$order_by) { // standard sorting by relevance $ranking_select = $this->term_ranking_calculation(); $order_by = "ORDER BY `Score` DESC"; } // Prepare area condition (will need join) $area_condition = ""; $area = $this->query->get('area'); if ($area) { $this->add_join($this->document_table); if (!$area instanceof nc_search_area) { $area = new nc_search_area($area); } $area_condition = "AND " . $area->get_sql_condition() . "\n"; } // Compose query // SELECT $query_string = "SELECT " . ($create_temp_table ? "" : "SQL_CALC_FOUND_ROWS ") . "`{$index}`.`Document_ID`,\n" . $ranking_select; // FROM $query_string .= "FROM `{$index}` FORCE INDEX (`Content`)\n"; // INNER JOINs foreach ($this->joins as $j) { $query_string .= "{$j}\n"; } // LEFT JOINs foreach ($this->left_joins as $j) { $query_string .= "{$j}\n"; } // WHERE [index_match_condition] // (1) Index match; (2) site/path filter $query_string .= "WHERE {$this->index_match}\n{$area_condition}"; // (3) Extra conditions if ($has_conditions && !$use_temp_table) { $query_string .= "AND " . join("\nAND ", $this->condition_stack[0]); } // ORDER $query_string .= "\n{$order_by}"; if ($use_temp_table) { $t = $this->get_temporary_table_name(); $queries = array("temp_table" => $t, "prefilter" => $query_string, "refinement" => "SELECT t.`Document_ID`, t.`Rank`\n" . "FROM `{$t}` AS t\n" . join("\n", $this->condition_joins) . "\n" . "WHERE t.`Rank` >= IFNULL(@rank_value,0) AND" . join("\nAND ", $this->condition_stack[0]) . "\n" . "ORDER BY t.`Rank`\n"); return $queries; } else { // LIMIT, OFFSET $query_string .= "\nLIMIT " . (int) $this->query->get('limit') . "\nOFFSET " . (int) $this->query->get('offset'); return $query_string; } }
/** * Поиск по индексу * @param nc_search_query $query * @param boolean $should_highlight * @return nc_search_result */ public function find(nc_search_query $query, $should_highlight = true) { nc_search::set_current_context(new nc_search_context(array('search_provider' => get_class($this), 'action' => 'searching', 'language' => $query->get('language')))); $index = $this->get_index(); $lucene_query = $this->get_lucene_query($query); if ($query->get('sort_by')) { // custom sort $lucene_result = $index->find($lucene_query, $query->get('sort_by'), SORT_STRING, $query->get('sort_direction')); } else { $lucene_result = $index->find($lucene_query); } $total_hits = count($lucene_result); $result = new nc_search_result(array(), $total_hits); $result->set_query($query); if (!$should_highlight) { $result->disable_highlighting(); } nc_search::set_current_context(); // truncate to get the requested page only $lucene_result = array_slice($lucene_result, $query->get('offset'), $query->get('limit')); // сформировать nc_search_result $result_ids = array(); foreach ($lucene_result as $hit) { $result_ids[] = $hit->doc_id; } if (count($result_ids)) { foreach ($result_ids as $i => $id) { $result_ids[$i] = (int) trim($id, "x"); } $id_list = join(", ", $result_ids); $doc = new nc_search_result_document(); $fields = $doc->get_column_names($query->get('options_to_fetch')); $query = "SELECT {$fields} FROM `%t%` WHERE `Document_ID` IN ({$id_list}) " . "ORDER BY FIELD(`Document_ID`, {$id_list})"; $result->select_from_database($query); } return $result; }
/** * * @param string|nc_search_query $query * @param boolean $highlight_matches * @throws nc_search_exception * @return nc_search_result */ public static function find($query, $highlight_matches = true) { if (self::should('EnableSearch')) { if (is_string($query)) { $query = new nc_search_query($query); } nc_search_util::set_utf_locale($query->get('language')); $result = self::get_provider()->find($query, $highlight_matches); nc_search_util::restore_locale(); return $result; } else { throw new nc_search_exception("Search module is disabled"); } }