diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/RangeExpression.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/RangeExpression.php index af6951a2ab..17cd9a5356 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/RangeExpression.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/RangeExpression.php @@ -20,22 +20,22 @@ class RangeExpression extends Node public static function lessThan(Key $key, $bound) { - return new self($key, $bound, false); + return new self($key, null, false, $bound, false); } public static function lessThanOrEqual(Key $key, $bound) { - return new self($key, $bound, true); + return new self($key, null, false, $bound, true); } public static function greaterThan(Key $key, $bound) { - return new self($key, null, false, $bound, false); + return new self($key, $bound, false); } public static function greaterThanOrEqual(Key $key, $bound) { - return new self($key, null, false, $bound, true); + return new self($key, $bound, true); } public function __construct(Key $key, $lb, $li = false, $hb = null, $hi = false) @@ -57,17 +57,17 @@ class RangeExpression extends Node if ($this->lower_bound !== null) { $this->assertValueCompatible($this->lower_bound, $context); if ($this->lower_inclusive) { - $params['lte'] = $this->lower_bound; + $params['gte'] = $this->lower_bound; } else { - $params['lt'] = $this->lower_bound; + $params['gt'] = $this->lower_bound; } } if ($this->higher_bound !== null) { $this->assertValueCompatible($this->higher_bound, $context); if ($this->higher_inclusive) { - $params['gte'] = $this->higher_bound; + $params['lte'] = $this->higher_bound; } else { - $params['gt'] = $this->higher_bound; + $params['lt'] = $this->higher_bound; } } @@ -98,16 +98,16 @@ class RangeExpression extends Node $string = ''; if ($this->lower_bound !== null) { if ($this->lower_inclusive) { - $string .= sprintf(' lte="%s"', $this->lower_bound); + $string .= sprintf(' gte="%s"', $this->lower_bound); } else { - $string .= sprintf(' lt="%s"', $this->lower_bound); + $string .= sprintf(' gt="%s"', $this->lower_bound); } } if ($this->higher_bound !== null) { if ($this->higher_inclusive) { - $string .= sprintf(' gte="%s"', $this->higher_bound); + $string .= sprintf(' lte="%s"', $this->higher_bound); } else { - $string .= sprintf(' gt="%s"', $this->higher_bound); + $string .= sprintf(' lt="%s"', $this->higher_bound); } } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryHelper.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryHelper.php index 9d652f86c7..a8c80d9f99 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryHelper.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryHelper.php @@ -2,6 +2,7 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\Search; +use Alchemy\Phrasea\SearchEngine\Elastic\Mapping; use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Field; class QueryHelper @@ -107,4 +108,42 @@ class QueryHelper return $query; } } + + public static function getRangeFromDateString($string) + { + $formats = ['Y/m/d', 'Y/m', 'Y']; + $deltas = ['+1 day', '+1 month', '+1 year']; + $to = null; + while ($format = array_pop($formats)) { + $delta = array_pop($deltas); + $from = date_create_from_format($format, $string); + if ($from !== false) { + // Rewind to start of range + $month = 1; + $day = 1; + switch ($format) { + case 'Y/m/d': + $day = (int) $from->format('d'); + case 'Y/m': + $month = (int) $from->format('m'); + case 'Y': + $year = (int) $from->format('Y'); + } + date_date_set($from, $year, $month, $day); + date_time_set($from, 0, 0, 0); + // Create end of the the range + $to = date_modify(clone $from, $delta); + break; + } + } + + if (!$from || !$to) { + throw new \InvalidArgumentException(sprintf('Invalid date "%s".', $string)); + } + + return [ + 'from' => $from->format(Mapping::DATE_FORMAT_CAPTION_PHP), + 'to' => $to->format(Mapping::DATE_FORMAT_CAPTION_PHP) + ]; + } } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php index 17955e1589..0c0edaec9b 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php @@ -4,6 +4,7 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\Search; use Alchemy\Phrasea\SearchEngine\Elastic\AST; use Alchemy\Phrasea\SearchEngine\Elastic\Exception\Exception; +use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryHelper; use Hoa\Compiler\Llk\TreeNode; use Hoa\Visitor\Element; use Hoa\Visitor\Visit; @@ -176,10 +177,29 @@ class QueryVisitor implements Visit private function visitEqualNode(TreeNode $node) { return $this->handleBinaryExpression($node, function($left, $right) { + if ($this->isDateKey($left)) { + try { + // Try to create a range for incomplete dates + $range = QueryHelper::getRangeFromDateString($right); + return new AST\KeyValue\RangeExpression( + $left, + $range['from'], true, + $range['to'], false + ); + } catch (\InvalidArgumentException $e) { + // Fall back to equal expression + } + } + return new AST\KeyValue\EqualExpression($left, $right); }); } + private function isDateKey(AST\KeyValue\Key $key) + { + return $key instanceof AST\KeyValue\TimestampKey; + } + private function visitTerm(Element $element) { $words = array();