Skip to content
Open
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
10 changes: 10 additions & 0 deletions src/BaseSeed.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,16 @@ public function insertOrSkip(string $tableName, array $data): void
$table->insertOrSkip($data)->save();
}

/**
* {@inheritDoc}
*/
public function insertOrUpdate(string $tableName, array $data, array $updateColumns, array $conflictColumns): void
{
// convert to table object
$table = new Table($tableName, [], $this->getAdapter());
$table->insertOrUpdate($data, $updateColumns, $conflictColumns)->save();
}

/**
* {@inheritDoc}
*/
Expand Down
79 changes: 65 additions & 14 deletions src/Db/Adapter/AbstractAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -622,9 +622,14 @@ public function fetchAll(string $sql): array
/**
* @inheritDoc
*/
public function insert(TableMetadata $table, array $row, ?InsertMode $mode = null): void
{
$sql = $this->generateInsertSql($table, $row, $mode);
public function insert(
TableMetadata $table,
array $row,
?InsertMode $mode = null,
?array $updateColumns = null,
?array $conflictColumns = null,
): void {
$sql = $this->generateInsertSql($table, $row, $mode, $updateColumns, $conflictColumns);

if ($this->isDryRunEnabled()) {
$this->io->out($sql);
Expand All @@ -649,10 +654,17 @@ public function insert(TableMetadata $table, array $row, ?InsertMode $mode = nul
* @param \Migrations\Db\Table\TableMetadata $table The table to insert into
* @param array $row The row to insert
* @param \Migrations\Db\InsertMode|null $mode Insert mode
* @param array<string>|null $updateColumns Columns to update on upsert conflict
* @param array<string>|null $conflictColumns Columns that define uniqueness for upsert (unused in MySQL)
* @return string
*/
protected function generateInsertSql(TableMetadata $table, array $row, ?InsertMode $mode = null): string
{
protected function generateInsertSql(
TableMetadata $table,
array $row,
?InsertMode $mode = null,
?array $updateColumns = null,
?array $conflictColumns = null,
): string {
$sql = sprintf(
'%s INTO %s ',
$this->getInsertPrefix($mode),
Expand All @@ -667,8 +679,10 @@ protected function generateInsertSql(TableMetadata $table, array $row, ?InsertMo
}
}

$upsertClause = $this->getUpsertClause($mode, $updateColumns, $conflictColumns);

if ($this->isDryRunEnabled()) {
$sql .= ' VALUES (' . implode(', ', array_map($this->quoteValue(...), $row)) . ');';
$sql .= ' VALUES (' . implode(', ', array_map($this->quoteValue(...), $row)) . ')' . $upsertClause . ';';

return $sql;
} else {
Expand All @@ -680,7 +694,7 @@ protected function generateInsertSql(TableMetadata $table, array $row, ?InsertMo
}
$values[] = $placeholder;
}
$sql .= ' VALUES (' . implode(',', $values) . ')';
$sql .= ' VALUES (' . implode(',', $values) . ')' . $upsertClause;

return $sql;
}
Expand All @@ -701,6 +715,29 @@ protected function getInsertPrefix(?InsertMode $mode = null): string
return 'INSERT';
}

/**
* Get the upsert clause for MySQL (ON DUPLICATE KEY UPDATE).
*
* @param \Migrations\Db\InsertMode|null $mode Insert mode
* @param array<string>|null $updateColumns Columns to update on conflict
* @param array<string>|null $conflictColumns Columns that define uniqueness (unused in MySQL)
* @return string
*/
protected function getUpsertClause(?InsertMode $mode, ?array $updateColumns, ?array $conflictColumns = null): string
{
if ($mode !== InsertMode::UPSERT || $updateColumns === null) {
return '';
}

$updates = [];
foreach ($updateColumns as $column) {
$quotedColumn = $this->quoteColumnName($column);
$updates[] = $quotedColumn . ' = VALUES(' . $quotedColumn . ')';
}

return ' ON DUPLICATE KEY UPDATE ' . implode(', ', $updates);
}

/**
* Quotes a database value.
*
Expand Down Expand Up @@ -748,9 +785,14 @@ protected function quoteString(string $value): string
/**
* @inheritDoc
*/
public function bulkinsert(TableMetadata $table, array $rows, ?InsertMode $mode = null): void
{
$sql = $this->generateBulkInsertSql($table, $rows, $mode);
public function bulkinsert(
TableMetadata $table,
array $rows,
?InsertMode $mode = null,
?array $updateColumns = null,
?array $conflictColumns = null,
): void {
$sql = $this->generateBulkInsertSql($table, $rows, $mode, $updateColumns, $conflictColumns);

if ($this->isDryRunEnabled()) {
$this->io->out($sql);
Expand Down Expand Up @@ -785,10 +827,17 @@ public function bulkinsert(TableMetadata $table, array $rows, ?InsertMode $mode
* @param \Migrations\Db\Table\TableMetadata $table The table to insert into
* @param array $rows The rows to insert
* @param \Migrations\Db\InsertMode|null $mode Insert mode
* @param array<string>|null $updateColumns Columns to update on upsert conflict
* @param array<string>|null $conflictColumns Columns that define uniqueness for upsert (unused in MySQL)
* @return string
*/
protected function generateBulkInsertSql(TableMetadata $table, array $rows, ?InsertMode $mode = null): string
{
protected function generateBulkInsertSql(
TableMetadata $table,
array $rows,
?InsertMode $mode = null,
?array $updateColumns = null,
?array $conflictColumns = null,
): string {
$sql = sprintf(
'%s INTO %s ',
$this->getInsertPrefix($mode),
Expand All @@ -799,11 +848,13 @@ protected function generateBulkInsertSql(TableMetadata $table, array $rows, ?Ins

$sql .= '(' . implode(', ', array_map($this->quoteColumnName(...), $keys)) . ') VALUES ';

$upsertClause = $this->getUpsertClause($mode, $updateColumns, $conflictColumns);

if ($this->isDryRunEnabled()) {
$values = array_map(function ($row) {
return '(' . implode(', ', array_map($this->quoteValue(...), $row)) . ')';
}, $rows);
$sql .= implode(', ', $values) . ';';
$sql .= implode(', ', $values) . $upsertClause . ';';

return $sql;
} else {
Expand All @@ -820,7 +871,7 @@ protected function generateBulkInsertSql(TableMetadata $table, array $rows, ?Ins
$query = '(' . implode(', ', $values) . ')';
$queries[] = $query;
}
$sql .= implode(',', $queries);
$sql .= implode(',', $queries) . $upsertClause;

return $sql;
}
Expand Down
20 changes: 18 additions & 2 deletions src/Db/Adapter/AdapterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -480,19 +480,35 @@ public function fetchAll(string $sql): array;
* @param \Migrations\Db\Table\TableMetadata $table Table where to insert data
* @param array $row Row
* @param \Migrations\Db\InsertMode|null $mode Insert mode
* @param array<string>|null $updateColumns Columns to update on upsert conflict
* @param array<string>|null $conflictColumns Columns that define uniqueness for upsert
* @return void
*/
public function insert(TableMetadata $table, array $row, ?InsertMode $mode = null): void;
public function insert(
TableMetadata $table,
array $row,
?InsertMode $mode = null,
?array $updateColumns = null,
?array $conflictColumns = null,
): void;

/**
* Inserts data into a table in a bulk.
*
* @param \Migrations\Db\Table\TableMetadata $table Table where to insert data
* @param array $rows Rows
* @param \Migrations\Db\InsertMode|null $mode Insert mode
* @param array<string>|null $updateColumns Columns to update on upsert conflict
* @param array<string>|null $conflictColumns Columns that define uniqueness for upsert
* @return void
*/
public function bulkinsert(TableMetadata $table, array $rows, ?InsertMode $mode = null): void;
public function bulkinsert(
TableMetadata $table,
array $rows,
?InsertMode $mode = null,
?array $updateColumns = null,
?array $conflictColumns = null,
): void;

/**
* Quotes a table name for use in a query.
Expand Down
22 changes: 16 additions & 6 deletions src/Db/Adapter/AdapterWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,27 @@ public function query(string $sql, array $params = []): mixed
/**
* @inheritDoc
*/
public function insert(TableMetadata $table, array $row, ?InsertMode $mode = null): void
{
$this->getAdapter()->insert($table, $row, $mode);
public function insert(
TableMetadata $table,
array $row,
?InsertMode $mode = null,
?array $updateColumns = null,
?array $conflictColumns = null,
): void {
$this->getAdapter()->insert($table, $row, $mode, $updateColumns, $conflictColumns);
}

/**
* @inheritDoc
*/
public function bulkinsert(TableMetadata $table, array $rows, ?InsertMode $mode = null): void
{
$this->getAdapter()->bulkinsert($table, $rows, $mode);
public function bulkinsert(
TableMetadata $table,
array $rows,
?InsertMode $mode = null,
?array $updateColumns = null,
?array $conflictColumns = null,
): void {
$this->getAdapter()->bulkinsert($table, $rows, $mode, $updateColumns, $conflictColumns);
}

/**
Expand Down
42 changes: 34 additions & 8 deletions src/Db/Adapter/PostgresAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -1152,8 +1152,13 @@ public function setSearchPath(): void
/**
* @inheritDoc
*/
public function insert(TableMetadata $table, array $row, ?InsertMode $mode = null): void
{
public function insert(
TableMetadata $table,
array $row,
?InsertMode $mode = null,
?array $updateColumns = null,
?array $conflictColumns = null,
): void {
$sql = sprintf(
'INSERT INTO %s ',
$this->quoteTableName($table->getName()),
Expand All @@ -1172,7 +1177,7 @@ public function insert(TableMetadata $table, array $row, ?InsertMode $mode = nul
$override = self::OVERRIDE_SYSTEM_VALUE . ' ';
}

$conflictClause = $this->getConflictClause($mode);
$conflictClause = $this->getConflictClause($mode, $updateColumns, $conflictColumns);

if ($this->isDryRunEnabled()) {
$sql .= ' ' . $override . 'VALUES (' . implode(', ', array_map($this->quoteValue(...), $row)) . ')' . $conflictClause . ';';
Expand All @@ -1198,8 +1203,13 @@ public function insert(TableMetadata $table, array $row, ?InsertMode $mode = nul
/**
* @inheritDoc
*/
public function bulkinsert(TableMetadata $table, array $rows, ?InsertMode $mode = null): void
{
public function bulkinsert(
TableMetadata $table,
array $rows,
?InsertMode $mode = null,
?array $updateColumns = null,
?array $conflictColumns = null,
): void {
$sql = sprintf(
'INSERT INTO %s ',
$this->quoteTableName($table->getName()),
Expand All @@ -1215,7 +1225,7 @@ public function bulkinsert(TableMetadata $table, array $rows, ?InsertMode $mode

$sql .= '(' . implode(', ', array_map($this->quoteColumnName(...), $keys)) . ') ' . $override . 'VALUES ';

$conflictClause = $this->getConflictClause($mode);
$conflictClause = $this->getConflictClause($mode, $updateColumns, $conflictColumns);

if ($this->isDryRunEnabled()) {
$values = array_map(function ($row) {
Expand Down Expand Up @@ -1258,14 +1268,30 @@ public function bulkinsert(TableMetadata $table, array $rows, ?InsertMode $mode
* Get the ON CONFLICT clause based on insert mode.
*
* @param \Migrations\Db\InsertMode|null $mode Insert mode
* @param array<string>|null $updateColumns Columns to update on upsert conflict
* @param array<string>|null $conflictColumns Columns that define uniqueness for upsert
* @return string
*/
protected function getConflictClause(?InsertMode $mode = null): string
{
protected function getConflictClause(
?InsertMode $mode = null,
?array $updateColumns = null,
?array $conflictColumns = null,
): string {
if ($mode === InsertMode::IGNORE) {
return ' ON CONFLICT DO NOTHING';
}

if ($mode === InsertMode::UPSERT && $updateColumns !== null && $conflictColumns !== null) {
$quotedConflictColumns = array_map($this->quoteColumnName(...), $conflictColumns);
$updates = [];
foreach ($updateColumns as $column) {
$quotedColumn = $this->quoteColumnName($column);
$updates[] = $quotedColumn . ' = EXCLUDED.' . $quotedColumn;
}

return ' ON CONFLICT (' . implode(', ', $quotedConflictColumns) . ') DO UPDATE SET ' . implode(', ', $updates);
}

return '';
}

Expand Down
24 changes: 24 additions & 0 deletions src/Db/Adapter/SqliteAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -1702,4 +1702,28 @@ protected function getInsertPrefix(?InsertMode $mode = null): string

return 'INSERT';
}

/**
* Get the upsert clause for SQLite (ON CONFLICT ... DO UPDATE SET).
*
* @param \Migrations\Db\InsertMode|null $mode Insert mode
* @param array<string>|null $updateColumns Columns to update on conflict
* @param array<string>|null $conflictColumns Columns that define uniqueness for upsert
* @return string
*/
protected function getUpsertClause(?InsertMode $mode, ?array $updateColumns, ?array $conflictColumns = null): string
{
if ($mode !== InsertMode::UPSERT || $updateColumns === null || $conflictColumns === null) {
return '';
}

$quotedConflictColumns = array_map($this->quoteColumnName(...), $conflictColumns);
$updates = [];
foreach ($updateColumns as $column) {
$quotedColumn = $this->quoteColumnName($column);
$updates[] = $quotedColumn . ' = excluded.' . $quotedColumn;
}

return ' ON CONFLICT (' . implode(', ', $quotedConflictColumns) . ') DO UPDATE SET ' . implode(', ', $updates);
}
}
22 changes: 16 additions & 6 deletions src/Db/Adapter/SqlserverAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -1007,9 +1007,14 @@ public function migrated(MigrationInterface $migration, string $direction, strin
/**
* @inheritDoc
*/
public function insert(TableMetadata $table, array $row, ?InsertMode $mode = null): void
{
$sql = $this->generateInsertSql($table, $row, $mode);
public function insert(
TableMetadata $table,
array $row,
?InsertMode $mode = null,
?array $updateColumns = null,
?array $conflictColumns = null,
): void {
$sql = $this->generateInsertSql($table, $row, $mode, $updateColumns, $conflictColumns);

$sql = $this->updateSQLForIdentityInsert($table->getName(), $sql);

Expand All @@ -1033,9 +1038,14 @@ public function insert(TableMetadata $table, array $row, ?InsertMode $mode = nul
/**
* @inheritDoc
*/
public function bulkinsert(TableMetadata $table, array $rows, ?InsertMode $mode = null): void
{
$sql = $this->generateBulkInsertSql($table, $rows, $mode);
public function bulkinsert(
TableMetadata $table,
array $rows,
?InsertMode $mode = null,
?array $updateColumns = null,
?array $conflictColumns = null,
): void {
$sql = $this->generateBulkInsertSql($table, $rows, $mode, $updateColumns, $conflictColumns);

$sql = $this->updateSQLForIdentityInsert($table->getName(), $sql);

Expand Down
Loading