diff --git a/src/Scaffolding/TableImplementation.php b/src/Scaffolding/TableImplementation.php index 3127a130fba66c0574e69575554f7e4037d277dc..4533bee67ae0a9f9ec0d3f47abd27c82e51e34d6 100644 --- a/src/Scaffolding/TableImplementation.php +++ b/src/Scaffolding/TableImplementation.php @@ -28,7 +28,6 @@ use Grifart\Tables\TypeResolver; use Nette\PhpGenerator as Code; use Nette\Utils\Paginator; use function Grifart\ClassScaffolder\Definition\Types\resolve; -use function Phun\map; use function usort; final class TableImplementation implements Capability @@ -159,6 +158,65 @@ final class TableImplementation implements Capability $namespace->addUse(TooManyRowsFound::class); + $classType->addMethod('getOneBy') + ->setParameters([ + (new Code\Parameter('conditions'))->setType(Condition::class . '|array'), + ]) + ->addComment('@param Condition|Condition[] $conditions') + ->addComment('@return ' . $namespace->simplifyName($this->rowClass)) + ->addComment('@throws RowNotFound') + ->setReturnType($this->rowClass) + ->addBody('[$row, $count] = $this->tableManager->findOneBy($this, $conditions);') + ->addBody('\assert($row instanceof ? || $row === null);', [new Code\Literal($namespace->simplifyName($this->rowClass))]) + ->addBody('if ($row === null) { throw new RowNotFound(); }') + ->addBody('if ($count > 1) { throw new TooManyRowsFound(); }') + ->addBody('return $row;'); + + $classType->addMethod('findOneBy') + ->setParameters([ + (new Code\Parameter('conditions'))->setType(Condition::class . '|array'), + ]) + ->addComment('@param Condition|Condition[] $conditions') + ->addComment('@return ' . $namespace->simplifyName($this->rowClass)) + ->addComment('@throws RowNotFound') + ->setReturnType($this->rowClass) + ->setReturnNullable() + ->addBody('[$row, $count] = $this->tableManager->findOneBy($this, $conditions);') + ->addBody('\assert($row instanceof ? || $row === null);', [new Code\Literal($namespace->simplifyName($this->rowClass))]) + ->addBody('if ($count > 1) { throw new TooManyRowsFound(); }') + ->addBody('return $row;'); + + $classType->addMethod('getFirstBy') + ->setParameters([ + (new Code\Parameter('conditions'))->setType(Condition::class . '|array'), + (new Code\Parameter('orderBy'))->setType('array')->setDefaultValue([]), + ]) + ->addComment('@param Condition|Condition[] $conditions') + ->addComment('@param array<OrderBy|Expression<mixed>> $orderBy') + ->addComment('@return ' . $namespace->simplifyName($this->rowClass)) + ->addComment('@throws RowNotFound') + ->setReturnType($this->rowClass) + ->addBody('[$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false);') + ->addBody('\assert($row instanceof ? || $row === null);', [new Code\Literal($namespace->simplifyName($this->rowClass))]) + ->addBody('if ($row === null) { throw new RowNotFound(); }') + ->addBody('return $row;'); + + $classType->addMethod('findFirstBy') + ->setParameters([ + (new Code\Parameter('conditions'))->setType(Condition::class . '|array'), + (new Code\Parameter('orderBy'))->setType('array')->setDefaultValue([]), + ]) + ->addComment('@param Condition|Condition[] $conditions') + ->addComment('@param array<OrderBy|Expression<mixed>> $orderBy') + ->addComment('@return ' . $namespace->simplifyName($this->rowClass)) + ->addComment('@throws RowNotFound') + ->setReturnType($this->rowClass) + ->setReturnNullable() + ->addBody('[$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false);') + ->addBody('\assert($row instanceof ? || $row === null);', [new Code\Literal($namespace->simplifyName($this->rowClass))]) + ->addBody('return $row;'); + + $classType->addMethod('getBy') ->setParameters([ (new Code\Parameter('conditions'))->setType(Condition::class . '|array'), @@ -166,11 +224,9 @@ final class TableImplementation implements Capability ->addComment('@param Condition|Condition[] $conditions') ->addComment('@return ' . $namespace->simplifyName($this->rowClass)) ->addComment('@throws RowNotFound') + ->addAttribute(\Deprecated::class, ['Use getOneBy() instead.']) ->setReturnType($this->rowClass) - ->addBody('$result = $this->findBy($conditions);') - ->addBody('if (\count($result) === 0) { throw new RowNotFound(); }') - ->addBody('if (\count($result) > 1) { throw new TooManyRowsFound(); }') - ->addBody('return $result[0];'); + ->addBody('return $this->getOneBy($conditions);'); $newMethod = $classType->addMethod('new') diff --git a/src/TableManager.php b/src/TableManager.php index acbf929754ff0b27455643c39eb45fe1efca894c..4b6880e1657b52496d065af8ff7b302841df5c86 100644 --- a/src/TableManager.php +++ b/src/TableManager.php @@ -137,6 +137,56 @@ final class TableManager return $modelRows; } + /** + * @template TableType of Table + * @param TableType $table + * @param Condition|Condition[] $conditions + * @param array<OrderBy|Expression<mixed>> $orderBy + * @return array{Row|null, int} + */ + public function findOneBy(Table $table, Condition|array $conditions, array $orderBy = [], bool $checkCount = true): array + { + $result = $this->connection->query( + 'SELECT *', + 'FROM %n.%n', $table::getSchema(), $table::getTableName(), + 'WHERE %ex', (\is_array($conditions) ? Composite::and(...$conditions) : $conditions)->toSql()->getValues(), + 'ORDER BY %by', \count($orderBy) > 0 + ? map($orderBy, function (OrderBy|Expression $orderBy) { + if ($orderBy instanceof Expression) { + $orderBy = new OrderByDirection($orderBy); + } + + return $orderBy->toSql()->getValues(); + }) + : [['%sql', 'true::boolean']], + '%lmt', $checkCount ? 2 : 1, + ); + + foreach ($table::getDatabaseColumns() as $column) { + $result->setType($column->getName(), NULL); + } + + $dibiRows = $result->fetchAll(); + + /** @var class-string<Row> $rowClass */ + $rowClass = $table::getRowClass(); + $modelRows = []; + foreach ($dibiRows as $dibiRow) { + \assert($dibiRow instanceof \Dibi\Row); + $modelRows[] = $rowClass::reconstitute( + mapWithKeys( + $dibiRow->toArray(), + static fn(string $columnName, mixed $value) => $value !== null ? $table->getTypeOf($columnName)->fromDatabase($value) : null, + ), + ); + } + + return [ + $modelRows[0] ?? null, + \count($modelRows), + ]; + } + /** * @template TableType of Table * @param TableType $table diff --git a/tests/ConfigTableTest.phpt b/tests/ConfigTableTest.phpt new file mode 100644 index 0000000000000000000000000000000000000000..a705a0d9e1acf25397a46c908a35aff51c5c195c --- /dev/null +++ b/tests/ConfigTableTest.phpt @@ -0,0 +1,57 @@ +<?php + +declare(strict_types=1); + +namespace Grifart\Tables\Tests; + +use Grifart\Tables\RowNotFound; +use Grifart\Tables\Tests\Fixtures\ConfigTable; +use Grifart\Tables\Tests\Fixtures\TestFixtures; +use Grifart\Tables\TooManyRowsFound; +use Tester\Assert; + +require __DIR__ . '/bootstrap.php'; + +$connection = connect(); + +$connection->nativeQuery("TRUNCATE TABLE public.config"); +$connection->nativeQuery("INSERT INTO public.config (id, key, value) VALUES ('4bd6f9a9-cccf-4e1d-bbdd-bcd89406f65a', 'key1', 'same value'), ('d9d29fb0-6c6e-48b9-a60f-8c8136fe0840', 'key2', 'same value'), ('c10c5e42-a2fe-4fbd-97f7-0d4d3e27541e', 'key3', 'different value');"); + +$table = new ConfigTable( + TestFixtures::createTableManager($connection), + TestFixtures::createTypeResolver($connection), +); + +$row = $table->getOneBy($table->key()->is('key1')); +Assert::same('same value', $row->getValue()); + +Assert::throws(fn() => $table->getOneBy($table->key()->is('key4')), RowNotFound::class); +Assert::throws(fn() => $table->getOneBy($table->value()->is('same value')), TooManyRowsFound::class); + +$row = $table->findOneBy($table->key()->is('key1')); +Assert::same('same value', $row->getValue()); + +Assert::null($table->findOneBy($table->key()->is('key4'))); +Assert::throws(fn() => $table->findOneBy($table->value()->is('same value')), TooManyRowsFound::class); + +$row = $table->getFirstBy($table->key()->is('key1')); +Assert::same('same value', $row->getValue()); + +Assert::throws(fn() => $table->getFirstBy($table->key()->is('key4')), RowNotFound::class); + +$row = $table->getFirstBy($table->value()->is('same value'), [$table->key()->ascending()]); +Assert::same('key1', $row->getKey()); + +$row = $table->getFirstBy($table->value()->is('same value'), [$table->key()->descending()]); +Assert::same('key2', $row->getKey()); + +$row = $table->findFirstBy($table->key()->is('key1')); +Assert::same('same value', $row->getValue()); + +Assert::null($table->findFirstBy($table->key()->is('key4'))); + +$row = $table->findFirstBy($table->value()->is('same value'), [$table->key()->ascending()]); +Assert::same('key1', $row->getKey()); + +$row = $table->findFirstBy($table->value()->is('same value'), [$table->key()->descending()]); +Assert::same('key2', $row->getKey()); diff --git a/tests/Fixtures/.definition.php b/tests/Fixtures/.definition.php index f160884eb9c6332034a25e51552b732123c6c277..7f6dc2a5f3b9bb40a4c2a93755d9c7c99eb7cb81 100644 --- a/tests/Fixtures/.definition.php +++ b/tests/Fixtures/.definition.php @@ -24,6 +24,14 @@ return [ TestsTable::class, TestPrimaryKey::class, ), + ...$tableDefinitions->for( + 'public', + 'config', + ConfigRow::class, + ConfigModifications::class, + ConfigTable::class, + ConfigPrimaryKey::class, + ), ...$tableDefinitions->for( 'public', 'package', diff --git a/tests/Fixtures/ConfigModifications.php b/tests/Fixtures/ConfigModifications.php new file mode 100644 index 0000000000000000000000000000000000000000..207bab37a2a0f468e163f535dd70ef80ed510f9b --- /dev/null +++ b/tests/Fixtures/ConfigModifications.php @@ -0,0 +1,56 @@ +<?php + +/** + * Do not edit. This is generated file. Modify definition file instead. + */ + +declare(strict_types=1); + +namespace Grifart\Tables\Tests\Fixtures; + +use Grifart\Tables\Modifications; +use Grifart\Tables\ModificationsTrait; + +/** + * @implements Modifications<ConfigTable> + */ +final class ConfigModifications implements Modifications +{ + /** @use ModificationsTrait<ConfigTable> */ + use ModificationsTrait; + + public static function update(ConfigPrimaryKey $primaryKey): self + { + return self::_update($primaryKey); + } + + + public static function new(): self + { + return self::_new(); + } + + + public static function forTable(): string + { + return ConfigTable::class; + } + + + public function modifyId(Uuid $id): void + { + $this->modifications['id'] = $id; + } + + + public function modifyKey(string $key): void + { + $this->modifications['key'] = $key; + } + + + public function modifyValue(string $value): void + { + $this->modifications['value'] = $value; + } +} diff --git a/tests/Fixtures/ConfigPrimaryKey.php b/tests/Fixtures/ConfigPrimaryKey.php new file mode 100644 index 0000000000000000000000000000000000000000..1200f310109a73c4b305556838112b90938bc8c4 --- /dev/null +++ b/tests/Fixtures/ConfigPrimaryKey.php @@ -0,0 +1,52 @@ +<?php + +/** + * Do not edit. This is generated file. Modify definition file instead. + */ + +declare(strict_types=1); + +namespace Grifart\Tables\Tests\Fixtures; + +use Grifart\Tables\Conditions\Composite; +use Grifart\Tables\Conditions\Condition; +use Grifart\Tables\PrimaryKey; +use Grifart\Tables\Table; +use function Grifart\Tables\Conditions\equalTo; + +/** + * @implements PrimaryKey<ConfigTable> + */ +final class ConfigPrimaryKey implements PrimaryKey +{ + private function __construct( + private Uuid $id, + ) { + } + + + public static function from(Uuid $id): self + { + return new self($id); + } + + + public static function fromRow(ConfigRow $row): self + { + return self::from($row->getId()); + } + + + public function getCondition(Table $table): Condition + { + return Composite::and( + $table->id()->is(equalTo($this->id)), + ); + } + + + public function getId(): Uuid + { + return $this->id; + } +} diff --git a/tests/Fixtures/ConfigRow.php b/tests/Fixtures/ConfigRow.php new file mode 100644 index 0000000000000000000000000000000000000000..691e6ef5a822480236843c0555dcfaf004f1b321 --- /dev/null +++ b/tests/Fixtures/ConfigRow.php @@ -0,0 +1,46 @@ +<?php + +/** + * Do not edit. This is generated file. Modify definition file instead. + */ + +declare(strict_types=1); + +namespace Grifart\Tables\Tests\Fixtures; + +use Grifart\Tables\Row; + +final class ConfigRow implements Row +{ + private function __construct( + private Uuid $id, + private string $key, + private string $value, + ) { + } + + + public function getId(): Uuid + { + return $this->id; + } + + + public function getKey(): string + { + return $this->key; + } + + + public function getValue(): string + { + return $this->value; + } + + + public static function reconstitute(array $values): static + { + /** @var array{id: Uuid, key: string, value: string} $values */ + return new static($values['id'], $values['key'], $values['value']); + } +} diff --git a/tests/Fixtures/ConfigTable.php b/tests/Fixtures/ConfigTable.php new file mode 100644 index 0000000000000000000000000000000000000000..605dd7294e124c6fef22862846951d30b5f490dc --- /dev/null +++ b/tests/Fixtures/ConfigTable.php @@ -0,0 +1,316 @@ +<?php + +/** + * Do not edit. This is generated file. Modify definition file instead. + */ + +declare(strict_types=1); + +namespace Grifart\Tables\Tests\Fixtures; + +use Grifart\Tables\Column; +use Grifart\Tables\ColumnMetadata; +use Grifart\Tables\ColumnNotFound; +use Grifart\Tables\Conditions\Condition; +use Grifart\Tables\DefaultOrExistingValue; +use Grifart\Tables\Expression; +use Grifart\Tables\GivenSearchCriteriaHaveNotMatchedAnyRows; +use Grifart\Tables\OrderBy\OrderBy; +use Grifart\Tables\RowNotFound; +use Grifart\Tables\RowWithGivenPrimaryKeyAlreadyExists; +use Grifart\Tables\Table; +use Grifart\Tables\TableManager; +use Grifart\Tables\TooManyRowsFound; +use Grifart\Tables\Type; +use Grifart\Tables\TypeResolver; +use Nette\Utils\Paginator; + +final class ConfigTable implements Table +{ + public const ID = 'id'; + public const KEY = 'key'; + public const VALUE = 'value'; + + /** @var array{id: Column<self, Uuid>, key: Column<self, string>, value: Column<self, string>} */ + private array $columns; + + + public static function getSchema(): string + { + return 'public'; + } + + + public static function getTableName(): string + { + return 'config'; + } + + + public static function getPrimaryKeyClass(): string + { + return ConfigPrimaryKey::class; + } + + + public static function getRowClass(): string + { + return ConfigRow::class; + } + + + public static function getModificationClass(): string + { + return ConfigModifications::class; + } + + + /** + * @return ColumnMetadata[] + */ + public static function getDatabaseColumns(): array + { + return [ + 'id' => new ColumnMetadata('id', 'uuid', false, false, false), + 'key' => new ColumnMetadata('key', 'text', false, false, false), + 'value' => new ColumnMetadata('value', 'text', false, false, false) + ]; + } + + + public function find(ConfigPrimaryKey $primaryKey): ?ConfigRow + { + $row = $this->tableManager->find($this, $primaryKey); + \assert($row instanceof ConfigRow || $row === NULL); + return $row; + } + + + /** + * @throws RowNotFound + */ + public function get(ConfigPrimaryKey $primaryKey): ConfigRow + { + $row = $this->find($primaryKey); + if ($row === NULL) { + throw new RowNotFound(); + } + return $row; + } + + + /** + * @param OrderBy[] $orderBy + * @return ConfigRow[] + */ + public function getAll(array $orderBy = [], ?Paginator $paginator = null): array + { + /** @var ConfigRow[] $result */ + $result = $this->tableManager->getAll($this, $orderBy, $paginator); + return $result; + } + + + /** + * @param Condition|Condition[] $conditions + * @param array<OrderBy|Expression<mixed>> $orderBy + * @return ConfigRow[] + */ + public function findBy(Condition|array $conditions, array $orderBy = [], ?Paginator $paginator = null): array + { + /** @var ConfigRow[] $result */ + $result = $this->tableManager->findBy($this, $conditions, $orderBy, $paginator); + return $result; + } + + + /** + * @param Condition|Condition[] $conditions + * @return ConfigRow + * @throws RowNotFound + */ + public function getOneBy(Condition|array $conditions): ConfigRow + { + [$row, $count] = $this->tableManager->findOneBy($this, $conditions); + \assert($row instanceof ConfigRow || $row === null); + if ($row === null) { throw new RowNotFound(); } + if ($count > 1) { throw new TooManyRowsFound(); } + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @return ConfigRow + * @throws RowNotFound + */ + public function findOneBy(Condition|array $conditions): ?ConfigRow + { + [$row, $count] = $this->tableManager->findOneBy($this, $conditions); + \assert($row instanceof ConfigRow || $row === null); + if ($count > 1) { throw new TooManyRowsFound(); } + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @param array<OrderBy|Expression<mixed>> $orderBy + * @return ConfigRow + * @throws RowNotFound + */ + public function getFirstBy(Condition|array $conditions, array $orderBy = []): ConfigRow + { + [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); + \assert($row instanceof ConfigRow || $row === null); + if ($row === null) { throw new RowNotFound(); } + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @param array<OrderBy|Expression<mixed>> $orderBy + * @return ConfigRow + * @throws RowNotFound + */ + public function findFirstBy(Condition|array $conditions, array $orderBy = []): ?ConfigRow + { + [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); + \assert($row instanceof ConfigRow || $row === null); + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @return ConfigRow + * @throws RowNotFound + */ + #[\Deprecated('Use getOneBy() instead.')] + public function getBy(Condition|array $conditions): ConfigRow + { + return $this->getOneBy($conditions); + } + + + public function new(Uuid $id, string $key, string $value): ConfigModifications + { + $modifications = ConfigModifications::new(); + $modifications->modifyId($id); + $modifications->modifyKey($key); + $modifications->modifyValue($value); + return $modifications; + } + + + public function edit( + ConfigRow|ConfigPrimaryKey $rowOrKey, + Uuid|DefaultOrExistingValue $id = \Grifart\Tables\Unchanged, + string|DefaultOrExistingValue $key = \Grifart\Tables\Unchanged, + string|DefaultOrExistingValue $value = \Grifart\Tables\Unchanged, + ): ConfigModifications + { + $primaryKey = $rowOrKey instanceof ConfigPrimaryKey ? $rowOrKey : ConfigPrimaryKey::fromRow($rowOrKey); + $modifications = ConfigModifications::update($primaryKey); + if (!$id instanceof DefaultOrExistingValue) { + $modifications->modifyId($id); + } + if (!$key instanceof DefaultOrExistingValue) { + $modifications->modifyKey($key); + } + if (!$value instanceof DefaultOrExistingValue) { + $modifications->modifyValue($value); + } + return $modifications; + } + + + /** + * @throws RowWithGivenPrimaryKeyAlreadyExists + * @throws GivenSearchCriteriaHaveNotMatchedAnyRows + */ + public function save(ConfigModifications $changes): void + { + $this->tableManager->save($this, $changes); + } + + + /** + * @throws RowWithGivenPrimaryKeyAlreadyExists + */ + public function insert(ConfigModifications $changes): void + { + $this->tableManager->insert($this, $changes); + } + + + /** + * @throws GivenSearchCriteriaHaveNotMatchedAnyRows + */ + public function update(ConfigModifications $changes): void + { + $this->tableManager->update($this, $changes); + } + + + public function delete(ConfigRow|ConfigPrimaryKey $rowOrKey): void + { + $primaryKey = $rowOrKey instanceof ConfigPrimaryKey ? $rowOrKey : ConfigPrimaryKey::fromRow($rowOrKey); + $this->tableManager->delete($this, $primaryKey); + } + + + public function __construct( + private TableManager $tableManager, + private TypeResolver $typeResolver, + ) { + /** @var Column<self, Uuid> $id */ + $id = Column::from($this, self::getDatabaseColumns()['id'], $this->typeResolver); + /** @var Column<self, string> $key */ + $key = Column::from($this, self::getDatabaseColumns()['key'], $this->typeResolver); + /** @var Column<self, string> $value */ + $value = Column::from($this, self::getDatabaseColumns()['value'], $this->typeResolver); + $this->columns = ['id' => $id, 'key' => $key, 'value' => $value]; + } + + + /** + * @return Column<self, Uuid> + */ + public function id(): Column + { + return $this->columns['id']; + } + + + /** + * @return Column<self, string> + */ + public function key(): Column + { + return $this->columns['key']; + } + + + /** + * @return Column<self, string> + */ + public function value(): Column + { + return $this->columns['value']; + } + + + /** + * @internal + * @return Type<mixed> + */ + public function getTypeOf(string $columnName): Type + { + $column = $this->columns[$columnName] ?? throw ColumnNotFound::of($columnName, \get_class($this)); + /** @var Type<mixed> $type */ + $type = $column->getType(); + return $type; + } +} diff --git a/tests/Fixtures/GeneratedTable.php b/tests/Fixtures/GeneratedTable.php index bd78f9deeea3c7a5fed5a8cd60f98a33832673a9..5dbe6bfb297ac19707312dd063b7e8af7d1a13ab 100644 --- a/tests/Fixtures/GeneratedTable.php +++ b/tests/Fixtures/GeneratedTable.php @@ -129,12 +129,68 @@ final class GeneratedTable implements Table * @return GeneratedRow * @throws RowNotFound */ + public function getOneBy(Condition|array $conditions): GeneratedRow + { + [$row, $count] = $this->tableManager->findOneBy($this, $conditions); + \assert($row instanceof GeneratedRow || $row === null); + if ($row === null) { throw new RowNotFound(); } + if ($count > 1) { throw new TooManyRowsFound(); } + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @return GeneratedRow + * @throws RowNotFound + */ + public function findOneBy(Condition|array $conditions): ?GeneratedRow + { + [$row, $count] = $this->tableManager->findOneBy($this, $conditions); + \assert($row instanceof GeneratedRow || $row === null); + if ($count > 1) { throw new TooManyRowsFound(); } + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @param array<OrderBy|Expression<mixed>> $orderBy + * @return GeneratedRow + * @throws RowNotFound + */ + public function getFirstBy(Condition|array $conditions, array $orderBy = []): GeneratedRow + { + [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); + \assert($row instanceof GeneratedRow || $row === null); + if ($row === null) { throw new RowNotFound(); } + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @param array<OrderBy|Expression<mixed>> $orderBy + * @return GeneratedRow + * @throws RowNotFound + */ + public function findFirstBy(Condition|array $conditions, array $orderBy = []): ?GeneratedRow + { + [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); + \assert($row instanceof GeneratedRow || $row === null); + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @return GeneratedRow + * @throws RowNotFound + */ + #[\Deprecated('Use getOneBy() instead.')] public function getBy(Condition|array $conditions): GeneratedRow { - $result = $this->findBy($conditions); - if (\count($result) === 0) { throw new RowNotFound(); } - if (\count($result) > 1) { throw new TooManyRowsFound(); } - return $result[0]; + return $this->getOneBy($conditions); } diff --git a/tests/Fixtures/PackagesTable.php b/tests/Fixtures/PackagesTable.php index bbf63641526f72568c1dabef0672cad662bb60d0..4f6ca4d3d040a6fac92e197fea54421128537cf5 100644 --- a/tests/Fixtures/PackagesTable.php +++ b/tests/Fixtures/PackagesTable.php @@ -129,12 +129,68 @@ final class PackagesTable implements Table * @return PackageRow * @throws RowNotFound */ + public function getOneBy(Condition|array $conditions): PackageRow + { + [$row, $count] = $this->tableManager->findOneBy($this, $conditions); + \assert($row instanceof PackageRow || $row === null); + if ($row === null) { throw new RowNotFound(); } + if ($count > 1) { throw new TooManyRowsFound(); } + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @return PackageRow + * @throws RowNotFound + */ + public function findOneBy(Condition|array $conditions): ?PackageRow + { + [$row, $count] = $this->tableManager->findOneBy($this, $conditions); + \assert($row instanceof PackageRow || $row === null); + if ($count > 1) { throw new TooManyRowsFound(); } + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @param array<OrderBy|Expression<mixed>> $orderBy + * @return PackageRow + * @throws RowNotFound + */ + public function getFirstBy(Condition|array $conditions, array $orderBy = []): PackageRow + { + [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); + \assert($row instanceof PackageRow || $row === null); + if ($row === null) { throw new RowNotFound(); } + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @param array<OrderBy|Expression<mixed>> $orderBy + * @return PackageRow + * @throws RowNotFound + */ + public function findFirstBy(Condition|array $conditions, array $orderBy = []): ?PackageRow + { + [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); + \assert($row instanceof PackageRow || $row === null); + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @return PackageRow + * @throws RowNotFound + */ + #[\Deprecated('Use getOneBy() instead.')] public function getBy(Condition|array $conditions): PackageRow { - $result = $this->findBy($conditions); - if (\count($result) === 0) { throw new RowNotFound(); } - if (\count($result) > 1) { throw new TooManyRowsFound(); } - return $result[0]; + return $this->getOneBy($conditions); } diff --git a/tests/Fixtures/TestFixtures.php b/tests/Fixtures/TestFixtures.php index 0b03a8600336501e92adcc46520b79e885201e85..212d43e7a11c3587ece4f9ee3b0958086c7c808e 100644 --- a/tests/Fixtures/TestFixtures.php +++ b/tests/Fixtures/TestFixtures.php @@ -26,6 +26,7 @@ final class TestFixtures $typeResolver = new TypeResolver($connection); $typeResolver->addResolutionByLocation(new Identifier('public', 'test', 'id'), new UuidType()); $typeResolver->addResolutionByLocation(new Identifier('public', 'test', 'score'), IntType::integer()); + $typeResolver->addResolutionByLocation(new Identifier('public', 'config', 'id'), new UuidType()); $typeResolver->addResolutionByLocation(new Identifier('public', 'package', 'version'), new TupleVersionType()); $typeResolver->addResolutionByLocation(new Identifier('public', 'package', 'previousVersions'), ArrayType::of(new VersionType())); return $typeResolver; diff --git a/tests/Fixtures/TestsTable.php b/tests/Fixtures/TestsTable.php index 0a14d748fb34e17c91dc9d897655335855bcdca6..65f7f5f41b8cc99f1675dd0eadb43b3ab2de5adc 100644 --- a/tests/Fixtures/TestsTable.php +++ b/tests/Fixtures/TestsTable.php @@ -129,12 +129,68 @@ final class TestsTable implements Table * @return TestRow * @throws RowNotFound */ + public function getOneBy(Condition|array $conditions): TestRow + { + [$row, $count] = $this->tableManager->findOneBy($this, $conditions); + \assert($row instanceof TestRow || $row === null); + if ($row === null) { throw new RowNotFound(); } + if ($count > 1) { throw new TooManyRowsFound(); } + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @return TestRow + * @throws RowNotFound + */ + public function findOneBy(Condition|array $conditions): ?TestRow + { + [$row, $count] = $this->tableManager->findOneBy($this, $conditions); + \assert($row instanceof TestRow || $row === null); + if ($count > 1) { throw new TooManyRowsFound(); } + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @param array<OrderBy|Expression<mixed>> $orderBy + * @return TestRow + * @throws RowNotFound + */ + public function getFirstBy(Condition|array $conditions, array $orderBy = []): TestRow + { + [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); + \assert($row instanceof TestRow || $row === null); + if ($row === null) { throw new RowNotFound(); } + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @param array<OrderBy|Expression<mixed>> $orderBy + * @return TestRow + * @throws RowNotFound + */ + public function findFirstBy(Condition|array $conditions, array $orderBy = []): ?TestRow + { + [$row] = $this->tableManager->findOneBy($this, $conditions, $orderBy, checkCount: false); + \assert($row instanceof TestRow || $row === null); + return $row; + } + + + /** + * @param Condition|Condition[] $conditions + * @return TestRow + * @throws RowNotFound + */ + #[\Deprecated('Use getOneBy() instead.')] public function getBy(Condition|array $conditions): TestRow { - $result = $this->findBy($conditions); - if (\count($result) === 0) { throw new RowNotFound(); } - if (\count($result) > 1) { throw new TooManyRowsFound(); } - return $result[0]; + return $this->getOneBy($conditions); } diff --git a/tests/Scaffolding/ScaffoldingTest.phpt b/tests/Scaffolding/ScaffoldingTest.phpt index 4e76ed83e3b659a897c82545102a38b8f4a32ade..148d0c58d70eb9418dd35eb7372bc148c54cca1d 100644 --- a/tests/Scaffolding/ScaffoldingTest.phpt +++ b/tests/Scaffolding/ScaffoldingTest.phpt @@ -34,5 +34,5 @@ $results = $fileProcessor->processFile( }, ); -Assert::count(12, $results->getDefinitions()); +Assert::count(16, $results->getDefinitions()); Assert::true($results->isSuccessful()); diff --git a/tests/TableTest.php b/tests/TableTest.php index e4db509f51999f83d788b406c04fd97483286343..aaad186104fa1c703d3a900ab1c28ed3c8a62dc1 100644 --- a/tests/TableTest.php +++ b/tests/TableTest.php @@ -112,7 +112,7 @@ $nullDetails = $table->findBy($table->details()->is(null)); Assert::count(1, $nullDetails); Assert::same(0, $nullDetails[0]->getScore()); -$unique = $table->getBy($table->score()->is(42)); +$unique = $table->getOneBy($table->score()->is(42)); Assert::same(42, $unique->getScore()); $table->update($table->edit( diff --git a/tests/initializeDatabase.php b/tests/initializeDatabase.php index b88144ce2a64afc8995721c1d65b4f7db0a3dd80..7f388be4f30b7bc835b452ca41176609d2e15772 100644 --- a/tests/initializeDatabase.php +++ b/tests/initializeDatabase.php @@ -25,6 +25,14 @@ CREATE TABLE IF NOT EXISTS public.test ( ); SQL); +$connection->nativeQuery(<<<SQL +CREATE TABLE IF NOT EXISTS public.config ( + id uuid NOT NULL PRIMARY KEY, + key text NOT NULL UNIQUE, + value text NOT NULL +); +SQL); + $connection->nativeQuery(<<<SQL CREATE TYPE public."packageVersion" AS (major int, minor int, patch int); CREATE TABLE IF NOT EXISTS public.package (