Skip to content
Closed
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
1 change: 1 addition & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
return Innmind\CodingStandard\CodingStandard::config([
'properties',
'fixtures',
'proofs',
'src',
]);
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [Unreleased]

### Added

- `Formal\AccessLayer\Query\Update::join()`

## 4.1.0 - 2025-03-21

### Added
Expand Down
80 changes: 79 additions & 1 deletion proofs/connection/pdo.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
Query\Select,
Query\Select\Join,
Query\DropTable,
Query\Update,
Row,
Table,
Table\Column,
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -509,7 +587,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,
Expand Down
5 changes: 3 additions & 2 deletions proofs/query/parameter/type.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
),
Expand Down
2 changes: 1 addition & 1 deletion proofs/query/where.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
75 changes: 72 additions & 3 deletions src/Query/Update.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use Formal\AccessLayer\{
Query,
Query\Select\Join,
Table\Name,
Row,
Driver,
Expand All @@ -13,6 +14,7 @@
use Innmind\Immutable\{
Sequence,
Str,
Monoid\Concat,
};

/**
Expand All @@ -22,15 +24,22 @@ final class Update implements Query
{
private Name|Name\Aliased $table;
private Row $row;
/** @var Sequence<Join> */
private Sequence $joins;
private Where $where;

/**
* @param Sequence<Join> $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;
}

Expand All @@ -39,14 +48,25 @@ 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
{
return new self(
$this->table,
$this->row,
$this->joins,
Where::of($specification),
);
}
Expand All @@ -70,12 +90,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,
);
}

Expand Down
Loading