Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Generic TableGateway implementation, without select()
- Loading branch information
1 parent
0146df7
commit 724cb82
Showing
9 changed files
with
763 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of sad_spirit/pg_gateway package | ||
* | ||
* (c) Alexey Borzov <avb@php.net> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace sad_spirit\pg_gateway\fragments; | ||
|
||
use sad_spirit\pg_gateway\{ | ||
Fragment, | ||
ParameterHolder, | ||
Parametrized, | ||
SelectProxy, | ||
exceptions\InvalidArgumentException | ||
}; | ||
use sad_spirit\pg_builder\{ | ||
Insert, | ||
Statement | ||
}; | ||
|
||
/** | ||
* Wrapper for SelectProxy object passed as $values to GenericTableGateway::insert() | ||
*/ | ||
class InsertSelectFragment implements Fragment, Parametrized | ||
{ | ||
private SelectProxy $select; | ||
|
||
public function __construct(SelectProxy $select) | ||
{ | ||
$this->select = $select; | ||
} | ||
|
||
public function applyTo(Statement $statement): void | ||
{ | ||
if (!$statement instanceof Insert) { | ||
throw new InvalidArgumentException(\sprintf( | ||
"This fragment can only be added to INSERT statements, instance of %s given", | ||
\get_class($statement) | ||
)); | ||
} | ||
$statement->values = $this->select->createSelectAST(); | ||
} | ||
|
||
public function getPriority(): int | ||
{ | ||
return Fragment::PRIORITY_HIGHEST; | ||
} | ||
|
||
public function getKey(): ?string | ||
{ | ||
return $this->select->getKey(); | ||
} | ||
|
||
public function getParameterHolder(): ?ParameterHolder | ||
{ | ||
return $this->select->getParameterHolder(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of sad_spirit/pg_gateway package | ||
* | ||
* (c) Alexey Borzov <avb@php.net> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace sad_spirit\pg_gateway\gateways; | ||
|
||
use sad_spirit\pg_gateway\{ | ||
FragmentList, | ||
SelectProxy, | ||
TableGateway, | ||
TableLocator, | ||
fragments\ClosureFragment, | ||
fragments\InsertSelectFragment, | ||
fragments\SetClauseFragment, | ||
exceptions\InvalidArgumentException, | ||
exceptions\LogicException, | ||
metadata\Columns, | ||
metadata\PrimaryKey, | ||
metadata\References | ||
}; | ||
use sad_spirit\pg_builder\{ | ||
Delete, | ||
Insert, | ||
NativeStatement, | ||
SelectCommon, | ||
Update | ||
}; | ||
use sad_spirit\pg_builder\nodes\{ | ||
Identifier, | ||
QualifiedName, | ||
lists\SetClauseList, | ||
range\InsertTarget, | ||
range\UpdateOrDeleteTarget | ||
}; | ||
use sad_spirit\pg_wrapper\{ | ||
Connection, | ||
ResultSet | ||
}; | ||
|
||
/** | ||
* A generic implementation of TableGateway | ||
*/ | ||
class GenericTableGateway implements TableGateway | ||
{ | ||
private QualifiedName $name; | ||
protected TableLocator $tableLocator; | ||
private ?Columns $columns = null; | ||
private ?PrimaryKey $primaryKey = null; | ||
private ?References $references = null; | ||
|
||
public function __construct(QualifiedName $name, TableLocator $tableLocator) | ||
{ | ||
$this->name = $name; | ||
$this->tableLocator = $tableLocator; | ||
} | ||
|
||
public function getName(): QualifiedName | ||
{ | ||
return clone $this->name; | ||
} | ||
|
||
public function getConnection(): Connection | ||
{ | ||
return $this->tableLocator->getConnection(); | ||
} | ||
|
||
public function getColumns(): Columns | ||
{ | ||
return $this->columns ??= new Columns($this->getConnection(), $this->name); | ||
} | ||
|
||
public function getPrimaryKey(): PrimaryKey | ||
{ | ||
return $this->primaryKey ??= new PrimaryKey($this->getConnection(), $this->name); | ||
} | ||
|
||
public function getReferences(): References | ||
{ | ||
return $this->references ??= new References($this->getConnection(), $this->name); | ||
} | ||
|
||
public function delete($fragments = null, array $parameters = []): ResultSet | ||
{ | ||
$fragmentList = FragmentList::normalize($fragments) | ||
->mergeParameters($parameters); | ||
|
||
return $this->execute($this->createDeleteStatement($fragmentList), $fragmentList); | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
* @psalm-suppress RedundantConditionGivenDocblockType | ||
*/ | ||
public function insert($values, $fragments = null, array $parameters = []): ResultSet | ||
{ | ||
$fragmentList = FragmentList::normalize($fragments) | ||
->mergeParameters($parameters); | ||
|
||
if ($values instanceof SelectProxy) { | ||
$fragmentList->add(new InsertSelectFragment($values)); | ||
} elseif ($values instanceof SelectCommon) { | ||
$fragmentList->add(new ClosureFragment( | ||
static function (Insert $insert) use ($values) { | ||
$insert->values = $values; | ||
} | ||
)); | ||
} elseif (\is_array($values)) { | ||
if ([] !== $values) { | ||
$fragmentList->add(new SetClauseFragment( | ||
$this->getColumns(), | ||
$this->tableLocator, | ||
$values | ||
)); | ||
} | ||
} else { | ||
throw new InvalidArgumentException(sprintf( | ||
"\$values should be either of: an array, an instance of SelectCommon," | ||
. " an implementation of SelectProxy; %s given", | ||
\is_object($values) ? 'object(' . \get_class($values) . ')' : \gettype($values) | ||
)); | ||
} | ||
|
||
return $this->execute($this->createInsertStatement($fragmentList), $fragmentList); | ||
} | ||
|
||
public function select($fragments = null, array $parameters = []): SelectProxy | ||
{ | ||
throw new LogicException('Not implemented yet'); | ||
} | ||
|
||
public function update(array $set, $fragments = null, array $parameters = []): ResultSet | ||
{ | ||
$native = $this->createUpdateStatement($list = new FragmentList( | ||
new SetClauseFragment($this->getColumns(), $this->tableLocator, $set), | ||
FragmentList::normalize($fragments) | ||
->mergeParameters($parameters) | ||
)); | ||
|
||
return $this->execute($native, $list); | ||
} | ||
|
||
/** | ||
* Executes the given $statement possibly using parameters from $fragments | ||
* | ||
* @param NativeStatement $statement | ||
* @param FragmentList $fragments | ||
* @return ResultSet | ||
*/ | ||
private function execute(NativeStatement $statement, FragmentList $fragments): ResultSet | ||
{ | ||
return [] === $statement->getParameterTypes() | ||
? $this->getConnection()->execute($statement->getSql()) | ||
: $statement->executeParams($this->getConnection(), $fragments->getParameters()); | ||
} | ||
|
||
/** | ||
* Generates a DELETE statement using given fragments | ||
* | ||
* @param FragmentList $fragments | ||
* @return NativeStatement | ||
*/ | ||
public function createDeleteStatement(FragmentList $fragments): NativeStatement | ||
{ | ||
return $this->tableLocator->createNativeStatementUsingCache( | ||
function () use ($fragments): Delete { | ||
$delete = $this->tableLocator->getStatementFactory()->delete(new UpdateOrDeleteTarget( | ||
$this->getName(), | ||
new Identifier(self::ALIAS_SELF) | ||
)); | ||
$fragments->applyTo($delete); | ||
|
||
return $delete; | ||
}, | ||
$this->generateStatementKey(self::STATEMENT_DELETE, $fragments) | ||
); | ||
} | ||
|
||
/** | ||
* Generates an INSERT statement using given fragments | ||
* | ||
* @param FragmentList $fragments | ||
* @return NativeStatement | ||
*/ | ||
public function createInsertStatement(FragmentList $fragments): NativeStatement | ||
{ | ||
return $this->tableLocator->createNativeStatementUsingCache( | ||
function () use ($fragments): Insert { | ||
$insert = $this->tableLocator->getStatementFactory()->insert(new InsertTarget( | ||
$this->getName(), | ||
new Identifier(TableGateway::ALIAS_SELF) | ||
)); | ||
$fragments->applyTo($insert); | ||
return $insert; | ||
}, | ||
$this->generateStatementKey(self::STATEMENT_INSERT, $fragments) | ||
); | ||
} | ||
|
||
/** | ||
* Generates an UPDATE statement using given fragments | ||
* | ||
* @param FragmentList $fragments | ||
* @return NativeStatement | ||
*/ | ||
public function createUpdateStatement(FragmentList $fragments): NativeStatement | ||
{ | ||
return $this->tableLocator->createNativeStatementUsingCache( | ||
function () use ($fragments): Update { | ||
$update = $this->tableLocator->getStatementFactory()->update( | ||
new UpdateOrDeleteTarget( | ||
$this->getName(), | ||
new Identifier(TableGateway::ALIAS_SELF) | ||
), | ||
new SetClauseList() | ||
); | ||
$fragments->applyTo($update); | ||
return $update; | ||
}, | ||
$this->generateStatementKey(self::STATEMENT_UPDATE, $fragments) | ||
); | ||
} | ||
|
||
/** | ||
* Returns a cache key for the statement being generated | ||
*/ | ||
protected function generateStatementKey(string $statementType, FragmentList $fragments): ?string | ||
{ | ||
if (null === ($fragmentKey = $fragments->getKey())) { | ||
return null; | ||
} | ||
return \sprintf( | ||
'%s.%s.%s.%s', | ||
$this->getConnection()->getConnectionId(), | ||
$statementType, | ||
TableLocator::hash($this->getName()), | ||
$fragmentKey | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
-- Fixture for DeleteTest | ||
|
||
create table victim ( | ||
id integer not null | ||
); | ||
|
||
insert into victim | ||
values (1), | ||
(2), | ||
(3), | ||
(10); | ||
|
||
create table foo ( | ||
id integer not null, | ||
name text not null, | ||
constraint foo_pkey primary key (id) | ||
); | ||
|
||
insert into foo values (1, 'one'); | ||
insert into foo values (2, 'two'); | ||
insert into foo values (3, 'many'); | ||
|
||
|
||
create table bar ( | ||
id integer not null, | ||
foo_id integer null, | ||
name text not null, | ||
constraint bar_pkey primary key (id), | ||
constraint foo_fkey foreign key (foo_id) | ||
references foo (id) | ||
on delete restrict | ||
); | ||
|
||
insert into bar values (1, null, 'some stuff'); | ||
insert into bar values (2, 2, 'a pair of something'); | ||
insert into bar values (3, 2, 'a third one'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
-- Cleanup after DeleteTest | ||
|
||
drop table if exists victim; | ||
|
||
drop table if exists foo cascade; | ||
|
||
drop table if exists bar; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
-- Fixture for InsertTest | ||
|
||
create table insert_test ( | ||
id integer not null generated by default as identity, | ||
title text default 'Some default title', | ||
added timestamp with time zone default now() | ||
); | ||
|
||
create table source_test ( | ||
id integer, | ||
title text, | ||
|
||
constraint source_test_pkey primary key (id) | ||
); | ||
|
||
insert into source_test values (-1, 'Minus first title'); | ||
insert into source_test values (-2, 'Minus second title'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
-- Cleanup after InsertTest | ||
|
||
drop table if exists insert_test; | ||
|
||
drop table if exists source_test; |
Oops, something went wrong.