Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/Bravo3/Orm/Drivers/DriverInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,23 @@ public function removeSortedIndex($key, $value);
*/
public function getSortedIndex($key, $reverse = false, $start = null, $stop = null);

/**
* Get a range values in a sorted index filtered by member values values
*
* If $start/$stop are null then they are assumed to be the start/end of the entire set
*
* @param string $key
* @param bool $reverse
* @param int $min_score
* @param int $max_score
* @param int $start
* @param int $stop
*
* @return string[]
*/
public function getSortedFilteredIndex($key, $reverse = false, $min_score = '-inf', $max_score = '+inf',
$start = null, $stop = null);

/**
* Get the size of a sorted index, without any filters applied
*
Expand Down
46 changes: 46 additions & 0 deletions src/Bravo3/Orm/Drivers/Redis/RedisDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,52 @@ public function getSortedIndex($key, $reverse = false, $start = null, $stop = nu
}
}

/**
* Get a range values in a sorted index filtered by member values values
*
* If $start/$stop are null then they are assumed to be the start/end of the entire set
*
* @param string $key
* @param bool $reverse
* @param int $min_score
* @param int $max_score
* @param int $start
* @param int $stop
*
* @return string[]
*/
public function getSortedFilteredIndex($key, $reverse = false, $min_score = '-inf', $max_score = '+inf',
$start = null, $stop = null)
{
if ($reverse) {
return $this->clientCmd(
'zrevrangebyscore',
[
$key,
$min_score === null ? '-inf' : $min_score,
$max_score === null ? '+inf' : $max_score,
'limit' => [
$start === null ? 0 : $start,
$stop === null ? -1 : $stop,
]
]
);
} else {
return $this->clientCmd(
'zrangebyscore',
[
$key,
$min_score === null ? '-inf' : $min_score,
$max_score === null ? '+inf' : $max_score,
'limit' => [
$start === null ? 0 : $start,
$stop === null ? -1 : $stop,
]
]
);
}
}

/**
* Create a debug log
*
Expand Down
87 changes: 87 additions & 0 deletions src/Bravo3/Orm/Query/ScoreFilterQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php
namespace Bravo3\Orm\Query;

use Bravo3\Orm\Enum\Direction;

class ScoreFilterQuery extends SortedQuery implements QueryInterface
{
/**
* @var int
*/
protected $min_score;

/**
* @var int
*/
protected $max_score;

/**
* @param object $entity Entity to retrieve relationships from
* @param string $relationship_name Name of the relationship on provided entity
* @param string $sort_by Sort by column
* @param Direction $direction Assumes ascending if omitted
* @param int $min_score Starting member values to filter the sorted data-set
* @param int $max_score Maximum value to filter out the members within the sorted data-set
* @param int $start Start index (inclusive), null/0 for beginning of set
* @param int $end Stop index (inclusive), null/-1 for end of set, -2 for penultimate record
*/
public function __construct(
$entity,
$relationship_name,
$sort_by,
Direction $direction = null,
$min_score,
$max_score,
$start = null,
$end = null
) {
parent::__construct($entity, $relationship_name, $sort_by, $direction, $start, $end);
$this->min_score = $min_score;
$this->max_score = $max_score;
}

/**
* Get starting member values to filter the sorted data-set
*
* @return int
*/
public function getMinScore()
{
return $this->min_score;
}

/**
* Set starting member values to filter the sorted data-set
*
* @param int $min_score
* @return $this
*/
public function setMinScore($min_score)
{
$this->min_score = $min_score;
return $this;
}

/**
* Get maximum value to filter out the members within the sorted data-set
*
* @return int
*/
public function getMaxScore()
{
return $this->max_score;
}

/**
* Set maximum value to filter out the members within the sorted data-set
*
* @param int $max_score
* @return $this
*/
public function setMaxScore($max_score)
{
$this->max_score = $max_score;
return $this;
}

}
17 changes: 17 additions & 0 deletions src/Bravo3/Orm/Services/EntityManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Bravo3\Orm\Proxy\OrmProxyInterface;
use Bravo3\Orm\Query\IndexedQuery;
use Bravo3\Orm\Query\QueryResult;
use Bravo3\Orm\Query\ScoreFilterQuery;
use Bravo3\Orm\Query\SortedQuery;
use Bravo3\Orm\Serialisers\JsonSerialiser;
use Bravo3\Orm\Serialisers\SerialiserMap;
Expand Down Expand Up @@ -403,6 +404,22 @@ public function sortedQuery(SortedQuery $query, $check_full_set_size = false, $u
return $this->getQueryManager()->sortedQuery($query, $check_full_set_size, $use_cache);
}

/**
* Get all foreign entities filtered by a given score range
*
* If you have applied a limit to the query but need to know the full size of the unfiltered set, you must set
* $check_full_set_size to true to gather this information at the expense of a second database query.
*
* @param ScoreFilterQuery $query
* @param bool $check_full_set_size
* @param bool $use_cache
* @return QueryResult
*/
public function scoreFilterQuery(ScoreFilterQuery $query, $check_full_set_size = false, $use_cache = true)
{
return $this->getQueryManager()->scoreFilterQuery($query, $check_full_set_size, $use_cache);
}

/**
* Will force a database update of an entity
*
Expand Down
53 changes: 53 additions & 0 deletions src/Bravo3/Orm/Services/QueryManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Bravo3\Orm\Mappers\Metadata\Entity;
use Bravo3\Orm\Query\IndexedQuery;
use Bravo3\Orm\Query\QueryResult;
use Bravo3\Orm\Query\ScoreFilterQuery;
use Bravo3\Orm\Query\SortedQuery;
use Bravo3\Orm\Services\Io\Reader;

Expand Down Expand Up @@ -109,6 +110,58 @@ public function sortedQuery(SortedQuery $query, $check_full_set_size = false, $u
return new QueryResult($this->entity_manager, $query, $results, $full_size, $use_cache);
}

/**
* Get all foreign entities filtered by the range provided
*
* If you have applied a limit to the query but need to know the full size of the unfiltered set, you must set
* $check_full_set_size to true to gather this information at the expense of a second database query.
*
* @param ScoreFilterQuery $query
* @param bool $check_full_set_size
* @param bool $use_cache
* @return QueryResult
*/
public function scoreFilterQuery(ScoreFilterQuery $query, $check_full_set_size = false, $use_cache = true)
{
$metadata = $this->getMapper()->getEntityMetadata($query->getClassName());

if ($query->getRelationshipName()) {
// Entity relationship based query
$reader = new Reader($metadata, $query->getEntity());
$relationship = $metadata->getRelationshipByName($query->getRelationshipName());

if (!$relationship) {
throw new InvalidArgumentException('Relationship "'.$query->getRelationshipName().'" does not exist');
}

// Important, else the QueryResult class will try to hydrate the wrong entity
$query->setClassName($relationship->getTarget());
$key = $this->getKeyScheme()->getSortIndexKey($relationship, $query->getSortBy(), $reader->getId());
} else {
// Table based query
$key = $this->getKeyScheme()->getTableSortKey($metadata->getTableName(), $query->getSortBy());
}

$results = $this->getDriver()->getSortedFilteredIndex(
$key,
$query->getDirection() == Direction::DESC(),
$query->getMinScore(),
$query->getMaxScore(),
$query->getStart(),
$query->getEnd()
);

if (!$query->getStart() && !$query->getEnd()) {
$full_size = count($results);
} elseif ($check_full_set_size) {
$full_size = $this->getDriver()->getSortedIndexSize($key);
} else {
$full_size = null;
}

return new QueryResult($this->entity_manager, $query, $results, $full_size, $use_cache);
}

/**
* Persist entity indices
*
Expand Down
47 changes: 47 additions & 0 deletions tests/Bravo3/Orm/Tests/Indices/ScoreFilterQueryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
namespace Bravo3\Orm\Tests\Indices;

use Bravo3\Orm\Enum\Direction;
use Bravo3\Orm\Query\ScoreFilterQuery;
use Bravo3\Orm\Tests\AbstractOrmTest;
use Bravo3\Orm\Tests\Entities\OneToMany\Article;
use Bravo3\Orm\Tests\Entities\OneToMany\Category;

class ScoreFilterQueryTest extends AbstractOrmTest
{
public function testScoreFiltering()
{
$em = $this->getEntityManager();

$category = new Category();
$category->setId(600);
$em->persist($category);

for ($i = 0; $i < 15; $i++) {
$article = new Article();
$article->setId(601 + $i);
$article->setTitle('Art '.(601 + $i));
$time = new \DateTime();
$time->modify('+'.($i + 1).' minutes');
$article->setSortDate($time);
$article->setCanonicalCategory($category);
$em->persist($article);
}

$em->flush();

/** @var Article $article */
// Date sorting -
$time = new \DateTime();
$time->modify('+5 minutes');
$start_time = $time->getTimestamp();

$time = new \DateTime();
$time->modify('+10 minutes');
$end_time = $time->getTimestamp();

// Check if you only get partial result set filtered by the date range
$results = $em->scoreFilterQuery(new ScoreFilterQuery($category, 'articles', 'sort_date', Direction::ASC(), $start_time, $end_time));
$this->assertCount(6, $results);
}
}