BUGFIX: fix TranslatableSearchFormText by supporting fulltext search for MSSQL and using extendedSQL function call that augments queries properly (previously it was using DB::query which does not augment). Added wait to TranslatableSearchFormText so the test actually passes.

This commit is contained in:
Mateusz Uzdowski 2010-08-31 05:52:48 +00:00
parent da356e5a01
commit bef5b0ed05

View File

@ -147,7 +147,7 @@ class MSSQLDatabase extends SS_Database {
'CharacterSet' => 'UTF-8', 'CharacterSet' => 'UTF-8',
'MultipleActiveResultSets' => true 'MultipleActiveResultSets' => true
); );
} }
$this->dbConn = sqlsrv_connect($parameters['server'], $connectionInfo); $this->dbConn = sqlsrv_connect($parameters['server'], $connectionInfo);
} }
@ -225,8 +225,9 @@ class MSSQLDatabase extends SS_Database {
/** /**
* Sleep until the catalog has been fully rebuilt. This is a busy wait designed for situations * Sleep until the catalog has been fully rebuilt. This is a busy wait designed for situations
* when you need to be sure the index is up to date - for example in unit tests. * when you need to be sure the index is up to date - for example in unit tests.
* TODO: add a wrapper to DB, so we don't need to check if this function exists every time *
* before we call it from the user code? * TODO: move this to Database class? Can we assume this will be useful for all databases?
* Also see the wrapper functions "waitUntilIndexingFinished" in SearchFormTest and TranslatableSearchFormTest
* *
* @param int $maxWaitingTime Time in seconds to wait for the database. * @param int $maxWaitingTime Time in seconds to wait for the database.
*/ */
@ -1231,39 +1232,52 @@ class MSSQLDatabase extends SS_Database {
return $results; return $results;
} }
// Strip unfriendly characters, SQLServer "CONTAINS" predicate will crash on & and | and ignore others anyway.
if (function_exists('mb_ereg_replace')) {
$keywords = mb_ereg_replace('[^\w\s]', '', trim($keywords));
}
else {
$keywords = Convert::raw2sql(str_replace(array('&','|','!','"','\''), '', trim($keywords)));
}
// Concat with ANDs
$keywords = explode(' ', $keywords);
$keywords = implode(' AND ', $keywords);
//Get a list of all the tables and columns we'll be searching on: //Get a list of all the tables and columns we'll be searching on:
$result = DB::query('EXEC sp_help_fulltext_columns'); $fulltextColumns = DB::query('EXEC sp_help_fulltext_columns');
$queries = array();
// Sort the columns back into tables.
$tables = array(); $tables = array();
foreach($fulltextColumns as $column) {
foreach($result as $row){ // Skip extension tables.
if(substr($row['TABLE_NAME'], -5)!='_Live' && substr($row['TABLE_NAME'], -9)!='_versions') { if(substr($column['TABLE_NAME'], -5)=='_Live' || substr($column['TABLE_NAME'], -9)=='_versions') continue;
$thisSql = "SELECT \"ID\", '{$row['TABLE_NAME']}' AS Source FROM \"{$row['TABLE_NAME']}\" WHERE (".
"CONTAINS(\"{$row['FULLTEXT_COLUMN_NAME']}\", '$keywords')"; // Add the column to table.
if(strpos($row['TABLE_NAME'], 'SiteTree') === 0) { $table = &$tables[$column['TABLE_NAME']];
$thisSql .= " AND ShowInSearch != 0)";//" OR (Title LIKE '%$keywords%' OR Title LIKE '%$htmlEntityKeywords%')"; if (!$table) $table = array($column['FULLTEXT_COLUMN_NAME']);
} else { else array_push($table, $column['FULLTEXT_COLUMN_NAME']);
$thisSql .= ')';
}
$tables[] = $thisSql;
}
} }
$query = implode(' UNION ', $tables); // Create one query per each table, columns not used.
$result = DB::query($query); foreach($tables as $tableName=>$columns){
$join = $this->fullTextSearchMSSQL($tableName, $keywords);
// Check if we need to add ShowInSearch
$where = null;
if(strpos($tableName, 'SiteTree') === 0) {
$where = array("\"$tableName\".\"ShowInSearch\"!=0");
}
// Join with CONTAINSTABLE, a full text searcher that includes relevance factor
$queries[$tableName] = singleton($tableName)->extendedSQL($where);
$queries[$tableName]->from = array("\"$tableName\" INNER JOIN $join AS \"ft\" ON \"$tableName\".\"ID\"=\"ft\".\"KEY\"");
$queries[$tableName]->select = array("\"$tableName\".\"ID\"", "'$tableName' AS Source", "\"Rank\" AS \"Relevance\"");
$queries[$tableName]->orderby = null;
}
// Generate SQL and count totals
$querySQLs = array();
foreach($queries as $query) {
$querySQLs[] = $query->sql();
}
// Unite the SQL
$fullQuery = implode(" UNION ", $querySQLs) . " ORDER BY $sortBy";
// Perform the search
$result = DB::query($fullQuery);
// Regenerate the DataObjects, apply security
$totalCount = 0; $totalCount = 0;
foreach($result as $row) { foreach($result as $row) {
$record = DataObject::get_by_id($row['Source'], $row['ID']); $record = DataObject::get_by_id($row['Source'], $row['ID']);
@ -1304,34 +1318,48 @@ class MSSQLDatabase extends SS_Database {
/** /**
* Returns a SQL fragment for querying a fulltext search index * Returns a SQL fragment for querying a fulltext search index
* @param $fields array The list of field names to search on *
* @param $tableName specific - table name
* @param $keywords string The search query * @param $keywords string The search query
* @param $booleanSearch A MySQL-specific flag to switch to boolean search * @param $fields array The list of field names to search on, or null to include all
*/ */
function fullTextSearchSQL($fields, $keywords, $booleanSearch = false) { function fullTextSearchMSSQL($tableName, $keywords, $fields = null) {
$fieldNames = '"' . implode('", "', $fields) . '"'; // Make sure we are getting an array of fields
if (isset($fields) && !is_array($fields)) $fields = array($fields);
$SQL_keywords = Convert::raw2sql($keywords); // Strip unfriendly characters, SQLServer "CONTAINS" predicate will crash on & and | and ignore others anyway.
if (function_exists('mb_ereg_replace')) {
$keywords = mb_ereg_replace('[^\w\s]', '', trim($keywords));
}
else {
$keywords = Convert::raw2sql(str_replace(array('&','|','!','"','\''), '', trim($keywords)));
}
// Remove stopwords, concat with ANDs
$keywords = explode(' ', $keywords);
$keywords = self::removeStopwords($keywords);
$keywords = implode(' AND ', $keywords);
return "FREETEXT (($fieldNames), '$SQL_keywords')"; if ($fields) $fieldNames = '"' . implode('", "', $fields) . '"';
else $fieldNames = "*";
return "FREETEXTTABLE(\"$tableName\", ($fieldNames), '$keywords')";
} }
/** /**
* Remove noise words that would kill a MSSQL full-text query * Remove stopwords that would kill a MSSQL full-text query
* *
* @param string $keywords * @param array $keywords
* @return string $keywords with noise words removed *
* @author Tom Rix * @return array $keywords with stopwords removed
*/ */
static public function removeNoiseWords($keywords) { static public function removeStopwords($keywords) {
$goodWords = array(); $goodKeywords = array();
foreach (explode(' ', $keywords) as $word) { foreach($keywords as $keyword) {
// @todo we may want to remove +'s -'s etc too if (in_array($keyword, self::$noiseWords)) continue;
if (!in_array($word, self::$noiseWords)) { $goodKeywords[] = trim($keyword);
$goodWords[] = $word;
}
} }
return join(' ', $goodWords); return $goodKeywords;
} }
/* /*
@ -1662,4 +1690,4 @@ class MSSQLQuery extends SS_Query {
return false; return false;
} }
} }