mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
ENHANCEMENT: First cut of running SearchContext through DataList/DataQuery. Note that the eventual goal is probably to ditch SearchContext entirely.
This commit is contained in:
parent
f83abe416c
commit
c615c4eb91
@ -352,7 +352,86 @@ class DataQuery {
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Traverse the relationship fields, and add the table
|
||||
* mappings to the query object state. This has to be called
|
||||
* in any overloaded {@link SearchFilter->apply()} methods manually.
|
||||
*
|
||||
* @param $relation The array/dot-syntax relation to follow
|
||||
* @return The model class of the related item
|
||||
*/
|
||||
function applyRelation($relation) {
|
||||
// NO-OP
|
||||
if(!$relation) return $this->dataClass;
|
||||
|
||||
if(is_string($relation)) $relation = explode(".", $relation);
|
||||
|
||||
$modelClass = $this->dataClass;
|
||||
|
||||
foreach($relation as $rel) {
|
||||
$model = singleton($modelClass);
|
||||
if ($component = $model->has_one($rel)) {
|
||||
if(!$this->query->isJoinedTo($component)) {
|
||||
$foreignKey = $model->getReverseAssociation($component);
|
||||
$this->query->leftJoin($component, "\"$component\".\"ID\" = \"{$modelClass}\".\"{$foreignKey}ID\"");
|
||||
|
||||
/**
|
||||
* add join clause to the component's ancestry classes so that the search filter could search on its
|
||||
* ancester fields.
|
||||
*/
|
||||
$ancestry = ClassInfo::ancestry($component, true);
|
||||
if(!empty($ancestry)){
|
||||
$ancestry = array_reverse($ancestry);
|
||||
foreach($ancestry as $ancestor){
|
||||
if($ancestor != $component){
|
||||
$this->query->innerJoin($ancestor, "\"$component\".\"ID\" = \"$ancestor\".\"ID\"");
|
||||
$component=$ancestor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$modelClass = $component;
|
||||
|
||||
} elseif ($component = $model->has_many($rel)) {
|
||||
if(!$this->query->isJoinedTo($component)) {
|
||||
$ancestry = $model->getClassAncestry();
|
||||
$foreignKey = $model->getRemoteJoinField($rel);
|
||||
$this->query->leftJoin($component, "\"$component\".\"{$foreignKey}\" = \"{$ancestry[0]}\".\"ID\"");
|
||||
/**
|
||||
* add join clause to the component's ancestry classes so that the search filter could search on its
|
||||
* ancestor fields.
|
||||
*/
|
||||
$ancestry = ClassInfo::ancestry($component, true);
|
||||
if(!empty($ancestry)){
|
||||
$ancestry = array_reverse($ancestry);
|
||||
foreach($ancestry as $ancestor){
|
||||
if($ancestor != $component){
|
||||
$this->query->innerJoin($ancestor, "\"$component\".\"ID\" = \"$ancestor\".\"ID\"");
|
||||
$component=$ancestor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$modelClass = $component;
|
||||
|
||||
} elseif ($component = $model->many_many($rel)) {
|
||||
list($parentClass, $componentClass, $parentField, $componentField, $relationTable) = $component;
|
||||
$parentBaseClass = ClassInfo::baseDataClass($parentClass);
|
||||
$componentBaseClass = ClassInfo::baseDataClass($componentClass);
|
||||
$this->query->innerJoin($relationTable, "\"$relationTable\".\"$parentField\" = \"$parentBaseClass\".\"ID\"");
|
||||
$this->query->leftJoin($componentBaseClass, "\"$relationTable\".\"$componentField\" = \"$componentBaseClass\".\"ID\"");
|
||||
if(ClassInfo::hasTable($componentClass)) {
|
||||
$this->query->leftJoin($componentClass, "\"$relationTable\".\"$componentField\" = \"$componentClass\".\"ID\"");
|
||||
}
|
||||
$modelClass = $componentClass;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $modelClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the given fields from the given table
|
||||
*/
|
||||
|
@ -115,19 +115,17 @@ class SearchContext extends Object {
|
||||
* @return SQLQuery
|
||||
*/
|
||||
public function getQuery($searchParams, $sort = false, $limit = false, $existingQuery = null) {
|
||||
$model = singleton($this->modelClass);
|
||||
|
||||
if($existingQuery) {
|
||||
$query = $existingQuery;
|
||||
} else {
|
||||
$query = $model->extendedSQL();
|
||||
}
|
||||
|
||||
$SQL_limit = Convert::raw2sql($limit);
|
||||
$query->limit($SQL_limit);
|
||||
|
||||
$SQL_sort = (!empty($sort)) ? Convert::raw2sql($sort) : singleton($this->modelClass)->stat('default_sort');
|
||||
$query->orderby($SQL_sort);
|
||||
if($existingQuery) {
|
||||
if(!($existingQuery instanceof DataList)) throw new InvalidArgumentException("existingQuery must be DataList");
|
||||
if($existingQuery->dataClass() != $this->modelClass) throw new InvalidArgumentException("existingQuery's dataClass is " . $existingQuery->dataClass() . ", $this->modelClass expected.");
|
||||
$query = $existingQuery;
|
||||
|
||||
} else {
|
||||
$query = DataList::create($this->modelClass);
|
||||
}
|
||||
|
||||
$query->limit($limit);
|
||||
$query->sort($sort);
|
||||
|
||||
// hack to work with $searchParems when it's an Object
|
||||
$searchParamArray = array();
|
||||
@ -143,15 +141,12 @@ class SearchContext extends Object {
|
||||
$filter->setModel($this->modelClass);
|
||||
$filter->setValue($value);
|
||||
if(! $filter->isEmpty()) {
|
||||
$filter->apply($query);
|
||||
$filter->apply($query->dataQuery());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$query->connective = $this->connective;
|
||||
$query->distinct = true;
|
||||
|
||||
$model->extend('augmentSQL', $query);
|
||||
if($this->connective != "AND") throw new Exception("SearchContext connective '$this->connective' not supported after ORM-rewrite.");
|
||||
|
||||
return $query;
|
||||
}
|
||||
@ -168,18 +163,9 @@ class SearchContext extends Object {
|
||||
*/
|
||||
public function getResults($searchParams, $sort = false, $limit = false) {
|
||||
$searchParams = array_filter($searchParams, array($this,'clearEmptySearchFields'));
|
||||
|
||||
$query = $this->getQuery($searchParams, $sort, $limit);
|
||||
|
||||
// use if a raw SQL query is needed
|
||||
$results = new DataObjectSet();
|
||||
foreach($query->execute() as $row) {
|
||||
$className = $row['RecordClassName'];
|
||||
$results->push(new $className($row));
|
||||
}
|
||||
return $results;
|
||||
//
|
||||
//return DataObject::get($this->modelClass, $query->getFilter(), "", "", $limit);
|
||||
|
||||
// getQuery actually returns a DataList
|
||||
return $this->getQuery($searchParams, $sort, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,9 +23,9 @@ class EndsWithFilter extends SearchFilter {
|
||||
*
|
||||
* @return unknown
|
||||
*/
|
||||
public function apply(SQLQuery $query) {
|
||||
$query = $this->applyRelation($query);
|
||||
$query->where($this->getDbName() . " LIKE '%" . Convert::raw2sql($this->getValue()) . "'");
|
||||
public function apply(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$query->filter($this->getDbName() . " LIKE '%" . Convert::raw2sql($this->getValue()) . "'");
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
|
@ -20,9 +20,9 @@ class ExactMatchFilter extends SearchFilter {
|
||||
*
|
||||
* @return unknown
|
||||
*/
|
||||
public function apply(SQLQuery $query) {
|
||||
$query = $this->applyRelation($query);
|
||||
return $query->where(sprintf(
|
||||
public function apply(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
return $query->filter(sprintf(
|
||||
"%s = '%s'",
|
||||
$this->getDbName(),
|
||||
Convert::raw2sql($this->getValue())
|
||||
|
@ -14,9 +14,8 @@
|
||||
*/
|
||||
class ExactMatchMultiFilter extends SearchFilter {
|
||||
|
||||
public function apply(SQLQuery $query) {
|
||||
$query = $this->applyRelation($query);
|
||||
|
||||
public function apply(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
// hack
|
||||
// PREVIOUS $values = explode(',',$this->getValue());
|
||||
$values = array();
|
||||
@ -40,7 +39,7 @@ class ExactMatchMultiFilter extends SearchFilter {
|
||||
}
|
||||
$SQL_valueStr = "'" . implode("','", $values) . "'";
|
||||
|
||||
return $query->where(sprintf(
|
||||
return $query->filter(sprintf(
|
||||
"%s IN (%s)",
|
||||
$this->getDbName(),
|
||||
$SQL_valueStr
|
||||
|
@ -27,7 +27,7 @@
|
||||
*/
|
||||
class FulltextFilter extends SearchFilter {
|
||||
|
||||
public function apply(SQLQuery $query) {
|
||||
public function apply(DataQuery $query) {
|
||||
$query->where(sprintf(
|
||||
"MATCH (%s) AGAINST ('%s')",
|
||||
$this->getDbName(),
|
||||
|
@ -12,9 +12,9 @@ class GreaterThanFilter extends SearchFilter {
|
||||
/**
|
||||
* @return $query
|
||||
*/
|
||||
public function apply(SQLQuery $query) {
|
||||
$query = $this->applyRelation($query);
|
||||
return $query->where(sprintf(
|
||||
public function apply(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
return $query->filter(sprintf(
|
||||
"%s > '%s'",
|
||||
$this->getDbName(),
|
||||
Convert::raw2sql($this->getDbFormattedValue())
|
||||
|
@ -12,9 +12,9 @@ class LessThanFilter extends SearchFilter {
|
||||
/**
|
||||
* @return $query
|
||||
*/
|
||||
public function apply(SQLQuery $query) {
|
||||
$query = $this->applyRelation($query);
|
||||
return $query->where(sprintf(
|
||||
public function apply(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
return $query->filter(sprintf(
|
||||
"%s < '%s'",
|
||||
$this->getDbName(),
|
||||
Convert::raw2sql($this->getDbFormattedValue())
|
||||
|
@ -7,8 +7,8 @@
|
||||
*/
|
||||
class NegationFilter extends SearchFilter {
|
||||
|
||||
public function apply(SQLQuery $query) {
|
||||
return $query->where(sprintf(
|
||||
public function apply(DataQuery $query) {
|
||||
return $query->filter(sprintf(
|
||||
"%s != '%s'",
|
||||
$this->getDbName(),
|
||||
Convert::raw2sql($this->getValue())
|
||||
|
@ -12,9 +12,9 @@
|
||||
*/
|
||||
class PartialMatchFilter extends SearchFilter {
|
||||
|
||||
public function apply(SQLQuery $query) {
|
||||
$query = $this->applyRelation($query);
|
||||
return $query->where(sprintf(
|
||||
public function apply(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
return $query->filter(sprintf(
|
||||
"%s LIKE '%%%s%%'",
|
||||
$this->getDbName(),
|
||||
Convert::raw2sql($this->getValue())
|
||||
|
@ -149,96 +149,7 @@ abstract class SearchFilter extends Object {
|
||||
$dbField->setValue($this->value);
|
||||
return $dbField->RAW();
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the relationship fields, and add the table
|
||||
* mappings to the query object state. This has to be called
|
||||
* in any overloaded {@link SearchFilter->apply()} methods manually.
|
||||
*
|
||||
* @todo try to make this implicitly triggered so it doesn't have to be manually called in child filters
|
||||
* @param SQLQuery $query
|
||||
* @return SQLQuery
|
||||
*/
|
||||
function applyRelation($query) {
|
||||
if (is_array($this->relation)) {
|
||||
foreach($this->relation as $rel) {
|
||||
$model = singleton($this->model);
|
||||
if ($component = $model->has_one($rel)) {
|
||||
if(!$query->isJoinedTo($component)) {
|
||||
$foreignKey = $model->getReverseAssociation($component);
|
||||
$query->leftJoin($component, "\"$component\".\"ID\" = \"{$this->model}\".\"{$foreignKey}ID\"");
|
||||
|
||||
/**
|
||||
* add join clause to the component's ancestry classes so that the search filter could search on its
|
||||
* ancester fields.
|
||||
*/
|
||||
$ancestry = ClassInfo::ancestry($component, true);
|
||||
if(!empty($ancestry)){
|
||||
$ancestry = array_reverse($ancestry);
|
||||
foreach($ancestry as $ancestor){
|
||||
if($ancestor != $component){
|
||||
$query->innerJoin($ancestor, "\"$component\".\"ID\" = \"$ancestor\".\"ID\"");
|
||||
$component=$ancestor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->model = $component;
|
||||
} elseif ($component = $model->has_many($rel)) {
|
||||
if(!$query->isJoinedTo($component)) {
|
||||
$ancestry = $model->getClassAncestry();
|
||||
$foreignKey = $model->getRemoteJoinField($rel);
|
||||
$query->leftJoin($component, "\"$component\".\"{$foreignKey}\" = \"{$ancestry[0]}\".\"ID\"");
|
||||
/**
|
||||
* add join clause to the component's ancestry classes so that the search filter could search on its
|
||||
* ancestor fields.
|
||||
*/
|
||||
$ancestry = ClassInfo::ancestry($component, true);
|
||||
if(!empty($ancestry)){
|
||||
$ancestry = array_reverse($ancestry);
|
||||
foreach($ancestry as $ancestor){
|
||||
if($ancestor != $component){
|
||||
$query->innerJoin($ancestor, "\"$component\".\"ID\" = \"$ancestor\".\"ID\"");
|
||||
$component=$ancestor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->model = $component;
|
||||
} elseif ($component = $model->many_many($rel)) {
|
||||
list($parentClass, $componentClass, $parentField, $componentField, $relationTable) = $component;
|
||||
$parentBaseClass = ClassInfo::baseDataClass($parentClass);
|
||||
$componentBaseClass = ClassInfo::baseDataClass($componentClass);
|
||||
$query->innerJoin($relationTable, "\"$relationTable\".\"$parentField\" = \"$parentBaseClass\".\"ID\"");
|
||||
$query->leftJoin($componentBaseClass, "\"$relationTable\".\"$componentField\" = \"$componentBaseClass\".\"ID\"");
|
||||
if(ClassInfo::hasTable($componentClass)) {
|
||||
$query->leftJoin($componentClass, "\"$relationTable\".\"$componentField\" = \"$componentClass\".\"ID\"");
|
||||
}
|
||||
$this->model = $componentClass;
|
||||
|
||||
// Experimental support for user-defined relationships via a "(relName)Query" method
|
||||
// This will likely be dropped in 2.4 for a system that makes use of Lazy Data Lists.
|
||||
} elseif($model->hasMethod($rel.'Query')) {
|
||||
// Get the query representing the join - it should have "$ID" in the filter
|
||||
$newQuery = $model->{"{$rel}Query"}();
|
||||
if($newQuery) {
|
||||
// Get the table to join to
|
||||
//DATABASE ABSTRACTION: I don't think we need this line anymore:
|
||||
$newModel = str_replace('`','',array_shift($newQuery->from));
|
||||
// Get the filter to use on the join
|
||||
$ancestry = $model->getClassAncestry();
|
||||
$newFilter = "(" . str_replace('$ID', "\"{$ancestry[0]}\".\"ID\"" , implode(") AND (", $newQuery->where) ) . ")";
|
||||
$query->leftJoin($newModel, $newFilter);
|
||||
$this->model = $newModel;
|
||||
} else {
|
||||
$this->name = "NULL";
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Apply filter criteria to a SQL query.
|
||||
@ -246,7 +157,7 @@ abstract class SearchFilter extends Object {
|
||||
* @param SQLQuery $query
|
||||
* @return SQLQuery
|
||||
*/
|
||||
abstract public function apply(SQLQuery $query);
|
||||
abstract public function apply(DataQuery $query);
|
||||
|
||||
/**
|
||||
* Determines if a field has a value,
|
||||
|
@ -23,9 +23,9 @@ class StartsWithFilter extends SearchFilter {
|
||||
*
|
||||
* @return unknown
|
||||
*/
|
||||
public function apply(SQLQuery $query) {
|
||||
$query = $this->applyRelation($query);
|
||||
$query->where($this->getDbName() . " LIKE '" . Convert::raw2sql($this->getValue()) . "%'");
|
||||
public function apply(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$query->filter($this->getDbName() . " LIKE '" . Convert::raw2sql($this->getValue()) . "%'");
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
|
@ -14,8 +14,8 @@
|
||||
*/
|
||||
class StartsWithMultiFilter extends SearchFilter {
|
||||
|
||||
public function apply(SQLQuery $query) {
|
||||
$query = $this->applyRelation($query);
|
||||
public function apply(DataQuery $query) {
|
||||
$this->model = $query->applyRelation($this->relation);
|
||||
$values = explode(',', $this->getValue());
|
||||
|
||||
foreach($values as $value) {
|
||||
@ -25,7 +25,7 @@ class StartsWithMultiFilter extends SearchFilter {
|
||||
);
|
||||
}
|
||||
|
||||
return $query->where(implode(" OR ", $matches));
|
||||
return $query->filter(implode(" OR ", $matches));
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
|
@ -12,8 +12,8 @@
|
||||
*/
|
||||
class SubstringFilter extends SearchFilter {
|
||||
|
||||
public function apply(SQLQuery $query) {
|
||||
return $query->where(sprintf(
|
||||
public function apply(DataQuery $query) {
|
||||
return $query->filter(sprintf(
|
||||
"LOCATE('%s', %s) != 0",
|
||||
Convert::raw2sql($this->getValue()),
|
||||
$this->getDbName()
|
||||
|
@ -65,6 +65,7 @@ class SQLQueryTest extends SapphireTest {
|
||||
}
|
||||
|
||||
function testSelectWithPredicateFilters() {
|
||||
/* this is no longer part of this
|
||||
$query = new SQLQuery();
|
||||
$query->select(array("Name"))->from("SQLQueryTest_DO");
|
||||
|
||||
@ -77,6 +78,7 @@ class SQLQueryTest extends SapphireTest {
|
||||
$match->apply($query);
|
||||
|
||||
$this->assertEquals("SELECT Name FROM SQLQueryTest_DO WHERE (\"SQLQueryTest_DO\".\"Name\" = 'Value') AND (\"SQLQueryTest_DO\".\"Meta\" LIKE '%Value%')", $query->sql());
|
||||
*/
|
||||
}
|
||||
|
||||
function testSelectWithLimitClause() {
|
||||
|
Loading…
Reference in New Issue
Block a user