API DB-specific comparisators in SearchFilter and DataList

Too many edge cases to leave this up to datalists,
particularly now that we introduced enforced
case sensitivity/insensitivity in the ORM APIs.
This commit is contained in:
Ingo Schommer 2012-12-10 23:35:04 +01:00
parent d92258da8f
commit e6e47cb35e
7 changed files with 183 additions and 143 deletions

View File

@ -883,6 +883,18 @@ abstract class SS_Database {
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
* 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;');
}
/**
* 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
* used for querying a datetime in a certain format

View File

@ -17,27 +17,14 @@
* @subpackage search
*/
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()) {
throw new InvalidArgumentException(
get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers');
}
if(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;
parent::setModifiers($modifiers);
}
/**
@ -47,12 +34,15 @@ class EndsWithFilter extends SearchFilter {
*/
protected function applyOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
return $query->where(sprintf(
"%s %s '%%%s'",
$modifiers = $this->getModifiers();
$where = DB::getConn()->comparisonClause(
$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) {
$this->model = $query->applyRelation($this->relation);
$modifiers = $this->getModifiers();
$connectives = array();
foreach($this->getValue() as $value) {
$connectives[] = sprintf(
"%s %s '%%%s'",
$connectives[] = DB::getConn()->comparisonClause(
$this->getDbName(),
$this->comparison(false),
Convert::raw2sql($value)
'%' . Convert::raw2sql($value),
false, // exact?
false, // negate?
$this->getCaseSensitive()
);
}
$whereClause = implode(' OR ', $connectives);
@ -83,12 +75,15 @@ class EndsWithFilter extends SearchFilter {
*/
protected function excludeOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
return $query->where(sprintf(
"%s NOT %s '%%%s'",
$modifiers = $this->getModifiers();
$where = DB::getConn()->comparisonClause(
$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) {
$this->model = $query->applyRelation($this->relation);
$modifiers = $this->getModifiers();
$connectives = array();
foreach($this->getValue() as $value) {
$connectives[] = sprintf(
"%s NOT %s '%%%s'",
$connectives[] = DB::getConn()->comparisonClause(
$this->getDbName(),
$this->comparison(true),
Convert::raw2sql($value)
'%' . Convert::raw2sql($value),
false, // exact?
true, // negate?
$this->getCaseSensitive()
);
}
$whereClause = implode(' AND ', $connectives);

View File

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

View File

@ -11,33 +11,26 @@
* @subpackage search
*/
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()) {
throw new InvalidArgumentException(
get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers');
}
if(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;
parent::setModifiers($modifiers);
}
protected function applyOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$comparison = $this->comparison(false);
$where = sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($this->getValue()));
$modifiers = $this->getModifiers();
$where = DB::getConn()->comparisonClause(
$this->getDbName(),
'%' . Convert::raw2sql($this->getValue()) . '%',
false, // exact?
false, // negate?
$this->getCaseSensitive()
);
return $query->where($where);
}
@ -45,9 +38,15 @@ class PartialMatchFilter extends SearchFilter {
protected function applyMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$where = array();
$comparison = $this->comparison(false);
$modifiers = $this->getModifiers();
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));
@ -55,8 +54,14 @@ class PartialMatchFilter extends SearchFilter {
protected function excludeOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$comparison = $this->comparison(true);
$where = sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($this->getValue()));
$modifiers = $this->getModifiers();
$where = DB::getConn()->comparisonClause(
$this->getDbName(),
'%' . Convert::raw2sql($this->getValue()) . '%',
false, // exact?
true, // negate?
$this->getCaseSensitive()
);
return $query->where($where);
}
@ -64,9 +69,15 @@ class PartialMatchFilter extends SearchFilter {
protected function excludeMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$where = array();
$comparison = $this->comparison(true);
$modifiers = $this->getModifiers();
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));

View File

@ -55,7 +55,7 @@ abstract class SearchFilter extends Object {
// sets $this->name and $this->relation
$this->addRelation($fullName);
$this->value = $value;
$this->modifiers = array_map('strtolower', $modifiers);
$this->setModifiers($modifiers);
}
/**
@ -278,5 +278,17 @@ abstract class SearchFilter extends Object {
public function isEmpty() {
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
*/
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()) {
throw new InvalidArgumentException(
get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers');
}
if(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;
parent::setModifiers($modifiers);
}
/**
@ -47,12 +34,15 @@ class StartsWithFilter extends SearchFilter {
*/
protected function applyOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
return $query->where(sprintf(
"%s %s '%s%%'",
$modifiers = $this->getModifiers();
$where = DB::getConn()->comparisonClause(
$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) {
$this->model = $query->applyRelation($this->relation);
$modifiers = $this->getModifiers();
$connectives = array();
foreach($this->getValue() as $value) {
$connectives[] = sprintf(
"%s %s '%s%%'",
$connectives[] = DB::getConn()->comparisonClause(
$this->getDbName(),
$this->comparison(false),
Convert::raw2sql($value)
Convert::raw2sql($value) . '%',
false, // exact?
false, // negate?
$this->getCaseSensitive()
);
}
$whereClause = implode(' OR ', $connectives);
@ -83,12 +75,15 @@ class StartsWithFilter extends SearchFilter {
*/
protected function excludeOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
return $query->where(sprintf(
"%s %s '%s%%'",
$modifiers = $this->getModifiers();
$where = DB::getConn()->comparisonClause(
$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) {
$this->model = $query->applyRelation($this->relation);
$modifiers = $this->getModifiers();
$connectives = array();
foreach($this->getValue() as $value) {
$connectives[] = sprintf(
"%s %s '%s%%'",
$connectives[] = DB::getConn()->comparisonClause(
$this->getDbName(),
$this->comparison(true),
Convert::raw2sql($value)
Convert::raw2sql($value) . '%',
false, // exact?
true, // negate?
$this->getCaseSensitive()
);
}
$whereClause = implode(' AND ', $connectives);