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
20 changes: 20 additions & 0 deletions system/Database/BaseBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2878,6 +2878,26 @@ public function insert($set = null, ?bool $escape = null)
return false;
}

/**
* Inserts a row and returns the insert ID.
*
* @param array<array-key, mixed>|object|null $set
*
* @return false|int|string
*
* @throws DatabaseException
*/
public function insertGetID($set = null, ?bool $escape = null)
{
$result = $this->insert($set, $escape);

if ($result === false || $result instanceof Query || $this->db->affectedRows() < 1) {
return false;
}

return $this->db->insertID();
}

/**
* @internal This is a temporary solution.
*
Expand Down
1 change: 1 addition & 0 deletions system/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ class Model extends BaseModel
'getCompiledInsert',
'getCompiledSelect',
'getCompiledUpdate',
'insertGetID',
];

public function __construct(?ConnectionInterface $db = null, ?ValidationInterface $validation = null)
Expand Down
105 changes: 105 additions & 0 deletions tests/system/Database/Builder/InsertTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace CodeIgniter\Database\Builder;

use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\Query;
use CodeIgniter\Database\RawSql;
Expand All @@ -39,6 +40,51 @@ protected function setUp(): void
$this->db = new MockConnection([]);
}

private function insertGetIDConnection(int $affectedRows = 1, int $insertID = 123): MockConnection
{
return new class ($affectedRows, $insertID) extends MockConnection {
public function __construct(
private readonly int $affectedRowCount,
private readonly int $lastInsertID,
) {
parent::__construct([]);
}

/**
* @param mixed|null $binds
*/
public function query(string $sql, $binds = null, bool $setEscapeFlags = true, string $queryClass = ''): bool|Query
{
$query = new Query($this);
$query->setQuery($sql, $binds, $setEscapeFlags);

$this->lastQuery = $query;

if ($this->pretend) {
return $query;
}

$this->resultID = $this->simpleQuery($query->getQuery());

if ($this->resultID === false) {
return false;
}

return $query->isWriteType();
}

public function affectedRows(): int
{
return $this->affectedRowCount;
}

public function insertID(): int
{
return $this->lastInsertID;
}
};
}

public function testInsertArray(): void
{
$builder = $this->db->table('jobs');
Expand Down Expand Up @@ -145,6 +191,65 @@ public function testThrowsExceptionOnNoValuesSet(): void
$builder->testMode()->insert(null, true);
}

public function testInsertGetIDReturnsInsertID(): void
{
$db = $this->insertGetIDConnection(insertID: 456);
$db->shouldReturn('execute', new class () {});

$insertID = (new BaseBuilder('jobs', $db))->insertGetID([
'name' => 'Grocery Sales',
]);

$this->assertSame(456, $insertID);
}

public function testInsertGetIDReturnsFalseWhenInsertFails(): void
{
$db = $this->insertGetIDConnection();
$db->shouldReturn('execute', false);

$insertID = (new BaseBuilder('jobs', $db))->insertGetID([
'name' => 'Grocery Sales',
]);

$this->assertFalse($insertID);
}

public function testInsertGetIDReturnsFalseWhenNoRowsAreInserted(): void
{
$db = $this->insertGetIDConnection(affectedRows: 0);
$db->shouldReturn('execute', new class () {});

$insertID = (new BaseBuilder('jobs', $db))->insertGetID([
'name' => 'Grocery Sales',
]);

$this->assertFalse($insertID);
}

public function testInsertGetIDReturnsFalseInTestMode(): void
{
$db = $this->insertGetIDConnection();

$insertID = (new BaseBuilder('jobs', $db))->testMode()->insertGetID([
'name' => 'Grocery Sales',
]);

$this->assertFalse($insertID);
}

public function testInsertGetIDReturnsFalseInPretendMode(): void
{
$db = $this->insertGetIDConnection();
$db->pretend();

$insertID = (new BaseBuilder('jobs', $db))->insertGetID([
'name' => 'Grocery Sales',
]);

$this->assertFalse($insertID);
}

public function testInsertBatch(): void
{
$builder = $this->db->table('jobs');
Expand Down
13 changes: 13 additions & 0 deletions tests/system/Models/GetCompiledModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,17 @@ public function testGetCompiledUpdate(): void
->set('email', 'mark@example.com')
->getCompiledUpdate();
}

public function testInsertGetID(): void
{
$this->expectException(ModelException::class);
$this->expectExceptionMessage('You cannot use "insertGetID()" in "Tests\Support\Models\UserObjModel".');

$this->createModel(UserObjModel::class);

$this->model->insertGetID([
'name' => 'Mark',
'email' => 'mark@example.com',
]);
}
}
1 change: 1 addition & 0 deletions user_guide_src/source/changelogs/v4.8.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ Query Builder
- Added ``exists()`` and ``doesntExist()`` to Query Builder to check whether the current Query Builder query would return at least one row. See :ref:`query-builder-exists`.
- Added ``explain()`` to Query Builder to run execution-plan queries for the current ``SELECT`` query. See :ref:`query-builder-explain`.
- Added ``havingBetween()``, ``orHavingBetween()``, ``havingNotBetween()``, and ``orHavingNotBetween()`` to Query Builder. See :ref:`query-builder-having-between`.
- Added ``insertGetID()`` to Query Builder to insert a row and return the insert ID reported by the database driver. See :ref:`query-builder-insert-get-id`.
- Added ``whereBetween()``, ``orWhereBetween()``, ``whereNotBetween()``, and ``orWhereNotBetween()`` to Query Builder. See :ref:`query-builder-where-between`.
- Added ``whereColumn()`` and ``orWhereColumn()`` to compare one column to another column while protecting identifiers by default. See :ref:`query-builder-where-column`.
- Added ``whereExists()``, ``orWhereExists()``, ``whereNotExists()``, and ``orWhereNotExists()`` to add ``EXISTS`` and ``NOT EXISTS`` subquery conditions. See :ref:`query-builder-where-exists`.
Expand Down
23 changes: 23 additions & 0 deletions user_guide_src/source/database/query_builder.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,20 @@ Here is an example using an object:

The first parameter is an object.

.. _query-builder-insert-get-id:

$builder->insertGetID()
-----------------------

.. versionadded:: 4.8.0

Inserts a row and returns the insert ID reported by the database driver:

.. literalinclude:: query_builder/130.php

This method returns ``false`` if the insert fails, no row is inserted, or the
builder is in test mode. It uses the same insert ID behavior as ``$db->insertID()``.

$builder->ignore()
------------------

Expand Down Expand Up @@ -2284,6 +2298,15 @@ Class Reference

Compiles and executes an ``INSERT`` statement.

.. php:method:: insertGetID([$set = null[, $escape = null]])

:param array $set: An associative array of field/value pairs
:param bool $escape: Whether to escape values
:returns: The insert ID reported by the database driver, or ``false`` on failure
:rtype: int|string|false

Compiles and executes an ``INSERT`` statement and returns the insert ID.

.. php:method:: insertBatch([$set = null[, $escape = null[, $batch_size = 100]]])

:param array $set: Data to insert
Expand Down
9 changes: 9 additions & 0 deletions user_guide_src/source/database/query_builder/130.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

$data = [
'title' => 'My title',
'name' => 'My Name',
'date' => '2022-01-01',
];

$insertID = $builder->insertGetID($data);
Loading