Merge pull request #775 from simonwelsh/exclude-filter

Exclude filter
This commit is contained in:
Sam Minnée 2012-10-09 15:58:19 -07:00
commit 7023669754
16 changed files with 652 additions and 152 deletions

View File

@ -205,13 +205,18 @@ This would be equivalent to a SQL query of
### Search Filter Modifiers
The where clauses showcased in the previous two sections (filter and exclude) specify case-insensitive exact
The where clauses showcased in the previous two sections (filter and exclude) specify exact
matches by default. However, there are a number of suffixes that you can put on field names to change this
behaviour `":StartsWith"`, `":EndsWith"`, `":PartialMatch"`, `":GreaterThan"`, `":LessThan"`, `":Negation"`.
Each of these suffixes is represented in the ORM as a subclass of `[api:SearchFilter]`. Developers can define
their own SearchFilters if needing to extend the ORM filter and exclude behaviours.
These suffixes can also take modifiers themselves. The modifiers currently supported are `":not"`, `":nocase"`
and `":case"`. These negate the filter, make it case-insensitive and make it case-sensitive respectively. The
default comparison uses the database's default. For MySQL and MSSQL, this is case-insensitive. For PostgreSQL,
this is case-sensitive.
The following is a query which will return everyone whose first name doesn't start with S, who has logged in
since 1/1/2011.

View File

@ -374,38 +374,15 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
* Return a new instance of the list with an added filter
*/
public function addFilter($filterArray) {
$SQL_Statements = array();
foreach($filterArray as $field => $value) {
if(is_array($value)) {
$customQuery = 'IN (\''.implode('\',\'',Convert::raw2sql($value)).'\')';
} else {
$customQuery = '= \''.Convert::raw2sql($value).'\'';
}
if(stristr($field,':')) {
$fieldArgs = explode(':',$field);
$field = array_shift($fieldArgs);
foreach($fieldArgs as $fieldArg){
$comparisor = $this->applyFilterContext($field, $fieldArg, $value);
}
} else {
if($field == 'ID') {
$field = sprintf('"%s"."ID"', ClassInfo::baseDataClass($this->dataClass));
} else {
$field = '"' . Convert::raw2sql($field) . '"';
}
$SQL_Statements[] = $field . ' ' . $customQuery;
}
$fieldArgs = explode(':', $field);
$field = array_shift($fieldArgs);
$filterType = array_shift($fieldArgs);
$modifiers = $fieldArgs;
$this->applyFilterContext($field, $filterType, $modifiers, $value);
}
if(!count($SQL_Statements)) return $this;
return $this->alterDataQuery_30(function($query) use ($SQL_Statements){
foreach($SQL_Statements as $SQL_Statement){
$query->where($SQL_Statement);
}
});
return $this;
}
/**
@ -459,16 +436,22 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
*
* @param string $field - the fieldname in the db
* @param string $comparisators - example StartsWith, relates to a filtercontext
* @param array $modifiers - Modifiers to pass to the filter, ie not,nocase
* @param string $value - the value that the filtercontext will use for matching
* @todo Deprecated SearchContexts and pull their functionality into the core of the ORM
*/
private function applyFilterContext($field, $comparisators, $value) {
private function applyFilterContext($field, $comparisators, $modifiers, $value) {
$t = singleton($this->dataClass())->dbObject($field);
$className = "{$comparisators}Filter";
if(!class_exists($className)){
throw new InvalidArgumentException('There are no '.$comparisators.' comparisator');
if($comparisators) {
$className = "{$comparisators}Filter";
} else {
$className = 'ExactMatchFilter';
}
$t = new $className($field,$value);
if(!class_exists($className)){
$className = 'ExactMatchFilter';
array_unshift($modifiers, $comparisators);
}
$t = new $className($field, $value, $modifiers);
$t->apply($this->dataQuery());
}
@ -500,25 +483,29 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
throw new InvalidArgumentException('Incorrect number of arguments passed to exclude()');
}
$SQL_Statements = array();
foreach($whereArguments as $fieldName => $value) {
if($fieldName == 'ID') {
$fieldName = sprintf('"%s"."ID"', ClassInfo::baseDataClass($this->dataClass));
} else {
$fieldName = '"' . Convert::raw2sql($fieldName) . '"';
return $this->alterDataQuery(function($query, $list) use ($whereArguments) {
$subquery = $query->disjunctiveGroup();
foreach($whereArguments as $field => $value) {
$fieldArgs = explode(':', $field);
$field = array_shift($fieldArgs);
$filterType = array_shift($fieldArgs);
$modifiers = $fieldArgs;
// This is here since PHP 5.3 can't call protected/private methods in a closure.
$t = singleton($list->dataClass())->dbObject($field);
if($filterType) {
$className = "{$filterType}Filter";
} else {
$className = 'ExactMatchFilter';
}
if(!class_exists($className)){
$className = 'ExactMatchFilter';
array_unshift($modifiers, $filterType);
}
$t = new $className($field, $value, $modifiers);
$t->exclude($subquery);
}
if(is_array($value)){
$SQL_Statements[] = ($fieldName . ' NOT IN (\''.implode('\',\'', Convert::raw2sql($value)).'\')');
} else {
$SQL_Statements[] = ($fieldName . ' != \''.Convert::raw2sql($value).'\'');
}
}
if(!count($SQL_Statements)) return $this;
return $this->alterDataQuery_30(function($query) use ($SQL_Statements){
$query->whereAny($SQL_Statements);
});
}

View File

@ -17,23 +17,102 @@
* @subpackage search
*/
class EndsWithFilter extends SearchFilter {
protected function comparison($exclude = false) {
$modifiers = $this->getModifiers();
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;
}
/**
* Applies a match on the trailing characters of a field value.
*
* @return unknown
* @return DataQuery
*/
public function apply(DataQuery $query) {
protected function applyOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
return $query->where(sprintf(
"%s %s '%%%s'",
$this->getDbName(),
(DB::getConn() instanceof PostgreSQLDatabase) ? 'ILIKE' : 'LIKE',
$this->comparison(false),
Convert::raw2sql($this->getValue())
));
}
/**
* Applies a match on the trailing characters of a field value.
* Matches against one of the many values.
*
* @return DataQuery
*/
protected function applyMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$connectives = array();
foreach($this->getValue() as $value) {
$connectives[] = sprintf(
"%s %s '%%%s'",
$this->getDbName(),
$this->comparison(false),
Convert::raw2sql($value)
);
}
$whereClause = implode(' OR ', $connectives);
return $query->where($whereClause);
}
/**
* Excludes a match on the trailing characters of a field value.
*
* @return DataQuery
*/
protected function excludeOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
return $query->where(sprintf(
"%s NOT %s '%%%s'",
$this->getDbName(),
$this->comparison(true),
Convert::raw2sql($this->getValue())
));
}
/**
* Excludes a match on the trailing characters of a field value.
* Excludes a field if it matches any of the values.
*
* @return DataQuery
*/
protected function excludeMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$connectives = array();
foreach($this->getValue() as $value) {
$connectives[] = sprintf(
"%s NOT %s '%%%s'",
$this->getDbName(),
$this->comparison(true),
Convert::raw2sql($value)
);
}
$whereClause = implode(' AND ', $connectives);
return $query->where($whereClause);
}
public function isEmpty() {
return $this->getValue() == null || $this->getValue() == '';
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
}
}

View File

@ -14,22 +14,134 @@
* @subpackage search
*/
class ExactMatchFilter extends SearchFilter {
protected function comparison($exclude = false) {
$modifiers = $this->getModifiers();
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;
}
/**
* Applies an exact match (equals) on a field value.
*
* @return unknown
* @return DataQuery
*/
public function apply(DataQuery $query) {
protected function applyOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
return $query->where(sprintf(
"%s = '%s'",
"%s %s '%s'",
$this->getDbName(),
$this->comparison(false),
Convert::raw2sql($this->getValue())
));
}
/**
* Applies an exact match (equals) on a field value against multiple
* possible values.
*
* @return DataQuery
*/
protected function applyMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$values = array();
foreach($this->getValue() as $value) {
$values[] = Convert::raw2sql($value);
}
if($this->comparison(false) == '=') {
// Neither :case nor :nocase
$valueStr = "'" . implode("', '", $values) . "'";
return $query->where(sprintf(
'%s IN (%s)',
$this->getDbName(),
$valueStr
));
} else {
foreach($values as &$v) {
$v = sprintf(
"%s %s '%s'",
$this->getDbName(),
$this->comparison(false),
$v
);
}
$where = implode(' OR ', $values);
return $query->where($where);
}
}
/**
* Excludes an exact match (equals) on a field value.
*
* @return DataQuery
*/
protected function excludeOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
return $query->where(sprintf(
"%s %s '%s'",
$this->getDbName(),
$this->comparison(true),
Convert::raw2sql($this->getValue())
));
}
/**
* Excludes an exact match (equals) on a field value against multiple
* possible values.
*
* @return DataQuery
*/
protected function excludeMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$values = array();
foreach($this->getValue() as $value) {
$values[] = Convert::raw2sql($value);
}
if($this->comparison(false) == '=') {
// Neither :case nor :nocase
$valueStr = "'" . implode("', '", $values) . "'";
return $query->where(sprintf(
'%s NOT IN (%s)',
$this->getDbName(),
$valueStr
));
} else {
foreach($values as &$v) {
$v = sprintf(
"%s %s '%s'",
$this->getDbName(),
$this->comparison(true),
$v
);
}
$where = implode(' OR ', $values);
return $query->where($where);
}
}
public function isEmpty() {
return $this->getValue() == null || $this->getValue() == '';
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
}
}

View File

@ -7,46 +7,46 @@
/**
* Checks if a value is in a given set.
* SQL syntax used: Column IN ('val1','val2')
* @deprecated 3.1 Use ExactMatchFilter instead
*
* @todo Add negation (NOT IN)6
* @package framework
* @subpackage search
*/
class ExactMatchMultiFilter extends SearchFilter {
function __construct($fullName, $value = false, array $modifiers = array()) {
Deprecation::notice('3.1', 'Use ExactMatchFilter instead.');
parent::__construct($fullName, $value, $modifiers);
}
public function apply(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
// hack
// PREVIOUS $values = explode(',',$this->getValue());
$values = array();
if (is_string($this->getValue())) {
if (!is_array($this->getValue())) {
$values = explode(',',$this->getValue());
} else {
$values = $this->getValue();
}
else {
foreach($this->getValue() as $v) {
$values[] = $v;
}
$filter = new ExactMatchFilter($this->getFullName(), $values, $this->getModifiers());
return $filter->apply($query);
}
protected function applyOne(DataQuery $query) {
/* NO OP */
}
public function exclude(DataQuery $query) {
if (!is_array($this->getValue())) {
$values = explode(',',$this->getValue());
} else {
$values = $this->getValue();
}
if(! $values) return false;
for($i = 0; $i < count($values); $i++) {
if(! is_numeric($values[$i])) {
// @todo Fix string replacement to only replace leading and tailing quotes
$values[$i] = str_replace("'", '', $values[$i]);
$values[$i] = Convert::raw2sql($values[$i]);
}
}
$SQL_valueStr = "'" . implode("','", $values) . "'";
return $query->where(sprintf(
"%s IN (%s)",
$this->getDbName(),
$SQL_valueStr
));
$filter = new ExactMatchFilter($this->getFullName(), $values, $this->getModifiers());
return $filter->exclude($query);
}
protected function excludeOne(DataQuery $query) {
/* NO OP */
}
public function isEmpty() {
return $this->getValue() == null || $this->getValue() == '';
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
}
}

View File

@ -27,7 +27,7 @@
*/
class FulltextFilter extends SearchFilter {
public function apply(DataQuery $query) {
protected function applyOne(DataQuery $query) {
return $query->where(sprintf(
"MATCH (%s) AGAINST ('%s')",
$this->getDbName(),
@ -35,7 +35,15 @@ class FulltextFilter extends SearchFilter {
));
}
protected function excludeOne(DataQuery $query) {
return $query->where(sprintf(
"NOT MATCH (%s) AGAINST ('%s')",
$this->getDbName(),
Convert::raw2sql($this->getValue())
));
}
public function isEmpty() {
return $this->getValue() == null || $this->getValue() == '';
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
}
}

View File

@ -10,9 +10,9 @@
class GreaterThanFilter extends SearchFilter {
/**
* @return $query
* @return DataQuery
*/
public function apply(DataQuery $query) {
protected function applyOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$value = $this->getDbFormattedValue();
@ -21,8 +21,21 @@ class GreaterThanFilter extends SearchFilter {
return $query->where($filter);
}
/**
* @return DataQuery
*/
protected function excludeOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$value = $this->getDbFormattedValue();
if(is_numeric($value)) $filter = sprintf("%s <= %s", $this->getDbName(), Convert::raw2sql($value));
else $filter = sprintf("%s <= '%s'", $this->getDbName(), Convert::raw2sql($value));
return $query->where($filter);
}
public function isEmpty() {
return $this->getValue() == null || $this->getValue() == '';
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
}
}

View File

@ -10,9 +10,9 @@
class LessThanFilter extends SearchFilter {
/**
* @return $query
* @return DataQuery
*/
public function apply(DataQuery $query) {
protected function applyOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$value = $this->getDbFormattedValue();
@ -21,8 +21,21 @@ class LessThanFilter extends SearchFilter {
return $query->where($filter);
}
/**
* @return DataQuery
*/
protected function excludeOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$value = $this->getDbFormattedValue();
if(is_numeric($value)) $filter = sprintf("%s >= %s", $this->getDbName(), Convert::raw2sql($value));
else $filter = sprintf("%s >= '%s'", $this->getDbName(), Convert::raw2sql($value));
return $query->where($filter);
}
public function isEmpty() {
return $this->getValue() == null || $this->getValue() == '';
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
}
}

View File

@ -2,19 +2,33 @@
/**
* Matches on rows where the field is not equal to the given value.
*
* @deprecated 3.1 Use ExactMatchFilter:not instead
* @package framework
* @subpackage search
*/
class NegationFilter extends SearchFilter {
public function apply(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
return $query->where(sprintf(
"%s != '%s'",
$this->getDbName(),
Convert::raw2sql($this->getValue())
));
function __construct($fullName, $value = false, array $modifiers = array()) {
Deprecation::notice('3.1', 'Use ExactMatchFilter:not instead.');
$modifiers[] = 'not';
parent::__construct($fullName, $value, $modifiers);
}
public function apply(DataQuery $query) {
$filter = new ExactMatchFilter($this->getFullName(), $this->getValue(), $this->getModifiers());
return $filter->apply($query);
}
protected function applyOne(DataQuery $query) {
/* NO OP */
}
public function exclude(DataQuery $query) {
$filter = new ExactMatchFilter($this->getFullName(), $this->getValue(), $this->getModifiers());
return $filter->exclude($query);
}
protected function excludeOne(DataQuery $query) {
/* NO OP */
}
}

View File

@ -11,24 +11,68 @@
* @subpackage search
*/
class PartialMatchFilter extends SearchFilter {
protected function comparison($exclude = false) {
$modifiers = $this->getModifiers();
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;
}
public function apply(DataQuery $query) {
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()));
return $query->where($where);
}
protected function applyMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$where = array();
$comparison = (DB::getConn() instanceof PostgreSQLDatabase) ? 'ILIKE' : 'LIKE';
if(is_array($this->getValue())) {
foreach($this->getValue() as $value) {
$where[]= sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($value));
}
} else {
$where[] = sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($this->getValue()));
$comparison = $this->comparison(false);
foreach($this->getValue() as $value) {
$where[]= sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($value));
}
return $query->where(implode(' OR ', $where));
}
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()));
return $query->where($where);
}
protected function excludeMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$where = array();
$comparison = $this->comparison(true);
foreach($this->getValue() as $value) {
$where[]= sprintf("%s %s '%%%s%%'", $this->getDbName(), $comparison, Convert::raw2sql($value));
}
return $query->where(implode(' AND ', $where));
}
public function isEmpty() {
return $this->getValue() == null || $this->getValue() == '';
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
}
}

View File

@ -30,6 +30,11 @@ abstract class SearchFilter extends Object {
*/
protected $value;
/**
* @var array
*/
protected $modifiers;
/**
* @var string Name of a has-one, has-many or many-many relation (not the classname).
* Set in the constructor as part of the name in dot-notation, and used in
@ -43,12 +48,14 @@ abstract class SearchFilter extends Object {
* the necessary tables (e.g. "Comments.Name" to join the "Comments" has-many relationship and
* search the "Name" column when applying this filter to a SiteTree class).
* @param mixed $value
* @param array $modifiers
*/
public function __construct($fullName, $value = false) {
public function __construct($fullName, $value = false, array $modifiers = array()) {
$this->fullName = $fullName;
// sets $this->name and $this->relation
$this->addRelation($fullName);
$this->value = $value;
$this->modifiers = array_map('strtolower', $modifiers);
}
/**
@ -95,6 +102,24 @@ abstract class SearchFilter extends Object {
public function getValue() {
return $this->value;
}
/**
* Set the current modifiers to apply to the filter
*
* @param array $modifiers
*/
public function setModifiers(array $modifiers) {
$this->modifiers = array_map('strtolower', $modifiers);
}
/**
* Accessor for the current modifiers to apply to the filter.
*
* @return array
*/
public function getModifiers() {
return $this->modifiers;
}
/**
* The original name of the field.
@ -173,10 +198,74 @@ abstract class SearchFilter extends Object {
/**
* Apply filter criteria to a SQL query.
*
* @param SQLQuery $query
* @return SQLQuery
* @param DataQuery $query
* @return DataQuery
*/
abstract public function apply(DataQuery $query);
public function apply(DataQuery $query) {
if(($key = array_search('not', $this->modifiers)) !== false) {
unset($this->modifiers[$key]);
return $this->exclude($query);
}
if(is_array($this->value)) {
return $this->applyMany($query);
} else {
return $this->applyOne($query);
}
}
/**
* Apply filter criteria to a SQL query with a single value.
*
* @param DataQuery $query
* @return DataQuery
*/
abstract protected function applyOne(DataQuery $query);
/**
* Apply filter criteria to a SQL query with an array of values.
*
* @param DataQuery $query
* @return DataQuery
*/
protected function applyMany(DataQuery $query) {
throw new InvalidArgumentException(get_class($this) . "can't be used to filter by a list of items.");
}
/**
* Exclude filter criteria from a SQL query.
*
* @param DataQuery $query
* @return DataQuery
*/
public function exclude(DataQuery $query) {
if(($key = array_search('not', $this->modifiers)) !== false) {
unset($this->modifiers[$key]);
return $this->apply($query);
}
if(is_array($this->value)) {
return $this->excludeMany($query);
} else {
return $this->excludeOne($query);
}
}
/**
* Exclude filter criteria from a SQL query with a single value.
*
* @param DataQuery $query
* @return DataQuery
*/
abstract protected function excludeOne(DataQuery $query);
/**
* Exclude filter criteria from a SQL query with an array of values.
*
* @param DataQuery $query
* @return DataQuery
*/
protected function excludeMany(DataQuery $query) {
throw new InvalidArgumentException(get_class($this) . "can't be used to filter by a list of items.");
}
/**
* Determines if a field has a value,

View File

@ -17,23 +17,102 @@
* @subpackage search
*/
class StartsWithFilter extends SearchFilter {
protected function comparison($exclude = false) {
$modifiers = $this->getModifiers();
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;
}
/**
* Applies a substring match on a field value.
* Applies a match on the starting characters of a field value.
*
* @return unknown
* @return DataQuery
*/
public function apply(DataQuery $query) {
protected function applyOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
return $query->where(sprintf(
"%s %s '%s%%'",
$this->getDbName(),
(DB::getConn() instanceof PostgreSQLDatabase) ? 'ILIKE' : 'LIKE',
$this->comparison(false),
Convert::raw2sql($this->getValue())
));
}
/**
* Applies a match on the starting characters of a field value.
* Matches against one of the many values.
*
* @return DataQuery
*/
protected function applyMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$connectives = array();
foreach($this->getValue() as $value) {
$connectives[] = sprintf(
"%s %s '%s%%'",
$this->getDbName(),
$this->comparison(false),
Convert::raw2sql($value)
);
}
$whereClause = implode(' OR ', $connectives);
return $query->where($whereClause);
}
/**
* Excludes a match on the starting characters of a field value.
*
* @return DataQuery
*/
protected function excludeOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
return $query->where(sprintf(
"%s %s '%s%%'",
$this->getDbName(),
$this->comparison(true),
Convert::raw2sql($this->getValue())
));
}
/**
* Excludes a match on the starting characters of a field value.
* Excludes a field if it matches any of the values.
*
* @return DataQuery
*/
protected function excludeMany(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$connectives = array();
foreach($this->getValue() as $value) {
$connectives[] = sprintf(
"%s %s '%s%%'",
$this->getDbName(),
$this->comparison(true),
Convert::raw2sql($value)
);
}
$whereClause = implode(' AND ', $connectives);
return $query->where($whereClause);
}
public function isEmpty() {
return $this->getValue() == null || $this->getValue() == '';
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
}
}

View File

@ -6,29 +6,47 @@
/**
* Checks if a value starts with one of the items of in a given set.
* SQL syntax used: Column IN ('val1','val2')
* @deprecated 3.1 Use StartsWithFilter instead
*
* @todo Add negation (NOT IN)6
* @package framework
* @subpackage search
*/
class StartsWithMultiFilter extends SearchFilter {
function __construct($fullName, $value = false, array $modifiers = array()) {
Deprecation::notice('3.1', 'Use StartsWithFilter instead.');
parent::__construct($fullName, $value, $modifiers);
}
public function apply(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
$values = explode(',', $this->getValue());
foreach($values as $value) {
$matches[] = sprintf("%s LIKE '%s%%'",
$this->getDbName(),
Convert::raw2sql(str_replace("'", '', $value))
);
if (!is_array($this->getValue())) {
$values = explode(',',$this->getValue());
} else {
$values = $this->getValue();
}
return $query->where(implode(" OR ", $matches));
$filter = new StartsWithFilter($this->getFullName(), $values, $this->getModifiers());
return $filter->apply($query);
}
protected function applyOne(DataQuery $query) {
/* NO OP */
}
public function exclude(DataQuery $query) {
if (!is_array($this->getValue())) {
$values = explode(',',$this->getValue());
} else {
$values = $this->getValue();
}
$filter = new StartsWithFilter($this->getFullName(), $values, $this->getModifiers());
return $filter->exclude($query);
}
protected function excludeOne(DataQuery $query) {
/* NO OP */
}
public function isEmpty() {
return $this->getValue() == null || $this->getValue() == '';
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
}
}

View File

@ -12,22 +12,33 @@
* @subpackage search
*/
class SubstringFilter extends PartialMatchFilter {
public function __construct($fullName, $value = false) {
public function __construct($fullName, $value = false, array $modifiers = array()) {
Deprecation::notice('3.0', 'PartialMatchFilter instead.');
SearchFilter::__construct($fullName, $value);
parent::__construct($fullName, $value, $modifiers);
}
public function apply(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
return $query->where(sprintf(
"LOCATE('%s', %s) != 0",
Convert::raw2sql($this->getValue()),
$this->getDbName()
));
$values = $this->getValue();
$filter = new PartialMatchFilter($this->getFullName(), $values, $this->getModifiers());
return $filter->apply($query);
}
protected function applyOne(DataQuery $query) {
/* NO OP */
}
public function exclude(DataQuery $query) {
$values = $this->getValue();
$filter = new PartialMatchFilter($this->getFullName(), $values, $this->getModifiers());
return $filter->exclude($query);
}
protected function excludeOne(DataQuery $query) {
/* NO OP */
}
public function isEmpty() {
return $this->getValue() == null || $this->getValue() == '';
return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
}
}

View File

@ -25,7 +25,7 @@ class WithinRangeFilter extends SearchFilter {
$this->max = $max;
}
public function apply(DataQuery $query) {
protected function applyOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
return $query->where(sprintf(
"%s >= '%s' AND %s <= '%s'",
@ -35,6 +35,15 @@ class WithinRangeFilter extends SearchFilter {
Convert::raw2sql($this->max)
));
}
}
protected function excludeOne(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
return $query->where(sprintf(
"%s < '%s' OR %s > '%s'",
$this->getDbName(),
Convert::raw2sql($this->min),
$this->getDbName(),
Convert::raw2sql($this->max)
));
}
}

View File

@ -442,10 +442,21 @@ class DataListTest extends SapphireTest {
$list = $list->filter(array(
'Name'=>array('Bob','Phil'),
'TeamID'=>array($this->idFromFixture('DataObjectTest_Team', 'team1'))));
$this->assertEquals(1, $list->count(), 'There should be one comments');
$this->assertEquals(1, $list->count(), 'There should be one comment');
$this->assertEquals('Bob', $list->first()->Name, 'Only comment should be from Bob');
}
public function testFilterWithModifiers() {
$list = DataObjectTest_TeamComment::get();
$nocaseList = $list->filter('Name:nocase', 'bob');
$this->assertEquals(1, $nocaseList->count(), 'There should be one comment');
$caseList = $list->filter('Name:case', 'bob');
$this->assertEquals(0, $caseList->count(), 'There should be no comments');
$gtList = $list->filter('TeamID:GreaterThan:not',
$this->idFromFixture('DataObjectTest_Team', 'team1'));
$this->assertEquals(2, $gtList->count());
}
public function testFilterAndExcludeById() {
$id = $this->idFromFixture('DataObjectTest_SubTeam', 'subteam1');
$list = DataObjectTest_SubTeam::get()->filter('ID', $id);
@ -501,7 +512,7 @@ class DataListTest extends SapphireTest {
*/
public function testMultipleExclude() {
$list = DataObjectTest_TeamComment::get();
$list->exclude(array('Name'=>'Bob', 'Comment'=>'This is a team comment by Bob'));
$list = $list->exclude(array('Name'=>'Bob', 'Comment'=>'This is a team comment by Bob'));
$this->assertEquals(2, $list->count());
}
@ -514,9 +525,17 @@ class DataListTest extends SapphireTest {
$list = $list->exclude('Name', 'Bob');
$this->assertContains(
'WHERE ("Comment" = \'Phil is a unique guy, and comments on team2\') AND ("Name" != \'Bob\')',
'WHERE ("DataObjectTest_TeamComment"."Comment" = '
. '\'Phil is a unique guy, and comments on team2\') '
. 'AND (("DataObjectTest_TeamComment"."Name" != \'Bob\'))',
$list->sql());
}
public function testExcludeWithSearchFilter() {
$list = DataObjectTest_TeamComment::get();
$list = $list->exclude('Name:LessThan', 'Bob');
$this->assertContains('WHERE (("DataObjectTest_TeamComment"."Name" >= \'Bob\'))', $list->sql());
}
/**
* $list->exclude(array('Name'=>'bob, 'Age'=>array(21, 43))); // exclude bob with Age 21 or 43