From 34b43ffc4ad9f978a15d001bd5d1003578f008ca Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Wed, 2 Apr 2025 11:13:16 +0200 Subject: [PATCH 1/2] run cs fixer on proofs --- .php-cs-fixer.dist.php | 1 + proofs/connection/pdo.php | 2 +- proofs/query/parameter/type.php | 5 +++-- proofs/query/where.php | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 9d2a065..1c078fc 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -3,5 +3,6 @@ return Innmind\CodingStandard\CodingStandard::config([ 'properties', 'fixtures', + 'proofs', 'src', ]); diff --git a/proofs/connection/pdo.php b/proofs/connection/pdo.php index ac566fe..1798767 100644 --- a/proofs/connection/pdo.php +++ b/proofs/connection/pdo.php @@ -509,7 +509,7 @@ static function($assert, $int) use ($connection) { ]), )); - $assert->throws(fn() => $connection(Insert::into( + $assert->throws(static fn() => $connection(Insert::into( $table, Row::of([ 'id' => $int, diff --git a/proofs/query/parameter/type.php b/proofs/query/parameter/type.php index 0546baa..34c59dd 100644 --- a/proofs/query/parameter/type.php +++ b/proofs/query/parameter/type.php @@ -55,8 +55,9 @@ static function($assert, $string) { 'Type::for() unsupported data', given( Set\Elements::of( - new \stdClass, - new class {}, + new stdClass, + new class { + }, static fn() => null, \tmpfile(), ), diff --git a/proofs/query/where.php b/proofs/query/where.php index 24aa1ea..61e03e8 100644 --- a/proofs/query/where.php +++ b/proofs/query/where.php @@ -339,7 +339,7 @@ static function($assert, $column, $value1, $value2, $value3, $values) { Set\Strings::any(), Set\Strings::any(), ), - static function($assert, $column, $leftValue, $rightValue){ + static function($assert, $column, $leftValue, $rightValue) { $specification = Property::of( $column->name()->toString(), Sign::equality, From aa299e4962d569582954cff9c56aa2254d030b01 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Wed, 2 Apr 2025 12:08:14 +0200 Subject: [PATCH 2/2] allow to join tables when updating --- CHANGELOG.md | 6 +++ proofs/connection/pdo.php | 78 +++++++++++++++++++++++++++++++++++++++ src/Query/Update.php | 75 +++++++++++++++++++++++++++++++++++-- 3 files changed, 156 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86408de..6950bb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [Unreleased] + +### Added + +- `Formal\AccessLayer\Query\Update::join()` + ## 4.0.0 - 2024-07-14 ### Added diff --git a/proofs/connection/pdo.php b/proofs/connection/pdo.php index 1798767..b9d1e61 100644 --- a/proofs/connection/pdo.php +++ b/proofs/connection/pdo.php @@ -13,6 +13,7 @@ Query\Select, Query\Select\Join, Query\DropTable, + Query\Update, Row, Table, Table\Column, @@ -477,6 +478,83 @@ static function($assert) use ($connection) { }, ); + yield test( + "Update join({$driver->name})", + static function($assert) use ($connection) { + $parent = Table\Name::of('test_join_update_parent')->as('parent'); + $child = Table\Name::of('test_join_update_child')->as('child'); + $connection(CreateTable::ifNotExists( + $child->name(), + Column::of( + Column\Name::of('id'), + Column\Type::int(), + ), + Column::of( + Column\Name::of('name'), + Column\Type::varchar(), + ), + )->primaryKey(Column\Name::of('id'))); + $connection( + CreateTable::ifNotExists( + $parent->name(), + Column::of( + Column\Name::of('id'), + Column\Type::int(), + ), + Column::of( + Column\Name::of('child'), + Column\Type::int(), + ), + ) + ->primaryKey(Column\Name::of('id')) + ->foreignKey( + Column\Name::of('child'), + $child->name(), + Column\Name::of('id'), + ), + ); + $connection(Insert::into( + $child->name(), + Row::of([ + 'id' => 1, + 'name' => 'a', + ]), + )); + $connection(Insert::into( + $parent->name(), + Row::of([ + 'id' => 1, + 'child' => 1, + ]), + )); + + $connection( + Update::set( + $child, + Row::new(Row\Value::of( + Column\Name::of('name'), + 'b', + )), + )->join( + Join::left($parent)->on( + Column\Name::of('child')->in($parent), + Column\Name::of('id')->in($child), + ), + ), + ); + + $rows = $connection(Select::from($child)) + ->map(static fn($row) => $row->toArray()) + ->toList(); + $assert + ->expected([['id' => 1, 'name' => 'b']]) + ->same($rows); + + $connection(DropTable::named($parent->name())); + $connection(DropTable::named($child->name())); + }, + ); + yield proof( "Unique constraint({$driver->name})", given(Set\Integers::between(0, 1_000_000)), diff --git a/src/Query/Update.php b/src/Query/Update.php index f23a9ae..a0efff4 100644 --- a/src/Query/Update.php +++ b/src/Query/Update.php @@ -5,6 +5,7 @@ use Formal\AccessLayer\{ Query, + Query\Select\Join, Table\Name, Row, Driver, @@ -13,6 +14,7 @@ use Innmind\Immutable\{ Sequence, Str, + Monoid\Concat, }; /** @@ -22,15 +24,22 @@ final class Update implements Query { private Name|Name\Aliased $table; private Row $row; + /** @var Sequence */ + private Sequence $joins; private Where $where; + /** + * @param Sequence $joins + */ private function __construct( Name|Name\Aliased $table, Row $row, + Sequence $joins, Where $where, ) { $this->table = $table; $this->row = $row; + $this->joins = $joins; $this->where = $where; } @@ -39,7 +48,17 @@ private function __construct( */ public static function set(Name|Name\Aliased $table, Row $row): self { - return new self($table, $row, Where::everything()); + return new self($table, $row, Sequence::of(), Where::everything()); + } + + public function join(Join $join): self + { + return new self( + $this->table, + $this->row, + ($this->joins)($join), + $this->where, + ); } public function where(Specification $specification): self @@ -47,6 +66,7 @@ public function where(Specification $specification): self return new self( $this->table, $this->row, + $this->joins, Where::of($specification), ); } @@ -68,12 +88,61 @@ public function sql(Driver $driver): string ->values() ->map(static fn($value) => "{$value->columnSql($driver)} = ?"); + if ($driver === Driver::mysql) { + /** @var non-empty-string */ + return \sprintf( + 'UPDATE %s%s SET %s %s', + $this->table->sql($driver), + $this + ->joins + ->map(static fn($join) => $join->sql($driver)) + ->map(Str::of(...)) + ->fold(new Concat) + ->toString(), + Str::of(', ')->join($columns)->toString(), + $this->where->sql($driver), + ); + } + + $where = $this->where->sql($driver); + [$from, $where] = $this->joins->match( + static fn($first, $rest) => [ + \sprintf( + ' FROM %s%s', + $first->table()->sql($driver), + $rest + ->map(static fn($join) => $join->sql($driver)) + ->map(Str::of(...)) + ->fold(new Concat) + ->toString(), + ), + $first->condition()->match( + static function($pair) use ($driver, $where) { + [$left, $right] = $pair; + $condition = \sprintf( + '%s = %s', + $left->sql($driver), + $right->sql($driver), + ); + + return match ($where) { + '' => 'WHERE '.$condition, + default => $where.' AND '.$condition, + }; + }, + static fn() => $where, + ), + ], + static fn() => ['', $where], + ); + /** @var non-empty-string */ return \sprintf( - 'UPDATE %s SET %s %s', + 'UPDATE %s SET %s%s %s', $this->table->sql($driver), Str::of(', ')->join($columns)->toString(), - $this->where->sql($driver), + $from, + $where, ); }