mirror of
https://github.com/silverstripe/silverstripe-fulltextsearch
synced 2024-10-22 14:05:29 +02:00
Merge pull request #150 from silverstripe-terraformers/feature/complex-filtering
Add nested filtering via Criteria and Criterion objects.
This commit is contained in:
commit
1f156dfbb4
@ -4,3 +4,8 @@ Name: fulltextsearchconfig
|
|||||||
SilverStripe\ORM\DataObject:
|
SilverStripe\ORM\DataObject:
|
||||||
extensions:
|
extensions:
|
||||||
- SilverStripe\FullTextSearch\Search\Extensions\SearchUpdater_ObjectHandler
|
- SilverStripe\FullTextSearch\Search\Extensions\SearchUpdater_ObjectHandler
|
||||||
|
|
||||||
|
SilverStripe\Core\Injector\Injector:
|
||||||
|
SilverStripe\FullTextSearch\Search\Queries\SearchQuery:
|
||||||
|
calls:
|
||||||
|
- [ setHandler, [ %$SilverStripe\FullTextSearch\Search\Adapters\SolrSearchAdapter ]]
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
- [Searching value ranges](04_querying.md#searching-value-ranges)
|
- [Searching value ranges](04_querying.md#searching-value-ranges)
|
||||||
- [Empty or existing values](04_querying.md#empty-or-existing-values)
|
- [Empty or existing values](04_querying.md#empty-or-existing-values)
|
||||||
- [Executing your query](04_querying.md#executing-your-query)
|
- [Executing your query](04_querying.md#executing-your-query)
|
||||||
|
- [Complex filtering with Criteria](04_querying.md#complex-filtering-with-criteria)
|
||||||
- Advanced configuration
|
- Advanced configuration
|
||||||
- [Facets](05_advanced_configuration.md#facets)
|
- [Facets](05_advanced_configuration.md#facets)
|
||||||
- [Using multiple indexes](05_advanced_configuration.md#multiple-indexes)
|
- [Using multiple indexes](05_advanced_configuration.md#multiple-indexes)
|
||||||
|
@ -128,3 +128,164 @@ The return value of a `search()` call is an object which contains a few properti
|
|||||||
* `Suggestion`: (optional) Any suggested spelling corrections in the original query notation
|
* `Suggestion`: (optional) Any suggested spelling corrections in the original query notation
|
||||||
* `SuggestionNice`: (optional) Any suggested spelling corrections for display (without query notation)
|
* `SuggestionNice`: (optional) Any suggested spelling corrections for display (without query notation)
|
||||||
* `SuggestionQueryString` (optional) Link to repeat the search with suggested spelling corrections
|
* `SuggestionQueryString` (optional) Link to repeat the search with suggested spelling corrections
|
||||||
|
|
||||||
|
## Complex Filtering with Criteria
|
||||||
|
|
||||||
|
### Filtering related Objects
|
||||||
|
|
||||||
|
* `SearchCriteriaInterface`: Interface for `SearchCriterion` and `SearchCriteria` classes.
|
||||||
|
* `SearchCriterion`: An object containing a single field filter (target field, comparison value, comparison type).
|
||||||
|
* `SearchCriteria`: An object containing a collection of `SearchCriterion` and/or `SearchCriteria` with conjunctions (IE: `AND`, `OR`) between each.
|
||||||
|
* `SearchQueryWriter`: A class used to generate a query string based on a `SearchCriterion`.
|
||||||
|
* `SearchAdapterInterface`: An Interface for our SearchAdapters. This adapter will control what `SearchQueryWriter` is used for each `SearchCriteria`.
|
||||||
|
|
||||||
|
### General usage
|
||||||
|
|
||||||
|
We need 3 things to create a `SearchCriterion`:
|
||||||
|
|
||||||
|
* **`Target`**: EG the field in our Search Index that we want to filter against.
|
||||||
|
* **`Value`**: The value we want to use for comparison.
|
||||||
|
* **`Comparison`**: The type of comparison (EG: `EQUAL`, `IN`, etc).
|
||||||
|
|
||||||
|
All currently supported comparisons can be found as constants in `SearchCriterion`.
|
||||||
|
|
||||||
|
### Creating a new `SearchCriterion`
|
||||||
|
|
||||||
|
#### Method 1a and 1b
|
||||||
|
|
||||||
|
```php
|
||||||
|
// `EQUAL` is the default comparison for `SearchCriterion`, so no third param is required.
|
||||||
|
$criterion = new SearchCriterion('Product_Title', 'My Product');
|
||||||
|
|
||||||
|
// Or use the `create` static method.
|
||||||
|
$criterion = SearchCriterion::create('Product_Title', 'My Product');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating a new `SearchCriteria`
|
||||||
|
|
||||||
|
`SearchCriteria` has a property called `$clauses` which is a collection of `SearchCriterion` (above) and/or `SearchCriteria` (allowing for infinite nesting of clauses), along with the conjunction used between each clause (IE: `AND`, `OR`). We want to build up our `SearchCriteria` by adding to it's `$clauses` collection.
|
||||||
|
|
||||||
|
`SearchCriteria` can either be passed an object that implements `SearchCriteriaInterface`, or it can be passed the `Target`, `Value`, and `Comparison` (like above).
|
||||||
|
|
||||||
|
#### Method 1
|
||||||
|
|
||||||
|
Instantiate a new `SearchCriteria` by providing an already instantiated `SearchCriterion` object. This `$criterion` will be added as the first item in the `$clauses` collection.
|
||||||
|
|
||||||
|
```php
|
||||||
|
$criteria = SearchCriteria::create($criterion);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Method 2
|
||||||
|
|
||||||
|
Instantiate a new `SearchCriteria` objects and define the `Target`, `Value`, and `Comparison`. `SearchCriteria` will create a new `SearchCriterion` object based on the values, and add it to the `$clauses` collection.
|
||||||
|
|
||||||
|
```php
|
||||||
|
$criteria = SearchCriteria::create('Product_CatID', array(21, 24, 25), AbstractCriterion::IN);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding additional `SearchCriterion` to our `SearchCriteria`
|
||||||
|
|
||||||
|
When you want to add more complexity to your `SearchCriteria`, there are two methods available:
|
||||||
|
|
||||||
|
* `addAnd`: Add a new `SearchCriterion` or `SearchCriteria` with an `AND` conjunction.
|
||||||
|
* `addOr`: Add a new `SearchCriterion` or `SearchCriteria` with an `OR` conjunction.
|
||||||
|
|
||||||
|
#### Method 1
|
||||||
|
|
||||||
|
Use method chaining to create a `SearchCriterion` with two clauses.
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Filter by products with stock that are in either of these 3 categories.
|
||||||
|
$criteria = SearchCriteria::create('Product_CatID', array(21, 24, 25), AbstractCriterion::IN)
|
||||||
|
->addAnd('Product_Stock', 0, AbstractSearchCriterion::GREATER_THAN);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Method 2
|
||||||
|
|
||||||
|
Systematically add clauses to your already instantiated `SearchCriteria`.
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Filter by products in either of these 3 categories.
|
||||||
|
$criteria = SearchCriteria::create('Product_CatID', array(21, 24, 25), AbstractCriterion::IN);
|
||||||
|
|
||||||
|
... other stuff
|
||||||
|
|
||||||
|
// Filter by products with stock.
|
||||||
|
$criteria->addAnd('Product_StockLevel', 0, AbstractCriterion::GREATER_THAN);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding multiple levels of filtering to our `SearchCriteria`
|
||||||
|
|
||||||
|
`SearchCriteria` also allows you to pass in other `SearchCriteria` objects as you instantiate it and as you use the `addAnd` and `addOr` methods.
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Filter by products that are in either of these 3 categories with stock.
|
||||||
|
$stockCategoryCriteria = SearchCriteria::create('Product_CatID', array(21, 24, 25), AbstractCriterion::IN)
|
||||||
|
->addAnd('Product_Stock', 0, AbstractSearchCriterion::GREATER_THAN);
|
||||||
|
|
||||||
|
// Filter by products in Category ID 1 with stock over 5.
|
||||||
|
$legoCriteria = SearchCriteria::create('Product_CatID', 1, AbstractCriterion::EQUAL)
|
||||||
|
->addAnd('Product_Stock', 5, AbstractSearchCriterion::GREATER_THAN);
|
||||||
|
|
||||||
|
// Combine the two criteria with an `OR` conjunction
|
||||||
|
$criteria = SearchCriteria::create($stockCategoryCriteria)
|
||||||
|
->addOr($legoCriteria);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding `SearchCriteria` to our `SearchQuery`
|
||||||
|
|
||||||
|
Our `SearchQuery` class now has a property called `$criteria` which holds all of our `SearchCriteria`. You can add new `SearchCriteria` by using `SearchQuery::filterBy()`.
|
||||||
|
|
||||||
|
#### Method 1
|
||||||
|
|
||||||
|
Pass in an already instantiated `SearchCriteria` object. If you implemented complex filtering (above), you will probably need to follow this method - fully creating your `SearchCriteria` first, and then passing it to the `SearchQuery`.
|
||||||
|
|
||||||
|
```php
|
||||||
|
$query->filterBy($criteria);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Method 2a
|
||||||
|
Where basic (single level) filtering is ok, the `SearchQuery::filterBy()` method can be used to create your `SearchCriterion` and `SearchCriteria` object.
|
||||||
|
|
||||||
|
```php
|
||||||
|
$query->filterBy('Product_CatID', array(21, 24, 25), AbstractCriterion::IN);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Method 2b
|
||||||
|
The `filterBy()` method will return the **current** `SearchCriteria`, this allows you to method chain the `addAnd` and `addOr` methods.
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Filter by products with stock that are in either of these 3 categories.
|
||||||
|
$searchQuery->filterBy('Product_CategoryID', array(21, 24, 25), AbstractCriterion::IN)
|
||||||
|
->addAnd('Product_StockLevel', 0, AbstractCriterion::GREATER_THAN);
|
||||||
|
```
|
||||||
|
|
||||||
|
Each item in the `$criteria` collection are treated with an `AND` conjunction (matching current `filter`/`exclude` functionality).
|
||||||
|
|
||||||
|
### Search Query Writers
|
||||||
|
|
||||||
|
Provided are 3 different `SearchQueryWriter`s for Solr:
|
||||||
|
|
||||||
|
* `SolrSearchQueryWriter_Basic`
|
||||||
|
* `SolrSearchQueryWriter_In`
|
||||||
|
* `SolrSearchQueryWriter_Range`
|
||||||
|
|
||||||
|
When these Writers are provided a `SearchCriterion`, they will generate the desired query string.
|
||||||
|
|
||||||
|
### Search Adapters
|
||||||
|
|
||||||
|
Search Adapters need to provide the following information:
|
||||||
|
|
||||||
|
* What is the search engine's conjunction strings? (EG: are they "AND" and "OR", or are they "&&" and "||", etc).
|
||||||
|
* What is the desired comparison container string? (EG: "**+(** query here **)**") for Solr).
|
||||||
|
* Most importantly - how to generate the query string from a `SearchCriterion`.
|
||||||
|
|
||||||
|
The `SolrSearchAdapter` uses `SearchQueryWriter`s (above) to generate query strings from a `SearchCriterion`.
|
||||||
|
|
||||||
|
### Customising your `SearchCriterion`/`SearchQueryWriter`
|
||||||
|
|
||||||
|
If you find that you do not want your `SearchCriterion` being parsed by one of the default `SearchQueryWriter`s (for whatever reason), you can optionally pass your own `SearchQueryWriter` to your `SearchCriterion` either as the **fourth parameter** when instantiating it, or by calling `setSearchQueryWriter()`.
|
||||||
|
|
||||||
|
If this value is set, then the (default Solr) Adapter will always use the provided `SearchQueryWriter`, rather than deciding for itself.
|
||||||
|
|
||||||
|
This should allow you to have full control over how your query strings are being generated if the default `SearchQueryWriter`s are not cutting it for you.
|
56
src/Search/Adapters/SearchAdapterInterface.php
Normal file
56
src/Search/Adapters/SearchAdapterInterface.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Adapters;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Search\Criteria\SearchCriterion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface SearchAdapterInterface
|
||||||
|
* @package SilverStripe\FullTextSearch\Adapters
|
||||||
|
*/
|
||||||
|
interface SearchAdapterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Parameter $conjunction should be CONJUNCTION_AND or CONJUNCTION_OR, and your Adapter should return the
|
||||||
|
* appropriate string representation of that conjunction.
|
||||||
|
*
|
||||||
|
* @param string $conjunction
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getConjunctionFor($conjunction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Due to the fact that we have filter criteria coming from legacy methods (as well as our Criteria), you may find
|
||||||
|
* that you need to prepend (or append) something to your group of Criteria statements.
|
||||||
|
*
|
||||||
|
* EG: For Solr, we need to add a "+" between the default filters, and our Criteria.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPrependToCriteriaComponent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAppendToCriteriaComponent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define how each of your comparisons should be contained.
|
||||||
|
*
|
||||||
|
* EG: For Solr, we wrap each comparison in ().
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getOpenComparisonContainer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCloseComparisonContainer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SearchCriterion $criterion
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generateQueryString(SearchCriterion $criterion);
|
||||||
|
}
|
115
src/Search/Adapters/SolrSearchAdapter.php
Normal file
115
src/Search/Adapters/SolrSearchAdapter.php
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Adapters;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Search\Criteria\SearchCriteria;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Criteria\SearchCriterion;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Queries\AbstractSearchQueryWriter;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Writers\SolrSearchQueryWriterBasic;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Writers\SolrSearchQueryWriterIn;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Writers\SolrSearchQueryWriterRange;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SolrSearchAdapter
|
||||||
|
* @package SilverStripe\FullTextSearch\Search\Adapters
|
||||||
|
*/
|
||||||
|
class SolrSearchAdapter implements SearchAdapterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param SearchCriterion $criterion
|
||||||
|
* @return string
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function generateQueryString(SearchCriterion $criterion)
|
||||||
|
{
|
||||||
|
$writer = $this->getSearchQueryWriter($criterion);
|
||||||
|
|
||||||
|
return $writer->generateQueryString($criterion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $conjunction
|
||||||
|
* @return string
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function getConjunctionFor($conjunction)
|
||||||
|
{
|
||||||
|
switch ($conjunction) {
|
||||||
|
case SearchCriteria::CONJUNCTION_AND:
|
||||||
|
case SearchCriteria::CONJUNCTION_OR:
|
||||||
|
return sprintf(' %s ', $conjunction);
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
sprintf('Invalid conjunction supplied to SolrSearchAdapter: "%s".', $conjunction)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPrependToCriteriaComponent()
|
||||||
|
{
|
||||||
|
return '+';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAppendToCriteriaComponent()
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getOpenComparisonContainer()
|
||||||
|
{
|
||||||
|
return '(';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCloseComparisonContainer()
|
||||||
|
{
|
||||||
|
return ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SearchCriterion $searchCriterion
|
||||||
|
* @return AbstractSearchQueryWriter
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
protected function getSearchQueryWriter(SearchCriterion $searchCriterion)
|
||||||
|
{
|
||||||
|
if ($searchCriterion->getSearchQueryWriter() instanceof AbstractSearchQueryWriter) {
|
||||||
|
// The user has defined their own SearchQueryWriter, so we should just return it.
|
||||||
|
return $searchCriterion->getSearchQueryWriter();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($searchCriterion->getComparison()) {
|
||||||
|
case SearchCriterion::EQUAL:
|
||||||
|
case SearchCriterion::NOT_EQUAL:
|
||||||
|
return SolrSearchQueryWriterBasic::create();
|
||||||
|
case SearchCriterion::IN:
|
||||||
|
case SearchCriterion::NOT_IN:
|
||||||
|
return SolrSearchQueryWriterIn::create();
|
||||||
|
case SearchCriterion::GREATER_EQUAL:
|
||||||
|
case SearchCriterion::GREATER_THAN:
|
||||||
|
case SearchCriterion::LESS_EQUAL:
|
||||||
|
case SearchCriterion::LESS_THAN:
|
||||||
|
case SearchCriterion::ISNULL:
|
||||||
|
case SearchCriterion::ISNOTNULL:
|
||||||
|
return SolrSearchQueryWriterRange::create();
|
||||||
|
case SearchCriterion::CUSTOM:
|
||||||
|
// CUSTOM requires a SearchQueryWriter be provided. One can't have been provided, or it would have been
|
||||||
|
// picked up at the top of the method.
|
||||||
|
throw new InvalidArgumentException('SearchQueryWriter undefined or unsupported in SearchCriterion');
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException('Unsupported comparison type in SolrSearchAdapter');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
239
src/Search/Criteria/SearchCriteria.php
Normal file
239
src/Search/Criteria/SearchCriteria.php
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Criteria;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Search\Adapters\SearchAdapterInterface;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Queries\AbstractSearchQueryWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SearchCriteria
|
||||||
|
* @package SilverStripe\FullTextSearch\Criteria
|
||||||
|
*/
|
||||||
|
class SearchCriteria implements SearchCriteriaInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string
|
||||||
|
*/
|
||||||
|
const CONJUNCTION_AND = 'AND';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string
|
||||||
|
*/
|
||||||
|
const CONJUNCTION_OR = 'OR';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of SearchCriterion and SearchCriteria.
|
||||||
|
*
|
||||||
|
* @var SearchCriteriaInterface[]
|
||||||
|
*/
|
||||||
|
protected $clauses = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The conjunctions used between Criteria (AND/OR).
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $conjunctions = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SearchAdapterInterface|null
|
||||||
|
*/
|
||||||
|
protected $adapter = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You can pass through a string value, Criteria object, or Criterion object for $target.
|
||||||
|
*
|
||||||
|
* String value might be "SiteTree_Title" or whatever field in your index that you're trying to target.
|
||||||
|
*
|
||||||
|
* If you require complex filtering then you can build your Criteria object first with multiple layers/levels of
|
||||||
|
* Criteria, and then pass it in here when you're ready.
|
||||||
|
*
|
||||||
|
* If you have your own Criterion object that you've created that you want to use, you can also pass that in here.
|
||||||
|
*
|
||||||
|
* @param string|SearchCriterion $target
|
||||||
|
* @param mixed $value
|
||||||
|
* @param string|null $comparison
|
||||||
|
* @param AbstractSearchQueryWriter $searchQueryWriter
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
$target,
|
||||||
|
$value = null,
|
||||||
|
$comparison = null,
|
||||||
|
AbstractSearchQueryWriter $searchQueryWriter = null
|
||||||
|
) {
|
||||||
|
$this->addClause($this->getCriterionForCondition($target, $value, $comparison, $searchQueryWriter));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static create method provided so that you can perform method chaining.
|
||||||
|
*
|
||||||
|
* @param $target
|
||||||
|
* @param null $value
|
||||||
|
* @param null $comparison
|
||||||
|
* @param AbstractSearchQueryWriter $searchQueryWriter
|
||||||
|
* @return SearchCriteria
|
||||||
|
*/
|
||||||
|
public static function create(
|
||||||
|
$target,
|
||||||
|
$value = null,
|
||||||
|
$comparison = null,
|
||||||
|
AbstractSearchQueryWriter $searchQueryWriter = null
|
||||||
|
) {
|
||||||
|
return new SearchCriteria($target, $value, $comparison, $searchQueryWriter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null|SearchAdapterInterface
|
||||||
|
*/
|
||||||
|
public function getAdapter()
|
||||||
|
{
|
||||||
|
return $this->adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SearchAdapterInterface $adapter
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setAdapter(SearchAdapterInterface $adapter)
|
||||||
|
{
|
||||||
|
$this->adapter = $adapter;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $ps Current prepared statement.
|
||||||
|
* @return void
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function appendPreparedStatementTo(&$ps)
|
||||||
|
{
|
||||||
|
$adapter = $this->getAdapter();
|
||||||
|
|
||||||
|
if (!$adapter instanceof SearchAdapterInterface) {
|
||||||
|
throw new \Exception('No adapter has been applied to SearchCriteria');
|
||||||
|
}
|
||||||
|
|
||||||
|
$ps .= $adapter->getOpenComparisonContainer();
|
||||||
|
|
||||||
|
foreach ($this->getClauses() as $key => $clause) {
|
||||||
|
$clause->setAdapter($adapter);
|
||||||
|
$clause->appendPreparedStatementTo($ps);
|
||||||
|
|
||||||
|
// There's always one less conjunction then there are clauses.
|
||||||
|
if ($this->getConjunction($key) !== null) {
|
||||||
|
$ps .= $adapter->getConjunctionFor($this->getConjunction($key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$ps .= $adapter->getCloseComparisonContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|SearchCriteriaInterface $target
|
||||||
|
* @param mixed $value
|
||||||
|
* @param string|null $comparison
|
||||||
|
* @param AbstractSearchQueryWriter $searchQueryWriter
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addAnd(
|
||||||
|
$target,
|
||||||
|
$value = null,
|
||||||
|
$comparison = null,
|
||||||
|
AbstractSearchQueryWriter $searchQueryWriter = null
|
||||||
|
) {
|
||||||
|
$criterion = $this->getCriterionForCondition($target, $value, $comparison, $searchQueryWriter);
|
||||||
|
|
||||||
|
$this->addConjunction(SearchCriteria::CONJUNCTION_AND);
|
||||||
|
$this->addClause($criterion);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|SearchCriteriaInterface $target
|
||||||
|
* @param mixed $value
|
||||||
|
* @param string|null $comparison
|
||||||
|
* @param AbstractSearchQueryWriter $searchQueryWriter
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addOr(
|
||||||
|
$target,
|
||||||
|
$value = null,
|
||||||
|
$comparison = null,
|
||||||
|
AbstractSearchQueryWriter $searchQueryWriter = null
|
||||||
|
) {
|
||||||
|
$criterion = $this->getCriterionForCondition($target, $value, $comparison, $searchQueryWriter);
|
||||||
|
|
||||||
|
$this->addConjunction(SearchCriteria::CONJUNCTION_OR);
|
||||||
|
$this->addClause($criterion);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|SearchCriteriaInterface $target
|
||||||
|
* @param mixed $value
|
||||||
|
* @param string $comparison
|
||||||
|
* @param AbstractSearchQueryWriter $searchQueryWriter
|
||||||
|
* @return SearchCriteriaInterface
|
||||||
|
*/
|
||||||
|
protected function getCriterionForCondition(
|
||||||
|
$target,
|
||||||
|
$value,
|
||||||
|
$comparison,
|
||||||
|
AbstractSearchQueryWriter $searchQueryWriter = null
|
||||||
|
) {
|
||||||
|
if ($target instanceof SearchCriteriaInterface) {
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SearchCriterion($target, $value, $comparison, $searchQueryWriter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return SearchCriteriaInterface[]
|
||||||
|
*/
|
||||||
|
protected function getClauses()
|
||||||
|
{
|
||||||
|
return $this->clauses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SearchCriteriaInterface $criterion
|
||||||
|
*/
|
||||||
|
protected function addClause($criterion)
|
||||||
|
{
|
||||||
|
$this->clauses[] = $criterion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
protected function getConjunctions()
|
||||||
|
{
|
||||||
|
return $this->conjunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $key
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
protected function getConjunction($key)
|
||||||
|
{
|
||||||
|
$conjunctions = $this->getConjunctions();
|
||||||
|
if (!array_key_exists($key, $conjunctions)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $conjunctions[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $conjunction
|
||||||
|
*/
|
||||||
|
protected function addConjunction($conjunction)
|
||||||
|
{
|
||||||
|
$this->conjunctions[] = $conjunction;
|
||||||
|
}
|
||||||
|
}
|
36
src/Search/Criteria/SearchCriteriaInterface.php
Normal file
36
src/Search/Criteria/SearchCriteriaInterface.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Criteria;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Search\Adapters\SearchAdapterInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface SearchCriteriaInterface
|
||||||
|
*
|
||||||
|
* SearchCriteria and SearchCriterion objects must implement this interface.
|
||||||
|
*/
|
||||||
|
interface SearchCriteriaInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The method used in all SearchCriterion to generate and append their filter query statements.
|
||||||
|
*
|
||||||
|
* This is also used in SearchCriteria to loop through it's collected SearchCriterion and append the above. This
|
||||||
|
* allows us to have SearchCriteria and SearchCriterion in the same collections (allowing us to have complex nested
|
||||||
|
* filtering).
|
||||||
|
*
|
||||||
|
* @param $ps
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function appendPreparedStatementTo(&$ps);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return SearchAdapterInterface
|
||||||
|
*/
|
||||||
|
public function getAdapter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SearchAdapterInterface $adapter
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setAdapter(SearchAdapterInterface $adapter);
|
||||||
|
}
|
269
src/Search/Criteria/SearchCriterion.php
Normal file
269
src/Search/Criteria/SearchCriterion.php
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Criteria;
|
||||||
|
|
||||||
|
use SilverStripe\Core\Injector\Injectable;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Adapters\SearchAdapterInterface;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Queries\AbstractSearchQueryWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SearchCriterion
|
||||||
|
* @package SilverStripe\FullTextSearch\Search\Criteria
|
||||||
|
*/
|
||||||
|
class SearchCriterion implements SearchCriteriaInterface
|
||||||
|
{
|
||||||
|
use Injectable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* field:value
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const EQUAL = 'EQUAL';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* -field:value
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const NOT_EQUAL = 'NOT_EQUAL';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* field:[value TO *]
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const GREATER_EQUAL = 'GREATER_EQUAL';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* field:{value TO *}
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const GREATER_THAN = 'GREATER_THAN';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* field:[* TO value]
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const LESS_EQUAL = 'LESS_EQUAL';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* field:{* TO value}
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const LESS_THAN = 'LESS_THAN';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (field:value1 field:value2 field:value3)
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const IN = 'IN';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* -(field:value1 field:value2 field:value3)
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const NOT_IN = 'NOT_IN';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* field:[* TO *]
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const ISNULL = 'ISNULL';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* -field:[* TO *]
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const ISNOTNULL = 'ISNOTNULL';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom Criterion with it's own SearchQueryWriter
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const CUSTOM = 'CUSTOM';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $comparison;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The table and field that this Criterion is applied to.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var mixed
|
||||||
|
*/
|
||||||
|
protected $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SearchAdapterInterface
|
||||||
|
*/
|
||||||
|
protected $adapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var AbstractSearchQueryWriter
|
||||||
|
*/
|
||||||
|
protected $searchQueryWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $target
|
||||||
|
* @param string|array $value
|
||||||
|
* @param string|null $comparison
|
||||||
|
* @param AbstractSearchQueryWriter $searchQueryWriter
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
$target,
|
||||||
|
$value,
|
||||||
|
$comparison = null,
|
||||||
|
AbstractSearchQueryWriter $searchQueryWriter = null
|
||||||
|
) {
|
||||||
|
// EQUAL is our default comparison.
|
||||||
|
if ($comparison === null) {
|
||||||
|
$comparison = SearchCriterion::EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setTarget($target);
|
||||||
|
$this->setValue($value);
|
||||||
|
$this->setComparison($comparison);
|
||||||
|
$this->setSearchQueryWriter($searchQueryWriter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return SearchAdapterInterface
|
||||||
|
*/
|
||||||
|
public function getAdapter()
|
||||||
|
{
|
||||||
|
return $this->adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SearchAdapterInterface $adapter
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setAdapter(SearchAdapterInterface $adapter)
|
||||||
|
{
|
||||||
|
$this->adapter = $adapter;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $ps
|
||||||
|
* @return void
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function appendPreparedStatementTo(&$ps)
|
||||||
|
{
|
||||||
|
$adapter = $this->getAdapter();
|
||||||
|
|
||||||
|
if (!$adapter instanceof SearchAdapterInterface) {
|
||||||
|
throw new \Exception('No adapter has been applied to SearchCriteria');
|
||||||
|
}
|
||||||
|
|
||||||
|
$ps .= $adapter->generateQueryString($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String values should be passed into our filter string with quotation marks and escaping.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getQuoteValue($value)
|
||||||
|
{
|
||||||
|
if (is_string($value)) {
|
||||||
|
return sprintf('"%s"', $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return AbstractSearchQueryWriter
|
||||||
|
*/
|
||||||
|
public function getSearchQueryWriter()
|
||||||
|
{
|
||||||
|
return $this->searchQueryWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param AbstractSearchQueryWriter $searchQueryWriter
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setSearchQueryWriter($searchQueryWriter)
|
||||||
|
{
|
||||||
|
$this->searchQueryWriter = $searchQueryWriter;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getComparison()
|
||||||
|
{
|
||||||
|
return $this->comparison;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $comparison
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
protected function setComparison($comparison)
|
||||||
|
{
|
||||||
|
$this->comparison = $comparison;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTarget()
|
||||||
|
{
|
||||||
|
return $this->target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $target
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
protected function setTarget($target)
|
||||||
|
{
|
||||||
|
$this->target = $target;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|array
|
||||||
|
*/
|
||||||
|
public function getValue()
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|array $value
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
protected function setValue($value)
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
21
src/Search/Queries/AbstractSearchQueryWriter.php
Normal file
21
src/Search/Queries/AbstractSearchQueryWriter.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Queries;
|
||||||
|
|
||||||
|
use SilverStripe\Core\Injector\Injectable;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Criteria\SearchCriterion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AbstractSearchQueryWriter
|
||||||
|
* @package SilverStripe\FullTextSearch\Search\Queries
|
||||||
|
*/
|
||||||
|
abstract class AbstractSearchQueryWriter
|
||||||
|
{
|
||||||
|
use Injectable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SearchCriterion $searchCriterion
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
abstract public function generateQueryString(SearchCriterion $searchCriterion);
|
||||||
|
}
|
@ -3,6 +3,9 @@
|
|||||||
namespace SilverStripe\FullTextSearch\Search\Queries;
|
namespace SilverStripe\FullTextSearch\Search\Queries;
|
||||||
|
|
||||||
use SilverStripe\Dev\Deprecation;
|
use SilverStripe\Dev\Deprecation;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Adapters\SearchAdapterInterface;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Criteria\SearchCriteria;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Criteria\SearchCriteriaInterface;
|
||||||
use SilverStripe\View\ViewableData;
|
use SilverStripe\View\ViewableData;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
|
|
||||||
@ -27,11 +30,25 @@ class SearchQuery extends ViewableData
|
|||||||
public $require = [];
|
public $require = [];
|
||||||
public $exclude = [];
|
public $exclude = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SearchCriteriaInterface[]
|
||||||
|
*/
|
||||||
|
public $criteria = [];
|
||||||
|
|
||||||
protected $start = 0;
|
protected $start = 0;
|
||||||
protected $limit = -1;
|
protected $limit = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SearchAdapterInterface
|
||||||
|
*/
|
||||||
|
protected $adapter = null;
|
||||||
|
|
||||||
/** These are the API functions */
|
/** These are the API functions */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SearchQuery constructor.
|
||||||
|
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||||
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
if (self::$missing === null) {
|
if (self::$missing === null) {
|
||||||
@ -42,12 +59,24 @@ class SearchQuery extends ViewableData
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SearchAdapterInterface $adapter
|
||||||
|
* @return SearchQuery
|
||||||
|
*/
|
||||||
|
public function setHandler(SearchAdapterInterface $adapter)
|
||||||
|
{
|
||||||
|
$this->adapter = $adapter;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $text Search terms. Exact format (grouping, boolean expressions, etc.) depends on
|
* @param string $text Search terms. Exact format (grouping, boolean expressions, etc.) depends on
|
||||||
* the search implementation.
|
* the search implementation.
|
||||||
* @param array $fields Limits the search to specific fields (using composite field names)
|
* @param array $fields Limits the search to specific fields (using composite field names)
|
||||||
* @param array $boost Map of composite field names to float values. The higher the value,
|
* @param array $boost Map of composite field names to float values. The higher the value,
|
||||||
* the more important the field gets for relevancy.
|
* the more important the field gets for relevancy.
|
||||||
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function addSearchTerm($text, $fields = null, $boost = [])
|
public function addSearchTerm($text, $fields = null, $boost = [])
|
||||||
{
|
{
|
||||||
@ -68,6 +97,7 @@ class SearchQuery extends ViewableData
|
|||||||
* @param string $text See {@link addSearchTerm()}
|
* @param string $text See {@link addSearchTerm()}
|
||||||
* @param array $fields See {@link addSearchTerm()}
|
* @param array $fields See {@link addSearchTerm()}
|
||||||
* @param array $boost See {@link addSearchTerm()}
|
* @param array $boost See {@link addSearchTerm()}
|
||||||
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function addFuzzySearchTerm($text, $fields = null, $boost = [])
|
public function addFuzzySearchTerm($text, $fields = null, $boost = [])
|
||||||
{
|
{
|
||||||
@ -116,6 +146,7 @@ class SearchQuery extends ViewableData
|
|||||||
*
|
*
|
||||||
* @param string $field Composite name of the field
|
* @param string $field Composite name of the field
|
||||||
* @param mixed $values Scalar value, array of values, or an instance of SearchQuery_Range
|
* @param mixed $values Scalar value, array of values, or an instance of SearchQuery_Range
|
||||||
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function addFilter($field, $values)
|
public function addFilter($field, $values)
|
||||||
{
|
{
|
||||||
@ -138,6 +169,7 @@ class SearchQuery extends ViewableData
|
|||||||
*
|
*
|
||||||
* @param string $field
|
* @param string $field
|
||||||
* @param mixed $values
|
* @param mixed $values
|
||||||
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function addExclude($field, $values)
|
public function addExclude($field, $values)
|
||||||
{
|
{
|
||||||
@ -155,6 +187,37 @@ class SearchQuery extends ViewableData
|
|||||||
return $this->exclude;
|
return $this->exclude;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You can pass through a string value, Criteria object, or Criterion object for $target.
|
||||||
|
*
|
||||||
|
* String value might be "SiteTree_Title" or whatever field in your index that you're trying to target.
|
||||||
|
*
|
||||||
|
* If you require complex filtering then you can build your Criteria object first with multiple layers/levels of
|
||||||
|
* Criteria, and then pass it in here when you're ready.
|
||||||
|
*
|
||||||
|
* If you have your own Criterion object that you've created that you want to use, you can also pass that in here.
|
||||||
|
*
|
||||||
|
* @param string|SearchCriteriaInterface $target
|
||||||
|
* @param mixed $value
|
||||||
|
* @param string|null $comparison
|
||||||
|
* @param AbstractSearchQueryWriter $searchQueryWriter
|
||||||
|
* @return SearchCriteriaInterface
|
||||||
|
*/
|
||||||
|
public function filterBy(
|
||||||
|
$target,
|
||||||
|
$value = null,
|
||||||
|
$comparison = null,
|
||||||
|
AbstractSearchQueryWriter $searchQueryWriter = null
|
||||||
|
) {
|
||||||
|
if (!$target instanceof SearchCriteriaInterface) {
|
||||||
|
$target = new SearchCriteria($target, $value, $comparison, $searchQueryWriter);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addCriteria($target);
|
||||||
|
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
|
||||||
public function setStart($start)
|
public function setStart($start)
|
||||||
{
|
{
|
||||||
$this->start = $start;
|
$this->start = $start;
|
||||||
@ -206,6 +269,14 @@ class SearchQuery extends ViewableData
|
|||||||
return $this->search || $this->classes || $this->require || $this->exclude;
|
return $this->search || $this->classes || $this->require || $this->exclude;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return SearchAdapterInterface
|
||||||
|
*/
|
||||||
|
public function getAdapter()
|
||||||
|
{
|
||||||
|
return $this->adapter;
|
||||||
|
}
|
||||||
|
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
return "Search Query\n";
|
return "Search Query\n";
|
||||||
@ -290,4 +361,32 @@ class SearchQuery extends ViewableData
|
|||||||
Deprecation::notice('4.0', 'Use setPageSize() instead');
|
Deprecation::notice('4.0', 'Use setPageSize() instead');
|
||||||
return $this->setPageSize($page);
|
return $this->setPageSize($page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return SearchCriteriaInterface[]
|
||||||
|
*/
|
||||||
|
public function getCriteria()
|
||||||
|
{
|
||||||
|
return $this->criteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SearchCriteriaInterface[] $criteria
|
||||||
|
* @return SearchQuery
|
||||||
|
*/
|
||||||
|
public function setCriteria($criteria)
|
||||||
|
{
|
||||||
|
$this->criteria = $criteria;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SearchCriteriaInterface $criteria
|
||||||
|
* @return SearchQuery
|
||||||
|
*/
|
||||||
|
public function addCriteria($criteria)
|
||||||
|
{
|
||||||
|
$this->criteria[] = $criteria;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -632,6 +632,8 @@ abstract class SolrIndex extends SearchIndex
|
|||||||
static::warn($e);
|
static::warn($e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -643,6 +645,7 @@ abstract class SolrIndex extends SearchIndex
|
|||||||
*
|
*
|
||||||
* @param array $classes List of non-obsolete classes in the same format as SolrIndex::getClasses()
|
* @param array $classes List of non-obsolete classes in the same format as SolrIndex::getClasses()
|
||||||
* @return bool Flag if successful
|
* @return bool Flag if successful
|
||||||
|
* @throws \Apache_Solr_HttpTransportException
|
||||||
*/
|
*/
|
||||||
public function clearObsoleteClasses($classes)
|
public function clearObsoleteClasses($classes)
|
||||||
{
|
{
|
||||||
@ -676,6 +679,8 @@ abstract class SolrIndex extends SearchIndex
|
|||||||
static::warn($e);
|
static::warn($e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -685,6 +690,8 @@ abstract class SolrIndex extends SearchIndex
|
|||||||
* @param array $params Extra request parameters passed through to Solr
|
* @param array $params Extra request parameters passed through to Solr
|
||||||
* @return ArrayData Map with the following keys:
|
* @return ArrayData Map with the following keys:
|
||||||
* - 'Matches': ArrayList of the matched object instances
|
* - 'Matches': ArrayList of the matched object instances
|
||||||
|
* @throws \Apache_Solr_HttpTransportException
|
||||||
|
* @throws \Apache_Solr_InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function search(SearchQuery $query, $offset = -1, $limit = -1, $params = array())
|
public function search(SearchQuery $query, $offset = -1, $limit = -1, $params = array())
|
||||||
{
|
{
|
||||||
@ -989,18 +996,57 @@ abstract class SolrIndex extends SearchIndex
|
|||||||
return $fq;
|
return $fq;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SearchQuery $searchQuery
|
||||||
|
* @return string
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
protected function getCriteriaComponent(SearchQuery $searchQuery)
|
||||||
|
{
|
||||||
|
if (count($searchQuery->getCriteria()) === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($searchQuery->getAdapter() === null) {
|
||||||
|
throw new \Exception('SearchQuery does not have a SearchAdapter applied');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to start with a positive conjunction.
|
||||||
|
$ps = $searchQuery->getAdapter()->getPrependToCriteriaComponent();
|
||||||
|
|
||||||
|
foreach ($searchQuery->getCriteria() as $clause) {
|
||||||
|
$clause->setAdapter($searchQuery->getAdapter());
|
||||||
|
$clause->appendPreparedStatementTo($ps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to start with a positive conjunction.
|
||||||
|
$ps .= $searchQuery->getAdapter()->getAppendToCriteriaComponent();
|
||||||
|
|
||||||
|
// Returned as an array because that's how `getFiltersComponent` expects it.
|
||||||
|
return $ps;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all filter conditions for this search
|
* Get all filter conditions for this search
|
||||||
*
|
*
|
||||||
* @param SearchQuery $searchQuery
|
* @param SearchQuery $searchQuery
|
||||||
* @return array
|
* @return array
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function getFiltersComponent(SearchQuery $searchQuery)
|
public function getFiltersComponent(SearchQuery $searchQuery)
|
||||||
{
|
{
|
||||||
return array_merge(
|
$criteriaComponent = $this->getCriteriaComponent($searchQuery);
|
||||||
|
|
||||||
|
$components = array_merge(
|
||||||
$this->getRequireFiltersComponent($searchQuery),
|
$this->getRequireFiltersComponent($searchQuery),
|
||||||
$this->getExcludeFiltersComponent($searchQuery)
|
$this->getExcludeFiltersComponent($searchQuery)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($criteriaComponent !== null) {
|
||||||
|
$components[] = $criteriaComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $components;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected $service;
|
protected $service;
|
||||||
|
54
src/Solr/Writers/SolrSearchQueryWriterBasic.php
Normal file
54
src/Solr/Writers/SolrSearchQueryWriterBasic.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Writers;
|
||||||
|
|
||||||
|
use SilverStripe\FullTextSearch\Search\Criteria\SearchCriterion;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Queries\AbstractSearchQueryWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SolrSearchQueryWriter_Basic
|
||||||
|
* @package SilverStripe\FullTextSearch\Solr\Writers
|
||||||
|
*/
|
||||||
|
class SolrSearchQueryWriterBasic extends AbstractSearchQueryWriter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var SearchCriterion $searchCriterion
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generateQueryString(SearchCriterion $searchCriterion)
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'%s(%s%s%s)',
|
||||||
|
$this->getComparisonPolarity($searchCriterion->getComparison()),
|
||||||
|
addslashes($searchCriterion->getTarget()),
|
||||||
|
$this->getComparisonConjunction(),
|
||||||
|
$searchCriterion->getQuoteValue($searchCriterion->getValue())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a positive (+) or negative (-) Solr comparison.
|
||||||
|
*
|
||||||
|
* @param string $comparison
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getComparisonPolarity($comparison)
|
||||||
|
{
|
||||||
|
switch ($comparison) {
|
||||||
|
case SearchCriterion::NOT_EQUAL:
|
||||||
|
return '-';
|
||||||
|
default:
|
||||||
|
return '+';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decide how we are comparing our left and right values.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getComparisonConjunction()
|
||||||
|
{
|
||||||
|
return ':';
|
||||||
|
}
|
||||||
|
}
|
86
src/Solr/Writers/SolrSearchQueryWriterIn.php
Normal file
86
src/Solr/Writers/SolrSearchQueryWriterIn.php
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Writers;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Criteria\SearchCriterion;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Queries\AbstractSearchQueryWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SolrSearchQueryWriter_In
|
||||||
|
* @package SilverStripe\FullTextSearch\Solr\Writers
|
||||||
|
*/
|
||||||
|
class SolrSearchQueryWriterIn extends AbstractSearchQueryWriter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param SearchCriterion $searchCriterion
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generateQueryString(SearchCriterion $searchCriterion)
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'%s%s',
|
||||||
|
$this->getComparisonPolarity($searchCriterion->getComparison()),
|
||||||
|
$this->getInComparisonString($searchCriterion)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a positive (+) or negative (-) Solr comparison.
|
||||||
|
*
|
||||||
|
* @param string $comparison
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getComparisonPolarity($comparison)
|
||||||
|
{
|
||||||
|
switch ($comparison) {
|
||||||
|
case SearchCriterion::NOT_IN:
|
||||||
|
return '-';
|
||||||
|
default:
|
||||||
|
return '+';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SearchCriterion $searchCriterion
|
||||||
|
* @return string
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
protected function getInComparisonString(SearchCriterion $searchCriterion)
|
||||||
|
{
|
||||||
|
$conditions = array();
|
||||||
|
|
||||||
|
if (!is_array($searchCriterion->getValue())) {
|
||||||
|
throw new InvalidArgumentException('Invalid value type for Criterion IN');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($searchCriterion->getValue() as $value) {
|
||||||
|
if (is_string($value)) {
|
||||||
|
// String values need to be wrapped in quotes and escaped.
|
||||||
|
$value = $searchCriterion->getQuoteValue($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$conditions[] = sprintf(
|
||||||
|
'%s%s%s',
|
||||||
|
addslashes($searchCriterion->getTarget()),
|
||||||
|
$this->getComparisonConjunction(),
|
||||||
|
$value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'(%s)',
|
||||||
|
implode(' ', $conditions)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decide how we are comparing our left and right values.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getComparisonConjunction()
|
||||||
|
{
|
||||||
|
return ':';
|
||||||
|
}
|
||||||
|
}
|
150
src/Solr/Writers/SolrSearchQueryWriterRange.php
Normal file
150
src/Solr/Writers/SolrSearchQueryWriterRange.php
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Solr\Writers;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Criteria\SearchCriterion;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Queries\AbstractSearchQueryWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SolrSearchQueryWriter_Range
|
||||||
|
* @package SilverStripe\FullTextSearch\Solr\Writers
|
||||||
|
*/
|
||||||
|
class SolrSearchQueryWriterRange extends AbstractSearchQueryWriter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param SearchCriterion $searchCriterion
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generateQueryString(SearchCriterion $searchCriterion)
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'%s(%s:%s%s%s%s%s)',
|
||||||
|
$this->getComparisonPolarity($searchCriterion->getComparison()),
|
||||||
|
addslashes($searchCriterion->getTarget()),
|
||||||
|
$this->getOpenComparisonContainer($searchCriterion->getComparison()),
|
||||||
|
$this->getLeftComparison($searchCriterion),
|
||||||
|
$this->getComparisonConjunction(),
|
||||||
|
$this->getRightComparison($searchCriterion),
|
||||||
|
$this->getCloseComparisonContainer($searchCriterion->getComparison())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a positive (+) or negative (-) Solr comparison.
|
||||||
|
*
|
||||||
|
* @param string $comparison
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getComparisonPolarity($comparison)
|
||||||
|
{
|
||||||
|
switch ($comparison) {
|
||||||
|
case SearchCriterion::ISNULL:
|
||||||
|
return '-';
|
||||||
|
default:
|
||||||
|
return '+';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the value that we want as our left comparison value.
|
||||||
|
*
|
||||||
|
* @param SearchCriterion $searchCriterion
|
||||||
|
* @return mixed|string
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
protected function getLeftComparison(SearchCriterion $searchCriterion)
|
||||||
|
{
|
||||||
|
switch ($searchCriterion->getComparison()) {
|
||||||
|
case SearchCriterion::GREATER_EQUAL:
|
||||||
|
case SearchCriterion::GREATER_THAN:
|
||||||
|
return $searchCriterion->getValue();
|
||||||
|
case SearchCriterion::ISNULL:
|
||||||
|
case SearchCriterion::ISNOTNULL:
|
||||||
|
case SearchCriterion::LESS_EQUAL:
|
||||||
|
case SearchCriterion::LESS_THAN:
|
||||||
|
return '*';
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException('Invalid comparison for RangeCriterion');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the value that we want as our right comparison value.
|
||||||
|
*
|
||||||
|
* @param SearchCriterion $searchCriterion
|
||||||
|
* @return mixed|string
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
protected function getRightComparison(SearchCriterion $searchCriterion)
|
||||||
|
{
|
||||||
|
switch ($searchCriterion->getComparison()) {
|
||||||
|
case SearchCriterion::GREATER_EQUAL:
|
||||||
|
case SearchCriterion::GREATER_THAN:
|
||||||
|
case SearchCriterion::ISNULL:
|
||||||
|
case SearchCriterion::ISNOTNULL:
|
||||||
|
return '*';
|
||||||
|
case SearchCriterion::LESS_EQUAL:
|
||||||
|
case SearchCriterion::LESS_THAN:
|
||||||
|
return $searchCriterion->getValue();
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException('Invalid comparison for RangeCriterion');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decide how we are comparing our left and right values.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getComparisonConjunction()
|
||||||
|
{
|
||||||
|
return ' TO ';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does our comparison need a container? EG: "[* TO *]"? If so, return the opening container brace.
|
||||||
|
*
|
||||||
|
* @param string $comparison
|
||||||
|
* @return string
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
protected function getOpenComparisonContainer($comparison)
|
||||||
|
{
|
||||||
|
switch ($comparison) {
|
||||||
|
case SearchCriterion::GREATER_EQUAL:
|
||||||
|
case SearchCriterion::LESS_EQUAL:
|
||||||
|
case SearchCriterion::ISNULL:
|
||||||
|
case SearchCriterion::ISNOTNULL:
|
||||||
|
return '[';
|
||||||
|
case SearchCriterion::GREATER_THAN:
|
||||||
|
case SearchCriterion::LESS_THAN:
|
||||||
|
return '{';
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException('Invalid comparison for RangeCriterion');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does our comparison need a container? EG: "[* TO *]"? If so, return the closing container brace.
|
||||||
|
*
|
||||||
|
* @param string $comparison
|
||||||
|
* @return string
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
protected function getCloseComparisonContainer($comparison)
|
||||||
|
{
|
||||||
|
switch ($comparison) {
|
||||||
|
case SearchCriterion::GREATER_EQUAL:
|
||||||
|
case SearchCriterion::LESS_EQUAL:
|
||||||
|
case SearchCriterion::ISNULL:
|
||||||
|
case SearchCriterion::ISNOTNULL:
|
||||||
|
return ']';
|
||||||
|
case SearchCriterion::GREATER_THAN:
|
||||||
|
case SearchCriterion::LESS_THAN:
|
||||||
|
return '}';
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException('Invalid comparison for RangeCriterion');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
242
tests/SolrWritersTest.php
Normal file
242
tests/SolrWritersTest.php
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests;
|
||||||
|
|
||||||
|
use \InvalidArgumentException;
|
||||||
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Adapters\SolrSearchAdapter;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Criteria\SearchCriteria;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Criteria\SearchCriterion;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Writers\SolrSearchQueryWriterBasic;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Writers\SolrSearchQueryWriterIn;
|
||||||
|
use SilverStripe\FullTextSearch\Solr\Writers\SolrSearchQueryWriterRange;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_FakeIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SolrWritersTest
|
||||||
|
* @package SilverStripe\FullTextSearch\Tests
|
||||||
|
*/
|
||||||
|
class SolrWritersTest extends SapphireTest
|
||||||
|
{
|
||||||
|
public function testBasicEqualQueryString()
|
||||||
|
{
|
||||||
|
$criteria = new SearchCriterion('Title', 'Test', SearchCriterion::EQUAL);
|
||||||
|
$writer = SolrSearchQueryWriterBasic::create();
|
||||||
|
$expected = '+(Title:"Test")';
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $writer->generateQueryString($criteria));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBasicNotEqualQueryString()
|
||||||
|
{
|
||||||
|
$criteria = new SearchCriterion('Title', 'Test', SearchCriterion::NOT_EQUAL);
|
||||||
|
$writer = SolrSearchQueryWriterBasic::create();
|
||||||
|
$expected = '-(Title:"Test")';
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $writer->generateQueryString($criteria));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBasicInQueryString()
|
||||||
|
{
|
||||||
|
$criteria = new SearchCriterion('ID', [1,2,3], SearchCriterion::IN);
|
||||||
|
$writer = SolrSearchQueryWriterIn::create();
|
||||||
|
$expected = '+(ID:1 ID:2 ID:3)';
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $writer->generateQueryString($criteria));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBasicNotInQueryString()
|
||||||
|
{
|
||||||
|
$criteria = new SearchCriterion('ID', [1,2,3], SearchCriterion::NOT_IN);
|
||||||
|
$writer = SolrSearchQueryWriterIn::create();
|
||||||
|
$expected = '-(ID:1 ID:2 ID:3)';
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $writer->generateQueryString($criteria));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBasicGreaterEqualQueryString()
|
||||||
|
{
|
||||||
|
$criteria = new SearchCriterion('Stock', 2, SearchCriterion::GREATER_EQUAL);
|
||||||
|
$writer = SolrSearchQueryWriterRange::create();
|
||||||
|
$expected = '+(Stock:[2 TO *])';
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $writer->generateQueryString($criteria));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBasicGreaterQueryString()
|
||||||
|
{
|
||||||
|
$criteria = new SearchCriterion('Stock', 2, SearchCriterion::GREATER_THAN);
|
||||||
|
$writer = SolrSearchQueryWriterRange::create();
|
||||||
|
$expected = '+(Stock:{2 TO *})';
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $writer->generateQueryString($criteria));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBasicLessEqualQueryString()
|
||||||
|
{
|
||||||
|
$criteria = new SearchCriterion('Stock', 2, SearchCriterion::LESS_EQUAL);
|
||||||
|
$writer = SolrSearchQueryWriterRange::create();
|
||||||
|
$expected = '+(Stock:[* TO 2])';
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $writer->generateQueryString($criteria));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBasicLessQueryString()
|
||||||
|
{
|
||||||
|
$criteria = new SearchCriterion('Stock', 2, SearchCriterion::LESS_THAN);
|
||||||
|
$writer = SolrSearchQueryWriterRange::create();
|
||||||
|
$expected = '+(Stock:{* TO 2})';
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $writer->generateQueryString($criteria));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBasicIsNullQueryString()
|
||||||
|
{
|
||||||
|
$criteria = new SearchCriterion('Stock', null, SearchCriterion::ISNULL);
|
||||||
|
$writer = SolrSearchQueryWriterRange::create();
|
||||||
|
$expected = '-(Stock:[* TO *])';
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $writer->generateQueryString($criteria));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBasicIsNotNullQueryString()
|
||||||
|
{
|
||||||
|
$criteria = new SearchCriterion('Stock', null, SearchCriterion::ISNOTNULL);
|
||||||
|
$writer = SolrSearchQueryWriterRange::create();
|
||||||
|
$expected = '+(Stock:[* TO *])';
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $writer->generateQueryString($criteria));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConjunction()
|
||||||
|
{
|
||||||
|
$adapter = new SolrSearchAdapter();
|
||||||
|
|
||||||
|
$this->assertEquals(' AND ', $adapter->getConjunctionFor(SearchCriteria::CONJUNCTION_AND));
|
||||||
|
$this->assertEquals(' OR ', $adapter->getConjunctionFor(SearchCriteria::CONJUNCTION_OR));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testConjunctionFailure()
|
||||||
|
{
|
||||||
|
$adapter = new SolrSearchAdapter();
|
||||||
|
$adapter->getConjunctionFor('FAIL');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function testComplexPositiveFilterQueryString()
|
||||||
|
{
|
||||||
|
$expected = '+((+(Page_TaxonomyTerms_ID:"Lego") AND +(Page_TaxonomyTerms_ID:"StarWars") AND +(Stock:[5 TO *]))';
|
||||||
|
$expected .= ' OR (+(Page_TaxonomyTerms_ID:"Books") AND +(Page_TaxonomyTerms_ID:"HarryPotter")';
|
||||||
|
$expected .= ' AND +(Stock:[1 TO *])))';
|
||||||
|
|
||||||
|
$legoCriteria = SearchCriteria::create(
|
||||||
|
'Page_TaxonomyTerms_ID',
|
||||||
|
[
|
||||||
|
'Lego',
|
||||||
|
],
|
||||||
|
SearchCriterion::IN
|
||||||
|
);
|
||||||
|
|
||||||
|
$legoCriteria->addAnd(
|
||||||
|
'Page_TaxonomyTerms_ID',
|
||||||
|
[
|
||||||
|
'StarWars',
|
||||||
|
],
|
||||||
|
SearchCriterion::IN
|
||||||
|
);
|
||||||
|
|
||||||
|
$legoCriteria->addAnd(
|
||||||
|
'Stock',
|
||||||
|
5,
|
||||||
|
SearchCriterion::GREATER_EQUAL
|
||||||
|
);
|
||||||
|
|
||||||
|
$booksCriteria = SearchCriteria::create(
|
||||||
|
'Page_TaxonomyTerms_ID',
|
||||||
|
[
|
||||||
|
'Books',
|
||||||
|
],
|
||||||
|
SearchCriterion::IN
|
||||||
|
);
|
||||||
|
|
||||||
|
$booksCriteria->addAnd(
|
||||||
|
'Page_TaxonomyTerms_ID',
|
||||||
|
[
|
||||||
|
'HarryPotter',
|
||||||
|
],
|
||||||
|
SearchCriterion::IN
|
||||||
|
);
|
||||||
|
|
||||||
|
$booksCriteria->addAnd(
|
||||||
|
'Stock',
|
||||||
|
1,
|
||||||
|
SearchCriterion::GREATER_EQUAL
|
||||||
|
);
|
||||||
|
|
||||||
|
// Combine the two criteria with an `OR` conjunction
|
||||||
|
$criteria = SearchCriteria::create($legoCriteria)->addOr($booksCriteria);
|
||||||
|
|
||||||
|
$query = SearchQuery::create();
|
||||||
|
$query->filterBy($criteria);
|
||||||
|
|
||||||
|
$index = new SolrIndexTest_FakeIndex();
|
||||||
|
|
||||||
|
$this->assertTrue(in_array($expected, $index->getFiltersComponent($query)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function testComplexNegativeFilterQueryString()
|
||||||
|
{
|
||||||
|
$expected = '+((-(Page_TaxonomyTerms_ID:"Lego" Page_TaxonomyTerms_ID:"StarWars") AND +(Stock:[* TO 5]))';
|
||||||
|
$expected .= ' OR (-(Page_TaxonomyTerms_ID:"Books" Page_TaxonomyTerms_ID:"HarryPotter")';
|
||||||
|
$expected .= ' AND +(Stock:[* TO 2])))';
|
||||||
|
|
||||||
|
$legoCriteria = SearchCriteria::create(
|
||||||
|
'Page_TaxonomyTerms_ID',
|
||||||
|
[
|
||||||
|
'Lego',
|
||||||
|
'StarWars',
|
||||||
|
],
|
||||||
|
SearchCriterion::NOT_IN
|
||||||
|
);
|
||||||
|
|
||||||
|
$legoCriteria->addAnd(
|
||||||
|
'Stock',
|
||||||
|
5,
|
||||||
|
SearchCriterion::LESS_EQUAL
|
||||||
|
);
|
||||||
|
|
||||||
|
$booksCriteria = SearchCriteria::create(
|
||||||
|
'Page_TaxonomyTerms_ID',
|
||||||
|
[
|
||||||
|
'Books',
|
||||||
|
'HarryPotter',
|
||||||
|
],
|
||||||
|
SearchCriterion::NOT_IN
|
||||||
|
);
|
||||||
|
|
||||||
|
$booksCriteria->addAnd(
|
||||||
|
'Stock',
|
||||||
|
2,
|
||||||
|
SearchCriterion::LESS_EQUAL
|
||||||
|
);
|
||||||
|
|
||||||
|
// Combine the two criteria with an `OR` conjunction
|
||||||
|
$criteria = SearchCriteria::create($legoCriteria)->addOr($booksCriteria);
|
||||||
|
|
||||||
|
$query = SearchQuery::create();
|
||||||
|
$query->filterBy($criteria);
|
||||||
|
|
||||||
|
$index = new SolrIndexTest_FakeIndex();
|
||||||
|
|
||||||
|
$this->assertTrue(in_array($expected, $index->getFiltersComponent($query)));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user