Skip to content

Commit

Permalink
PostgresDriver: add support for multiple returning columns
Browse files Browse the repository at this point in the history
  • Loading branch information
roxblnfk committed Feb 5, 2024
1 parent 16047de commit 7608a17
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 22 deletions.
7 changes: 6 additions & 1 deletion src/Driver/CompilerCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@ public function compile(QueryParameters $params, string $prefix, FragmentInterfa
*/
protected function hashInsertQuery(QueryParameters $params, array $tokens): string
{
$hash = 'i_' . $tokens['table'] . implode('_', $tokens['columns']) . '_r' . ($tokens['return'] ?? '');
$hash = \sprintf(
'i_%s%s_r%s',
$tokens['table'],
\implode('_', $tokens['columns']),
\implode('_', (array)($tokens['return'] ?? []))
);
foreach ($tokens['values'] as $value) {
if ($value instanceof FragmentInterface) {
if ($value instanceof Expression || $value instanceof Fragment) {
Expand Down
6 changes: 3 additions & 3 deletions src/Driver/Postgres/PostgresCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ protected function insertQuery(QueryParameters $params, Quoter $q, array $tokens
{
$result = parent::insertQuery($params, $q, $tokens);

if ($tokens['return'] === null) {
if (empty($tokens['return'])) {
return $result;
}

return sprintf(
return \sprintf(
'%s RETURNING %s',
$result,
$this->quoteIdentifier($tokens['return'])
\implode(',', \array_map([$this, 'quoteIdentifier'], $tokens['return']))
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Driver/Postgres/PostgresDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public function shouldUseDefinedSchemas(): bool
public function getPrimaryKey(string $prefix, string $table): ?string
{
$name = $prefix . $table;
if (array_key_exists($name, $this->primaryKeys)) {
if (\array_key_exists($name, $this->primaryKeys)) {
return $this->primaryKeys[$name];
}

Expand Down
37 changes: 21 additions & 16 deletions src/Driver/Postgres/Query/PostgresInsertQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Cycle\Database\Query\InsertQuery;
use Cycle\Database\Query\QueryInterface;
use Cycle\Database\Query\QueryParameters;
use Cycle\Database\StatementInterface;
use Throwable;

/**
Expand All @@ -30,8 +31,12 @@ class PostgresInsertQuery extends InsertQuery implements ReturningInterface
/** @var PostgresDriver|null */
protected ?DriverInterface $driver = null;

/** @deprecated */
protected ?string $returning = null;

/** @var list<FragmentInterface|non-empty-string> */
protected array $returningColumns = [];

public function withDriver(DriverInterface $driver, string $prefix = null): QueryInterface
{
$driver instanceof PostgresDriver or throw new BuilderException(
Expand All @@ -48,13 +53,9 @@ public function returning(string|FragmentInterface ...$columns): self
{
$columns === [] and throw new BuilderException('RETURNING clause should contain at least 1 column.');

if (count($columns) > 1) {
throw new BuilderException(
'Postgres driver supports only single column returning at this moment.'
);
}
$this->returning = \count($columns) === 1 ? \reset($columns) : null;

$this->returning = (string)$columns[0];
$this->returningColumns = \array_values($columns);

return $this;
}
Expand All @@ -69,6 +70,15 @@ public function run(): mixed
$result = $this->driver->query($queryString, $params->getParameters());

try {
if ($this->returningColumns !== []) {
if (\count($this->returningColumns) === 1) {
return $result->fetchColumn();
}

return $result->fetch(StatementInterface::FETCH_ASSOC);
}

// Return PK if no RETURNING clause is set
if ($this->getPrimaryKey() !== null) {
return $result->fetchColumn();
}
Expand All @@ -83,23 +93,18 @@ public function getTokens(): array
{
return [
'table' => $this->table,
'return' => $this->getPrimaryKey(),
'return' => $this->returningColumns,
'columns' => $this->columns,
'values' => $this->values,
];
}

private function getPrimaryKey(): ?string
{
$primaryKey = $this->returning;
if ($primaryKey === null && $this->driver !== null && $this->table !== null) {
try {
$primaryKey = $this->driver->getPrimaryKey($this->prefix, $this->table);
} catch (Throwable) {
return null;
}
try {
return $this->driver?->getPrimaryKey($this->prefix, $this->table);
} catch (Throwable) {
return null;
}

return $primaryKey;
}
}
3 changes: 2 additions & 1 deletion src/Query/InsertQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,9 @@ public function values(mixed $rowsets): self

/**
* Run the query and return last insert id.
* Returns an assoc array of values if multiple columns were specified as returning columns.
*
* @psalm-return int|non-empty-string|null
* @return array<non-empty-string, mixed>|int|non-empty-string|null
*/
public function run(): mixed
{
Expand Down
9 changes: 9 additions & 0 deletions src/Query/ReturningInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@

namespace Cycle\Database\Query;

use Cycle\Database\Exception\BuilderException;
use Cycle\Database\Injection\FragmentInterface;

interface ReturningInterface extends QueryInterface
{
/**
* Set returning column or expression.
*
* If set multiple columns and the driver supports it, then an insert result will be an array of values.
* If set one column and the driver supports it, then an insert result will be a single value,
* not an array of values.
*
* If set multiple columns and the driver does not support it, an exception will be thrown.
*
* @throws BuilderException
*/
public function returning(string|FragmentInterface ...$columns): self;
}

0 comments on commit 7608a17

Please sign in to comment.