diff --git a/src/Driver/Cassandra/Cassandra.php b/src/Driver/Cassandra/Cassandra.php index d6dabc5..c6742a6 100644 --- a/src/Driver/Cassandra/Cassandra.php +++ b/src/Driver/Cassandra/Cassandra.php @@ -221,7 +221,7 @@ public function query(Query $query): QueryResult $rawQueryResult = $this->rawQuery($queryString); - $result = new QueryResult((bool)$rawQueryResult); + $result = new QueryResult(); $result->setQueryString($queryString); foreach ($rawQueryResult as $resultRow) { /** @var class-string $modelClass */ diff --git a/src/Driver/Features/SearchCountableInterface.php b/src/Driver/Features/SearchCountableInterface.php new file mode 100644 index 0000000..81b618d --- /dev/null +++ b/src/Driver/Features/SearchCountableInterface.php @@ -0,0 +1,20 @@ +connect(); $result = mysqli_query($this->connection, $query); @@ -239,7 +239,7 @@ public function query(Query $query): QueryResult $rawQueryResult = $this->rawQuery($queryString); - $result = new QueryResult((bool)$rawQueryResult); + $result = new QueryResult(); $result->setQueryString($queryString); if ($query instanceof UpdateQuery || $query instanceof DeleteQuery) { $result->setAffectedRows(mysqli_affected_rows($this->connection)); diff --git a/src/Driver/OpenSearch/OpenSearch.php b/src/Driver/OpenSearch/OpenSearch.php index 30bebef..12e0948 100644 --- a/src/Driver/OpenSearch/OpenSearch.php +++ b/src/Driver/OpenSearch/OpenSearch.php @@ -5,6 +5,7 @@ use Aternos\Model\Driver\Driver; use Aternos\Model\Driver\Features\CRUDAbleInterface; use Aternos\Model\Driver\Features\SearchableInterface; +use Aternos\Model\Driver\Features\SearchCountableInterface; use Aternos\Model\Driver\OpenSearch\Authentication\OpenSearchAuthenticationInterface; use Aternos\Model\Driver\OpenSearch\Exception\HttpErrorResponseException; use Aternos\Model\Driver\OpenSearch\Exception\HttpTransportException; @@ -28,7 +29,7 @@ * * @package Aternos\Model\Driver\Search */ -class OpenSearch extends Driver implements CRUDAbleInterface, SearchableInterface +class OpenSearch extends Driver implements CRUDAbleInterface, SearchableInterface, SearchCountableInterface { public const ID = "opensearch"; protected string $id = self::ID; @@ -219,7 +220,7 @@ public function search(Search $search): SearchResult throw new SerializeException("Received invalid search response from OpenSearch"); } - $result = new SearchResult(true); + $result = new SearchResult(); if (isset($response->took) && is_int($response->took)) { $result->setSearchTime($response->took); } @@ -242,6 +243,30 @@ public function search(Search $search): SearchResult return $result; } + /** + * @param Search $search + * @return int + * @throws HttpErrorResponseException If the response status code is not 2xx + * @throws HttpTransportException If an error happens while the http client processes the request + * @throws SerializeException If an error happens during (de-)serialization + */ + public function searchCount(Search $search): int + { + /** @var class-string $modelClassName */ + $modelClassName = $search->getModelClassName(); + + $response = $this->request( + "GET", + $this->buildUrl($modelClassName::getName(), "_count"), + $search->getSearchQuery() + ); + if (!isset($response->count) || !is_int($response->count)) { + throw new SerializeException("Received invalid count response from OpenSearch"); + } + + return $response->count; + } + /** * @param stdClass $response * @param class-string $modelClass $modelClass diff --git a/src/Driver/Test/TestTable.php b/src/Driver/Test/TestTable.php index fc190c9..32cb3e6 100644 --- a/src/Driver/Test/TestTable.php +++ b/src/Driver/Test/TestTable.php @@ -66,7 +66,7 @@ public function query(Query $query): QueryResult $entries = $this->groupAndAggregateEntries($clonedEntries, $query->getGroup(), $query->getFields()); } - $queryResult = new QueryResult(true); + $queryResult = new QueryResult(); foreach ($entries as $entry) { if ($query instanceof SelectQuery) { /** @var class-string $modelClass */ diff --git a/src/GenericModel.php b/src/GenericModel.php index 9f5671f..9197b36 100644 --- a/src/GenericModel.php +++ b/src/GenericModel.php @@ -6,34 +6,32 @@ use Aternos\Model\Driver\DriverRegistry; use Aternos\Model\Driver\DriverRegistryInterface; use Aternos\Model\Driver\Test\TestDriver; -use Aternos\Model\Driver\Features\{CacheableInterface, - DeletableInterface, - DeleteQueryableInterface, - GettableInterface, - QueryableInterface, - SavableInterface, - SearchableInterface, - SelectQueryableInterface, - UpdateQueryableInterface -}; +use Aternos\Model\Driver\Features\CacheableInterface; +use Aternos\Model\Driver\Features\DeletableInterface; +use Aternos\Model\Driver\Features\DeleteQueryableInterface; +use Aternos\Model\Driver\Features\GettableInterface; +use Aternos\Model\Driver\Features\QueryableInterface; +use Aternos\Model\Driver\Features\SavableInterface; +use Aternos\Model\Driver\Features\SearchableInterface; +use Aternos\Model\Driver\Features\SearchCountableInterface; +use Aternos\Model\Driver\Features\SelectQueryableInterface; +use Aternos\Model\Driver\Features\UpdateQueryableInterface; use Aternos\Model\Driver\Mysqli\Mysqli; use Aternos\Model\Driver\Redis\Redis; -use Aternos\Model\Query\{CountField, - DeleteQuery, - GroupField, - Limit, - Query, - QueryResult, - QueryResultCollection, - SelectQuery, - UpdateQuery, - WhereCondition, - WhereGroup -}; +use Aternos\Model\Query\CountField; +use Aternos\Model\Query\DeleteQuery; +use Aternos\Model\Query\GroupField; +use Aternos\Model\Query\Limit; +use Aternos\Model\Query\Query; +use Aternos\Model\Query\QueryResult; +use Aternos\Model\Query\QueryResultCollection; +use Aternos\Model\Query\SelectQuery; +use Aternos\Model\Query\UpdateQuery; +use Aternos\Model\Query\WhereCondition; +use Aternos\Model\Query\WhereGroup; use Aternos\Model\Search\Search; use Aternos\Model\Search\SearchResult; use BadMethodCallException; -use Exception; /** * Class GenericModel @@ -241,6 +239,22 @@ protected static function getSearchableDrivers(): array return $drivers; } + /** + * Get all search-countable drivers from static::$drivers + * + * @return class-string[] + */ + protected static function getSearchCountableDrivers(): array + { + $drivers = []; + foreach (static::$drivers as $driver) { + if (static::getDriverRegistry()->isDriverInstanceOf($driver, SearchCountableInterface::class)) { + $drivers[] = $driver; + } + } + return $drivers; + } + /** * @param array $rawData * @return bool @@ -463,26 +477,35 @@ public static function query(Query $query): QueryResult $result = false; $results = []; + $lastException = null; foreach ($drivers as $queryableDriver) { /** @var QueryableInterface $driver */ $driver = static::getDriverRegistry()->getDriver($queryableDriver); - $result = $driver->query($query); + try { + $result = $driver->query($query); - if ($result->wasSuccessful() && $query instanceof SelectQuery) { - break; - } + if ($query instanceof SelectQuery) { + $lastException = null; + break; + } - if (!$query instanceof SelectQuery) { $results[] = $result; + } catch (ModelException $e) { + $lastException = $e; } } + if ($lastException !== null) { + /** @var ModelException|null $lastException */ + throw $lastException; + } + if ($result === false) { throw new BadMethodCallException("You can't query the model if no queryable driver is available."); } if (static::$registry) { - if ($query instanceof SelectQuery && $result->wasSuccessful() && $query->shouldSaveResultsToRegistry()) { + if ($query instanceof SelectQuery && $query->shouldSaveResultsToRegistry()) { foreach ($result as $model) { if ($model->getId() === null) { continue; @@ -495,7 +518,7 @@ public static function query(Query $query): QueryResult if ($query instanceof SelectQuery || count($results) === 1) { return $result; } else { - return new QueryResultCollection(true, $results); + return new QueryResultCollection($results); } } @@ -513,6 +536,7 @@ public static function query(Query $query): QueryResult * @param array|GroupField[]|string[]|null $group * @param bool $saveResultsToRegistry Whether results of this query should be saved in the model registry. * @return QueryResult|static[] + * @throws ModelException * @noinspection PhpDocSignatureInspection */ public static function select(null|WhereCondition|array|WhereGroup $where = null, @@ -527,14 +551,12 @@ public static function select(null|WhereCondition|array|WhereGroup $where = null /** * @param WhereCondition|array|WhereGroup|null $where - * @return int|null + * @return int + * @throws ModelException */ - public static function count(null|WhereCondition|array|WhereGroup $where = null): ?int + public static function count(null|WhereCondition|array|WhereGroup $where = null): int { $result = static::select(where: $where, fields: [new CountField()]); - if (!$result->wasSuccessful()) { - return null; - } if (count($result) === 0) { return 0; } @@ -666,28 +688,36 @@ public function set(array $data): QueryResult * * @param Search $search * @return SearchResult + * @throws ModelException */ public static function search(Search $search): SearchResult { $search->setModelClassName(static::class); $result = false; + $lastException = null; foreach (static::getSearchableDrivers() as $searchableDriver) { /** @var SearchableInterface $driver */ $driver = static::getDriverRegistry()->getDriver($searchableDriver); - $result = $driver->search($search); - - if ($result->wasSuccessful()) { - break; + try { + $result = $driver->search($search); + $lastException = null; + } catch (ModelException $e) { + $lastException = $e; } } + if ($lastException !== null) { + /** @var ModelException|null $lastException */ + throw $lastException; + } + if ($result === false) { throw new BadMethodCallException("You can't search the model if no searchable driver is available."); } if (static::$registry) { - if ($result->wasSuccessful() && count($result) > 0) { + if (count($result) > 0) { foreach ($result as $model) { if ($model->getId() === null) { continue; @@ -699,4 +729,39 @@ public static function search(Search $search): SearchResult return $result; } + + /** + * Count how many instances of the model match this search + * + * @param Search $search + * @return int + */ + public static function searchCount(Search $search): int + { + $search->setModelClassName(static::class); + + $result = false; + $lastException = null; + foreach (static::getSearchCountableDrivers() as $searchableDriver) { + /** @var SearchCountableInterface $driver */ + $driver = static::getDriverRegistry()->getDriver($searchableDriver); + try { + $result = $driver->searchCount($search); + $lastException = null; + } catch (ModelException $e) { + $lastException = $e; + } + } + + if ($lastException !== null) { + /** @var ModelException|null $lastException */ + throw $lastException; + } + + if ($result === false) { + throw new BadMethodCallException("You can't search the model if no search-countable driver is available."); + } + + return $result; + } } diff --git a/src/ModelCollection.php b/src/ModelCollection.php index 2e1475f..cea2ef8 100644 --- a/src/ModelCollection.php +++ b/src/ModelCollection.php @@ -14,9 +14,19 @@ */ class ModelCollection implements Iterator, Countable, ArrayAccess { - protected array $models = []; + protected array $models; protected int $iterator = 0; + /** + * ModelCollection constructor. + * + * @param TModel[] $models + */ + public function __construct(array $models = []) + { + $this->models = $models; + } + /** * Add a model * @@ -131,4 +141,4 @@ public function offsetUnset(mixed $offset): void unset($this->models[$offset]); } -} \ No newline at end of file +} diff --git a/src/ModelCollectionResult.php b/src/ModelCollectionResult.php deleted file mode 100644 index 50ec7df..0000000 --- a/src/ModelCollectionResult.php +++ /dev/null @@ -1,44 +0,0 @@ - - * @package Aternos\Model - */ -class ModelCollectionResult extends ModelCollection -{ - /** - * Success state of the result - * - * @var bool - */ - protected bool $success; - - /** - * ModelCollectionResult constructor. - * - * @param bool $success - * @param TModel[] $result - */ - public function __construct(bool $success, array $result = []) - { - $this->success = $success; - if (is_array($result)) { - $this->models = $result; - } - } - - /** - * Check if the query was successful - * - * @return bool - */ - public function wasSuccessful(): bool - { - return (bool)$this->success; - } -} \ No newline at end of file diff --git a/src/Query/QueryResult.php b/src/Query/QueryResult.php index 7bd90bc..d85978c 100644 --- a/src/Query/QueryResult.php +++ b/src/Query/QueryResult.php @@ -2,17 +2,17 @@ namespace Aternos\Model\Query; -use Aternos\Model\ModelCollectionResult; +use Aternos\Model\ModelCollection; use Aternos\Model\ModelInterface; /** * Class QueryResult * * @template TModel of ModelInterface - * @extends ModelCollectionResult + * @extends ModelCollection * @package Aternos\Model\Query */ -class QueryResult extends ModelCollectionResult +class QueryResult extends ModelCollection { /** * Raw query string that was executed @@ -33,13 +33,12 @@ class QueryResult extends ModelCollectionResult /** * QueryResult constructor. * - * @param bool $success - * @param TModel[] $result + * @param TModel[] $models * @param string|null $queryString */ - public function __construct(bool $success, array $result = [], ?string $queryString = null) + public function __construct(array $models = [], ?string $queryString = null) { - parent::__construct($success, $result); + parent::__construct($models); $this->queryString = $queryString; } @@ -84,4 +83,4 @@ public function setAffectedRows(?int $affectedRows): static } return $this; } -} \ No newline at end of file +} diff --git a/src/Query/QueryResultCollection.php b/src/Query/QueryResultCollection.php index dad10fa..d9cd8c9 100644 --- a/src/Query/QueryResultCollection.php +++ b/src/Query/QueryResultCollection.php @@ -18,25 +18,11 @@ class QueryResultCollection extends QueryResult /** * QueryResultCollection constructor. - * @param bool $success - * @param array|QueryResult[] $result + * @param array|QueryResult[] $models * @param string|null $queryString */ - public function __construct(bool $success, $result = [], ?string $queryString = null) + public function __construct($models = [], ?string $queryString = null) { - parent::__construct($success, $result, $queryString); + parent::__construct($models, $queryString); } - - /** - * @return bool - */ - public function wasSuccessful(): bool - { - foreach ($this->models as $result) { - if (!$result->wasSuccessful()) { - return false; - } - } - return true; - } -} \ No newline at end of file +} diff --git a/src/Search/SearchResult.php b/src/Search/SearchResult.php index f6d9b7e..3e17084 100644 --- a/src/Search/SearchResult.php +++ b/src/Search/SearchResult.php @@ -2,16 +2,16 @@ namespace Aternos\Model\Search; -use Aternos\Model\ModelCollectionResult; +use Aternos\Model\ModelCollection; use Aternos\Model\ModelInterface; /** * Class SearchResult * @template TModel of ModelInterface - * @extends ModelCollectionResult + * @extends ModelCollection * @package Aternos\Model\Search */ -class SearchResult extends ModelCollectionResult +class SearchResult extends ModelCollection { protected ?int $searchTime = null; protected ?int $totalCount = null; diff --git a/test/tests/TestDriverTest.php b/test/tests/TestDriverTest.php index fff588c..d78c917 100644 --- a/test/tests/TestDriverTest.php +++ b/test/tests/TestDriverTest.php @@ -347,7 +347,6 @@ public function testSelectLimitOffset(): void public function testSelectFields(): void { $models = TestModel::select(fields: ["id", "number"]); - $this->assertTrue($models->wasSuccessful()); $this->assertCount(10, $models); $this->assertEquals("1B", $models[1]->id); $this->assertNull($models[1]->text); @@ -366,7 +365,6 @@ public function testSelectCount(): void public function testSelectSum(): void { $result = TestModel::select(fields: [new SumField("number")]); - $this->assertTrue($result->wasSuccessful()); $this->assertEquals(45, $result[0]->number); // test if data wasn't changed @@ -383,7 +381,6 @@ public function testSelectAverage(): void (new SelectField("number")) ->setAlias("average") ->setFunction(SelectField::AVERAGE)]); - $this->assertTrue($result->wasSuccessful()); $this->assertEquals(4.5, $result[0]->getField("average")); } @@ -396,7 +393,6 @@ public function testSelectGroup(): void $model->save(); $models = TestModel::select(group: ["number"]); - $this->assertTrue($models->wasSuccessful()); $this->assertCount(10, $models); } @@ -409,7 +405,6 @@ public function testSelectGroupCount(): void $model->save(); $models = TestModel::select(fields: [new CountField(), new SelectField("number")], group: ["number"]); - $this->assertTrue($models->wasSuccessful()); $this->assertCount(10, $models); foreach ($models as $model) { if ($model->number === 1) { @@ -433,7 +428,6 @@ public function testSelectGroupSum(): void new SelectField("number")], group: ["number"] ); - $this->assertTrue($models->wasSuccessful()); $this->assertCount(10, $models); foreach ($models as $model) { if ($model->number === 5) { @@ -460,7 +454,6 @@ public function testSelectGroupAverage(): void new SelectField("text"), ], group: ["text"]); - $this->assertTrue($models->wasSuccessful()); $this->assertCount(10, $models); foreach ($models as $model) { if ($model->text === "A") { @@ -485,7 +478,6 @@ public function testSelectGroupMin(): void new SelectField("text"), ], group: ["text"]); - $this->assertTrue($models->wasSuccessful()); $this->assertCount(10, $models); foreach ($models as $model) { if ($model->text === "A") { @@ -510,7 +502,6 @@ public function testSelectGroupMax(): void new SelectField("text"), ], group: ["text"]); - $this->assertTrue($models->wasSuccessful()); $this->assertCount(10, $models); foreach ($models as $model) { if ($model->text === "A") { @@ -534,7 +525,6 @@ public function testUpdate(): void $model->save(); $result = TestModel::update(["text" => "C"], ["text" => "B"]); - $this->assertTrue($result->wasSuccessful()); $this->assertEquals(2, $result->getAffectedRows()); $model = TestModel::get("1B"); @@ -555,7 +545,6 @@ public function testDeleteQuery(): void $model->save(); $result = TestModel::query(new DeleteQuery(["text" => "B"])); - $this->assertTrue($result->wasSuccessful()); $this->assertEquals(2, $result->getAffectedRows()); $model = TestModel::get("1B");