An attempt to add more functionality to searching

- All words are trimmed
- Double quotes require the exact phrase ("Nathan Gray" will not match Nathan Brown or Gray Power)
- Modifiers + and - before a word will require or exclude the word (+test -fail), otherwise word is ORed
- User language modifiers AND, OR and NOT (uppercase) will be parsed.
- Combinations: tracker entry -testing -"fatal error"
Note that "entry" will not match "entries", and the results are not sorted by match strength.

All applications that use so_sql for searching should gain these benefits.
This commit is contained in:
Nathan Gray 2010-03-31 21:29:11 +00:00
parent 9127fb5d71
commit cc0f8f3c28

View File

@ -782,7 +782,8 @@ class so_sql
// if extending class or instanciator set columns to search, convert string criteria to array
if ($criteria && !is_array($criteria))
{
$criteria = $this->search2criteria($criteria,$wildcard,$op);
$search = $this->search2criteria($criteria,$wildcard,$op);
$criteria = array($search);
}
if (!is_array($criteria))
{
@ -1017,28 +1018,91 @@ class so_sql
* @param string $extra_col=null extra column to search
* @return array or column => value pairs
*/
protected function search2criteria($pattern,&$wildcard='',&$op='AND',$extra_col=null)
public function search2criteria($pattern,&$wildcard='',&$op='AND',$extra_col=null, $search_cols = array())
{
// This function can get called multiple times. Make sure it doesn't re-process.
if (empty($pattern) || is_array($pattern)) return $pattern;
if(strpos($pattern, 'CONCAT') !== false) {
return $pattern;
}
// prepend and append extra wildcard %, if pattern does NOT already contain wildcards
if (strpos($pattern,'*') === false && strpos($pattern,'?') === false)
{
$wildcard = '%'; // if pattern contains no wildcards, add them before AND after the pattern
}
else
{
$wildcard = ''; // no extra wildcard, if pattern already contains some
}
if ($pattern[0] != '!') $op = 'OR';
$criteria = array();
foreach(is_null($this->columns_to_search) ? $this->db_cols : $this->columns_to_search as $col)
{
$criteria[$col] = $pattern;
$filter = array();
$columns = '';
$or_list = array();
if(!$search_cols) {
$search_cols = is_null($this->columns_to_search) ? $this->db_cols : $this->columns_to_search;
}
if ($extra_col) $criteria[$extra_col] = $pattern;
if(!$search_cols) return '';
return $criteria;
// Concat all fields to be searched together, so the conditions operate across the whole record
foreach($search_cols as $col)
{
$columns .= "CAST(COALESCE($col,'') AS char),";
}
if(strlen($columns) > 0) {
$columns = 'CONCAT(' . substr($columns, 0, -1) . ')';
}
// Break the search string into tokens
$break = ' ';
$token = strtok($pattern, $break);
while($token) {
if($token == strtoupper(lang('AND'))) {
$token = '+'.strtok($break);
} elseif ($token == strtoupper(lang('OR'))) {
continue;
} elseif ($token == strtoupper(lang('NOT'))) {
$token = '-'.strtok($break);
}
if ($token[0]=='"') {
$token = substr($token, 1,strlen($token));
$token .= ' '.strtok('"');
}
// prepend and append extra wildcard %, if pattern does NOT already contain wildcards
if (strpos($token,'*') === false && strpos($token,'?') === false)
{
$wildcard = '%'; // if pattern contains no wildcards, add them before AND after the pattern
}
else
{
$wildcard = ''; // no extra wildcard, if pattern already contains some
}
switch($token[0]) {
case '+':
$op = 'AND';
$token = substr($token, 1, strlen($token));
break;
case '-':
case '!':
$op = 'NOT';
$token = substr($token, 1, strlen($token));
break;
default:
$op = 'OR';
continue;
}
$criteria[$op][] = " $columns LIKE " .
$GLOBALS['egw']->db->quote($wildcard.str_replace(array('%','_','*','?'),array('\\%','\\_','%','_'),$token).$wildcard);
$token = strtok($break);
}
if($criteria['NOT']) {
$filter[] = 'NOT (' . implode(' OR ', $criteria['NOT']) . ') ';
}
if($criteria['AND']) {
$filter[] = implode(' AND ', $criteria['AND']) . ' ';
}
if($criteria['OR']) {
$filter[] = '(' . implode(' OR ', $criteria['OR']) . ') ';
}
if ($extra_col) $filter[] = (strlen($filter) ? ' AND ' : ' ' ) . "$extra_col = $pattern";
$op = 'AND';
return $filter;
}
/**
@ -1381,4 +1445,4 @@ class so_sql_db2data_iterator implements Iterator
}
return false;
}
}
}