Skip to content

Commit

Permalink
Fluent: add ON CONFLICT for INSERT [WIP: Docs]
Browse files Browse the repository at this point in the history
  • Loading branch information
forrest79 committed Jan 17, 2024
1 parent ee1c566 commit 0633c49
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 12 deletions.
11 changes: 11 additions & 0 deletions src/Fluent/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,17 @@ public function rows(array $rows): Query
}


/**
* @param mixed ...$params
* @return QueryExecute
* @throws Exceptions\QueryException
*/
public function onConflict(string $targetAndAction, ...$params): Query
{
return $this->createQuery()->onConflict($targetAndAction, ...$params);
}


/**
* @return QueryExecute
* @throws Exceptions\QueryException
Expand Down
52 changes: 52 additions & 0 deletions src/Fluent/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class Query implements Sql
public const PARAM_OFFSET = 'offset';
public const PARAM_COMBINE_QUERIES = 'combine-queries';
public const PARAM_INSERT_COLUMNS = 'insert-columns';
public const PARAM_INSERT_ONCONFLICT = 'insert-onconflict';
public const PARAM_RETURNING = 'returning';
public const PARAM_DATA = 'data';
public const PARAM_ROWS = 'rows';
Expand Down Expand Up @@ -82,6 +83,7 @@ class Query implements Sql
self::PARAM_OFFSET => NULL,
self::PARAM_COMBINE_QUERIES => [],
self::PARAM_INSERT_COLUMNS => [],
self::PARAM_INSERT_ONCONFLICT => NULL,
self::PARAM_RETURNING => [],
self::PARAM_DATA => [],
self::PARAM_ROWS => [],
Expand Down Expand Up @@ -162,7 +164,9 @@ public function select(array $columns): self
public function distinct(): self
{
$this->resetQuery();

$this->params[self::PARAM_DISTINCT] = TRUE;

return $this;
}

Expand Down Expand Up @@ -335,7 +339,9 @@ private function addTable(string $type, $name, ?string $alias, $onCondition = NU
public function on(string $alias, $condition, ...$params): self
{
$this->resetQuery();

$this->getComplexParam(self::PARAM_ON_CONDITIONS, $alias)->add($condition, ...$params);

return $this;
}

Expand All @@ -359,7 +365,9 @@ public function lateral(string $alias): self
public function where($condition, ...$params): self
{
$this->resetQuery();

$this->getComplexParam(self::PARAM_WHERE)->add($condition, ...$params);

return $this;
}

Expand All @@ -371,8 +379,10 @@ public function where($condition, ...$params): self
public function whereAnd(array $conditions = []): Complex
{
$this->resetQuery();

$complex = Complex::createAnd($conditions, NULL, $this);
$this->getComplexParam(self::PARAM_WHERE)->add($complex);

return $complex;
}

Expand All @@ -384,8 +394,10 @@ public function whereAnd(array $conditions = []): Complex
public function whereOr(array $conditions = []): Complex
{
$this->resetQuery();

$complex = Complex::createOr($conditions, NULL, $this);
$this->getComplexParam(self::PARAM_WHERE)->add($complex);

return $complex;
}

Expand All @@ -398,7 +410,9 @@ public function whereOr(array $conditions = []): Complex
public function groupBy(string ...$columns): self
{
$this->resetQuery();

$this->params[self::PARAM_GROUPBY] = \array_merge($this->params[self::PARAM_GROUPBY], $columns);

return $this;
}

Expand All @@ -412,7 +426,9 @@ public function groupBy(string ...$columns): self
public function having($condition, ...$params): self
{
$this->resetQuery();

$this->getComplexParam(self::PARAM_HAVING)->add($condition, ...$params);

return $this;
}

Expand All @@ -424,8 +440,10 @@ public function having($condition, ...$params): self
public function havingAnd(array $conditions = []): Complex
{
$this->resetQuery();

$complex = Complex::createAnd($conditions, NULL, $this);
$this->getComplexParam(self::PARAM_HAVING)->add($complex);

return $complex;
}

Expand All @@ -437,8 +455,10 @@ public function havingAnd(array $conditions = []): Complex
public function havingOr(array $conditions = []): Complex
{
$this->resetQuery();

$complex = Complex::createOr($conditions, NULL, $this);
$this->getComplexParam(self::PARAM_HAVING)->add($complex);

return $complex;
}

Expand Down Expand Up @@ -469,7 +489,9 @@ private function getComplexParam(string $param, ?string $alias = NULL): Complex
public function orderBy(...$columns): self
{
$this->resetQuery();

$this->params[self::PARAM_ORDERBY] = \array_merge($this->params[self::PARAM_ORDERBY], $columns);

return $this;
}

Expand All @@ -481,7 +503,9 @@ public function orderBy(...$columns): self
public function limit(int $limit): self
{
$this->resetQuery();

$this->params[self::PARAM_LIMIT] = $limit;

return $this;
}

Expand All @@ -493,7 +517,9 @@ public function limit(int $limit): self
public function offset(int $offset): self
{
$this->resetQuery();

$this->params[self::PARAM_OFFSET] = $offset;

return $this;
}

Expand Down Expand Up @@ -578,8 +604,10 @@ public function insert(?string $into = NULL, ?array $columns = []): self
public function values(array $data): self
{
$this->resetQuery();

$this->queryType = self::QUERY_INSERT;
$this->params[self::PARAM_DATA] = $data + $this->params[self::PARAM_DATA];

return $this;
}

Expand All @@ -600,6 +628,22 @@ public function rows(array $rows): self
}


/**
* @param mixed ...$params
* @return static
* @throws Exceptions\QueryException
*/
public function onConflict(string $targetAndAction, ...$params): Query
{
$this->resetQuery();

\array_unshift($params, $targetAndAction);
$this->params[self::PARAM_INSERT_ONCONFLICT] = $params;

return $this;
}


/**
* @return static
* @throws Exceptions\QueryException
Expand All @@ -626,8 +670,10 @@ public function update(?string $table = NULL, ?string $alias = NULL): self
public function set(array $data): self
{
$this->resetQuery();

$this->queryType = self::QUERY_UPDATE;
$this->params[self::PARAM_DATA] = $data + $this->params[self::PARAM_DATA];

return $this;
}

Expand Down Expand Up @@ -658,7 +704,9 @@ public function delete(?string $from = NULL, ?string $alias = NULL): self
public function returning(array $returning): self
{
$this->resetQuery();

$this->params[self::PARAM_RETURNING] = \array_merge($this->params[self::PARAM_RETURNING], $returning);

return $this;
}

Expand Down Expand Up @@ -796,8 +844,10 @@ public function recursive(): self
public function prefix(string $queryPrefix, ...$params): self
{
$this->resetQuery();

\array_unshift($params, $queryPrefix);
$this->params[self::PARAM_PREFIX][] = $params;

return $this;
}

Expand All @@ -810,8 +860,10 @@ public function prefix(string $queryPrefix, ...$params): self
public function suffix(string $querySuffix, ...$params): self
{
$this->resetQuery();

\array_unshift($params, $querySuffix);
$this->params[self::PARAM_SUFFIX][] = $params;

return $this;
}

Expand Down
13 changes: 12 additions & 1 deletion src/Fluent/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* offset: int|NULL,
* combine-queries: list<array{0: string|Query|Db\Sql, 1: string}>,
* insert-columns: array<string>,
* insert-onconflict: array<mixed>|NULL,
* returning: array<int|string, string|int|Query|Db\Sql>,
* data: array<string, mixed>,
* rows: array<int, array<string, mixed>>,
Expand Down Expand Up @@ -163,10 +164,20 @@ private function createInsert(array $queryParams, array &$params): string
$data = ' VALUES(' . \implode('), (', $rows) . ')';
}

$onConflict = '';
$onConflictParams = $queryParams[Query::PARAM_INSERT_ONCONFLICT];
if ($onConflictParams !== NULL) {
$onConflict = ' ON CONFLICT ' . \array_shift($onConflictParams);

foreach ($onConflictParams as $param) {
$params[] = $param;
}
}

return $insert
. ($columns === ['*'] ? '' : '(' . \implode(', ', $columns) . ')')
. $data
. $this->getPrefixSuffix($queryParams, Query::PARAM_SUFFIX, $params)
. $onConflict
. $this->getReturning($queryParams, $params);
}

Expand Down
6 changes: 6 additions & 0 deletions src/Fluent/Sql.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@ function values(array $data): Query;
function rows(array $rows): Query;


/**
* @param mixed ...$params
*/
function onConflict(string $targetAndAction, ...$params): Query;


function update(?string $table = NULL, ?string $alias = NULL): Query;


Expand Down
17 changes: 17 additions & 0 deletions tests/Unit/FluentConnectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,23 @@ public function testRows(): void
}


public function testOnConflict(): void
{
$query = $this->fluentConnection
->onConflict('(name) DO UPDATE SET info = excluded.info')
->insert('table')
->values([
'name' => 'Bob',
'info' => 'Text',
])
->createSqlQuery()
->createQuery();

Tester\Assert::same('INSERT INTO table(name, info) VALUES($1, $2) ON CONFLICT (name) DO UPDATE SET info = excluded.info', $query->getSql());
Tester\Assert::same(['Bob', 'Text'], $query->getParams());
}


public function testUpdate(): void
{
$query = $this->fluentConnection
Expand Down
80 changes: 69 additions & 11 deletions tests/Unit/FluentQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,75 @@ public function testInsertSelectAllColumns(): void
}


public function testInsertOnConflict(): void
{
$query = $this->query()
->insert('table')
->values([
'name' => 'Bob',
'info' => 'Text',
])
->onConflict('(name) DO UPDATE SET info = excluded.info')
->createSqlQuery()
->createQuery();

Tester\Assert::same('INSERT INTO table(name, info) VALUES($1, $2) ON CONFLICT (name) DO UPDATE SET info = excluded.info', $query->getSql());
Tester\Assert::same(['Bob', 'Text'], $query->getParams());
}


public function testInsertOnConflictParams(): void
{
$query = $this->query()
->insert('table')
->values([
'name' => 'Bob',
'info' => 'Text',
])
->onConflict('(name) DO UPDATE SET info = excluded.info WHERE name != ?', 'Igor')
->createSqlQuery()
->createQuery();

Tester\Assert::same('INSERT INTO table(name, info) VALUES($1, $2) ON CONFLICT (name) DO UPDATE SET info = excluded.info WHERE name != $3', $query->getSql());
Tester\Assert::same(['Bob', 'Text', 'Igor'], $query->getParams());
}


public function testInsertOnConflictDoNothing(): void
{
$query = $this->query()
->insert('table')
->values([
'name' => 'Bob',
'info' => 'Text',
])
->onConflict(Fluent\Query::DO_NOTHING)
->createSqlQuery()
->createQuery();

Tester\Assert::same('INSERT INTO table(name, info) VALUES($1, $2) ON CONFLICT DO NOTHING', $query->getSql());
Tester\Assert::same(['Bob', 'Text'], $query->getParams());
}


public function testInsertOnConflictWithReturning(): void
{
$query = $this->query()
->insert('table')
->values([
'name' => 'Bob',
'info' => 'Text',
])
->onConflict('(name) DO UPDATE SET info = excluded.info')
->returning(['id'])
->createSqlQuery()
->createQuery();

Tester\Assert::same('INSERT INTO table(name, info) VALUES($1, $2) ON CONFLICT (name) DO UPDATE SET info = excluded.info RETURNING id', $query->getSql());
Tester\Assert::same(['Bob', 'Text'], $query->getParams());
}


public function testInsertSelectAllColumnsWithConcrete(): void
{
Tester\Assert::exception(function (): void {
Expand Down Expand Up @@ -1345,17 +1414,6 @@ public function testSimpleSuffix(): void

public function testSuffixWithReturning(): void
{
$query = $this->query()
->insert('table')
->values(['column' => 'value'])
->suffix('ON CONFLICT (column) DO NOTHING')
->returning(['column'])
->createSqlQuery()
->createQuery();

Tester\Assert::same('INSERT INTO table(column) VALUES($1) ON CONFLICT (column) DO NOTHING RETURNING column', $query->getSql());
Tester\Assert::same(['value'], $query->getParams());

$query = $this->query()
->update('table')
->set(['column' => 'value'])
Expand Down

0 comments on commit 0633c49

Please sign in to comment.