From 0191e1313ca8179bb09a2fc6f3d06de95768bc96 Mon Sep 17 00:00:00 2001 From: wagnerch Date: Sun, 22 Jul 2007 22:23:00 +0000 Subject: [PATCH 01/28] [feature/postgresql-fulltext-search] PostgreSQL fulltext search, version 1. PHPBB3-9730 --- README.fulltext_postgres | 23 + phpBB/includes/search/fulltext_postgres.php | 893 ++++++++++++++++++++ phpBB/install/schemas/fulltext_postgres.sql | 3 + phpBB/language/en/acp/search.php | 14 + 4 files changed, 933 insertions(+) create mode 100644 README.fulltext_postgres create mode 100644 phpBB/includes/search/fulltext_postgres.php create mode 100644 phpBB/install/schemas/fulltext_postgres.sql diff --git a/README.fulltext_postgres b/README.fulltext_postgres new file mode 100644 index 0000000000..b7b6aec9fd --- /dev/null +++ b/README.fulltext_postgres @@ -0,0 +1,23 @@ + +Fulltext Search for PostgreSQL +============================== + +Installation Instructions +1. Install the tsearch2 contribution by executing the tsearch2.sql script + from the PostgreSQL contrib directory. +2. Apply the fulltext_postgres.diff patch using patch(1) to the root + directory of your phpBB3 forum. + + $ patch -p0 word_length = array('min' => $config['fulltext_postgres_min_word_len'], 'max' => $config['fulltext_postgres_max_word_len']); + + if (version_compare(PHP_VERSION, '5.1.0', '>=') || (version_compare(PHP_VERSION, '5.0.0-dev', '<=') && version_compare(PHP_VERSION, '4.4.0', '>='))) + { + // While this is the proper range of PHP versions, PHP may not be linked with the bundled PCRE lib and instead with an older version + if (@preg_match('/\p{L}/u', 'a') !== false) + { + $this->pcre_properties = true; + } + } + + if (function_exists('mb_ereg')) + { + $this->mbstring_regex = true; + } + + if ($db->sql_layer == 'postgres') + { + $pgsql_version = explode('.', substr($db->sql_server_info(), 10)); + if ($pgsql_version[0] >= 8 && $pgsql_version[1] >= 3) + { + $this->tsearch_builtin = true; + } + + + if (!$this->tsearch_builtin) { + $db->sql_query("SELECT set_curcfg('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "')"); + } + } + + $error = false; + } + + /** + * Checks for correct PostgreSQL version and stores min/max word length in the config + */ + function init() + { + global $db; + + if ($db->sql_layer != 'postgres') + { + return $user->lang['FULLTEXT_POSTGRES_INCOMPATIBLE_VERSION']; + } + + if (!$this->tsearch_builtin) { + $sql = "SELECT c.relname + FROM pg_catalog.pg_class c + WHERE c.relkind = 'r' + AND c.relname = 'pg_ts_cfg' + AND pg_catalog.pg_table_is_visible(c.oid)"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (empty ($row['relname'])) + { + return $user->lang['FULLTEXT_POSTGRES_TS_NOT_FOUND']; + } + } + + return false; + } + + /** + * Splits keywords entered by a user into an array of words stored in $this->split_words + * Stores the tidied search query in $this->search_query + * + * @param string &$keywords Contains the keyword as entered by the user + * @param string $terms is either 'all' or 'any' + * @return bool false if no valid keywords were found and otherwise true + */ + function split_keywords(&$keywords, $terms) + { + global $config; + + if ($terms == 'all') + { + $match = array('#\sand\s#iu', '#\sor\s#iu', '#\snot\s#iu', '#\+#', '#-#', '#\|#'); + $replace = array(' +', ' |', ' -', ' +', ' -', ' |'); + + $keywords = preg_replace($match, $replace, $keywords); + } + + // Filter out as above + $split_keywords = preg_replace("#[\"\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords))); + + // Split words + if ($this->pcre_properties) + { + $split_keywords = preg_replace('#([^\p{L}\p{N}\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords))); + } + else if ($this->mbstring_regex) + { + $split_keywords = mb_ereg_replace('([^\w\'*"()])', '\\1\\1', str_replace('\'\'', '\' \'', trim($split_keywords))); + } + else + { + $split_keywords = preg_replace('#([^\w\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords))); + } + + if ($this->pcre_properties) + { + $matches = array(); + preg_match_all('#(?:[^\p{L}\p{N}*"()]|^)([+\-|]?(?:[\p{L}\p{N}*"()]+\'?)*[\p{L}\p{N}*"()])(?:[^\p{L}\p{N}*"()]|$)#u', $split_keywords, $matches); + $this->split_words = $matches[1]; + } + else if ($this->mbstring_regex) + { + mb_regex_encoding('UTF-8'); + mb_ereg_search_init($split_keywords, '(?:[^\w*"()]|^)([+\-|]?(?:[\w*"()]+\'?)*[\w*"()])(?:[^\w*"()]|$)'); + + while (($word = mb_ereg_search_regs())) + { + $this->split_words[] = $word[1]; + } + } + else + { + $matches = array(); + preg_match_all('#(?:[^\w*"()]|^)([+\-|]?(?:[\w*"()]+\'?)*[\w*"()])(?:[^\w*"()]|$)#u', $split_keywords, $matches); + $this->split_words = $matches[1]; + } + + // to allow phrase search, we need to concatenate quoted words + $tmp_split_words = array(); + $phrase = ''; + foreach ($this->split_words as $word) + { + if ($phrase) + { + $phrase .= ' ' . $word; + if (strpos($word, '"') !== false && substr_count($word, '"') % 2 == 1) + { + $tmp_split_words[] = $phrase; + $phrase = ''; + } + } + else if (strpos($word, '"') !== false && substr_count($word, '"') % 2 == 1) + { + $phrase = $word; + } + else + { + $tmp_split_words[] = $word . ' '; + } + } + if ($phrase) + { + $tmp_split_words[] = $phrase; + } + + $this->split_words = $tmp_split_words; + + unset($tmp_split_words); + unset($phrase); + + foreach ($this->split_words as $i => $word) + { + $clean_word = preg_replace('#^[+\-|"]#', '', $word); + + // check word length + $clean_len = utf8_strlen(str_replace('*', '', $clean_word)); + if (($clean_len < $config['fulltext_postgres_min_word_len']) || ($clean_len > $config['fulltext_postgres_max_word_len'])) + { + $this->common_words[] = $word; + unset($this->split_words[$i]); + } + } + + if ($terms == 'any') + { + $this->search_query = ''; + $this->tsearch_query = ''; + foreach ($this->split_words as $word) + { + if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0) || (strpos($word, '|') === 0)) + { + $word = substr($word, 1); + } + $this->search_query .= $word . ' '; + $this->tsearch_query .= '|' . $word . ' '; + } + } + else + { + $this->search_query = ''; + $this->tsearch_query = ''; + foreach ($this->split_words as $word) + { + if (strpos($word, '+') === 0) + { + $this->search_query .= $word . ' '; + $this->tsearch_query .= '&' . substr($word, 1) . ' '; + } + elseif (strpos($word, '-') === 0) + { + $this->search_query .= $word . ' '; + $this->tsearch_query .= '&!' . substr($word, 1) . ' '; + } + elseif (strpos($word, '|') === 0) + { + $this->search_query .= $word . ' '; + $this->tsearch_query .= '|' . substr($word, 1) . ' '; + } + else + { + $this->search_query .= '+' . $word . ' '; + $this->tsearch_query .= '&' . $word . ' '; + } + } + } + + $this->tsearch_query = substr($this->tsearch_query, 1); + $this->search_query = utf8_htmlspecialchars($this->search_query); + + if ($this->search_query) + { + $this->split_words = array_values($this->split_words); + sort($this->split_words); + return true; + } + return false; + } + + /** + * Turns text into an array of words + */ + function split_message($text) + { + global $config; + + // Split words + if ($this->pcre_properties) + { + $text = preg_replace('#([^\p{L}\p{N}\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); + } + else if ($this->mbstring_regex) + { + $text = mb_ereg_replace('([^\w\'*])', '\\1\\1', str_replace('\'\'', '\' \'', trim($text))); + } + else + { + $text = preg_replace('#([^\w\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); + } + + if ($this->pcre_properties) + { + $matches = array(); + preg_match_all('#(?:[^\p{L}\p{N}*]|^)([+\-|]?(?:[\p{L}\p{N}*]+\'?)*[\p{L}\p{N}*])(?:[^\p{L}\p{N}*]|$)#u', $text, $matches); + $text = $matches[1]; + } + else if ($this->mbstring_regex) + { + mb_regex_encoding('UTF-8'); + mb_ereg_search_init($text, '(?:[^\w*]|^)([+\-|]?(?:[\w*]+\'?)*[\w*])(?:[^\w*]|$)'); + + $text = array(); + while (($word = mb_ereg_search_regs())) + { + $text[] = $word[1]; + } + } + else + { + $matches = array(); + preg_match_all('#(?:[^\w*]|^)([+\-|]?(?:[\w*]+\'?)*[\w*])(?:[^\w*]|$)#u', $text, $matches); + $text = $matches[1]; + } + + // remove too short or too long words + $text = array_values($text); + for ($i = 0, $n = sizeof($text); $i < $n; $i++) + { + $text[$i] = trim($text[$i]); + if (utf8_strlen($text[$i]) < $config['fulltext_postgres_min_word_len'] || utf8_strlen($text[$i]) > $config['fulltext_postgres_max_word_len']) + { + unset($text[$i]); + } + } + + return array_values($text); + } + + /** + * Performs a search on keywords depending on display specific params. You have to run split_keywords() first. + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param string &$fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) + * @param string &$terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) + * @param array &$sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string &$sort_key is the key of $sort_by_sql for the selected sorting + * @param string &$sort_dir is either a or d representing ASC and DESC + * @param string &$sort_days specifies the maximum amount of days a post may be old + * @param array &$ex_fid_ary specifies an array of forum ids which should not be searched + * @param array &$m_approve_fid_ary specifies an array of forum ids in which the searcher is allowed to view unapproved posts + * @param int &$topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array &$author_ary an array of author ids if the author should be ignored during the search the array is empty + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + * + * @access public + */ + function keyword_search($type, &$fields, &$terms, &$sort_by_sql, &$sort_key, &$sort_dir, &$sort_days, &$ex_fid_ary, &$m_approve_fid_ary, &$topic_id, &$author_ary, &$id_ary, $start, $per_page) + { + global $config, $db; + + // No keywords? No posts. + if (!$this->search_query) + { + return false; + } + + // generate a search_key from all the options to identify the results + $search_key = md5(implode('#', array( + implode(', ', $this->split_words), + $type, + $fields, + $terms, + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + implode(',', $m_approve_fid_ary), + implode(',', $author_ary) + ))); + + // try reading the results from cache + $result_count = 0; + if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $result_count; + } + + $id_ary = array(); + + $join_topic = ($type == 'posts') ? false : true; + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; + break; + + case 't': + $join_topic = true; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + // Build some display specific sql strings + switch ($fields) + { + case 'titleonly': + $sql_match = 'p.post_subject'; + $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; + $join_topic = true; + break; + + case 'msgonly': + $sql_match = 'p.post_text'; + $sql_match_where = ''; + break; + + case 'firstpost': + $sql_match = 'p.post_subject, p.post_text'; + $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; + $join_topic = true; + break; + + default: + $sql_match = 'p.post_subject, p.post_text'; + $sql_match_where = ''; + break; + } + + if (!sizeof($m_approve_fid_ary)) + { + $m_approve_fid_sql = ' AND p.post_approved = 1'; + } + else if ($m_approve_fid_ary === array(-1)) + { + $m_approve_fid_sql = ''; + } + else + { + $m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')'; + } + + $sql_select = ($type == 'posts') ? 'p.post_id' : 'DISTINCT t.topic_id'; + $sql_from = ($join_topic) ? TOPICS_TABLE . ' t, ' : ''; + $field = ($type == 'posts') ? 'post_id' : 'topic_id'; + $sql_author = (sizeof($author_ary) == 1) ? ' = ' . $author_ary[0] : 'IN (' . implode(', ', $author_ary) . ')'; + + $sql_where_options = $sql_sort_join; + $sql_where_options .= ($topic_id) ? ' AND p.topic_id = ' . $topic_id : ''; + $sql_where_options .= ($join_topic) ? ' AND t.topic_id = p.topic_id' : ''; + $sql_where_options .= (sizeof($ex_fid_ary)) ? ' AND ' . $db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; + $sql_where_options .= $m_approve_fid_sql; + $sql_where_options .= (sizeof($author_ary)) ? ' AND p.poster_id ' . $sql_author : ''; + $sql_where_options .= ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $sql_where_options .= $sql_match_where; + + $tmp_sql_match = array(); + foreach (explode(',', $sql_match) as $sql_match_column) + { + if ($this->tsearch_builtin) + { + $tmp_sql_match[] = "to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', " . $sql_match_column . ") @@ to_tsquery ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', '" . $db->sql_escape($this->tsearch_query) . "')"; + } + else + { + $tmp_sql_match[] = "to_tsvector (" . $sql_match_column . ") @@ to_tsquery ('" . $db->sql_escape($this->tsearch_query) . "')"; + } + } + + $sql = "SELECT $sql_select + FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p + WHERE (" . implode(' OR ', $tmp_sql_match) . ") + $sql_where_options + ORDER BY $sql_sort"; + $result = $db->sql_query_limit($sql, $config['search_block_size'], $start); + + while ($row = $db->sql_fetchrow($result)) + { + $id_ary[] = $row[$field]; + } + $db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + + if (!sizeof($id_ary)) + { + return false; + } + + // if the total result count is not cached yet, retrieve it from the db + if (!$result_count) + { + $result_count = sizeof ($id_ary); + + if (!$result_count) + { + return false; + } + } + + // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page + $this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, (int) $per_page); + + return $result_count; + } + + /** + * Performs a search on an author's posts without caring about message contents. Depends on display specific params + * + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return total number of results + */ + function author_search($type, $firstpost_only, &$sort_by_sql, &$sort_key, &$sort_dir, &$sort_days, &$ex_fid_ary, &$m_approve_fid_ary, &$topic_id, &$author_ary, &$id_ary, $start, $per_page) + { + global $config, $db; + + // No author? No posts. + if (!sizeof($author_ary)) + { + return 0; + } + + // generate a search_key from all the options to identify the results + $search_key = md5(implode('#', array( + '', + $type, + ($firstpost_only) ? 'firstpost' : '', + '', + '', + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + implode(',', $m_approve_fid_ary), + implode(',', $author_ary) + ))); + + // try reading the results from cache + $result_count = 0; + if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $result_count; + } + + $id_ary = array(); + + // Create some display specific sql strings + $sql_author = $db->sql_in_set('p.poster_id', $author_ary); + $sql_fora = (sizeof($ex_fid_ary)) ? ' AND ' . $db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; + $sql_topic_id = ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : ''; + $sql_time = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $sql_firstpost = ($firstpost_only) ? ' AND p.post_id = t.topic_first_post_id' : ''; + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; + break; + + case 't': + $sql_sort_table = ($type == 'posts') ? TOPICS_TABLE . ' t, ' : ''; + $sql_sort_join = ($type == 'posts') ? ' AND t.topic_id = p.topic_id ' : ''; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + if (!sizeof($m_approve_fid_ary)) + { + $m_approve_fid_sql = ' AND p.post_approved = 1'; + } + else if ($m_approve_fid_ary == array(-1)) + { + $m_approve_fid_sql = ''; + } + else + { + $m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')'; + } + + // Build the query for really selecting the post_ids + if ($type == 'posts') + { + $sql = "SELECT p.post_id + FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + $sql_sort_join + $sql_time + ORDER BY $sql_sort"; + $field = 'post_id'; + } + else + { + $sql = "SELECT t.topic_id + FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + AND t.topic_id = p.topic_id + $sql_sort_join + $sql_time + GROUP BY t.topic_id, $sort_by_sql[$sort_key] + ORDER BY $sql_sort"; + $field = 'topic_id'; + } + + // Only read one block of posts from the db and then cache it + $result = $db->sql_query_limit($sql, $config['search_block_size'], $start); + + while ($row = $db->sql_fetchrow($result)) + { + $id_ary[] = $row[$field]; + } + $db->sql_freeresult($result); + + // retrieve the total result count if needed + if (!$result_count) + { + $result_count = sizeof ($id_ary); + + if (!$result_count) + { + return false; + } + } + + if (sizeof($id_ary)) + { + $this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, $per_page); + + return $result_count; + } + return false; + } + + /** + * Destroys cached search results, that contained one of the new words in a post so the results won't be outdated. + * + * @param string $mode contains the post mode: edit, post, reply, quote ... + */ + function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) + { + global $db; + + // Split old and new post/subject to obtain array of words + $split_text = $this->split_message($message); + $split_title = ($subject) ? $this->split_message($subject) : array(); + + $words = array_unique(array_merge($split_text, $split_title)); + + unset($split_text); + unset($split_title); + + // destroy cached search results containing any of the words removed or added + $this->destroy_cache($words, array($poster_id)); + + unset($words); + } + + /** + * Destroy cached results, that might be outdated after deleting a post + */ + function index_remove($post_ids, $author_ids, $forum_ids) + { + $this->destroy_cache(array(), $author_ids); + } + + /** + * Destroy old cache entries + */ + function tidy() + { + global $db, $config; + + // destroy too old cached search results + $this->destroy_cache(array()); + + set_config('search_last_gc', time(), true); + } + + /** + * Create fulltext index + */ + function create_index($acp_module, $u_action) + { + global $db, $config; + + // Make sure we can actually use PostgreSQL with fulltext indexes + if ($error = $this->init()) + { + return $error; + } + + if (empty($this->stats)) + { + $this->get_stats(); + } + + if (!isset($this->stats['post_subject'])) + { + $db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $config['fulltext_postgres_ts_name'] . "_post_subject ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', post_subject))"); + } + + if (!isset($this->stats['post_text'])) + { + $db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $config['fulltext_postgres_ts_name'] . "_post_text ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', post_text))"); + } + + $db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + + return false; + } + + /** + * Drop fulltext index + */ + function delete_index($acp_module, $u_action) + { + global $db; + + // Make sure we can actually use PostgreSQL with fulltext indexes + if ($error = $this->init()) + { + return $error; + } + + if (empty($this->stats)) + { + $this->get_stats(); + } + + if (isset($this->stats['post_subject'])) + { + $db->sql_query('DROP INDEX ' . $this->stats['post_subject']['relname']); + } + + if (isset($this->stats['post_text'])) + { + $db->sql_query('DROP INDEX ' . $this->stats['post_text']['relname']); + } + + $db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + + return false; + } + + /** + * Returns true if both FULLTEXT indexes exist + */ + function index_created() + { + if (empty($this->stats)) + { + $this->get_stats(); + } + + return (isset($this->stats['post_text']) && isset($this->stats['post_subject'])) ? true : false; + } + + /** + * Returns an associative array containing information about the indexes + */ + function index_stats() + { + global $user; + + if (empty($this->stats)) + { + $this->get_stats(); + } + + return array( + $user->lang['FULLTEXT_POSTGRES_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0, + ); + } + + function get_stats() + { + global $db, $config; + + $sql = "SELECT c2.relname, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS indexdef + FROM pg_catalog.pg_class c1, pg_catalog.pg_index i, pg_catalog.pg_class c2 + WHERE c1.relname = '" . POSTS_TABLE . "' + AND pg_catalog.pg_table_is_visible(c1.oid) + AND c1.oid = i.indrelid + AND i.indexrelid = c2.oid"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + // deal with older PostgreSQL versions which didn't use Index_type + if (strpos($row['indexdef'], 'to_tsvector') !== false) + { + if ($row['relname'] == POSTS_TABLE . '_' . $config['fulltext_postgres_ts_name'] . '_post_text' || $row['relname'] == POSTS_TABLE . '_post_text') + { + $this->stats['post_text'] = $row; + } + else if ($row['relname'] == POSTS_TABLE . '_' . $config['fulltext_postgres_ts_name'] . '_post_subject' || $row['relname'] == POSTS_TABLE . '_post_subject') + { + $this->stats['post_subject'] = $row; + } + } + } + $db->sql_freeresult($result); + + $this->stats['total_posts'] = $config['num_posts']; + } + + /** + * Display a note, that UTF-8 support is not available with certain versions of PHP + */ + function acp() + { + global $user, $config, $db; + + $tpl = ' +
+

' . $user->lang['FULLTEXT_POSTGRES_PCRE_EXPLAIN'] . '
+
' . (($this->pcre_properties) ? $user->lang['YES'] : $user->lang['NO']) . ' (PHP ' . PHP_VERSION . ')
+
+
+

' . $user->lang['FULLTEXT_POSTGRES_MBSTRING_EXPLAIN'] . '
+
' . (($this->mbstring_regex) ? $user->lang['YES'] : $user->lang['NO']). '
+
+
+

' . $user->lang['FULLTEXT_POSTGRES_TS_NAME_EXPLAIN'] . '
+
+
+
+

' . $user->lang['FULLTEXT_POSTGRES_MIN_WORD_LEN_EXPLAIN'] . '
+
+
+
+

' . $user->lang['FULLTEXT_POSTGRES_MAX_WORD_LEN_EXPLAIN'] . '
+
+
+ '; + + // These are fields required in the config table + return array( + 'tpl' => $tpl, + 'config' => array('fulltext_postgres_ts_name' => 'string', 'fulltext_postgres_min_word_len' => 'integer:0:255', 'fulltext_postgres_max_word_len' => 'integer:0:255') + ); + } +} + +?> diff --git a/phpBB/install/schemas/fulltext_postgres.sql b/phpBB/install/schemas/fulltext_postgres.sql new file mode 100644 index 0000000000..0be6648d0c --- /dev/null +++ b/phpBB/install/schemas/fulltext_postgres.sql @@ -0,0 +1,3 @@ +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_postgres_max_word_len', '254'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_postgres_min_word_len', '4'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_postgres_ts_name', 'default'); diff --git a/phpBB/language/en/acp/search.php b/phpBB/language/en/acp/search.php index 3dc89570bf..5fa5acbe40 100644 --- a/phpBB/language/en/acp/search.php +++ b/phpBB/language/en/acp/search.php @@ -61,6 +61,20 @@ $lang = array_merge($lang, array( 'FULLTEXT_MYSQL_MIN_SEARCH_CHARS_EXPLAIN' => 'Words with at least this many characters will be indexed for searching. You or your host can only change this setting by changing the mysql configuration.', 'FULLTEXT_MYSQL_MAX_SEARCH_CHARS_EXPLAIN' => 'Words with no more than this many characters will be indexed for searching. You or your host can only change this setting by changing the mysql configuration.', + 'FULLTEXT_POSTGRES_INCOMPATIBLE_VERSION' => 'The PostgreSQL fulltext backend can only be used with PostgreSQL.', + 'FULLTEXT_POSTGRES_TS_NOT_FOUND' => 'The PostgreSQL fulltext backend can only be used with Tsearch2.', + 'FULLTEXT_POSTGRES_TOTAL_POSTS' => 'Total number of indexed posts', + 'FULLTEXT_POSTGRES_MBSTRING' => 'Support for non-latin UTF-8 characters using mbstring:', + 'FULLTEXT_POSTGRES_PCRE' => 'Support for non-latin UTF-8 characters using PCRE:', + 'FULLTEXT_POSTGRES_TS_NAME' => 'Tsearch2 Configuration Profile:', + 'FULLTEXT_POSTGRES_MIN_WORD_LEN' => 'Minimum word length for keywords', + 'FULLTEXT_POSTGRES_MAX_WORD_LEN' => 'Maximum word length for keywords', + 'FULLTEXT_POSTGRES_MBSTRING_EXPLAIN' => 'If PCRE does not have unicode character properties, the search backend will try to use mbstring’s regular expression engine.', + 'FULLTEXT_POSTGRES_PCRE_EXPLAIN' => 'This search backend requires PCRE unicode character properties, only available in PHP 4.4, 5.1 and above, if you want to search for non-latin characters.', + 'FULLTEXT_POSTGRES_TS_NAME_EXPLAIN' => 'The Tsearch2 configuration profile used to determine the parser and dictionary.', + 'FULLTEXT_POSTGRES_MIN_WORD_LEN_EXPLAIN' => 'Words with at least this many characters will be included in the query to the database.', + 'FULLTEXT_POSTGRES_MAX_WORD_LEN_EXPLAIN' => 'Words with no more than this many characters will be included in the query to the database.', + 'GENERAL_SEARCH_SETTINGS' => 'General search settings', 'GO_TO_SEARCH_INDEX' => 'Go to search index page', From fa470c3792b1030c48f6eaa8216a2bcbb975cb11 Mon Sep 17 00:00:00 2001 From: wagnerch Date: Tue, 7 Aug 2007 03:52:00 +0000 Subject: [PATCH 02/28] [feature/postgresql-fulltext-search] PostgreSQL fulltext search, version 2. PHPBB3-9730 --- phpBB/includes/search/fulltext_postgres.php | 76 +++++++-------------- 1 file changed, 24 insertions(+), 52 deletions(-) diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php index 5f70ec2b24..0de456c2cd 100644 --- a/phpBB/includes/search/fulltext_postgres.php +++ b/phpBB/includes/search/fulltext_postgres.php @@ -35,7 +35,6 @@ class fulltext_postgres extends search_backend var $common_words = array(); var $pcre_properties = false; var $mbstring_regex = false; - var $tsearch_builtin = false; function fulltext_postgres(&$error) { @@ -59,16 +58,7 @@ class fulltext_postgres extends search_backend if ($db->sql_layer == 'postgres') { - $pgsql_version = explode('.', substr($db->sql_server_info(), 10)); - if ($pgsql_version[0] >= 8 && $pgsql_version[1] >= 3) - { - $this->tsearch_builtin = true; - } - - - if (!$this->tsearch_builtin) { - $db->sql_query("SELECT set_curcfg('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "')"); - } + $db->sql_query("SELECT set_curcfg('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "')"); } $error = false; @@ -86,20 +76,18 @@ class fulltext_postgres extends search_backend return $user->lang['FULLTEXT_POSTGRES_INCOMPATIBLE_VERSION']; } - if (!$this->tsearch_builtin) { - $sql = "SELECT c.relname - FROM pg_catalog.pg_class c - WHERE c.relkind = 'r' - AND c.relname = 'pg_ts_cfg' - AND pg_catalog.pg_table_is_visible(c.oid)"; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); + $sql = "SELECT c.relname + FROM pg_catalog.pg_class c + WHERE c.relkind = 'r' + AND c.relname = 'pg_ts_cfg' + AND pg_catalog.pg_table_is_visible(c.oid)"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); - if (empty ($row['relname'])) - { - return $user->lang['FULLTEXT_POSTGRES_TS_NOT_FOUND']; - } + if (empty ($row['relname'])) + { + return $user->lang['FULLTEXT_POSTGRES_TS_NOT_FOUND']; } return false; @@ -126,7 +114,7 @@ class fulltext_postgres extends search_backend } // Filter out as above - $split_keywords = preg_replace("#[\"\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords))); + $split_keywords = preg_replace("#[\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords))); // Split words if ($this->pcre_properties) @@ -458,14 +446,7 @@ class fulltext_postgres extends search_backend $tmp_sql_match = array(); foreach (explode(',', $sql_match) as $sql_match_column) { - if ($this->tsearch_builtin) - { - $tmp_sql_match[] = "to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', " . $sql_match_column . ") @@ to_tsquery ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', '" . $db->sql_escape($this->tsearch_query) . "')"; - } - else - { - $tmp_sql_match[] = "to_tsvector (" . $sql_match_column . ") @@ to_tsquery ('" . $db->sql_escape($this->tsearch_query) . "')"; - } + $tmp_sql_match[] = "to_tsvector (" . $sql_match_column . ") @@ to_tsquery ('" . $db->sql_escape($this->tsearch_query) . "')"; } $sql = "SELECT $sql_select @@ -616,7 +597,7 @@ class fulltext_postgres extends search_backend AND t.topic_id = p.topic_id $sql_sort_join $sql_time - GROUP BY t.topic_id, $sort_by_sql[$sort_key] + GROUP BY t.topic_id ORDER BY $sql_sort"; $field = 'topic_id'; } @@ -701,7 +682,7 @@ class fulltext_postgres extends search_backend */ function create_index($acp_module, $u_action) { - global $db, $config; + global $db; // Make sure we can actually use PostgreSQL with fulltext indexes if ($error = $this->init()) @@ -716,12 +697,12 @@ class fulltext_postgres extends search_backend if (!isset($this->stats['post_subject'])) { - $db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $config['fulltext_postgres_ts_name'] . "_post_subject ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', post_subject))"); + $db->sql_query('CREATE INDEX ' . POSTS_TABLE . '_post_subject ON ' . POSTS_TABLE . ' USING gist (to_tsvector (post_subject))'); } if (!isset($this->stats['post_text'])) { - $db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $config['fulltext_postgres_ts_name'] . "_post_text ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', post_text))"); + $db->sql_query('CREATE INDEX ' . POSTS_TABLE . '_post_text ON ' . POSTS_TABLE . ' USING gist (to_tsvector (post_text))'); } $db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); @@ -749,12 +730,12 @@ class fulltext_postgres extends search_backend if (isset($this->stats['post_subject'])) { - $db->sql_query('DROP INDEX ' . $this->stats['post_subject']['relname']); + $db->sql_query('DROP INDEX ' . POSTS_TABLE . '_post_subject'); } if (isset($this->stats['post_text'])) { - $db->sql_query('DROP INDEX ' . $this->stats['post_text']['relname']); + $db->sql_query('DROP INDEX ' . POSTS_TABLE . '_post_text'); } $db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); @@ -809,11 +790,11 @@ class fulltext_postgres extends search_backend // deal with older PostgreSQL versions which didn't use Index_type if (strpos($row['indexdef'], 'to_tsvector') !== false) { - if ($row['relname'] == POSTS_TABLE . '_' . $config['fulltext_postgres_ts_name'] . '_post_text' || $row['relname'] == POSTS_TABLE . '_post_text') + if ($row['relname'] == POSTS_TABLE . '_post_text') { $this->stats['post_text'] = $row; } - else if ($row['relname'] == POSTS_TABLE . '_' . $config['fulltext_postgres_ts_name'] . '_post_subject' || $row['relname'] == POSTS_TABLE . '_post_subject') + else if ($row['relname'] == POSTS_TABLE . '_post_subject') { $this->stats['post_subject'] = $row; } @@ -846,17 +827,8 @@ class fulltext_postgres extends search_backend if ($db->sql_layer == 'postgres') { - if ($this->tsearch_builtin) - { - $sql = 'SELECT cfgname AS ts_name - FROM pg_ts_config'; - } - else - { - $sql = 'SELECT * - FROM pg_ts_cfg'; - } - + $sql = 'SELECT * + FROM pg_ts_cfg'; $result = $db->sql_query($sql); while ($row = $db->sql_fetchrow($result)) From 07e946c1893751e9a9ede5297ed85df0f43cad1c Mon Sep 17 00:00:00 2001 From: we3b Date: Wed, 10 Feb 2010 10:31:00 +0000 Subject: [PATCH 03/28] [feature/postgresql-fulltext-search] PostgreSQL fulltext search, version 3. PHPBB3-9730 --- phpBB/includes/search/fulltext_postgres.php | 163 ++++++++++++++------ 1 file changed, 114 insertions(+), 49 deletions(-) diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php index 0de456c2cd..64ff923692 100644 --- a/phpBB/includes/search/fulltext_postgres.php +++ b/phpBB/includes/search/fulltext_postgres.php @@ -2,7 +2,7 @@ /** * * @package search -* @version $Id: fulltext_postgres.php,v 1.47 2007/06/09 11:08:57 acydburn Exp $ +* @version $Id: fulltext_postgres.php,v 1.49 2010/02/12 10:10:36 frantic Exp $ * @copyright (c) 2005 phpBB Group * @license http://opensource.org/licenses/gpl-license.php GNU Public License * @@ -35,6 +35,7 @@ class fulltext_postgres extends search_backend var $common_words = array(); var $pcre_properties = false; var $mbstring_regex = false; + var $tsearch_builtin = false; function fulltext_postgres(&$error) { @@ -58,7 +59,16 @@ class fulltext_postgres extends search_backend if ($db->sql_layer == 'postgres') { - $db->sql_query("SELECT set_curcfg('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "')"); + $pgsql_version = explode('.', substr($db->sql_server_info(), 10)); + if ($pgsql_version[0] >= 8 && $pgsql_version[1] >= 3) + { + $this->tsearch_builtin = true; + } + + + if (!$this->tsearch_builtin) { + $db->sql_query("SELECT set_curcfg('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "')"); + } } $error = false; @@ -76,18 +86,20 @@ class fulltext_postgres extends search_backend return $user->lang['FULLTEXT_POSTGRES_INCOMPATIBLE_VERSION']; } - $sql = "SELECT c.relname - FROM pg_catalog.pg_class c - WHERE c.relkind = 'r' - AND c.relname = 'pg_ts_cfg' - AND pg_catalog.pg_table_is_visible(c.oid)"; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); + if (!$this->tsearch_builtin) { + $sql = "SELECT c.relname + FROM pg_catalog.pg_class c + WHERE c.relkind = 'r' + AND c.relname = 'pg_ts_cfg' + AND pg_catalog.pg_table_is_visible(c.oid)"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); - if (empty ($row['relname'])) - { - return $user->lang['FULLTEXT_POSTGRES_TS_NOT_FOUND']; + if (empty ($row['relname'])) + { + return $user->lang['FULLTEXT_POSTGRES_TS_NOT_FOUND']; + } } return false; @@ -114,7 +126,7 @@ class fulltext_postgres extends search_backend } // Filter out as above - $split_keywords = preg_replace("#[\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords))); + $split_keywords = preg_replace("#[\"\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords))); // Split words if ($this->pcre_properties) @@ -317,16 +329,17 @@ class fulltext_postgres extends search_backend * Performs a search on keywords depending on display specific params. You have to run split_keywords() first. * * @param string $type contains either posts or topics depending on what should be searched for - * @param string &$fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) - * @param string &$terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) - * @param array &$sort_by_sql contains SQL code for the ORDER BY part of a query - * @param string &$sort_key is the key of $sort_by_sql for the selected sorting - * @param string &$sort_dir is either a or d representing ASC and DESC - * @param string &$sort_days specifies the maximum amount of days a post may be old - * @param array &$ex_fid_ary specifies an array of forum ids which should not be searched - * @param array &$m_approve_fid_ary specifies an array of forum ids in which the searcher is allowed to view unapproved posts - * @param int &$topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched - * @param array &$author_ary an array of author ids if the author should be ignored during the search the array is empty + * @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) + * @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param array $m_approve_fid_ary specifies an array of forum ids in which the searcher is allowed to view unapproved posts + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered * @param int $start indicates the first index of the page * @param int $per_page number of ids each page is supposed to contain @@ -334,7 +347,7 @@ class fulltext_postgres extends search_backend * * @access public */ - function keyword_search($type, &$fields, &$terms, &$sort_by_sql, &$sort_key, &$sort_dir, &$sort_days, &$ex_fid_ary, &$m_approve_fid_ary, &$topic_id, &$author_ary, &$id_ary, $start, $per_page) + function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) { global $config, $db; @@ -433,20 +446,41 @@ class fulltext_postgres extends search_backend $sql_from = ($join_topic) ? TOPICS_TABLE . ' t, ' : ''; $field = ($type == 'posts') ? 'post_id' : 'topic_id'; $sql_author = (sizeof($author_ary) == 1) ? ' = ' . $author_ary[0] : 'IN (' . implode(', ', $author_ary) . ')'; - + + if (sizeof($author_ary) && $author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = '(' . $db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else if (sizeof($author_ary)) + { + $sql_author = ' AND ' . $db->sql_in_set('p.poster_id', $author_ary); + } + else + { + $sql_author = ''; + } + $sql_where_options = $sql_sort_join; $sql_where_options .= ($topic_id) ? ' AND p.topic_id = ' . $topic_id : ''; $sql_where_options .= ($join_topic) ? ' AND t.topic_id = p.topic_id' : ''; $sql_where_options .= (sizeof($ex_fid_ary)) ? ' AND ' . $db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; $sql_where_options .= $m_approve_fid_sql; - $sql_where_options .= (sizeof($author_ary)) ? ' AND p.poster_id ' . $sql_author : ''; + $sql_where_options .= $sql_author; $sql_where_options .= ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; $sql_where_options .= $sql_match_where; $tmp_sql_match = array(); foreach (explode(',', $sql_match) as $sql_match_column) { - $tmp_sql_match[] = "to_tsvector (" . $sql_match_column . ") @@ to_tsquery ('" . $db->sql_escape($this->tsearch_query) . "')"; + if ($this->tsearch_builtin) + { + $tmp_sql_match[] = "to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', " . $sql_match_column . ") @@ to_tsquery ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', '" . $db->sql_escape($this->tsearch_query) . "')"; + } + else + { + $tmp_sql_match[] = "to_tsvector (" . $sql_match_column . ") @@ to_tsquery ('" . $db->sql_escape($this->tsearch_query) . "')"; + } } $sql = "SELECT $sql_select @@ -490,12 +524,25 @@ class fulltext_postgres extends search_backend /** * Performs a search on an author's posts without caring about message contents. Depends on display specific params * - * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered - * @param int $start indicates the first index of the page - * @param int $per_page number of ids each page is supposed to contain - * @return total number of results + * @param string $type contains either posts or topics depending on what should be searched for + * @param boolean $firstpost_only if true, only topic starting posts will be considered + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param array $m_approve_fid_ary specifies an array of forum ids in which the searcher is allowed to view unapproved posts + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + * + * @access public */ - function author_search($type, $firstpost_only, &$sort_by_sql, &$sort_key, &$sort_dir, &$sort_days, &$ex_fid_ary, &$m_approve_fid_ary, &$topic_id, &$author_ary, &$id_ary, $start, $per_page) + function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) { global $config, $db; @@ -517,7 +564,8 @@ class fulltext_postgres extends search_backend $topic_id, implode(',', $ex_fid_ary), implode(',', $m_approve_fid_ary), - implode(',', $author_ary) + implode(',', $author_ary), + $author_name, ))); // try reading the results from cache @@ -528,9 +576,17 @@ class fulltext_postgres extends search_backend } $id_ary = array(); - + // Create some display specific sql strings - $sql_author = $db->sql_in_set('p.poster_id', $author_ary); + if ($author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = '(' . $db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else + { + $sql_author = $db->sql_in_set('p.poster_id', $author_ary); + } $sql_fora = (sizeof($ex_fid_ary)) ? ' AND ' . $db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; $sql_topic_id = ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : ''; $sql_time = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; @@ -547,8 +603,8 @@ class fulltext_postgres extends search_backend break; case 't': - $sql_sort_table = ($type == 'posts') ? TOPICS_TABLE . ' t, ' : ''; - $sql_sort_join = ($type == 'posts') ? ' AND t.topic_id = p.topic_id ' : ''; + $sql_sort_table = ($type == 'posts' && !$firstpost_only) ? TOPICS_TABLE . ' t, ' : ''; + $sql_sort_join = ($type == 'posts' && !$firstpost_only) ? ' AND t.topic_id = p.topic_id ' : ''; break; case 'f': @@ -569,7 +625,7 @@ class fulltext_postgres extends search_backend { $m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')'; } - + // Build the query for really selecting the post_ids if ($type == 'posts') { @@ -597,7 +653,7 @@ class fulltext_postgres extends search_backend AND t.topic_id = p.topic_id $sql_sort_join $sql_time - GROUP BY t.topic_id + GROUP BY t.topic_id, $sort_by_sql[$sort_key] ORDER BY $sql_sort"; $field = 'topic_id'; } @@ -682,7 +738,7 @@ class fulltext_postgres extends search_backend */ function create_index($acp_module, $u_action) { - global $db; + global $db, $config; // Make sure we can actually use PostgreSQL with fulltext indexes if ($error = $this->init()) @@ -697,12 +753,12 @@ class fulltext_postgres extends search_backend if (!isset($this->stats['post_subject'])) { - $db->sql_query('CREATE INDEX ' . POSTS_TABLE . '_post_subject ON ' . POSTS_TABLE . ' USING gist (to_tsvector (post_subject))'); + $db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $config['fulltext_postgres_ts_name'] . "_post_subject ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', post_subject))"); } if (!isset($this->stats['post_text'])) { - $db->sql_query('CREATE INDEX ' . POSTS_TABLE . '_post_text ON ' . POSTS_TABLE . ' USING gist (to_tsvector (post_text))'); + $db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $config['fulltext_postgres_ts_name'] . "_post_text ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', post_text))"); } $db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); @@ -730,12 +786,12 @@ class fulltext_postgres extends search_backend if (isset($this->stats['post_subject'])) { - $db->sql_query('DROP INDEX ' . POSTS_TABLE . '_post_subject'); + $db->sql_query('DROP INDEX ' . $this->stats['post_subject']['relname']); } if (isset($this->stats['post_text'])) { - $db->sql_query('DROP INDEX ' . POSTS_TABLE . '_post_text'); + $db->sql_query('DROP INDEX ' . $this->stats['post_text']['relname']); } $db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); @@ -790,11 +846,11 @@ class fulltext_postgres extends search_backend // deal with older PostgreSQL versions which didn't use Index_type if (strpos($row['indexdef'], 'to_tsvector') !== false) { - if ($row['relname'] == POSTS_TABLE . '_post_text') + if ($row['relname'] == POSTS_TABLE . '_' . $config['fulltext_postgres_ts_name'] . '_post_text' || $row['relname'] == POSTS_TABLE . '_post_text') { $this->stats['post_text'] = $row; } - else if ($row['relname'] == POSTS_TABLE . '_post_subject') + else if ($row['relname'] == POSTS_TABLE . '_' . $config['fulltext_postgres_ts_name'] . '_post_subject' || $row['relname'] == POSTS_TABLE . '_post_subject') { $this->stats['post_subject'] = $row; } @@ -827,8 +883,17 @@ class fulltext_postgres extends search_backend if ($db->sql_layer == 'postgres') { - $sql = 'SELECT * - FROM pg_ts_cfg'; + if ($this->tsearch_builtin) + { + $sql = 'SELECT cfgname AS ts_name + FROM pg_ts_config'; + } + else + { + $sql = 'SELECT * + FROM pg_ts_cfg'; + } + $result = $db->sql_query($sql); while ($row = $db->sql_fetchrow($result)) From b1378f20af2b584fd282d9ecb4007e976ac74dc2 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 29 Apr 2010 15:14:12 -0400 Subject: [PATCH 04/28] [feature/postgresql-fulltext-search] Remove closing php tag PHPBB3-9730 --- phpBB/includes/search/fulltext_postgres.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php index 64ff923692..843ebe69b2 100644 --- a/phpBB/includes/search/fulltext_postgres.php +++ b/phpBB/includes/search/fulltext_postgres.php @@ -926,5 +926,3 @@ class fulltext_postgres extends search_backend ); } } - -?> From 98bc7fa6ce8dff493bdf0e09e1d09bc27eb31a6e Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 29 Apr 2010 15:15:23 -0400 Subject: [PATCH 05/28] [feature/postgresql-fulltext-search] Fixed braces Fixes braces in fulltext_postgres.php to comply with phpbb conventions. PHPBB3-9730 --- phpBB/includes/search/fulltext_postgres.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php index 843ebe69b2..18a5b402bf 100644 --- a/phpBB/includes/search/fulltext_postgres.php +++ b/phpBB/includes/search/fulltext_postgres.php @@ -66,7 +66,8 @@ class fulltext_postgres extends search_backend } - if (!$this->tsearch_builtin) { + if (!$this->tsearch_builtin) + { $db->sql_query("SELECT set_curcfg('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "')"); } } @@ -86,7 +87,8 @@ class fulltext_postgres extends search_backend return $user->lang['FULLTEXT_POSTGRES_INCOMPATIBLE_VERSION']; } - if (!$this->tsearch_builtin) { + if (!$this->tsearch_builtin) + { $sql = "SELECT c.relname FROM pg_catalog.pg_class c WHERE c.relkind = 'r' @@ -446,7 +448,7 @@ class fulltext_postgres extends search_backend $sql_from = ($join_topic) ? TOPICS_TABLE . ' t, ' : ''; $field = ($type == 'posts') ? 'post_id' : 'topic_id'; $sql_author = (sizeof($author_ary) == 1) ? ' = ' . $author_ary[0] : 'IN (' . implode(', ', $author_ary) . ')'; - + if (sizeof($author_ary) && $author_name) { // first one matches post of registered users, second one guests and deleted users @@ -460,7 +462,7 @@ class fulltext_postgres extends search_backend { $sql_author = ''; } - + $sql_where_options = $sql_sort_join; $sql_where_options .= ($topic_id) ? ' AND p.topic_id = ' . $topic_id : ''; $sql_where_options .= ($join_topic) ? ' AND t.topic_id = p.topic_id' : ''; @@ -576,7 +578,7 @@ class fulltext_postgres extends search_backend } $id_ary = array(); - + // Create some display specific sql strings if ($author_name) { @@ -625,7 +627,7 @@ class fulltext_postgres extends search_backend { $m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')'; } - + // Build the query for really selecting the post_ids if ($type == 'posts') { From 9f4219b1c7e74f86134b51fa637b513c5a670b5f Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Tue, 1 May 2012 16:09:08 +0530 Subject: [PATCH 06/28] [feature/postgresql-fulltext-search] minor changes Some changes in code to get it work against current develop. PosgreSQL Fulltext search works for new install now. PHPBB3-9730 --- phpBB/includes/search/fulltext_postgres.php | 18 ++++++++++++------ phpBB/install/schemas/fulltext_postgres.sql | 3 --- phpBB/install/schemas/schema_data.sql | 3 +++ 3 files changed, 15 insertions(+), 9 deletions(-) delete mode 100644 phpBB/install/schemas/fulltext_postgres.sql diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php index 18a5b402bf..6b95a4ff67 100644 --- a/phpBB/includes/search/fulltext_postgres.php +++ b/phpBB/includes/search/fulltext_postgres.php @@ -9,23 +9,19 @@ */ /** +* @ignore */ if (!defined('IN_PHPBB')) { exit; } -/** -* @ignore -*/ -include_once($phpbb_root_path . 'includes/search/search.' . $phpEx); - /** * fulltext_postgres * Fulltext search for PostgreSQL * @package search */ -class fulltext_postgres extends search_backend +class phpbb_search_fulltext_postgres extends phpbb_search_base { var $stats = array(); var $word_length = array(); @@ -75,6 +71,16 @@ class fulltext_postgres extends search_backend $error = false; } + /** + * Returns the name of this search backend to be displayed to administrators + * + * @return string Name + */ + function get_name() + { + return 'PostgreSQL Fulltext'; + } + /** * Checks for correct PostgreSQL version and stores min/max word length in the config */ diff --git a/phpBB/install/schemas/fulltext_postgres.sql b/phpBB/install/schemas/fulltext_postgres.sql deleted file mode 100644 index 0be6648d0c..0000000000 --- a/phpBB/install/schemas/fulltext_postgres.sql +++ /dev/null @@ -1,3 +0,0 @@ -INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_postgres_max_word_len', '254'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_postgres_min_word_len', '4'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_postgres_ts_name', 'default'); diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index 2ea5eca768..fa6cff1bbd 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -124,6 +124,9 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_native_co INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_native_load_upd', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_native_max_chars', '14'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_native_min_chars', '3'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_postgres_max_word_len', '254'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_postgres_min_word_len', '4'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_postgres_ts_name', 'simple'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('gzip_compress', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('hot_threshold', '25'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('icons_path', 'images/icons'); From 8a659964e4788144fde24c4f43362ab2450582f8 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Thu, 7 Jun 2012 01:11:12 +0530 Subject: [PATCH 07/28] [feature/postgresql-fulltext-search] database changes in database_update config entries are now added by database_update.php as well. PostgreSQL will work during update too. PHPBB3-9730 --- phpBB/install/database_update.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 665db1f2f0..3716c03030 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -2207,6 +2207,13 @@ function change_database_data(&$no_updates, $version) set_config('search_type', 'phpbb_search_' . $config['search_type']); } + if (!isset($config['fulltext_postgres_ts_name'])) + { + set_config('fulltext_postgres_max_word_len', 254); + set_config('fulltext_postgres_min_word_len', 4); + set_config('fulltext_postgres_ts_name', 'simple'); + } + if (!isset($config['load_jquery_cdn'])) { set_config('load_jquery_cdn', 0); From f83da6c0ef35ce1b8e49bc4b042e49b3d69d4589 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Sun, 10 Jun 2012 16:46:07 +0530 Subject: [PATCH 08/28] [feature/postgresql-fulltext-search] minor changes Changes to comply with other backend conventions. include $user as global variable to access it inside init(); PHPBB3-9730 --- phpBB/includes/search/fulltext_postgres.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php index 6b95a4ff67..ad39d16818 100644 --- a/phpBB/includes/search/fulltext_postgres.php +++ b/phpBB/includes/search/fulltext_postgres.php @@ -33,7 +33,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base var $mbstring_regex = false; var $tsearch_builtin = false; - function fulltext_postgres(&$error) + public function __construct(&$error) { global $db, $config; @@ -76,7 +76,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base * * @return string Name */ - function get_name() + public function get_name() { return 'PostgreSQL Fulltext'; } @@ -86,7 +86,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base */ function init() { - global $db; + global $db, $user; if ($db->sql_layer != 'postgres') { From d1a49e01cef336a44700134dc35abc88a485da94 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Sun, 10 Jun 2012 17:11:30 +0530 Subject: [PATCH 09/28] [feature/postgresql-fulltext-search] remove backward compatibility removes backward compatibility before PostgreSQL ver 8.3 if version is before 8.3 displays error. PHPBB3-9730 --- phpBB/includes/search/fulltext_postgres.php | 44 +++------------------ 1 file changed, 5 insertions(+), 39 deletions(-) diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php index ad39d16818..893c87549a 100644 --- a/phpBB/includes/search/fulltext_postgres.php +++ b/phpBB/includes/search/fulltext_postgres.php @@ -60,12 +60,6 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base { $this->tsearch_builtin = true; } - - - if (!$this->tsearch_builtin) - { - $db->sql_query("SELECT set_curcfg('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "')"); - } } $error = false; @@ -95,19 +89,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base if (!$this->tsearch_builtin) { - $sql = "SELECT c.relname - FROM pg_catalog.pg_class c - WHERE c.relkind = 'r' - AND c.relname = 'pg_ts_cfg' - AND pg_catalog.pg_table_is_visible(c.oid)"; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - if (empty ($row['relname'])) - { - return $user->lang['FULLTEXT_POSTGRES_TS_NOT_FOUND']; - } + return $user->lang['FULLTEXT_POSTGRES_TS_NOT_FOUND']; } return false; @@ -481,14 +463,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base $tmp_sql_match = array(); foreach (explode(',', $sql_match) as $sql_match_column) { - if ($this->tsearch_builtin) - { - $tmp_sql_match[] = "to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', " . $sql_match_column . ") @@ to_tsquery ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', '" . $db->sql_escape($this->tsearch_query) . "')"; - } - else - { - $tmp_sql_match[] = "to_tsvector (" . $sql_match_column . ") @@ to_tsquery ('" . $db->sql_escape($this->tsearch_query) . "')"; - } + $tmp_sql_match[] = "to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', " . $sql_match_column . ") @@ to_tsquery ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', '" . $db->sql_escape($this->tsearch_query) . "')"; } $sql = "SELECT $sql_select @@ -889,19 +864,10 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base

' . $user->lang['FULLTEXT_POSTGRES_TS_NAME_EXPLAIN'] . '
'; - if ($db->sql_layer == 'postgres' && $this->tsearch_builtin) + if ($db->sql_layer == 'postgres' && $this->tsearch_usable) { $sql = 'SELECT cfgname AS ts_name FROM pg_ts_config'; From 8c170eb6a0ce3b282baa81ea331c75c013367356 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Mon, 11 Jun 2012 16:34:47 +0530 Subject: [PATCH 14/28] [feature/postgresql-fulltext-search] use phpbb_pcre_utf8_support() Use phpBB's built in function for checking PCRE lib support. PHPBB3-9730 --- phpBB/includes/search/fulltext_postgres.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php index 7d1066e884..a0ddbcbe43 100644 --- a/phpBB/includes/search/fulltext_postgres.php +++ b/phpBB/includes/search/fulltext_postgres.php @@ -38,13 +38,10 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base $this->word_length = array('min' => $config['fulltext_postgres_min_word_len'], 'max' => $config['fulltext_postgres_max_word_len']); - if (version_compare(PHP_VERSION, '5.1.0', '>=') || (version_compare(PHP_VERSION, '5.0.0-dev', '<=') && version_compare(PHP_VERSION, '4.4.0', '>='))) + // PHP may not be linked with the bundled PCRE lib and instead with an older version + if (phpbb_pcre_utf8_support()) { - // While this is the proper range of PHP versions, PHP may not be linked with the bundled PCRE lib and instead with an older version - if (@preg_match('/\p{L}/u', 'a') !== false) - { - $this->pcre_properties = true; - } + $this->pcre_properties = true; } if (function_exists('mb_ereg')) From 8981399ae141d87aaa2f2f7994bca9cf6df3ec24 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Mon, 11 Jun 2012 16:49:34 +0530 Subject: [PATCH 15/28] [feature/postgresql-fulltext-search] change language for pgsql < 8.3 PHPBB3-9730 --- phpBB/includes/search/fulltext_postgres.php | 2 +- phpBB/language/en/acp/search.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php index a0ddbcbe43..66208f35c7 100644 --- a/phpBB/includes/search/fulltext_postgres.php +++ b/phpBB/includes/search/fulltext_postgres.php @@ -85,7 +85,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base if (!$this->tsearch_usable) { - return $user->lang['FULLTEXT_POSTGRES_TS_NOT_FOUND']; + return $user->lang['FULLTEXT_POSTGRES_TS_NOT_USABLE']; } return false; diff --git a/phpBB/language/en/acp/search.php b/phpBB/language/en/acp/search.php index 5fa5acbe40..a307bc3e07 100644 --- a/phpBB/language/en/acp/search.php +++ b/phpBB/language/en/acp/search.php @@ -62,7 +62,7 @@ $lang = array_merge($lang, array( 'FULLTEXT_MYSQL_MAX_SEARCH_CHARS_EXPLAIN' => 'Words with no more than this many characters will be indexed for searching. You or your host can only change this setting by changing the mysql configuration.', 'FULLTEXT_POSTGRES_INCOMPATIBLE_VERSION' => 'The PostgreSQL fulltext backend can only be used with PostgreSQL.', - 'FULLTEXT_POSTGRES_TS_NOT_FOUND' => 'The PostgreSQL fulltext backend can only be used with Tsearch2.', + 'FULLTEXT_POSTGRES_TS_NOT_USABLE' => 'The PostgreSQL fulltext backend can only be used with PostgreSQL 8.3 and above.', 'FULLTEXT_POSTGRES_TOTAL_POSTS' => 'Total number of indexed posts', 'FULLTEXT_POSTGRES_MBSTRING' => 'Support for non-latin UTF-8 characters using mbstring:', 'FULLTEXT_POSTGRES_PCRE' => 'Support for non-latin UTF-8 characters using PCRE:', From 84054afc9de614bb41f178f78a71c6966b9e0537 Mon Sep 17 00:00:00 2001 From: Dhruv Goel Date: Tue, 12 Jun 2012 04:18:39 +0530 Subject: [PATCH 16/28] [feature/postgresql-fulltext-search] remove mbstring support Remove the usage of mbstring regex engine when PCRE does not support UTF8 since its a requirement for phpbb 3.1 PHPBB3-9730 --- phpBB/includes/search/fulltext_postgres.php | 89 ++------------------- phpBB/language/en/acp/search.php | 2 - 2 files changed, 8 insertions(+), 83 deletions(-) diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php index 66208f35c7..f5d9b3c760 100644 --- a/phpBB/includes/search/fulltext_postgres.php +++ b/phpBB/includes/search/fulltext_postgres.php @@ -29,7 +29,6 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base var $tsearch_query; var $common_words = array(); var $pcre_properties = false; - var $mbstring_regex = false; var $tsearch_usable = false; public function __construct(&$error) @@ -44,11 +43,6 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base $this->pcre_properties = true; } - if (function_exists('mb_ereg')) - { - $this->mbstring_regex = true; - } - if ($db->sql_layer == 'postgres') { $pgsql_version = explode('.', substr($db->sql_server_info(), 10)); @@ -115,41 +109,10 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base $split_keywords = preg_replace("#[\"\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords))); // Split words - if ($this->pcre_properties) - { - $split_keywords = preg_replace('#([^\p{L}\p{N}\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords))); - } - else if ($this->mbstring_regex) - { - $split_keywords = mb_ereg_replace('([^\w\'*"()])', '\\1\\1', str_replace('\'\'', '\' \'', trim($split_keywords))); - } - else - { - $split_keywords = preg_replace('#([^\w\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords))); - } - - if ($this->pcre_properties) - { - $matches = array(); - preg_match_all('#(?:[^\p{L}\p{N}*"()]|^)([+\-|]?(?:[\p{L}\p{N}*"()]+\'?)*[\p{L}\p{N}*"()])(?:[^\p{L}\p{N}*"()]|$)#u', $split_keywords, $matches); - $this->split_words = $matches[1]; - } - else if ($this->mbstring_regex) - { - mb_regex_encoding('UTF-8'); - mb_ereg_search_init($split_keywords, '(?:[^\w*"()]|^)([+\-|]?(?:[\w*"()]+\'?)*[\w*"()])(?:[^\w*"()]|$)'); - - while (($word = mb_ereg_search_regs())) - { - $this->split_words[] = $word[1]; - } - } - else - { - $matches = array(); - preg_match_all('#(?:[^\w*"()]|^)([+\-|]?(?:[\w*"()]+\'?)*[\w*"()])(?:[^\w*"()]|$)#u', $split_keywords, $matches); - $this->split_words = $matches[1]; - } + $split_keywords = preg_replace('#([^\p{L}\p{N}\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords))); + $matches = array(); + preg_match_all('#(?:[^\p{L}\p{N}*"()]|^)([+\-|]?(?:[\p{L}\p{N}*"()]+\'?)*[\p{L}\p{N}*"()])(?:[^\p{L}\p{N}*"()]|$)#u', $split_keywords, $matches); + $this->split_words = $matches[1]; // to allow phrase search, we need to concatenate quoted words $tmp_split_words = array(); @@ -260,42 +223,10 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base global $config; // Split words - if ($this->pcre_properties) - { - $text = preg_replace('#([^\p{L}\p{N}\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); - } - else if ($this->mbstring_regex) - { - $text = mb_ereg_replace('([^\w\'*])', '\\1\\1', str_replace('\'\'', '\' \'', trim($text))); - } - else - { - $text = preg_replace('#([^\w\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); - } - - if ($this->pcre_properties) - { - $matches = array(); - preg_match_all('#(?:[^\p{L}\p{N}*]|^)([+\-|]?(?:[\p{L}\p{N}*]+\'?)*[\p{L}\p{N}*])(?:[^\p{L}\p{N}*]|$)#u', $text, $matches); - $text = $matches[1]; - } - else if ($this->mbstring_regex) - { - mb_regex_encoding('UTF-8'); - mb_ereg_search_init($text, '(?:[^\w*]|^)([+\-|]?(?:[\w*]+\'?)*[\w*])(?:[^\w*]|$)'); - - $text = array(); - while (($word = mb_ereg_search_regs())) - { - $text[] = $word[1]; - } - } - else - { - $matches = array(); - preg_match_all('#(?:[^\w*]|^)([+\-|]?(?:[\w*]+\'?)*[\w*])(?:[^\w*]|$)#u', $text, $matches); - $text = $matches[1]; - } + $text = preg_replace('#([^\p{L}\p{N}\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); + $matches = array(); + preg_match_all('#(?:[^\p{L}\p{N}*]|^)([+\-|]?(?:[\p{L}\p{N}*]+\'?)*[\p{L}\p{N}*])(?:[^\p{L}\p{N}*]|$)#u', $text, $matches); + $text = $matches[1]; // remove too short or too long words $text = array_values($text); @@ -858,10 +789,6 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base

' . $user->lang['FULLTEXT_POSTGRES_PCRE_EXPLAIN'] . '
' . (($this->pcre_properties) ? $user->lang['YES'] : $user->lang['NO']) . ' (PHP ' . PHP_VERSION . ')
-
-

' . $user->lang['FULLTEXT_POSTGRES_MBSTRING_EXPLAIN'] . '
-
' . (($this->mbstring_regex) ? $user->lang['YES'] : $user->lang['NO']). '
-

' . $user->lang['FULLTEXT_POSTGRES_TS_NAME_EXPLAIN'] . '