Skip to content

Commit 20d1295

Browse files
committed
allow RawSql for the primary key
1 parent 2f7abc3 commit 20d1295

File tree

5 files changed

+62
-30
lines changed

5 files changed

+62
-30
lines changed

system/BaseModel.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use CodeIgniter\Database\Exceptions\DatabaseException;
2020
use CodeIgniter\Database\Exceptions\DataException;
2121
use CodeIgniter\Database\Query;
22+
use CodeIgniter\Database\RawSql;
2223
use CodeIgniter\DataCaster\Cast\CastInterface;
2324
use CodeIgniter\DataConverter\DataConverter;
2425
use CodeIgniter\Entity\Cast\CastInterface as EntityCastInterface;
@@ -784,8 +785,8 @@ public function getInsertID()
784785
* Validates that the primary key values are valid for update/delete/insert operations.
785786
* Throws exception if invalid.
786787
*
787-
* @param int|list<int|string>|string $id
788-
* @param bool $allowArray Whether to allow array of IDs (true for update/delete, false for insert)
788+
* @param int|list<int|string>|RawSql|string $id
789+
* @param bool $allowArray Whether to allow array of IDs (true for update/delete, false for insert)
789790
*
790791
* @throws InvalidArgumentException
791792
*/
@@ -819,6 +820,11 @@ protected function validateID($id, bool $allowArray = true): void
819820
return;
820821
}
821822

823+
// Allow RawSql objects for complex scenarios
824+
if ($id instanceof RawSql) {
825+
return;
826+
}
827+
822828
// Check for invalid single values
823829
if (in_array($id, [null, 0, '0', '', true, false], true)) {
824830
$type = is_bool($id) ? 'boolean ' . var_export($id, true) : var_export($id, true);
@@ -1018,8 +1024,8 @@ public function insertBatch(?array $set = null, ?bool $escape = null, int $batch
10181024
* Updates a single record in the database. If an object is provided,
10191025
* it will attempt to convert it into an array.
10201026
*
1021-
* @param int|list<int|string>|string|null $id
1022-
* @param object|row_array|null $row
1027+
* @param int|list<int|string>|RawSql|string|null $id
1028+
* @param object|row_array|null $row
10231029
*
10241030
* @throws ReflectionException
10251031
*/
@@ -1147,8 +1153,8 @@ public function updateBatch(?array $set = null, ?string $index = null, int $batc
11471153
/**
11481154
* Deletes a single record from the database where $id matches.
11491155
*
1150-
* @param int|list<int|string>|string|null $id The rows primary key(s).
1151-
* @param bool $purge Allows overriding the soft deletes setting.
1156+
* @param int|list<int|string>|RawSql|string|null $id The rows primary key(s).
1157+
* @param bool $purge Allows overriding the soft deletes setting.
11521158
*
11531159
* @return bool|string Returns a SQL string if in test mode.
11541160
*

tests/system/Models/DeleteModelTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace CodeIgniter\Models;
1515

1616
use CodeIgniter\Database\Exceptions\DatabaseException;
17+
use CodeIgniter\Database\RawSql;
1718
use CodeIgniter\Exceptions\InvalidArgumentException;
1819
use CodeIgniter\Exceptions\ModelException;
1920
use PHPUnit\Framework\Attributes\DataProvider;
@@ -38,6 +39,17 @@ public function testDeleteBasics(): void
3839
$this->dontSeeInDatabase('job', ['name' => 'Developer']);
3940
}
4041

42+
public function testDeleteWithRawSql(): void
43+
{
44+
$this->createModel(JobModel::class);
45+
$this->seeInDatabase('job', ['name' => 'Developer']);
46+
47+
// RawSql objects should be allowed as primary key values
48+
$result = $this->model->delete(new RawSql('1'));
49+
$this->assertTrue($result);
50+
$this->dontSeeInDatabase('job', ['name' => 'Developer']);
51+
}
52+
4153
public function testDeleteFail(): void
4254
{
4355
// WARNING this value will persist! take care to roll it back.

tests/system/Models/UpdateModelTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use CodeIgniter\Database\Exceptions\DatabaseException;
1717
use CodeIgniter\Database\Exceptions\DataException;
18+
use CodeIgniter\Database\RawSql;
1819
use CodeIgniter\Entity\Entity;
1920
use CodeIgniter\Exceptions\InvalidArgumentException;
2021
use Config\Database;
@@ -107,6 +108,16 @@ public function testUpdateArray(): void
107108
$this->seeInDatabase('user', ['id' => 2, 'name' => 'Foo Bar']);
108109
}
109110

111+
public function testUpdateWithRawSql(): void
112+
{
113+
$this->createModel(UserModel::class);
114+
115+
// RawSql objects should be allowed as primary key values
116+
$result = $this->model->update(new RawSql('1'), ['name' => 'RawSql User']);
117+
$this->assertTrue($result);
118+
$this->seeInDatabase('user', ['id' => 1, 'name' => 'RawSql User']);
119+
}
120+
110121
public function testUpdateResultFail(): void
111122
{
112123
// WARNING this value will persist! take care to roll it back.

user_guide_src/source/changelogs/v4.7.0.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ Invalid primary key values will now throw ``InvalidArgumentException`` instead o
7070
- Nested arrays (e.g., ``[[1, 2]]``)
7171
- Arrays containing any of the above invalid values
7272

73+
**Note:** ``RawSql`` objects are allowed as primary key values for complex scenarios
74+
where you need to use raw SQL expressions.
75+
7376
This change improves error reporting by providing specific validation messages
7477
(e.g., "Invalid primary key: 0 is not allowed") instead of generic database errors, and
7578
prevents invalid queries from reaching the database.

utils/phpstan-baseline/missingType.property.neon

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -393,122 +393,122 @@ parameters:
393393
path: ../../tests/system/Models/SaveModelTest.php
394394

395395
-
396-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:199\:\:\$_options has no type specified\.$#'
396+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:210\:\:\$_options has no type specified\.$#'
397397
count: 1
398398
path: ../../tests/system/Models/UpdateModelTest.php
399399

400400
-
401-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:199\:\:\$country has no type specified\.$#'
401+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:210\:\:\$country has no type specified\.$#'
402402
count: 1
403403
path: ../../tests/system/Models/UpdateModelTest.php
404404

405405
-
406-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:199\:\:\$created_at has no type specified\.$#'
406+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:210\:\:\$created_at has no type specified\.$#'
407407
count: 1
408408
path: ../../tests/system/Models/UpdateModelTest.php
409409

410410
-
411-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:199\:\:\$deleted has no type specified\.$#'
411+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:210\:\:\$deleted has no type specified\.$#'
412412
count: 1
413413
path: ../../tests/system/Models/UpdateModelTest.php
414414

415415
-
416-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:199\:\:\$email has no type specified\.$#'
416+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:210\:\:\$email has no type specified\.$#'
417417
count: 1
418418
path: ../../tests/system/Models/UpdateModelTest.php
419419

420420
-
421-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:199\:\:\$id has no type specified\.$#'
421+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:210\:\:\$id has no type specified\.$#'
422422
count: 1
423423
path: ../../tests/system/Models/UpdateModelTest.php
424424

425425
-
426-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:199\:\:\$name has no type specified\.$#'
426+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:210\:\:\$name has no type specified\.$#'
427427
count: 1
428428
path: ../../tests/system/Models/UpdateModelTest.php
429429

430430
-
431-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:199\:\:\$updated_at has no type specified\.$#'
431+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:210\:\:\$updated_at has no type specified\.$#'
432432
count: 1
433433
path: ../../tests/system/Models/UpdateModelTest.php
434434

435435
-
436-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:218\:\:\$_options has no type specified\.$#'
436+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:229\:\:\$_options has no type specified\.$#'
437437
count: 1
438438
path: ../../tests/system/Models/UpdateModelTest.php
439439

440440
-
441-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:218\:\:\$country has no type specified\.$#'
441+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:229\:\:\$country has no type specified\.$#'
442442
count: 1
443443
path: ../../tests/system/Models/UpdateModelTest.php
444444

445445
-
446-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:218\:\:\$created_at has no type specified\.$#'
446+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:229\:\:\$created_at has no type specified\.$#'
447447
count: 1
448448
path: ../../tests/system/Models/UpdateModelTest.php
449449

450450
-
451-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:218\:\:\$deleted has no type specified\.$#'
451+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:229\:\:\$deleted has no type specified\.$#'
452452
count: 1
453453
path: ../../tests/system/Models/UpdateModelTest.php
454454

455455
-
456-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:218\:\:\$email has no type specified\.$#'
456+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:229\:\:\$email has no type specified\.$#'
457457
count: 1
458458
path: ../../tests/system/Models/UpdateModelTest.php
459459

460460
-
461-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:218\:\:\$id has no type specified\.$#'
461+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:229\:\:\$id has no type specified\.$#'
462462
count: 1
463463
path: ../../tests/system/Models/UpdateModelTest.php
464464

465465
-
466-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:218\:\:\$name has no type specified\.$#'
466+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:229\:\:\$name has no type specified\.$#'
467467
count: 1
468468
path: ../../tests/system/Models/UpdateModelTest.php
469469

470470
-
471-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:218\:\:\$updated_at has no type specified\.$#'
471+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:229\:\:\$updated_at has no type specified\.$#'
472472
count: 1
473473
path: ../../tests/system/Models/UpdateModelTest.php
474474

475475
-
476-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$_options has no type specified\.$#'
476+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$_options has no type specified\.$#'
477477
count: 1
478478
path: ../../tests/system/Models/UpdateModelTest.php
479479

480480
-
481-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$country has no type specified\.$#'
481+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$country has no type specified\.$#'
482482
count: 1
483483
path: ../../tests/system/Models/UpdateModelTest.php
484484

485485
-
486-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$created_at has no type specified\.$#'
486+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$created_at has no type specified\.$#'
487487
count: 1
488488
path: ../../tests/system/Models/UpdateModelTest.php
489489

490490
-
491-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$deleted has no type specified\.$#'
491+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$deleted has no type specified\.$#'
492492
count: 1
493493
path: ../../tests/system/Models/UpdateModelTest.php
494494

495495
-
496-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$email has no type specified\.$#'
496+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$email has no type specified\.$#'
497497
count: 1
498498
path: ../../tests/system/Models/UpdateModelTest.php
499499

500500
-
501-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$id has no type specified\.$#'
501+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$id has no type specified\.$#'
502502
count: 1
503503
path: ../../tests/system/Models/UpdateModelTest.php
504504

505505
-
506-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$name has no type specified\.$#'
506+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$name has no type specified\.$#'
507507
count: 1
508508
path: ../../tests/system/Models/UpdateModelTest.php
509509

510510
-
511-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$updated_at has no type specified\.$#'
511+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$updated_at has no type specified\.$#'
512512
count: 1
513513
path: ../../tests/system/Models/UpdateModelTest.php
514514

0 commit comments

Comments
 (0)