Skip to content

Update according changes in DB #409

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Apr 16, 2025
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a836ba4
First attempt
Tigrov Apr 30, 2024
19efa92
Merge branch 'refs/heads/master' into refactor-data-readers
Tigrov Apr 12, 2025
d01931c
Improve
Tigrov Apr 13, 2025
69962cf
Apply fixes from StyleCI
StyleCIBot Apr 13, 2025
7ba3e1a
Apply Rector changes (CI)
Tigrov Apr 13, 2025
ca7127e
Fix psalm
Tigrov Apr 13, 2025
0176983
Install db on psalm
Tigrov Apr 13, 2025
5d70092
Rerun tests
Tigrov Apr 13, 2025
ac87294
Rerun tests
Tigrov Apr 13, 2025
cf9be89
Rerun tests
Tigrov Apr 13, 2025
5a82870
Rerun tests
Tigrov Apr 13, 2025
a78b555
Fix tests
Tigrov Apr 13, 2025
e1ee83f
Fix tests
Tigrov Apr 13, 2025
0026812
Update tests
Tigrov Apr 13, 2025
5275023
Update tests
Tigrov Apr 13, 2025
0cafcf1
Rerun tests
Tigrov Apr 13, 2025
0d733ea
Rerun tests
Tigrov Apr 13, 2025
7639339
Update to changes in DB
Tigrov Apr 14, 2025
949b0a9
Rerun tests
Tigrov Apr 14, 2025
948bf72
Rename `subpackage` to `required-package` in workflows
Tigrov Apr 14, 2025
83fc70c
Update
Tigrov Apr 14, 2025
7d768df
Rerun tests
Tigrov Apr 14, 2025
7a5cbae
Rerun tests
Tigrov Apr 14, 2025
6b3d5dc
Rerun tests
Tigrov Apr 14, 2025
099130c
Rerun tests
Tigrov Apr 14, 2025
fb87739
Rerun tests
Tigrov Apr 14, 2025
abf4529
Rerun tests
Tigrov Apr 14, 2025
b0cf036
Rerun tests
Tigrov Apr 14, 2025
a309f5a
Change `@install-subpackage` to `@master`
Tigrov Apr 15, 2025
a0fc471
Merge branch 'master' into refactor-data-readers
Tigrov Apr 15, 2025
ebda6e4
Merge branch 'master' into refactor-data-readers
Tigrov Apr 15, 2025
16d8a2b
Remove `$batchSize` parameter from `ActiveQuery::each()`
Tigrov Apr 16, 2025
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
146 changes: 61 additions & 85 deletions src/ActiveQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Helper\DbArrayHelper;
use Yiisoft\Db\Query\BatchQueryResultInterface;
use Yiisoft\Db\Query\DataReaderInterface;
use Yiisoft\Db\Query\Query;
use Yiisoft\Db\Query\QueryInterface;
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
Expand All @@ -25,7 +25,6 @@
use function array_combine;
use function array_flip;
use function array_intersect_key;
use function array_key_first;
use function array_map;
use function array_merge;
use function array_values;
Expand Down Expand Up @@ -104,7 +103,7 @@
* @psalm-import-type ARClass from ActiveQueryInterface
* @psalm-import-type IndexKey from ArArrayHelper
*
* @psalm-property IndexKey $indexBy
* @psalm-property IndexKey|null $indexBy
* @psalm-suppress ClassMustBeFinal
*/
class ActiveQuery extends Query implements ActiveQueryInterface
Expand All @@ -125,23 +124,13 @@
parent::__construct($this->getARInstance()->db());
}

public function all(): array
public function each(int $batchSize = 100): DataReaderInterface
{
if ($this->shouldEmulateExecution()) {
return [];
}

return $this->populate($this->createCommand()->queryAll(), $this->indexBy);
}

public function batch(int $batchSize = 100): BatchQueryResultInterface
{
return parent::batch($batchSize)->setPopulatedMethod($this->populate(...));
}

public function each(int $batchSize = 100): BatchQueryResultInterface
{
return parent::each($batchSize)->setPopulatedMethod($this->populate(...));
/** @psalm-suppress InvalidArgument */
return $this->createCommand()
->query()
->indexBy($this->indexBy)
->resultCallback($this->populateOne(...));
}

/**
Expand Down Expand Up @@ -235,22 +224,25 @@
* @throws NotSupportedException
* @throws ReflectionException
* @throws Throwable
*
* @psalm-param list<array> $rows
* @psalm-return (
* $rows is non-empty-list<array>
* ? non-empty-list<ActiveRecordInterface|array>
* : list<ActiveRecordInterface|array>
* )
*/
public function populate(array $rows, Closure|string|null $indexBy = null): array
public function populate(array $rows): array
{
if (empty($rows)) {
return [];
}

$models = $this->createModels($rows);

if (empty($models)) {
return [];
if (!empty($this->join) && $this->indexBy === null) {
$rows = $this->removeDuplicatedRows($rows);
}

if (!empty($this->join) && $this->getIndexBy() === null) {
$models = $this->removeDuplicatedModels($models);
}
$models = $this->createModels($rows);

if (!empty($this->with)) {
$this->findWith($this->with, $models);
Expand All @@ -260,94 +252,68 @@
$this->addInverseRelations($models);
}

return ArArrayHelper::index($models, $indexBy);
return $models;
}

/**
* Removes duplicated models by checking their primary key values.
* Removes duplicated rows by checking their primary key values.
*
* This method is mainly called when a join query is performed, which may cause duplicated rows being returned.
*
* @param ActiveRecordInterface[]|array[] $models The models to be checked.
* @param array[] $rows The rows to be checked.
*
* @throws CircularReferenceException
* @throws Exception
* @throws InvalidConfigException
* @throws NotInstantiableException
*
* @return ActiveRecordInterface[]|array[] The distinctive models.
* @return array[] The distinctive rows.
*
* @psalm-param non-empty-list<array> $rows
* @psalm-return non-empty-list<array>
*/
private function removeDuplicatedModels(array $models): array
private function removeDuplicatedRows(array $rows): array
{
$model = reset($models);
$instance = $this->getARInstance();
$pks = $instance->primaryKey();

if ($this->asArray) {
$instance = $this->getARInstance();
$pks = $instance->primaryKey();
if (empty($pks)) {
throw new InvalidConfigException('Primary key of "' . $instance::class . '" can not be empty.');

Check warning on line 281 in src/ActiveQuery.php

View check run for this annotation

Codecov / codecov/patch

src/ActiveQuery.php#L281

Added line #L281 was not covered by tests
}

if (empty($pks)) {
throw new InvalidConfigException('Primary key of "' . $instance::class . '" can not be empty.');
}

foreach ($pks as $pk) {
/** @var array $model */
if (!isset($model[$pk])) {
return $models;
}
foreach ($pks as $pk) {
if (!isset($rows[0][$pk])) {
return $rows;
}
}

/** @var array[] $models */
if (count($pks) === 1) {
$hash = array_column($models, reset($pks));
} else {
$flippedPks = array_flip($pks);
$hash = array_map(
static fn (array $model): string => serialize(array_intersect_key($model, $flippedPks)),
$models
);
}
if (count($pks) === 1) {
$hash = array_column($rows, reset($pks));
} else {
/** @var ActiveRecordInterface $model */
$pks = $model->getPrimaryKey(true);

if (empty($pks)) {
throw new InvalidConfigException('Primary key of "' . $model::class . '" can not be empty.');
}

/** @var ActiveRecordInterface[] $models */
foreach ($pks as $pk) {
if ($pk === null) {
return $models;
}
}

if (count($pks) === 1) {
$key = array_key_first($pks);
$hash = array_map(
static fn (ActiveRecordInterface $model): string => (string) $model->get($key),
$models
);
} else {
$hash = array_map(
static fn (ActiveRecordInterface $model): string => serialize($model->getPrimaryKey(true)),
$models
);
}
$flippedPks = array_flip($pks);
$hash = array_map(
static fn (array $row): string => serialize(array_intersect_key($row, $flippedPks)),
$rows
);
}

return array_values(array_combine($hash, $models));
/** @psalm-var non-empty-list<array> */
return array_values(array_combine($hash, $rows));
}

public function one(): array|ActiveRecordInterface|null
{
/** @var array|null $row */
$row = parent::one();
if ($this->shouldEmulateExecution()) {
return null;
}

$row = $this->createCommand()->queryOne();

if ($row === null) {
return null;
}

return $this->populate([$row])[0];
return $this->populateOne($row);
}

/**
Expand Down Expand Up @@ -955,6 +921,11 @@
return new $class();
}

protected function index(array $rows): array
{
return ArArrayHelper::index($this->populate($rows), $this->indexBy);
}

private function createInstance(): static
{
return (new static($this->arClass))
Expand All @@ -974,4 +945,9 @@
->params($this->params)
->withQueries($this->withQueries);
}

private function populateOne(array $row): ActiveRecordInterface|array
{
return $this->populate([$row])[0];
}
}
14 changes: 11 additions & 3 deletions src/ActiveQueryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -351,11 +351,16 @@ public function sql(string|null $value): static;
*
* @param array[] $rows The raw query result from a database.
*
* @psalm-param IndexKey|null $indexBy
*
* @return ActiveRecordInterface[]|array[] The converted query result.
*
* @psalm-param list<array> $rows
* @psalm-return (
* $rows is non-empty-list<array>
* ? non-empty-list<ActiveRecordInterface|array>
* : list<ActiveRecordInterface|array>
* )
*/
public function populate(array $rows, Closure|string|null $indexBy = null): array;
public function populate(array $rows): array;

/**
* Returns related record(s).
Expand Down Expand Up @@ -640,6 +645,9 @@ public function one(): array|ActiveRecordInterface|null;
* @throws InvalidArgumentException|InvalidConfigException|NotSupportedException|Throwable If {@see link()} is
* invalid.
* @return ActiveRecordInterface[]|array[] The related models.
*
* @psalm-param non-empty-list<ActiveRecordInterface|array> $primaryModels
* @psalm-param-out non-empty-list<ActiveRecordInterface|array> $primaryModels
*/
public function populateRelation(string $name, array &$primaryModels): array;
}
26 changes: 17 additions & 9 deletions src/ActiveQueryTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,28 +107,35 @@ public function with(array|string ...$with): static
*
* @throws InvalidConfigException
* @return ActiveRecordInterface[]|array[] The model instances.
*
* @psalm-param non-empty-list<array> $rows
* @psalm-return non-empty-list<ActiveRecordInterface|array>
*/
protected function createModels(array $rows): array
{
if ($this->asArray) {
return $rows;
}

$arClassInstance = [];

foreach ($rows as $row) {
$arClass = $this->getARInstance();
if ($this->resultCallback !== null) {
$rows = ($this->resultCallback)($rows);

if (method_exists($arClass, 'instantiate')) {
$arClass = $arClass->instantiate($row);
if ($rows[0] instanceof ActiveRecordInterface) {
/** @psalm-var non-empty-list<ActiveRecordInterface> */
return $rows;
}
}

$models = [];

foreach ($rows as $row) {
$arClass = $this->getARInstance();
$arClass->populateRecord($row);

$arClassInstance[] = $arClass;
$models[] = $arClass;
}

return $arClassInstance;
return $models;
}

/**
Expand All @@ -144,7 +151,8 @@ protected function createModels(array $rows): array
* @throws ReflectionException
* @throws Throwable
*
* @param-out ActiveRecordInterface[]|array[] $models
* @psalm-param non-empty-list<ActiveRecordInterface|array> $models
* @psalm-param-out non-empty-list<ActiveRecordInterface|array> $models
*/
public function findWith(array $with, array &$models): void
{
Expand Down
26 changes: 14 additions & 12 deletions src/ActiveRelationTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ public function relatedRecords(): ActiveRecordInterface|array|null
*
* @throws InvalidConfigException
*
* @param-out ActiveRecordInterface[]|array[] $result
* @psalm-param non-empty-list<ActiveRecordInterface|array> $result
* @psalm-param-out non-empty-list<ActiveRecordInterface|array> $result
*/
private function addInverseRelations(array &$result): void
{
Expand Down Expand Up @@ -232,9 +233,10 @@ private function addInverseRelations(array &$result): void
}

/**
* @return ActiveRecordInterface[]|array[]
* @psalm-param non-empty-list<ActiveRecordInterface|array> $primaryModels
* @psalm-param-out non-empty-list<ActiveRecordInterface|array> $primaryModels
*
* @param-out ActiveRecordInterface[]|array[] $primaryModels
* @return ActiveRecordInterface[]|array[]
*/
public function populateRelation(string $name, array &$primaryModels): array
{
Expand Down Expand Up @@ -262,16 +264,16 @@ public function populateRelation(string $name, array &$primaryModels): array
$models = [$this->one()];
$this->populateInverseRelation($models, $primaryModels);

$primaryModel = reset($primaryModels);
$primaryModel = $primaryModels[0];

if ($primaryModel instanceof ActiveRecordInterface) {
$primaryModel->populateRelation($name, $models[0]);
} else {
/**
* @var array[] $primaryModels
* @psalm-suppress PossiblyNullArrayOffset
* @psalm-var non-empty-list<array> $primaryModels
* @psalm-suppress UndefinedInterfaceMethod
*/
$primaryModels[key($primaryModels)][$name] = $models[0];
$primaryModels[0][$name] = $models[0];
}

return $models;
Expand Down Expand Up @@ -319,12 +321,14 @@ public function populateRelation(string $name, array &$primaryModels): array

/**
* @throws \Yiisoft\Definitions\Exception\InvalidConfigException
*
* @psalm-param non-empty-list<ActiveRecordInterface|array> $primaryModels
*/
private function populateInverseRelation(
array &$models,
array $primaryModels,
): void {
if ($this->inverseOf === null || empty($models) || empty($primaryModels)) {
if ($this->inverseOf === null || empty($models)) {
return;
}

Expand Down Expand Up @@ -643,13 +647,11 @@ private function getModelKeys(ActiveRecordInterface|array $model, array $propert
* @throws Throwable
* @throws \Yiisoft\Definitions\Exception\InvalidConfigException
* @return array[]
*
* @psalm-param non-empty-list<ActiveRecordInterface|array> $primaryModels
*/
private function findJunctionRows(array $primaryModels): array
{
if (empty($primaryModels)) {
return [];
}

$this->filterByModels($primaryModels);

/** @var array[] */
Expand Down
Loading