Merge branch 'pulls/sqlite-case-matching-new'

This commit is contained in:
Ingo Schommer 2012-12-11 15:20:18 +01:00
commit 142ad15a47
7 changed files with 183 additions and 143 deletions

View File

@ -883,6 +883,18 @@ abstract class SS_Database {
return "'" . Convert::raw2sql($string) . "'"; return "'" . Convert::raw2sql($string) . "'";
} }
/**
* Generate a WHERE clause for text matching.
*
* @param String $field Quoted field name
* @param String $value Escaped search. Can include percentage wildcards.
* @param boolean $exact Exact matches or wildcard support.
* @param boolean $negate Negate the clause.
* @param boolean $caseSensitive Perform case sensitive search.
* @return String SQL
*/
abstract public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = false);
/** /**
* function to return an SQL datetime expression that can be used with the adapter in use * function to return an SQL datetime expression that can be used with the adapter in use
* used for querying a datetime in a certain format * used for querying a datetime in a certain format

View File

@ -1056,6 +1056,28 @@ class MySQLDatabase extends SS_Database {
$this->query('COMMIT AND ' . ($chain ? '' : 'NO ') . 'CHAIN;'); $this->query('COMMIT AND ' . ($chain ? '' : 'NO ') . 'CHAIN;');
} }
/**
* Generate a WHERE clause for text matching.
*
* @param String $field Quoted field name
* @param String $value Escaped search. Can include percentage wildcards.
* @param boolean $exact Exact matches or wildcard support.
* @param boolean $negate Negate the clause.
* @param boolean $caseSensitive Enforce case sensitivity if TRUE or FALSE.
* Stick with default collation if set to NULL.
* @return String SQL
*/
public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null) {
if($exact && $caseSensitive === null) {
$comp = ($negate) ? '!=' : '=';
} else {
$comp = ($caseSensitive) ? 'LIKE BINARY' : 'LIKE';
if($negate) $comp = 'NOT ' . $comp;
}
return sprintf("%s %s '%s'", $field, $comp, $value);
}
/** /**
* function to return an SQL datetime expression that can be used with MySQL * function to return an SQL datetime expression that can be used with MySQL
* used for querying a datetime in a certain format * used for querying a datetime in a certain format

View File

@ -17,27 +17,14 @@
* @subpackage search * @subpackage search
*/ */
class EndsWithFilter extends SearchFilter { class EndsWithFilter extends SearchFilter {
protected function comparison($exclude = false) {
$modifiers = $this->getModifiers(); public function setModifiers(array $modifiers) {
if(($extras = array_diff($modifiers, array('not', 'nocase', 'case'))) != array()) { if(($extras = array_diff($modifiers, array('not', 'nocase', 'case'))) != array()) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers'); get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers');
} }
if(DB::getConn() instanceof PostgreSQLDatabase) {
if(in_array('case', $modifiers)) { parent::setModifiers($modifiers);
$comparison = 'LIKE';
} else {
$comparison = 'ILIKE';
}
} elseif(in_array('case', $modifiers)) {
$comparison = 'LIKE BINARY';
} else {
$comparison = 'LIKE';
}
if($exclude) {
$comparison = 'NOT ' . $comparison;
}
return $comparison;
} }
/** /**
@ -47,12 +34,15 @@ class EndsWithFilter extends SearchFilter {
*/ */
protected function applyOne(DataQuery $query) { protected function applyOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
return $query->where(sprintf( $modifiers = $this->getModifiers();
"%s %s '%%%s'", $where = DB::getConn()->comparisonClause(
$this->getDbName(), $this->getDbName(),
$this->comparison(false), '%' . Convert::raw2sql($this->getValue()),
Convert::raw2sql($this->getValue()) false, // exact?
)); false, // negate?
$this->getCaseSensitive()
);
return $query->where($where);
} }
/** /**
@ -63,13 +53,15 @@ class EndsWithFilter extends SearchFilter {
*/ */
protected function applyMany(DataQuery $query) { protected function applyMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
$modifiers = $this->getModifiers();
$connectives = array(); $connectives = array();
foreach($this->getValue() as $value) { foreach($this->getValue() as $value) {
$connectives[] = sprintf( $connectives[] = DB::getConn()->comparisonClause(
"%s %s '%%%s'",
$this->getDbName(), $this->getDbName(),
$this->comparison(false), '%' . Convert::raw2sql($value),
Convert::raw2sql($value) false, // exact?
false, // negate?
$this->getCaseSensitive()
); );
} }
$whereClause = implode(' OR ', $connectives); $whereClause = implode(' OR ', $connectives);
@ -83,12 +75,15 @@ class EndsWithFilter extends SearchFilter {
*/ */
protected function excludeOne(DataQuery $query) { protected function excludeOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
return $query->where(sprintf( $modifiers = $this->getModifiers();
"%s NOT %s '%%%s'", $where = DB::getConn()->comparisonClause(
$this->getDbName(), $this->getDbName(),
$this->comparison(true), '%' . Convert::raw2sql($this->getValue()),
Convert::raw2sql($this->getValue()) false, // exact?
)); true, // negate?
$this->getCaseSensitive()
);
return $query->where($where);
} }
/** /**
@ -99,13 +94,15 @@ class EndsWithFilter extends SearchFilter {
*/ */
protected function excludeMany(DataQuery $query) { protected function excludeMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
$modifiers = $this->getModifiers();
$connectives = array(); $connectives = array();
foreach($this->getValue() as $value) { foreach($this->getValue() as $value) {
$connectives[] = sprintf( $connectives[] = DB::getConn()->comparisonClause(
"%s NOT %s '%%%s'",
$this->getDbName(), $this->getDbName(),
$this->comparison(true), '%' . Convert::raw2sql($value),
Convert::raw2sql($value) false, // exact?
true, // negate?
$this->getCaseSensitive()
); );
} }
$whereClause = implode(' AND ', $connectives); $whereClause = implode(' AND ', $connectives);

View File

@ -14,33 +14,14 @@
* @subpackage search * @subpackage search
*/ */
class ExactMatchFilter extends SearchFilter { class ExactMatchFilter extends SearchFilter {
protected function comparison($exclude = false) {
$modifiers = $this->getModifiers(); public function setModifiers(array $modifiers) {
if(($extras = array_diff($modifiers, array('not', 'nocase', 'case'))) != array()) { if(($extras = array_diff($modifiers, array('not', 'nocase', 'case'))) != array()) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers'); get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers');
} }
if(!in_array('case', $modifiers) && !in_array('nocase', $modifiers)) {
if($exclude) { parent::setModifiers($modifiers);
return '!=';
} else {
return '=';
}
} elseif(DB::getConn() instanceof PostgreSQLDatabase) {
if(in_array('case', $modifiers)) {
$comparison = 'LIKE';
} else {
$comparison = 'ILIKE';
}
} elseif(in_array('case', $modifiers)) {
$comparison = 'LIKE BINARY';
} else {
$comparison = 'LIKE';
}
if($exclude) {
$comparison = 'NOT ' . $comparison;
}
return $comparison;
} }
/** /**
@ -50,12 +31,15 @@ class ExactMatchFilter extends SearchFilter {
*/ */
protected function applyOne(DataQuery $query) { protected function applyOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
return $query->where(sprintf( $modifiers = $this->getModifiers();
"%s %s '%s'", $where = DB::getConn()->comparisonClause(
$this->getDbName(), $this->getDbName(),
$this->comparison(false), Convert::raw2sql($this->getValue()),
Convert::raw2sql($this->getValue()) true, // exact?
)); false, // negate?
$this->getCaseSensitive()
);
return $query->where($where);
} }
/** /**
@ -66,12 +50,12 @@ class ExactMatchFilter extends SearchFilter {
*/ */
protected function applyMany(DataQuery $query) { protected function applyMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
$modifiers = $this->getModifiers();
$values = array(); $values = array();
foreach($this->getValue() as $value) { foreach($this->getValue() as $value) {
$values[] = Convert::raw2sql($value); $values[] = Convert::raw2sql($value);
} }
if($this->comparison(false) == '=') { if(!in_array('case', $modifiers) && !in_array('nocase', $modifiers)) {
// Neither :case nor :nocase
$valueStr = "'" . implode("', '", $values) . "'"; $valueStr = "'" . implode("', '", $values) . "'";
return $query->where(sprintf( return $query->where(sprintf(
'%s IN (%s)', '%s IN (%s)',
@ -80,11 +64,12 @@ class ExactMatchFilter extends SearchFilter {
)); ));
} else { } else {
foreach($values as &$v) { foreach($values as &$v) {
$v = sprintf( $v = DB::getConn()->comparisonClause(
"%s %s '%s'",
$this->getDbName(), $this->getDbName(),
$this->comparison(false), $v,
$v true, // exact?
false, // negate?
$this->getCaseSensitive()
); );
} }
$where = implode(' OR ', $values); $where = implode(' OR ', $values);
@ -99,12 +84,15 @@ class ExactMatchFilter extends SearchFilter {
*/ */
protected function excludeOne(DataQuery $query) { protected function excludeOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
return $query->where(sprintf( $modifiers = $this->getModifiers();
"%s %s '%s'", $where = DB::getConn()->comparisonClause(
$this->getDbName(), $this->getDbName(),
$this->comparison(true), Convert::raw2sql($this->getValue()),
Convert::raw2sql($this->getValue()) true, // exact?
)); true, // negate?
$this->getCaseSensitive()
);
return $query->where($where);
} }
/** /**
@ -115,12 +103,12 @@ class ExactMatchFilter extends SearchFilter {
*/ */
protected function excludeMany(DataQuery $query) { protected function excludeMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
$modifiers = $this->getModifiers();
$values = array(); $values = array();
foreach($this->getValue() as $value) { foreach($this->getValue() as $value) {
$values[] = Convert::raw2sql($value); $values[] = Convert::raw2sql($value);
} }
if($this->comparison(false) == '=') { if(!in_array('case', $modifiers) && !in_array('nocase', $modifiers)) {
// Neither :case nor :nocase
$valueStr = "'" . implode("', '", $values) . "'"; $valueStr = "'" . implode("', '", $values) . "'";
return $query->where(sprintf( return $query->where(sprintf(
'%s NOT IN (%s)', '%s NOT IN (%s)',
@ -129,11 +117,12 @@ class ExactMatchFilter extends SearchFilter {
)); ));
} else { } else {
foreach($values as &$v) { foreach($values as &$v) {
$v = sprintf( $v = DB::getConn()->comparisonClause(
"%s %s '%s'",
$this->getDbName(), $this->getDbName(),
$this->comparison(true), $v,
$v true, // exact?
true, // negate?
$this->getCaseSensitive()
); );
} }
$where = implode(' OR ', $values); $where = implode(' OR ', $values);

View File

@ -11,33 +11,26 @@
* @subpackage search * @subpackage search
*/ */
class PartialMatchFilter extends SearchFilter { class PartialMatchFilter extends SearchFilter {
protected function comparison($exclude = false) {
$modifiers = $this->getModifiers(); public function setModifiers(array $modifiers) {
if(($extras = array_diff($modifiers, array('not', 'nocase', 'case'))) != array()) { if(($extras = array_diff($modifiers, array('not', 'nocase', 'case'))) != array()) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers'); get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers');
} }
if(DB::getConn() instanceof PostgreSQLDatabase) {
if(in_array('case', $modifiers)) { parent::setModifiers($modifiers);
$comparison = 'LIKE';
} else {
$comparison = 'ILIKE';
}
} elseif(in_array('case', $modifiers)) {
$comparison = 'LIKE BINARY';
} else {
$comparison = 'LIKE';
}
if($exclude) {
$comparison = 'NOT ' . $comparison;
}
return $comparison;
} }
protected function applyOne(DataQuery $query) { protected function applyOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
$comparison = $this->comparison(false); $modifiers = $this->getModifiers();
$where = sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($this->getValue())); $where = DB::getConn()->comparisonClause(
$this->getDbName(),
'%' . Convert::raw2sql($this->getValue()) . '%',
false, // exact?
false, // negate?
$this->getCaseSensitive()
);
return $query->where($where); return $query->where($where);
} }
@ -45,9 +38,15 @@ class PartialMatchFilter extends SearchFilter {
protected function applyMany(DataQuery $query) { protected function applyMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
$where = array(); $where = array();
$comparison = $this->comparison(false); $modifiers = $this->getModifiers();
foreach($this->getValue() as $value) { foreach($this->getValue() as $value) {
$where[]= sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($value)); $where[]= DB::getConn()->comparisonClause(
$this->getDbName(),
'%' . Convert::raw2sql($value) . '%',
false, // exact?
false, // negate?
$this->getCaseSensitive()
);
} }
return $query->where(implode(' OR ', $where)); return $query->where(implode(' OR ', $where));
@ -55,8 +54,14 @@ class PartialMatchFilter extends SearchFilter {
protected function excludeOne(DataQuery $query) { protected function excludeOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
$comparison = $this->comparison(true); $modifiers = $this->getModifiers();
$where = sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($this->getValue())); $where = DB::getConn()->comparisonClause(
$this->getDbName(),
'%' . Convert::raw2sql($this->getValue()) . '%',
false, // exact?
true, // negate?
$this->getCaseSensitive()
);
return $query->where($where); return $query->where($where);
} }
@ -64,9 +69,15 @@ class PartialMatchFilter extends SearchFilter {
protected function excludeMany(DataQuery $query) { protected function excludeMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
$where = array(); $where = array();
$comparison = $this->comparison(true); $modifiers = $this->getModifiers();
foreach($this->getValue() as $value) { foreach($this->getValue() as $value) {
$where[]= sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($value)); $where[]= DB::getConn()->comparisonClause(
$this->getDbName(),
'%' . Convert::raw2sql($value) . '%',
false, // exact?
true, // negate?
$this->getCaseSensitive()
);
} }
return $query->where(implode(' AND ', $where)); return $query->where(implode(' AND ', $where));

View File

@ -55,7 +55,7 @@ abstract class SearchFilter extends Object {
// sets $this->name and $this->relation // sets $this->name and $this->relation
$this->addRelation($fullName); $this->addRelation($fullName);
$this->value = $value; $this->value = $value;
$this->modifiers = array_map('strtolower', $modifiers); $this->setModifiers($modifiers);
} }
/** /**
@ -279,4 +279,16 @@ abstract class SearchFilter extends Object {
return false; return false;
} }
/**
* Determines case sensitivity based on {@link getModifiers()}.
*
* @return Mixed TRUE or FALSE to enforce sensitivity, NULL to use field collation.
*/
protected function getCaseSensitive() {
$modifiers = $this->getModifiers();
if(in_array('case', $modifiers)) return true;
else if(in_array('nocase', $modifiers)) return false;
else return null;
}
} }

View File

@ -17,27 +17,14 @@
* @subpackage search * @subpackage search
*/ */
class StartsWithFilter extends SearchFilter { class StartsWithFilter extends SearchFilter {
protected function comparison($exclude = false) {
$modifiers = $this->getModifiers(); public function setModifiers(array $modifiers) {
if(($extras = array_diff($modifiers, array('not', 'nocase', 'case'))) != array()) { if(($extras = array_diff($modifiers, array('not', 'nocase', 'case'))) != array()) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers'); get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers');
} }
if(DB::getConn() instanceof PostgreSQLDatabase) {
if(in_array('case', $modifiers)) { parent::setModifiers($modifiers);
$comparison = 'LIKE';
} else {
$comparison = 'ILIKE';
}
} elseif(in_array('case', $modifiers)) {
$comparison = 'LIKE BINARY';
} else {
$comparison = 'LIKE';
}
if($exclude) {
$comparison = 'NOT ' . $comparison;
}
return $comparison;
} }
/** /**
@ -47,12 +34,15 @@ class StartsWithFilter extends SearchFilter {
*/ */
protected function applyOne(DataQuery $query) { protected function applyOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
return $query->where(sprintf( $modifiers = $this->getModifiers();
"%s %s '%s%%'", $where = DB::getConn()->comparisonClause(
$this->getDbName(), $this->getDbName(),
$this->comparison(false), Convert::raw2sql($this->getValue()) . '%',
Convert::raw2sql($this->getValue()) false, // exact?
)); false, // negate?
$this->getCaseSensitive()
);
return $query->where($where);
} }
/** /**
@ -63,13 +53,15 @@ class StartsWithFilter extends SearchFilter {
*/ */
protected function applyMany(DataQuery $query) { protected function applyMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
$modifiers = $this->getModifiers();
$connectives = array(); $connectives = array();
foreach($this->getValue() as $value) { foreach($this->getValue() as $value) {
$connectives[] = sprintf( $connectives[] = DB::getConn()->comparisonClause(
"%s %s '%s%%'",
$this->getDbName(), $this->getDbName(),
$this->comparison(false), Convert::raw2sql($value) . '%',
Convert::raw2sql($value) false, // exact?
false, // negate?
$this->getCaseSensitive()
); );
} }
$whereClause = implode(' OR ', $connectives); $whereClause = implode(' OR ', $connectives);
@ -83,12 +75,15 @@ class StartsWithFilter extends SearchFilter {
*/ */
protected function excludeOne(DataQuery $query) { protected function excludeOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
return $query->where(sprintf( $modifiers = $this->getModifiers();
"%s %s '%s%%'", $where = DB::getConn()->comparisonClause(
$this->getDbName(), $this->getDbName(),
$this->comparison(true), Convert::raw2sql($this->getValue()) . '%',
Convert::raw2sql($this->getValue()) false, // exact?
)); true, // negate?
$this->getCaseSensitive()
);
return $query->where($where);
} }
/** /**
@ -99,13 +94,15 @@ class StartsWithFilter extends SearchFilter {
*/ */
protected function excludeMany(DataQuery $query) { protected function excludeMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation); $this->model = $query->applyRelation($this->relation);
$modifiers = $this->getModifiers();
$connectives = array(); $connectives = array();
foreach($this->getValue() as $value) { foreach($this->getValue() as $value) {
$connectives[] = sprintf( $connectives[] = DB::getConn()->comparisonClause(
"%s %s '%s%%'",
$this->getDbName(), $this->getDbName(),
$this->comparison(true), Convert::raw2sql($value) . '%',
Convert::raw2sql($value) false, // exact?
true, // negate?
$this->getCaseSensitive()
); );
} }
$whereClause = implode(' AND ', $connectives); $whereClause = implode(' AND ', $connectives);