From 992e753e9d37e839bc6a908371a6de2b92e2b037 Mon Sep 17 00:00:00 2001 From: wickedOne Date: Sun, 29 Dec 2019 09:12:15 +0100 Subject: [PATCH 1/5] select query builder expression builder to ease the use of solr's query language --- CHANGELOG.md | 1 + src/Builder/AbstractExpressionVisitor.php | 2 - src/Builder/Comparison.php | 2 - src/Builder/CompositeComparison.php | 2 - src/Builder/Select/ExpressionBuilder.php | 172 +++++++++ src/Builder/Select/FilterBuilder.php | 77 ++++ src/Builder/Select/QueryExpressionVisitor.php | 190 ++++++++++ src/Builder/Value.php | 2 - .../Builder/Select/SelectQueryBuilderTest.php | 357 ++++++++++++++++++ 9 files changed, 797 insertions(+), 8 deletions(-) create mode 100644 src/Builder/Select/ExpressionBuilder.php create mode 100644 src/Builder/Select/FilterBuilder.php create mode 100644 src/Builder/Select/QueryExpressionVisitor.php create mode 100644 tests/Builder/Select/SelectQueryBuilderTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c00ed5c3..0c9d4ce30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Support for the analytics component - Function builder +- Query builder - Solarium\Component\FacetSet::setMatches() - Solarium\Component\FacetSet::setExcludeTerms() - Solarium\Component\Facet\Field::setMatches() diff --git a/src/Builder/AbstractExpressionVisitor.php b/src/Builder/AbstractExpressionVisitor.php index 8c5a2dcf1..9297e8800 100644 --- a/src/Builder/AbstractExpressionVisitor.php +++ b/src/Builder/AbstractExpressionVisitor.php @@ -16,8 +16,6 @@ /** * Expression Visitor. * - * @codeCoverageIgnore - * * @author wicliff */ abstract class AbstractExpressionVisitor diff --git a/src/Builder/Comparison.php b/src/Builder/Comparison.php index 90dafb8ec..4d45a0ab0 100644 --- a/src/Builder/Comparison.php +++ b/src/Builder/Comparison.php @@ -14,8 +14,6 @@ /** * Comparison. * - * @codeCoverageIgnore - * * @author wicliff */ class Comparison implements ExpressionInterface diff --git a/src/Builder/CompositeComparison.php b/src/Builder/CompositeComparison.php index af3721594..e4b668258 100644 --- a/src/Builder/CompositeComparison.php +++ b/src/Builder/CompositeComparison.php @@ -16,8 +16,6 @@ /** * Composite Expression. * - * @codeCoverageIgnore - * * @author wicliff */ class CompositeComparison implements ExpressionInterface diff --git a/src/Builder/Select/ExpressionBuilder.php b/src/Builder/Select/ExpressionBuilder.php new file mode 100644 index 000000000..11801d0eb --- /dev/null +++ b/src/Builder/Select/ExpressionBuilder.php @@ -0,0 +1,172 @@ + + */ +class ExpressionBuilder +{ + /** + * @param null $x + * + * @throws \Solarium\Exception\RuntimeException + * + * @return \Solarium\Builder\CompositeComparison + */ + public function andX($x = null): CompositeComparison + { + return new CompositeComparison(CompositeComparison::TYPE_AND, \func_get_args()); + } + + /** + * @param null $x + * + * @throws \Solarium\Exception\RuntimeException + * + * @return \Solarium\Builder\CompositeComparison + */ + public function orX($x = null): CompositeComparison + { + return new CompositeComparison(CompositeComparison::TYPE_OR, \func_get_args()); + } + + /** + * @param string $field + * @param mixed $value + * + * @return \Solarium\Builder\Comparison + */ + public function eq(string $field, $value): Comparison + { + return new Comparison($field, Comparison::EQ, $value); + } + + /** + * @param string $field + * @param mixed $value + * + * @return \Solarium\Builder\Comparison + */ + public function neq(string $field, $value): Comparison + { + return new Comparison($field, Comparison::NEQ, $value); + } + + /** + * @param string $field + * @param mixed $value + * + * @return \Solarium\Builder\Comparison + */ + public function lt(string $field, $value): Comparison + { + return new Comparison($field, Comparison::LT, $value); + } + + /** + * @param string $field + * @param mixed $value + * + * @return \Solarium\Builder\Comparison + */ + public function gt(string $field, $value): Comparison + { + return new Comparison($field, Comparison::GT, $value); + } + + /** + * @param string $field + * @param mixed $value + * + * @return \Solarium\Builder\Comparison + */ + public function lte(string $field, $value): Comparison + { + return new Comparison($field, Comparison::LTE, $value); + } + + /** + * @param string $field + * @param mixed $value + * + * @return \Solarium\Builder\Comparison + */ + public function gte(string $field, $value): Comparison + { + return new Comparison($field, Comparison::GTE, $value); + } + + /** + * @param string $field + * @param mixed $value + * + * @return \Solarium\Builder\Comparison + */ + public function in(string $field, $value): Comparison + { + return new Comparison($field, Comparison::IN, $value); + } + + /** + * @param string $field + * @param mixed $value + * + * @return \Solarium\Builder\Comparison + */ + public function notIn(string $field, $value): Comparison + { + return new Comparison($field, Comparison::NIN, $value); + } + + /** + * @param string $field + * @param mixed $value + * + * @return \Solarium\Builder\Comparison + */ + public function range(string $field, $value): Comparison + { + return new Comparison($field, Comparison::RANGE, $value); + } + + /** + * @param string $field + * @param mixed $value + * + * @return \Solarium\Builder\Comparison + */ + public function regexp(string $field, $value): Comparison + { + return new Comparison($field, Comparison::REGEXP, $value); + } + + /** + * @param string $field + * @param mixed $value + * + * @return \Solarium\Builder\Comparison + */ + public function like(string $field, $value): Comparison + { + return new Comparison($field, Comparison::LIKE, $value); + } + + /** + * @param string $field + * @param mixed $value + * + * @return \Solarium\Builder\Comparison + */ + public function match(string $field, $value): Comparison + { + return new Comparison($field, Comparison::MATCH, $value); + } +} diff --git a/src/Builder/Select/FilterBuilder.php b/src/Builder/Select/FilterBuilder.php new file mode 100644 index 000000000..a673f9c2d --- /dev/null +++ b/src/Builder/Select/FilterBuilder.php @@ -0,0 +1,77 @@ + + */ +class FilterBuilder +{ + /** + * @var \Solarium\Builder\ExpressionInterface[] + */ + private $expressions = []; + + /** + * @var \Solarium\Builder\Select\ExpressionBuilder + */ + private static $expressionBuilder; + + /** + * @return static + */ + public static function create(): self + { + return new self(); + } + + /** + * @return \Solarium\Builder\Select\ExpressionBuilder + */ + public static function expr(): ExpressionBuilder + { + if (null === self::$expressionBuilder) { + self::$expressionBuilder = new ExpressionBuilder(); + } + + return self::$expressionBuilder; + } + + /** + * @param \Solarium\Builder\ExpressionInterface $comparison + * + * @return $this + */ + public function where(ExpressionInterface $comparison): self + { + $this->expressions[] = $comparison; + + return $this; + } + + /** + * Convenience method for readability. + * + * @param \Solarium\Builder\ExpressionInterface $comparison + * + * @return $this + */ + public function andWhere(ExpressionInterface $comparison): self + { + return $this->where($comparison); + } + + /** + * @return \Solarium\Builder\ExpressionInterface[] + */ + public function getExpressions(): array + { + return $this->expressions; + } +} diff --git a/src/Builder/Select/QueryExpressionVisitor.php b/src/Builder/Select/QueryExpressionVisitor.php new file mode 100644 index 000000000..a37c30a77 --- /dev/null +++ b/src/Builder/Select/QueryExpressionVisitor.php @@ -0,0 +1,190 @@ + + */ +class QueryExpressionVisitor extends AbstractExpressionVisitor +{ + /** + * @var Helper + */ + private $helper; + + /** + * Constructor. + */ + public function __construct() + { + $this->helper = new Helper(); + } + + /** + * @param \Solarium\Builder\ExpressionInterface $expression + * + * @throws \Solarium\Exception\RuntimeException + * + * @return mixed|string + */ + public function walkExpression(ExpressionInterface $expression) + { + $field = $expression->getField(); + $value = $expression->getValue()->getValue(); + + switch ($expression->getOperator()) { + case Comparison::EQ: + case Comparison::NEQ: + $strValue = $this->valueToString($value, ',', '"'); + + if ($value instanceof \DateTime) { + $strValue = sprintf('[%1$s TO %1$s]', $strValue); + } + + $not = (Comparison::NEQ === $expression->getOperator()) ? '-' : ''; + + return sprintf('%s%s:%s', $not, $field, $strValue); + case Comparison::GT: + return sprintf('%s:{%s TO *]', $field, $this->valueToString($value)); + case Comparison::GTE: + return sprintf('%s:[%s TO *]', $field, $this->valueToString($value)); + case Comparison::LT: + return sprintf('%s:[* TO %s}', $field, $this->valueToString($value)); + case Comparison::LTE: + return sprintf('%s:[* TO %s]', $field, $this->valueToString($value)); + case Comparison::RANGE: + if (\is_array($value)) { + if (2 === \count($value)) { + return sprintf('%s:[%s TO %s]', $field, $this->valueToString($value[0]), $this->valueToString($value[1])); + } + + if (1 === \count($value)) { + return sprintf('%s:[%s TO *]', $field, $this->valueToString($value[0])); + } + } + + throw new RuntimeException(sprintf('Invalid range value: %s', $value)); + case Comparison::IN: + if (\is_array($value)) { + return sprintf('%s:(%s)', $field, $this->valueToString($value, ' OR ', '"')); + } + + return sprintf('%s:%s', $field, $this->valueToString($value, ',', '"')); + case Comparison::LIKE: + case Comparison::MATCH: + if (\is_array($value)) { + return sprintf('%s:(%s)', $field, $this->valueToString($value, ' OR ', '', false)); + } + + return sprintf('%s:%s', $field, $this->valueToString($value, ',', '', false)); + case Comparison::NIN: + if (\is_array($value)) { + return sprintf('-%s:(%s)', $field, $this->valueToString($value, ' OR ', '"')); + } + + return sprintf('-%s:%s', $field, $this->valueToString($value, ',', '"')); + case Comparison::REGEXP: + if ('/' !== $value[0]) { + $value = sprintf('/%s/', $value); + } + + return sprintf('%s:%s', $field, $this->valueToString($value, ',', '', false)); + default: + throw new RuntimeException('Unknown comparison operator: '.$expression->getOperator()); + } + } + + /** + * {@inheritdoc} + */ + public function walkValue(Value $value) + { + return $value->getValue(); + } + + /** + * {@inheritdoc} + * + * @throws \Solarium\Exception\RuntimeException + */ + public function walkCompositeExpression(ExpressionInterface $expr) + { + $comparisons = []; + + foreach ($expr->getComparisons() as $child) { + $comparisons[] = $this->dispatch($child); + } + + switch ($expr->getType()) { + case CompositeComparison::TYPE_AND: + return implode(' AND ', $comparisons); + case CompositeComparison::TYPE_OR: + return implode(' OR ', $comparisons); + default: + throw new RuntimeException('Unknown composite '.$expr->getType()); + } + } + + /** + * @param mixed $value + * @param string $separator + * @param string $quote + * @param bool $escape + * + * @return string + */ + private function valueToString($value, string $separator = ',', string $quote = '', bool $escape = true): string + { + if (\is_array($value)) { + $ret = []; + + foreach ($value as $v) { + $ret[] = $this->typedValueToString($v, $quote, $escape); + } + + return implode($separator, $ret); + } + + return $this->typedValueToString($value, $quote, $escape); + } + + /** + * @param mixed $value + * @param string $quote + * @param bool $escape + * + * @return string + */ + private function typedValueToString($value, string $quote = '', $escape = true): string + { + if (null === $value) { + return '[* TO *]'; + } + + if ($value instanceof \DateTime) { + return $this->helper->formatDate($value); + } + + if (true === $escape && \is_string($value)) { + $value = $this->helper->escapeTerm($value); + } + + if (\is_string($value)) { + $value = sprintf('%1$s%2$s%1$s', $quote, $value); + } + + return (string) $value; + } +} diff --git a/src/Builder/Value.php b/src/Builder/Value.php index a6a9eea3b..3eb658f27 100644 --- a/src/Builder/Value.php +++ b/src/Builder/Value.php @@ -14,8 +14,6 @@ /** * Value. * - * @codeCoverageIgnore - * * @author wicliff */ class Value implements ExpressionInterface diff --git a/tests/Builder/Select/SelectQueryBuilderTest.php b/tests/Builder/Select/SelectQueryBuilderTest.php new file mode 100644 index 000000000..e5d434935 --- /dev/null +++ b/tests/Builder/Select/SelectQueryBuilderTest.php @@ -0,0 +1,357 @@ + + */ +class SelectQueryBuilderTest extends TestCase +{ + /** + * @var \Solarium\Builder\Select\QueryExpressionVisitor + */ + private $visitor; + + /** + * Set up. + */ + public function setUp(): void + { + $this->visitor = new QueryExpressionVisitor(); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testEquals(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->eq('foo', 'bar')); + + $this->assertSame('foo:"bar"', $this->visitor->dispatch($filter->getExpressions()[0])); + + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->eq('foo', date_create('2020-01-01'))); + + $this->assertSame('foo:[2020-01-01T00:00:00Z TO 2020-01-01T00:00:00Z]', $this->visitor->dispatch($filter->getExpressions()[0])); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testNullValue(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->eq('foo', null)); + + $this->assertSame('foo:[* TO *]', $this->visitor->dispatch($filter->getExpressions()[0])); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testDoesNotEqual(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->neq('foo', 'bar')); + + $this->assertSame('-foo:"bar"', $this->visitor->dispatch($filter->getExpressions()[0])); + + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->neq('foo', date_create('2020-01-01'))); + + $this->assertSame('-foo:[2020-01-01T00:00:00Z TO 2020-01-01T00:00:00Z]', $this->visitor->dispatch($filter->getExpressions()[0])); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testGreaterThan(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->gt('foo', 2)); + + $this->assertSame('foo:{2 TO *]', $this->visitor->dispatch($filter->getExpressions()[0])); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testGreaterThanEqual(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->gte('foo', 2)); + + $this->assertSame('foo:[2 TO *]', $this->visitor->dispatch($filter->getExpressions()[0])); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testLowerThan(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->lt('foo', 2)); + + $this->assertSame('foo:[* TO 2}', $this->visitor->dispatch($filter->getExpressions()[0])); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testLowerThanEqual(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->lte('foo', 2)); + + $this->assertSame('foo:[* TO 2]', $this->visitor->dispatch($filter->getExpressions()[0])); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testRange(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->range('foo', [2])); + + $this->assertSame('foo:[2 TO *]', $this->visitor->dispatch($filter->getExpressions()[0])); + + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->range('foo', [2, 5])); + + $this->assertSame('foo:[2 TO 5]', $this->visitor->dispatch($filter->getExpressions()[0])); + } + + /** + * @throws \Solarium\Exception\RuntimeException + */ + public function testRangeInvalidValue(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->range('foo', 'bar')); + + $this->expectException(RuntimeException::class); + + $this->visitor->dispatch($filter->getExpressions()[0]); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testIn(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->in('foo', [2, 5])); + + $this->assertSame('foo:(2 OR 5)', $this->visitor->dispatch($filter->getExpressions()[0])); + + $filter = FilterBuilder::create() + ->andWhere(FilterBuilder::expr()->in('foo', 'bar')); + + $this->assertSame('foo:"bar"', $this->visitor->dispatch($filter->getExpressions()[0])); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testNotIn(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->notIn('foo', [2, 5])); + + $this->assertSame('-foo:(2 OR 5)', $this->visitor->dispatch($filter->getExpressions()[0])); + + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->notIn('foo', 'bar')); + + $this->assertSame('-foo:"bar"', $this->visitor->dispatch($filter->getExpressions()[0])); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testLike(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->like('title', ['*foo', 'bar*'])); + + $this->assertSame('title:(*foo OR bar*)', $this->visitor->dispatch($filter->getExpressions()[0])); + + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->like('title', 'foo*')); + + $this->assertSame('title:foo*', $this->visitor->dispatch($filter->getExpressions()[0])); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testRegularExpression(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->regexp('title', '[0-9]{5}')); + + $this->assertSame('title:/[0-9]{5}/', $this->visitor->dispatch($filter->getExpressions()[0])); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testMatch(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->match('title', 'foo*')); + + $this->assertSame('title:foo*', $this->visitor->dispatch($filter->getExpressions()[0])); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testCompositeAnd(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->andX( + FilterBuilder::expr()->eq('title', 'foo'), + FilterBuilder::expr()->in('description', ['bar', 'baz']) + )); + + $this->assertSame('title:"foo" AND description:("bar" OR "baz")', $this->visitor->dispatch($filter->getExpressions()[0])); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testCompositeOr(): void + { + $filter = FilterBuilder::create() + ->where(FilterBuilder::expr()->orX( + FilterBuilder::expr()->eq('title', 'foo'), + FilterBuilder::expr()->in('description', ['bar', 'baz']) + )); + + $this->assertSame('title:"foo" OR description:("bar" OR "baz")', $this->visitor->dispatch($filter->getExpressions()[0])); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testVisitExpressions(): void + { + $expression = FilterBuilder::expr()->eq('title', 'foo'); + + $this->assertSame('title:"foo"', $expression->visit($this->visitor)); + + $compositeExpression = FilterBuilder::expr()->andX( + FilterBuilder::expr()->eq('title', 'foo'), + FilterBuilder::expr()->in('description', ['bar', 'baz']) + ); + + $this->assertSame('title:"foo" AND description:("bar" OR "baz")', $compositeExpression->visit($this->visitor)); + + $value = new Value('foo'); + $this->assertSame('foo', $value->visit($this->visitor)); + $this->assertSame('foo', $this->visitor->dispatch($value)); + } + + /** + * @throws \Solarium\Exception\RuntimeException + */ + public function testInvalidCompositeExpressionWithValue(): void + { + $this->expectException(RuntimeException::class); + + new CompositeComparison(CompositeComparison::TYPE_OR, [new Value('foo')]); + } + + /** + * @throws \Solarium\Exception\RuntimeException + */ + public function testInvalidCompositeExpressionWithObject(): void + { + $this->expectException(RuntimeException::class); + + new CompositeComparison(CompositeComparison::TYPE_AND, [new \DateTime()]); + } + + /** + * @throws \Solarium\Exception\RuntimeException + */ + public function testUnknownExpression(): void + { + $this->expectException(RuntimeException::class); + + $this->visitor->dispatch(new ExpressionDummy()); + } + + /** + * @throws \Solarium\Exception\RuntimeException + */ + public function testUnknownCompositeComparison(): void + { + $comparison = new CompositeComparison('TO', [FilterBuilder::expr()->eq('title', 'foo')]); + + $this->expectException(RuntimeException::class); + + $this->visitor->walkCompositeExpression($comparison); + } + + /** + * @throws \Solarium\Exception\RuntimeException + */ + public function testUnknownComparison(): void + { + $comparison = new Comparison('title', 'FOO', 'bar'); + + $this->expectException(RuntimeException::class); + + $this->visitor->walkExpression($comparison); + } +} + +/** + * ExpressionDummy. + * + * @author wicliff + */ +class ExpressionDummy implements ExpressionInterface +{ + /** + * {@inheritdoc} + */ + public function visit(AbstractExpressionVisitor $visitor) + { + return $visitor->walkExpression($this); + } +} From db2efd255a209b15b95806c6757e48f88682b03b Mon Sep 17 00:00:00 2001 From: wickedOne Date: Tue, 14 Jul 2020 08:01:35 +0200 Subject: [PATCH 2/5] set timezone to circumvent helper bug --- tests/Builder/Select/SelectQueryBuilderTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Builder/Select/SelectQueryBuilderTest.php b/tests/Builder/Select/SelectQueryBuilderTest.php index e5d434935..39361373a 100644 --- a/tests/Builder/Select/SelectQueryBuilderTest.php +++ b/tests/Builder/Select/SelectQueryBuilderTest.php @@ -46,7 +46,7 @@ public function testEquals(): void $this->assertSame('foo:"bar"', $this->visitor->dispatch($filter->getExpressions()[0])); $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->eq('foo', date_create('2020-01-01'))); + ->where(FilterBuilder::expr()->eq('foo', date_create('2020-01-01', new \DateTimeZone('UTC')))); $this->assertSame('foo:[2020-01-01T00:00:00Z TO 2020-01-01T00:00:00Z]', $this->visitor->dispatch($filter->getExpressions()[0])); } @@ -75,7 +75,7 @@ public function testDoesNotEqual(): void $this->assertSame('-foo:"bar"', $this->visitor->dispatch($filter->getExpressions()[0])); $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->neq('foo', date_create('2020-01-01'))); + ->where(FilterBuilder::expr()->neq('foo', date_create('2020-01-01', new \DateTimeZone('UTC')))); $this->assertSame('-foo:[2020-01-01T00:00:00Z TO 2020-01-01T00:00:00Z]', $this->visitor->dispatch($filter->getExpressions()[0])); } From d05405c82d8cbee294a1b4d6d5355e4ee06ed52b Mon Sep 17 00:00:00 2001 From: wickedOne Date: Tue, 14 Jul 2020 11:00:51 +0200 Subject: [PATCH 3/5] implementation and documentation - added query builder implementation in query trait - added query builder implementation for adding filter queries - added some documentation for the query builder --- docs/queries/query-helper/query-builder.md | 84 ++++++++++++ src/Builder/Comparison.php | 5 + src/Builder/Select/ExpressionBuilder.php | 10 ++ .../{FilterBuilder.php => QueryBuilder.php} | 2 +- src/Builder/Select/QueryExpressionVisitor.php | 2 + src/Component/QueryTrait.php | 20 +++ src/QueryType/Select/Query/Query.php | 24 ++++ ...ryBuilderTest.php => QueryBuilderTest.php} | 122 ++++++++++-------- .../Select/Query/AbstractQueryTest.php | 56 ++++++++ tests/QueryType/Select/Query/QueryTest.php | 44 +++++++ 10 files changed, 313 insertions(+), 56 deletions(-) create mode 100644 docs/queries/query-helper/query-builder.md rename src/Builder/Select/{FilterBuilder.php => QueryBuilder.php} (98%) rename tests/Builder/Select/{SelectQueryBuilderTest.php => QueryBuilderTest.php} (71%) diff --git a/docs/queries/query-helper/query-builder.md b/docs/queries/query-helper/query-builder.md new file mode 100644 index 000000000..ef1f87300 --- /dev/null +++ b/docs/queries/query-helper/query-builder.md @@ -0,0 +1,84 @@ +Query builder +------------- + +The query builder is a simple helper class to help writing and maintaining (filter) queries using Solr's query language. +While the query will only accept a single (composite) expression, the addition of filter queries can consist of multiple expressions. + +Query example +------------- +```php +createSelect(); + +$expr = QueryBuilder::expr(); +$builder = QueryBuilder::create() + ->where($expr->andX( + $expr->eq('foo', 'bar'), + $expr->eq('baz', 'qux') + )) +; + +$query->setQueryFromQueryBuilder($builder); + +// which would be equal to +$query->setQuery('foo:"bar" AND baz:"qux"'); +``` + +Filter Query example +------------- +```php +createSelect(); + +$expr = QueryBuilder::expr(); +$builder = QueryBuilder::create() + ->where($expr->eq('foo', 'bar')), + ->andWhere($expr->neq('baz', 'qux') +); + +$query->addFilterQueriesFromQueryBuilder($builder); + +// which would be equal to +$value = 'foo:"bar"'; +$query->addFilterQuery(['key' => sha1($value), 'query' => $value]); +$value = '-baz:"qux"'; +$query->addFilterQuery(['key' => sha1($value), 'query' => $value]); +``` + +Complex filter queries +---------------------- +While the ``addFilterQueriesFromQueryBuilder`` method only provides in setting the facet query key and actual query, the ``QueryBuilder`` can be used in the construction of more complex facet queries. +If one, for example, need to add a tag to the filter query the following method could be used. +```php +createSelect(); + +$expr = QueryBuilder::expr(); +$visitor = new QueryExpressionVisitor(); + +$builder = QueryBuilder::create() + ->where($expr->eq('foo', 'bar')) +); + +$query->addFilterQuery([ + 'key' => 'my-key, + 'query' => $visitor->dispatch($builder->getExpression()[0]), + 'local_tag' => 'my-tag', +]); +``` \ No newline at end of file diff --git a/src/Builder/Comparison.php b/src/Builder/Comparison.php index 4d45a0ab0..1c46f03f5 100644 --- a/src/Builder/Comparison.php +++ b/src/Builder/Comparison.php @@ -78,6 +78,11 @@ class Comparison implements ExpressionInterface */ public const MATCH = 'MATCH'; + /** + * Empty. + */ + public const EMPTY = 'EMPTY'; + /** * @var string */ diff --git a/src/Builder/Select/ExpressionBuilder.php b/src/Builder/Select/ExpressionBuilder.php index 11801d0eb..d2f78edd6 100644 --- a/src/Builder/Select/ExpressionBuilder.php +++ b/src/Builder/Select/ExpressionBuilder.php @@ -169,4 +169,14 @@ public function match(string $field, $value): Comparison { return new Comparison($field, Comparison::MATCH, $value); } + + /** + * @param string $field + * + * @return \Solarium\Builder\Comparison + */ + public function empty(string $field): Comparison + { + return new Comparison($field, Comparison::EMPTY, null); + } } diff --git a/src/Builder/Select/FilterBuilder.php b/src/Builder/Select/QueryBuilder.php similarity index 98% rename from src/Builder/Select/FilterBuilder.php rename to src/Builder/Select/QueryBuilder.php index a673f9c2d..c65aad3aa 100644 --- a/src/Builder/Select/FilterBuilder.php +++ b/src/Builder/Select/QueryBuilder.php @@ -11,7 +11,7 @@ * * @author wicliff */ -class FilterBuilder +class QueryBuilder { /** * @var \Solarium\Builder\ExpressionInterface[] diff --git a/src/Builder/Select/QueryExpressionVisitor.php b/src/Builder/Select/QueryExpressionVisitor.php index a37c30a77..e3904b42d 100644 --- a/src/Builder/Select/QueryExpressionVisitor.php +++ b/src/Builder/Select/QueryExpressionVisitor.php @@ -101,6 +101,8 @@ public function walkExpression(ExpressionInterface $expression) } return sprintf('%s:%s', $field, $this->valueToString($value, ',', '', false)); + case Comparison::EMPTY: + return sprintf('(*:* NOT %s:*)', $field); default: throw new RuntimeException('Unknown comparison operator: '.$expression->getOperator()); } diff --git a/src/Component/QueryTrait.php b/src/Component/QueryTrait.php index f0680845a..6f38d6ad8 100644 --- a/src/Component/QueryTrait.php +++ b/src/Component/QueryTrait.php @@ -9,6 +9,10 @@ namespace Solarium\Component; +use Solarium\Builder\Select\QueryBuilder; +use Solarium\Builder\Select\QueryExpressionVisitor; +use Solarium\Exception\RuntimeException; + /** * Query Trait. */ @@ -34,6 +38,22 @@ public function setQuery(string $query, array $bind = null): QueryInterface return $this->setOption('query', trim($query)); } + /** + * @param \Solarium\Builder\Select\QueryBuilder $builder + * + * @return \Solarium\Component\QueryInterface + * + * @throws \Solarium\Exception\RuntimeException + */ + public function setQueryFromQueryBuilder(QueryBuilder $builder): QueryInterface + { + if (1 !== count($builder->getExpressions())) { + throw new RuntimeException('The QueryBuilder can only contain one expression when setting the query. Use ExpressionBuilder::andX or ExpressionBuilder::orX to combine expressions.'); + } + + return $this->setOption('query', (new QueryExpressionVisitor())->dispatch($builder->getExpressions()[0])); + } + /** * Get query option. * diff --git a/src/QueryType/Select/Query/Query.php b/src/QueryType/Select/Query/Query.php index 1c9acee57..fb1a154df 100644 --- a/src/QueryType/Select/Query/Query.php +++ b/src/QueryType/Select/Query/Query.php @@ -9,6 +9,8 @@ namespace Solarium\QueryType\Select\Query; +use Solarium\Builder\Select\QueryBuilder; +use Solarium\Builder\Select\QueryExpressionVisitor; use Solarium\Component\Analytics\Analytics; use Solarium\Component\ComponentAwareQueryInterface; use Solarium\Component\ComponentAwareQueryTrait; @@ -591,6 +593,28 @@ public function addFilterQueries(array $filterQueries): self return $this; } + /** + * Add multiple filter queries from the QueryBuilder. + * + * @param \Solarium\Builder\Select\QueryBuilder $builder + * + * @return $this + * + * @throws \Solarium\Exception\RuntimeException + */ + public function addFilterQueriesFromQueryBuilder(QueryBuilder $builder): self + { + $visitor = new QueryExpressionVisitor(); + + foreach ($builder->getExpressions() as $expression) { + $value = $visitor->dispatch($expression); + + $this->addFilterQuery(new FilterQuery(['key' => sha1($value), 'query' => $value])); + } + + return $this; + } + /** * Get a filterquery. * diff --git a/tests/Builder/Select/SelectQueryBuilderTest.php b/tests/Builder/Select/QueryBuilderTest.php similarity index 71% rename from tests/Builder/Select/SelectQueryBuilderTest.php rename to tests/Builder/Select/QueryBuilderTest.php index 39361373a..b9602b2e4 100644 --- a/tests/Builder/Select/SelectQueryBuilderTest.php +++ b/tests/Builder/Select/QueryBuilderTest.php @@ -5,14 +5,14 @@ namespace Solarium\Tests\Builder\Select; use PHPUnit\Framework\TestCase; -use Solarium\Exception\RuntimeException; use Solarium\Builder\AbstractExpressionVisitor; use Solarium\Builder\Comparison; use Solarium\Builder\CompositeComparison; use Solarium\Builder\ExpressionInterface; -use Solarium\Builder\Select\FilterBuilder; +use Solarium\Builder\Select\QueryBuilder; use Solarium\Builder\Select\QueryExpressionVisitor; use Solarium\Builder\Value; +use Solarium\Exception\RuntimeException; /** * Select Query Builder Test. @@ -40,13 +40,13 @@ public function setUp(): void */ public function testEquals(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->eq('foo', 'bar')); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->eq('foo', 'bar')); $this->assertSame('foo:"bar"', $this->visitor->dispatch($filter->getExpressions()[0])); - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->eq('foo', date_create('2020-01-01', new \DateTimeZone('UTC')))); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->eq('foo', date_create('2020-01-01', new \DateTimeZone('UTC')))); $this->assertSame('foo:[2020-01-01T00:00:00Z TO 2020-01-01T00:00:00Z]', $this->visitor->dispatch($filter->getExpressions()[0])); } @@ -57,8 +57,8 @@ public function testEquals(): void */ public function testNullValue(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->eq('foo', null)); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->eq('foo', null)); $this->assertSame('foo:[* TO *]', $this->visitor->dispatch($filter->getExpressions()[0])); } @@ -69,13 +69,13 @@ public function testNullValue(): void */ public function testDoesNotEqual(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->neq('foo', 'bar')); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->neq('foo', 'bar')); $this->assertSame('-foo:"bar"', $this->visitor->dispatch($filter->getExpressions()[0])); - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->neq('foo', date_create('2020-01-01', new \DateTimeZone('UTC')))); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->neq('foo', date_create('2020-01-01', new \DateTimeZone('UTC')))); $this->assertSame('-foo:[2020-01-01T00:00:00Z TO 2020-01-01T00:00:00Z]', $this->visitor->dispatch($filter->getExpressions()[0])); } @@ -86,8 +86,8 @@ public function testDoesNotEqual(): void */ public function testGreaterThan(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->gt('foo', 2)); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->gt('foo', 2)); $this->assertSame('foo:{2 TO *]', $this->visitor->dispatch($filter->getExpressions()[0])); } @@ -98,8 +98,8 @@ public function testGreaterThan(): void */ public function testGreaterThanEqual(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->gte('foo', 2)); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->gte('foo', 2)); $this->assertSame('foo:[2 TO *]', $this->visitor->dispatch($filter->getExpressions()[0])); } @@ -110,8 +110,8 @@ public function testGreaterThanEqual(): void */ public function testLowerThan(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->lt('foo', 2)); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->lt('foo', 2)); $this->assertSame('foo:[* TO 2}', $this->visitor->dispatch($filter->getExpressions()[0])); } @@ -122,8 +122,8 @@ public function testLowerThan(): void */ public function testLowerThanEqual(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->lte('foo', 2)); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->lte('foo', 2)); $this->assertSame('foo:[* TO 2]', $this->visitor->dispatch($filter->getExpressions()[0])); } @@ -134,13 +134,13 @@ public function testLowerThanEqual(): void */ public function testRange(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->range('foo', [2])); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->range('foo', [2])); $this->assertSame('foo:[2 TO *]', $this->visitor->dispatch($filter->getExpressions()[0])); - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->range('foo', [2, 5])); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->range('foo', [2, 5])); $this->assertSame('foo:[2 TO 5]', $this->visitor->dispatch($filter->getExpressions()[0])); } @@ -150,8 +150,8 @@ public function testRange(): void */ public function testRangeInvalidValue(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->range('foo', 'bar')); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->range('foo', 'bar')); $this->expectException(RuntimeException::class); @@ -164,13 +164,13 @@ public function testRangeInvalidValue(): void */ public function testIn(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->in('foo', [2, 5])); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->in('foo', [2, 5])); $this->assertSame('foo:(2 OR 5)', $this->visitor->dispatch($filter->getExpressions()[0])); - $filter = FilterBuilder::create() - ->andWhere(FilterBuilder::expr()->in('foo', 'bar')); + $filter = QueryBuilder::create() + ->andWhere(QueryBuilder::expr()->in('foo', 'bar')); $this->assertSame('foo:"bar"', $this->visitor->dispatch($filter->getExpressions()[0])); } @@ -181,13 +181,13 @@ public function testIn(): void */ public function testNotIn(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->notIn('foo', [2, 5])); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->notIn('foo', [2, 5])); $this->assertSame('-foo:(2 OR 5)', $this->visitor->dispatch($filter->getExpressions()[0])); - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->notIn('foo', 'bar')); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->notIn('foo', 'bar')); $this->assertSame('-foo:"bar"', $this->visitor->dispatch($filter->getExpressions()[0])); } @@ -198,13 +198,13 @@ public function testNotIn(): void */ public function testLike(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->like('title', ['*foo', 'bar*'])); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->like('title', ['*foo', 'bar*'])); $this->assertSame('title:(*foo OR bar*)', $this->visitor->dispatch($filter->getExpressions()[0])); - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->like('title', 'foo*')); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->like('title', 'foo*')); $this->assertSame('title:foo*', $this->visitor->dispatch($filter->getExpressions()[0])); } @@ -215,8 +215,8 @@ public function testLike(): void */ public function testRegularExpression(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->regexp('title', '[0-9]{5}')); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->regexp('title', '[0-9]{5}')); $this->assertSame('title:/[0-9]{5}/', $this->visitor->dispatch($filter->getExpressions()[0])); } @@ -227,22 +227,34 @@ public function testRegularExpression(): void */ public function testMatch(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->match('title', 'foo*')); + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->match('title', 'foo*')); $this->assertSame('title:foo*', $this->visitor->dispatch($filter->getExpressions()[0])); } + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testEmpty(): void + { + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->empty('title')); + + $this->assertSame('(*:* NOT title:*)', $this->visitor->dispatch($filter->getExpressions()[0])); + } + /** * @throws \PHPUnit\Framework\ExpectationFailedException * @throws \Solarium\Exception\RuntimeException */ public function testCompositeAnd(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->andX( - FilterBuilder::expr()->eq('title', 'foo'), - FilterBuilder::expr()->in('description', ['bar', 'baz']) + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->andX( + QueryBuilder::expr()->eq('title', 'foo'), + QueryBuilder::expr()->in('description', ['bar', 'baz']) )); $this->assertSame('title:"foo" AND description:("bar" OR "baz")', $this->visitor->dispatch($filter->getExpressions()[0])); @@ -254,10 +266,10 @@ public function testCompositeAnd(): void */ public function testCompositeOr(): void { - $filter = FilterBuilder::create() - ->where(FilterBuilder::expr()->orX( - FilterBuilder::expr()->eq('title', 'foo'), - FilterBuilder::expr()->in('description', ['bar', 'baz']) + $filter = QueryBuilder::create() + ->where(QueryBuilder::expr()->orX( + QueryBuilder::expr()->eq('title', 'foo'), + QueryBuilder::expr()->in('description', ['bar', 'baz']) )); $this->assertSame('title:"foo" OR description:("bar" OR "baz")', $this->visitor->dispatch($filter->getExpressions()[0])); @@ -269,13 +281,13 @@ public function testCompositeOr(): void */ public function testVisitExpressions(): void { - $expression = FilterBuilder::expr()->eq('title', 'foo'); + $expression = QueryBuilder::expr()->eq('title', 'foo'); $this->assertSame('title:"foo"', $expression->visit($this->visitor)); - $compositeExpression = FilterBuilder::expr()->andX( - FilterBuilder::expr()->eq('title', 'foo'), - FilterBuilder::expr()->in('description', ['bar', 'baz']) + $compositeExpression = QueryBuilder::expr()->andX( + QueryBuilder::expr()->eq('title', 'foo'), + QueryBuilder::expr()->in('description', ['bar', 'baz']) ); $this->assertSame('title:"foo" AND description:("bar" OR "baz")', $compositeExpression->visit($this->visitor)); @@ -320,7 +332,7 @@ public function testUnknownExpression(): void */ public function testUnknownCompositeComparison(): void { - $comparison = new CompositeComparison('TO', [FilterBuilder::expr()->eq('title', 'foo')]); + $comparison = new CompositeComparison('TO', [QueryBuilder::expr()->eq('title', 'foo')]); $this->expectException(RuntimeException::class); diff --git a/tests/QueryType/Select/Query/AbstractQueryTest.php b/tests/QueryType/Select/Query/AbstractQueryTest.php index fd153330e..0f43e245f 100644 --- a/tests/QueryType/Select/Query/AbstractQueryTest.php +++ b/tests/QueryType/Select/Query/AbstractQueryTest.php @@ -3,11 +3,14 @@ namespace Solarium\Tests\QueryType\Select\Query; use PHPUnit\Framework\TestCase; +use Solarium\Builder\Select\QueryBuilder; +use Solarium\Builder\Select\QueryExpressionVisitor; use Solarium\Component\Analytics\Analytics; use Solarium\Component\MoreLikeThis; use Solarium\Core\Client\Client; use Solarium\Exception\InvalidArgumentException; use Solarium\Exception\OutOfBoundsException; +use Solarium\Exception\RuntimeException; use Solarium\QueryType\Select\Query\FilterQuery; use Solarium\QueryType\Select\Query\Query; @@ -749,4 +752,57 @@ public function testSetAndGetSplitOnWhitespace() $this->query->setSplitOnWhitespace(false); $this->assertFalse($this->query->getSplitOnWhitespace()); } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testSetQueryFromQueryBuilder(): void + { + $visitor = new QueryExpressionVisitor(); + $builder = QueryBuilder::create() + ->where(QueryBuilder::expr()->eq('foo', 'bar')); + + $this->query->setQueryFromQueryBuilder($builder); + + self::assertSame($visitor->dispatch($builder->getExpressions()[0]), $this->query->getQuery()); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testSetCompositeQueryFromQueryBuilder(): void + { + $expr = QueryBuilder::expr(); + $visitor = new QueryExpressionVisitor(); + + $builder = QueryBuilder::create() + ->where($expr->andX( + $expr->eq('foo', 'bar'), + $expr->eq('baz', 'qux') + )) + ; + + $this->query->setQueryFromQueryBuilder($builder); + + self::assertSame($visitor->dispatch($builder->getExpressions()[0]), $this->query->getQuery()); + } + + /** + * @throws \Solarium\Exception\RuntimeException + */ + public function testSetQueryFromQueryBuilderException(): void + { + $this->expectException(RuntimeException::class); + + $expr = QueryBuilder::expr(); + + $builder = QueryBuilder::create() + ->where($expr->eq('foo', 'bar')) + ->andWhere($expr->eq('baz', 'qux')) + ; + + $this->query->setQueryFromQueryBuilder($builder); + } } diff --git a/tests/QueryType/Select/Query/QueryTest.php b/tests/QueryType/Select/Query/QueryTest.php index 9356ddbb2..982a60697 100644 --- a/tests/QueryType/Select/Query/QueryTest.php +++ b/tests/QueryType/Select/Query/QueryTest.php @@ -2,6 +2,8 @@ namespace Solarium\Tests\QueryType\Select\Query; +use Solarium\Builder\Select\QueryBuilder; +use Solarium\Builder\Select\QueryExpressionVisitor; use Solarium\QueryType\Select\Query\Query; class QueryTest extends AbstractQueryTest @@ -10,4 +12,46 @@ public function setUp(): void { $this->query = new Query(); } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testSetFacetQueryFromQueryBuilder(): void + { + $visitor = new QueryExpressionVisitor(); + $builder = QueryBuilder::create() + ->where(QueryBuilder::expr()->eq('foo', 'bar')); + + $this->query->addFilterQueriesFromQueryBuilder($builder); + + $value = $visitor->dispatch($builder->getExpressions()[0]); + $filterQuery = $this->query->getFilterQuery(sha1($value)); + + self::assertSame($value, $filterQuery->getQuery()); + } + + /** + * @throws \PHPUnit\Framework\Exception + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \Solarium\Exception\RuntimeException + */ + public function testSetMultipleFilterQueriesFromQueryBuilder(): void + { + $visitor = new QueryExpressionVisitor(); + $expr = QueryBuilder::expr(); + + $builder = QueryBuilder::create() + ->where($expr->eq('foo', 'bar')) + ->andWhere($expr->eq('baz', 'qux')) + ; + + $this->query->addFilterQueriesFromQueryBuilder($builder); + + $first = $visitor->dispatch($builder->getExpressions()[0]); + $second = $visitor->dispatch($builder->getExpressions()[1]); + + self::assertArrayHasKey(sha1($first), $this->query->getFilterQueries()); + self::assertArrayHasKey(sha1($second), $this->query->getFilterQueries()); + } } From 2f66d196588c51ead0c07860bed4dd0d39435b81 Mon Sep 17 00:00:00 2001 From: wickedOne Date: Tue, 14 Jul 2020 11:29:36 +0200 Subject: [PATCH 4/5] fixed invalid class name --- tests/Builder/Select/QueryBuilderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Builder/Select/QueryBuilderTest.php b/tests/Builder/Select/QueryBuilderTest.php index b9602b2e4..5a7a8e9dc 100644 --- a/tests/Builder/Select/QueryBuilderTest.php +++ b/tests/Builder/Select/QueryBuilderTest.php @@ -19,7 +19,7 @@ * * @author wicliff */ -class SelectQueryBuilderTest extends TestCase +class QueryBuilderTest extends TestCase { /** * @var \Solarium\Builder\Select\QueryExpressionVisitor From 3d607d05389d22565ac7c81ada69da5b37e9502d Mon Sep 17 00:00:00 2001 From: wickedOne Date: Sun, 19 Jul 2020 17:57:26 +0200 Subject: [PATCH 5/5] style fixes --- src/Builder/Select/ExpressionBuilder.php | 7 +++++++ src/Builder/Select/QueryBuilder.php | 7 +++++++ src/Builder/Select/QueryExpressionVisitor.php | 11 +++++++++-- src/Component/QueryTrait.php | 6 +++--- src/QueryType/Select/Query/Query.php | 4 ++-- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/Builder/Select/ExpressionBuilder.php b/src/Builder/Select/ExpressionBuilder.php index d2f78edd6..f6cb84b65 100644 --- a/src/Builder/Select/ExpressionBuilder.php +++ b/src/Builder/Select/ExpressionBuilder.php @@ -2,6 +2,13 @@ declare(strict_types=1); +/* + * This file is part of the Solarium package. + * + * For the full copyright and license information, please view the COPYING + * file that was distributed with this source code. + */ + namespace Solarium\Builder\Select; use Solarium\Builder\Comparison; diff --git a/src/Builder/Select/QueryBuilder.php b/src/Builder/Select/QueryBuilder.php index c65aad3aa..3cf53915d 100644 --- a/src/Builder/Select/QueryBuilder.php +++ b/src/Builder/Select/QueryBuilder.php @@ -2,6 +2,13 @@ declare(strict_types=1); +/* + * This file is part of the Solarium package. + * + * For the full copyright and license information, please view the COPYING + * file that was distributed with this source code. + */ + namespace Solarium\Builder\Select; use Solarium\Builder\ExpressionInterface; diff --git a/src/Builder/Select/QueryExpressionVisitor.php b/src/Builder/Select/QueryExpressionVisitor.php index e3904b42d..8f5e2dcfb 100644 --- a/src/Builder/Select/QueryExpressionVisitor.php +++ b/src/Builder/Select/QueryExpressionVisitor.php @@ -2,6 +2,13 @@ declare(strict_types=1); +/* + * This file is part of the Solarium package. + * + * For the full copyright and license information, please view the COPYING + * file that was distributed with this source code. + */ + namespace Solarium\Builder\Select; use Solarium\Builder\AbstractExpressionVisitor; @@ -104,7 +111,7 @@ public function walkExpression(ExpressionInterface $expression) case Comparison::EMPTY: return sprintf('(*:* NOT %s:*)', $field); default: - throw new RuntimeException('Unknown comparison operator: '.$expression->getOperator()); + throw new RuntimeException(sprintf('Unknown comparison operator: %s', $expression->getOperator())); } } @@ -135,7 +142,7 @@ public function walkCompositeExpression(ExpressionInterface $expr) case CompositeComparison::TYPE_OR: return implode(' OR ', $comparisons); default: - throw new RuntimeException('Unknown composite '.$expr->getType()); + throw new RuntimeException(sprintf('Unknown composite %s', $expr->getType())); } } diff --git a/src/Component/QueryTrait.php b/src/Component/QueryTrait.php index 6f38d6ad8..1be135e02 100644 --- a/src/Component/QueryTrait.php +++ b/src/Component/QueryTrait.php @@ -41,13 +41,13 @@ public function setQuery(string $query, array $bind = null): QueryInterface /** * @param \Solarium\Builder\Select\QueryBuilder $builder * - * @return \Solarium\Component\QueryInterface - * * @throws \Solarium\Exception\RuntimeException + * + * @return \Solarium\Component\QueryInterface */ public function setQueryFromQueryBuilder(QueryBuilder $builder): QueryInterface { - if (1 !== count($builder->getExpressions())) { + if (1 !== \count($builder->getExpressions())) { throw new RuntimeException('The QueryBuilder can only contain one expression when setting the query. Use ExpressionBuilder::andX or ExpressionBuilder::orX to combine expressions.'); } diff --git a/src/QueryType/Select/Query/Query.php b/src/QueryType/Select/Query/Query.php index fb1a154df..589ab0436 100644 --- a/src/QueryType/Select/Query/Query.php +++ b/src/QueryType/Select/Query/Query.php @@ -598,9 +598,9 @@ public function addFilterQueries(array $filterQueries): self * * @param \Solarium\Builder\Select\QueryBuilder $builder * - * @return $this - * * @throws \Solarium\Exception\RuntimeException + * + * @return $this */ public function addFilterQueriesFromQueryBuilder(QueryBuilder $builder): self {