Skip to content

Commit 7ea6311

Browse files
committed
Resolve merge conflict with 5.x branch
Accepted the 5.x branch's documentation for insertOrUpdate() and insertOrSkip() which has the correct three-argument API signature.
2 parents bc30a22 + dee5b42 commit 7ea6311

18 files changed

+258
-112
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Migrations plugin for CakePHP
22

33
[![CI](https://github.com/cakephp/migrations/actions/workflows/ci.yml/badge.svg)](https://github.com/cakephp/migrations/actions/workflows/ci.yml)
4-
[![Coverage Status](https://img.shields.io/codecov/c/github/cakephp/migrations/3.x.svg?style=flat-square)](https://app.codecov.io/github/cakephp/migrations/tree/3.x)
4+
[![Coverage Status](https://img.shields.io/codecov/c/github/cakephp/migrations/5.x.svg?style=flat-square)](https://app.codecov.io/github/cakephp/migrations/tree/5.x)
55
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.txt)
66
[![Total Downloads](https://img.shields.io/packagist/dt/cakephp/migrations.svg?style=flat-square)](https://packagist.org/packages/cakephp/migrations)
77

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@
4646
},
4747
"autoload-dev": {
4848
"psr-4": {
49+
"Blog\\": "tests/test_app/Plugin/Blog/src/",
4950
"Cake\\Test\\": "./vendor/cakephp/cakephp/tests/",
5051
"Migrations\\Test\\": "tests/",
52+
"Migrator\\": "tests/test_app/Plugin/Migrator/src/",
5153
"SimpleSnapshot\\": "tests/test_app/Plugin/SimpleSnapshot/src/",
5254
"TestApp\\": "tests/test_app/App/",
5355
"TestBlog\\": "tests/test_app/Plugin/TestBlog/src/",

docs/en/seeding.rst

Lines changed: 47 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -450,87 +450,88 @@ within your seed class and then use the ``insert()`` method to insert data:
450450
You must call the ``saveData()`` method to commit your data to the table.
451451
Migrations will buffer data until you do so.
452452

453-
Upserting Data
454-
--------------
453+
Insert Modes
454+
============
455+
456+
In addition to the standard ``insert()`` method, Migrations provides specialized
457+
insert methods for handling conflicts with existing data.
455458

456-
.. versionadded:: 5.0.0
457-
``insertOrUpdate()`` and ``insertOrSkip()`` were added in 5.0.0.
459+
Insert or Skip
460+
--------------
458461

459-
For seeds that may be run multiple times, you can use ``insertOrUpdate()`` to insert
460-
new records or update existing ones based on conflict columns. This is particularly
461-
useful for configuration or reference data that should always reflect certain values:
462+
The ``insertOrSkip()`` method inserts rows but silently skips any that would
463+
violate a unique constraint:
462464

463465
.. code-block:: php
464466
465467
<?php
466468
467469
use Migrations\BaseSeed;
468470
469-
class ConfigSeed extends BaseSeed
471+
class CurrencySeed extends BaseSeed
470472
{
471473
public function run(): void
472474
{
473475
$data = [
474-
[
475-
'key' => 'site_name',
476-
'value' => 'My Application',
477-
],
478-
[
479-
'key' => 'maintenance_mode',
480-
'value' => 'false',
481-
],
476+
['code' => 'USD', 'name' => 'US Dollar'],
477+
['code' => 'EUR', 'name' => 'Euro'],
482478
];
483479
484-
$settings = $this->table('settings');
485-
// For PostgreSQL and SQLite, you must specify the conflict column(s)
486-
$settings->insertOrUpdate($data, ['key'])
487-
->saveData();
488-
489-
// For MySQL, conflict columns are optional (uses all unique constraints)
490-
// $settings->insertOrUpdate($data)->saveData();
480+
$this->table('currencies')
481+
->insertOrSkip($data)
482+
->saveData();
491483
}
492484
}
493485
494-
.. note::
495-
496-
The ``$conflictColumns`` parameter behavior differs by database:
486+
Insert or Update (Upsert)
487+
-------------------------
497488

498-
- **MySQL**: The parameter is ignored because MySQL's ``ON DUPLICATE KEY UPDATE``
499-
automatically applies to all unique constraints.
500-
- **PostgreSQL/SQLite**: The parameter is required and specifies which column(s)
501-
to use for conflict detection.
502-
503-
Insert or Skip
504-
~~~~~~~~~~~~~~
505-
506-
If you want to insert records only when they don't already exist (without updating),
507-
use the ``insertOrSkip()`` method:
489+
The ``insertOrUpdate()`` method performs an "upsert" operation - inserting new
490+
rows and updating existing rows that conflict on unique columns:
508491

509492
.. code-block:: php
510493
511494
<?php
512495
513496
use Migrations\BaseSeed;
514497
515-
class DefaultRolesSeed extends BaseSeed
498+
class ExchangeRateSeed extends BaseSeed
516499
{
517500
public function run(): void
518501
{
519502
$data = [
520-
['name' => 'admin', 'description' => 'Administrator'],
521-
['name' => 'user', 'description' => 'Regular User'],
522-
['name' => 'guest', 'description' => 'Guest User'],
503+
['code' => 'USD', 'rate' => 1.0000],
504+
['code' => 'EUR', 'rate' => 0.9234],
523505
];
524506
525-
$roles = $this->table('roles');
526-
// Skip inserting if a role with the same name already exists
527-
$roles->insertOrSkip($data, ['name'])
528-
->saveData();
507+
$this->table('exchange_rates')
508+
->insertOrUpdate($data, ['rate'], ['code'])
509+
->saveData();
529510
}
530511
}
531512
532-
This is useful for seeding default data that should not overwrite any customizations
533-
made by users.
513+
The method takes three arguments:
514+
515+
- ``$data``: The rows to insert (same format as ``insert()``)
516+
- ``$updateColumns``: Which columns to update when a conflict occurs
517+
- ``$conflictColumns``: Which columns define uniqueness (must have a unique index)
518+
519+
.. warning::
520+
521+
Database-specific behavior differences:
522+
523+
**MySQL**: Uses ``ON DUPLICATE KEY UPDATE``. The ``$conflictColumns`` parameter
524+
is ignored because MySQL automatically applies the update to *all* unique
525+
constraint violations on the table. Passing ``$conflictColumns`` will trigger
526+
a warning. If your table has multiple unique constraints, be aware that a
527+
conflict on *any* of them will trigger the update.
528+
529+
**PostgreSQL/SQLite**: Uses ``ON CONFLICT (...) DO UPDATE SET``. The
530+
``$conflictColumns`` parameter is required and specifies exactly which unique
531+
constraint should trigger the update. A ``RuntimeException`` will be thrown
532+
if this parameter is empty.
533+
534+
**SQL Server**: Not currently supported. Use separate insert/update logic.
534535

535536
Truncating Tables
536537
=================

docs/en/writing-migrations.rst

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2381,3 +2381,76 @@ Changing templates
23812381

23822382
See :ref:`custom-seed-migration-templates` for how to customize the templates
23832383
used to generate migrations.
2384+
2385+
Database-Specific Limitations
2386+
=============================
2387+
2388+
While Migrations aims to provide a database-agnostic API, some features have
2389+
database-specific limitations or are not available on all platforms.
2390+
2391+
SQL Server
2392+
----------
2393+
2394+
The following features are not supported on SQL Server:
2395+
2396+
**Check Constraints**
2397+
2398+
Check constraints are not currently implemented for SQL Server. Attempting to
2399+
use ``addCheckConstraint()`` or ``dropCheckConstraint()`` will throw a
2400+
``BadMethodCallException``.
2401+
2402+
**Table Comments**
2403+
2404+
SQL Server does not support table comments. Attempting to use ``changeComment()``
2405+
will throw a ``BadMethodCallException``.
2406+
2407+
**INSERT IGNORE / insertOrSkip()**
2408+
2409+
SQL Server does not support the ``INSERT IGNORE`` syntax used by ``insertOrSkip()``.
2410+
This method will throw a ``RuntimeException`` on SQL Server. Use ``insertOrUpdate()``
2411+
instead for upsert operations, which uses ``MERGE`` statements on SQL Server.
2412+
2413+
SQLite
2414+
------
2415+
2416+
**Foreign Key Names**
2417+
2418+
SQLite does not support named foreign keys. The foreign key constraint name option
2419+
is ignored when creating foreign keys on SQLite.
2420+
2421+
**Table Comments**
2422+
2423+
SQLite does not support table comments directly. Comments are stored as metadata
2424+
but not in the database itself.
2425+
2426+
**Check Constraint Modifications**
2427+
2428+
SQLite does not support ``ALTER TABLE`` operations for check constraints. Adding or
2429+
dropping check constraints requires recreating the entire table, which is handled
2430+
automatically by the adapter.
2431+
2432+
**Table Partitioning**
2433+
2434+
SQLite does not support table partitioning.
2435+
2436+
PostgreSQL
2437+
----------
2438+
2439+
**KEY Partitioning**
2440+
2441+
PostgreSQL does not support MySQL's ``KEY`` partitioning type. Use ``HASH``
2442+
partitioning instead for similar distribution behavior.
2443+
2444+
MySQL/MariaDB
2445+
-------------
2446+
2447+
**insertOrUpdate() Conflict Columns**
2448+
2449+
For MySQL, the ``$conflictColumns`` parameter in ``insertOrUpdate()`` is ignored
2450+
because MySQL's ``ON DUPLICATE KEY UPDATE`` automatically applies to all unique
2451+
constraints. PostgreSQL and SQLite require this parameter to be specified.
2452+
2453+
**MariaDB GIS/Geometry**
2454+
2455+
Some geometry column features may not work correctly on MariaDB due to differences
2456+
in GIS implementation compared to MySQL.

phpstan-baseline.neon

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,5 @@
11
parameters:
22
ignoreErrors:
3-
-
4-
message: '#^Call to an undefined method object\:\:loadHelper\(\)\.$#'
5-
identifier: method.notFound
6-
count: 1
7-
path: src/Command/BakeMigrationCommand.php
8-
9-
-
10-
message: '#^Call to an undefined method object\:\:loadHelper\(\)\.$#'
11-
identifier: method.notFound
12-
count: 1
13-
path: src/Command/BakeMigrationDiffCommand.php
14-
15-
-
16-
message: '#^Call to an undefined method object\:\:loadHelper\(\)\.$#'
17-
identifier: method.notFound
18-
count: 1
19-
path: src/Command/BakeMigrationSnapshotCommand.php
20-
213
-
224
message: '#^PHPDoc tag @var with type string is not subtype of native type non\-falsy\-string\|true\.$#'
235
identifier: varTag.nativeType

src/Command/BakeMigrationCommand.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ public static function defaultName(): string
4848
public function bake(string $name, Arguments $args, ConsoleIo $io): void
4949
{
5050
EventManager::instance()->on('Bake.initialize', function (Event $event): void {
51-
$event->getSubject()->loadHelper('Migrations.Migration');
51+
/** @var \Bake\View\BakeView $view */
52+
$view = $event->getSubject();
53+
$view->loadHelper('Migrations.Migration');
5254
});
5355
$this->_name = $name;
5456

src/Command/BakeMigrationDiffCommand.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ public function bake(string $name, Arguments $args, ConsoleIo $io): void
128128
assert($connection instanceof Connection);
129129

130130
EventManager::instance()->on('Bake.initialize', function (Event $event) use ($collection, $connection): void {
131-
$event->getSubject()->loadHelper('Migrations.Migration', [
131+
/** @var \Bake\View\BakeView $view */
132+
$view = $event->getSubject();
133+
$view->loadHelper('Migrations.Migration', [
132134
'collection' => $collection,
133135
'connection' => $connection,
134136
]);

src/Command/BakeMigrationSnapshotCommand.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ public function bake(string $name, Arguments $args, ConsoleIo $io): void
5959
assert($connection instanceof Connection);
6060

6161
EventManager::instance()->on('Bake.initialize', function (Event $event) use ($collection, $connection): void {
62-
$event->getSubject()->loadHelper('Migrations.Migration', [
62+
/** @var \Bake\View\BakeView $view */
63+
$view = $event->getSubject();
64+
$view->loadHelper('Migrations.Migration', [
6365
'collection' => $collection,
6466
'connection' => $connection,
6567
]);

src/Db/Adapter/AbstractAdapter.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,10 @@ protected function getInsertPrefix(?InsertMode $mode = null): string
730730
/**
731731
* Get the upsert clause for MySQL (ON DUPLICATE KEY UPDATE).
732732
*
733+
* MySQL's ON DUPLICATE KEY UPDATE applies to all unique key constraints on the table,
734+
* so the $conflictColumns parameter is not used. If you pass conflictColumns when using
735+
* MySQL, a warning will be triggered.
736+
*
733737
* @param \Migrations\Db\InsertMode|null $mode Insert mode
734738
* @param array<string>|null $updateColumns Columns to update on conflict
735739
* @param array<string>|null $conflictColumns Columns that define uniqueness (unused in MySQL)
@@ -741,6 +745,14 @@ protected function getUpsertClause(?InsertMode $mode, ?array $updateColumns, ?ar
741745
return '';
742746
}
743747

748+
if ($conflictColumns !== null) {
749+
trigger_error(
750+
'The $conflictColumns parameter is ignored by MySQL. ' .
751+
'MySQL\'s ON DUPLICATE KEY UPDATE applies to all unique constraints on the table.',
752+
E_USER_WARNING,
753+
);
754+
}
755+
744756
$updates = [];
745757
foreach ($updateColumns as $column) {
746758
$quotedColumn = $this->quoteColumnName($column);

src/Db/Adapter/PostgresAdapter.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,10 +1288,15 @@ public function bulkinsert(
12881288
/**
12891289
* Get the ON CONFLICT clause based on insert mode.
12901290
*
1291+
* PostgreSQL requires explicit conflict columns to determine which unique constraint
1292+
* should trigger the update. Unlike MySQL's ON DUPLICATE KEY UPDATE which applies
1293+
* to all unique constraints, PostgreSQL's ON CONFLICT clause must specify the columns.
1294+
*
12911295
* @param \Migrations\Db\InsertMode|null $mode Insert mode
12921296
* @param array<string>|null $updateColumns Columns to update on upsert conflict
1293-
* @param array<string>|null $conflictColumns Columns that define uniqueness for upsert
1297+
* @param array<string>|null $conflictColumns Columns that define uniqueness for upsert (required for PostgreSQL)
12941298
* @return string
1299+
* @throws \RuntimeException When using UPSERT mode without conflictColumns
12951300
*/
12961301
protected function getConflictClause(
12971302
?InsertMode $mode = null,
@@ -1302,7 +1307,13 @@ protected function getConflictClause(
13021307
return ' ON CONFLICT DO NOTHING';
13031308
}
13041309

1305-
if ($mode === InsertMode::UPSERT && $updateColumns !== null && $conflictColumns !== null) {
1310+
if ($mode === InsertMode::UPSERT) {
1311+
if ($conflictColumns === null || $conflictColumns === []) {
1312+
throw new RuntimeException(
1313+
'PostgreSQL requires the $conflictColumns parameter for insertOrUpdate(). ' .
1314+
'Specify the columns that have a unique constraint to determine conflict resolution.',
1315+
);
1316+
}
13061317
$quotedConflictColumns = array_map($this->quoteColumnName(...), $conflictColumns);
13071318
$updates = [];
13081319
foreach ($updateColumns as $column) {

0 commit comments

Comments
 (0)