Skip to content

Commit bb2e614

Browse files
authored
Merge pull request #6917 from kenjis/feat-db-trans-suppress-expression
feat: do not throw exceptions during transactions by default
2 parents 8641cde + 0308366 commit bb2e614

7 files changed

Lines changed: 376 additions & 4 deletions

File tree

system/Database/BaseConnection.php

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
* @property string $DBDriver
3030
* @property string $DBPrefix
3131
* @property string $DSN
32-
* @property mixed $encrypt
32+
* @property array|bool $encrypt
3333
* @property array $failover
3434
* @property string $hostname
3535
* @property Query $lastQuery
@@ -320,6 +320,11 @@ abstract class BaseConnection implements ConnectionInterface
320320
*/
321321
protected $transFailure = false;
322322

323+
/**
324+
* Whether to throw exceptions during transaction
325+
*/
326+
protected bool $transException = false;
327+
323328
/**
324329
* Array of table aliases.
325330
*
@@ -610,7 +615,15 @@ public function query(string $sql, $binds = null, bool $setEscapeFlags = true, s
610615
$this->transStatus = false;
611616
}
612617

613-
if ($this->DBDebug) {
618+
if (
619+
$this->DBDebug
620+
&& (
621+
// Not in transactions
622+
$this->transDepth === 0
623+
// In transactions, do not throw exception by default.
624+
|| $this->transException
625+
)
626+
) {
614627
// We call this function in order to roll-back queries
615628
// if transactions are enabled. If we don't call this here
616629
// the error message will trigger an exit, causing the
@@ -721,6 +734,18 @@ public function transStart(bool $testMode = false): bool
721734
return $this->transBegin($testMode);
722735
}
723736

737+
/**
738+
* If set to true, exceptions are thrown during transactions.
739+
*
740+
* @return $this
741+
*/
742+
public function transException(bool $transExcetion)
743+
{
744+
$this->transException = $transExcetion;
745+
746+
return $this;
747+
}
748+
724749
/**
725750
* Complete Transaction
726751
*/
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CodeIgniter 4 framework.
5+
*
6+
* (c) CodeIgniter Foundation <admin@codeigniter.com>
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace CodeIgniter\Database\Live;
13+
14+
/**
15+
* @group DatabaseLive
16+
*
17+
* @internal
18+
*/
19+
final class TransactionDBDebugFalseTest extends TransactionDBDebugTrueTest
20+
{
21+
protected function setUp(): void
22+
{
23+
parent::setUp();
24+
25+
$this->disableDBDebug();
26+
}
27+
28+
protected function tearDown(): void
29+
{
30+
parent::tearDown();
31+
32+
$this->enableDBDebug();
33+
}
34+
35+
public function testTransStartTransException()
36+
{
37+
$builder = $this->db->table('job');
38+
39+
$this->db->transException(true)->transStart();
40+
41+
$jobData = [
42+
'name' => 'Grocery Sales',
43+
'description' => 'Discount!',
44+
];
45+
$builder->insert($jobData);
46+
47+
// Duplicate entry '1' for key 'PRIMARY'
48+
$jobData = [
49+
'id' => 1,
50+
'name' => 'Comedian',
51+
'description' => 'Theres something in your teeth',
52+
];
53+
$builder->insert($jobData);
54+
55+
$this->db->transComplete();
56+
57+
$this->dontSeeInDatabase('job', ['name' => 'Grocery Sales']);
58+
}
59+
}
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CodeIgniter 4 framework.
5+
*
6+
* (c) CodeIgniter Foundation <admin@codeigniter.com>
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace CodeIgniter\Database\Live;
13+
14+
use CodeIgniter\Database\Exceptions\DatabaseException;
15+
use CodeIgniter\Test\CIUnitTestCase;
16+
use CodeIgniter\Test\DatabaseTestTrait;
17+
use Config\Database;
18+
use Tests\Support\Database\Seeds\CITestSeeder;
19+
20+
/**
21+
* @group DatabaseLive
22+
*
23+
* @internal
24+
*
25+
* @no-final
26+
*/
27+
class TransactionDBDebugTrueTest extends CIUnitTestCase
28+
{
29+
use DatabaseTestTrait;
30+
31+
protected $refresh = true;
32+
protected $seed = CITestSeeder::class;
33+
34+
protected function setUp(): void
35+
{
36+
// Reset connection instance.
37+
$this->db = Database::connect($this->DBGroup, false);
38+
39+
parent::setUp();
40+
}
41+
42+
/**
43+
* Sets $DBDebug to false.
44+
*
45+
* WARNING: this value will persist! take care to roll it back.
46+
*/
47+
protected function disableDBDebug(): void
48+
{
49+
$this->setPrivateProperty($this->db, 'DBDebug', false);
50+
}
51+
52+
/**
53+
* Sets $DBDebug to true.
54+
*/
55+
protected function enableDBDebug(): void
56+
{
57+
$this->setPrivateProperty($this->db, 'DBDebug', true);
58+
}
59+
60+
public function testTransStart()
61+
{
62+
$builder = $this->db->table('job');
63+
64+
$this->db->transStart();
65+
66+
$jobData = [
67+
'name' => 'Grocery Sales',
68+
'description' => 'Discount!',
69+
];
70+
$builder->insert($jobData);
71+
72+
// Duplicate entry '1' for key 'PRIMARY'
73+
$jobData = [
74+
'id' => 1,
75+
'name' => 'Comedian',
76+
'description' => 'Theres something in your teeth',
77+
];
78+
$builder->insert($jobData);
79+
80+
$this->db->transComplete();
81+
82+
$this->assertFalse($this->db->transStatus());
83+
$this->dontSeeInDatabase('job', ['name' => 'Grocery Sales']);
84+
}
85+
86+
public function testTransStartTransException()
87+
{
88+
$builder = $this->db->table('job');
89+
$e = null;
90+
91+
try {
92+
$this->db->transException(true)->transStart();
93+
94+
$jobData = [
95+
'name' => 'Grocery Sales',
96+
'description' => 'Discount!',
97+
];
98+
$builder->insert($jobData);
99+
100+
// Duplicate entry '1' for key 'PRIMARY'
101+
$jobData = [
102+
'id' => 1,
103+
'name' => 'Comedian',
104+
'description' => 'Theres something in your teeth',
105+
];
106+
$builder->insert($jobData);
107+
108+
$this->db->transComplete();
109+
} catch (DatabaseException $e) {
110+
// Do nothing.
111+
}
112+
113+
$this->assertInstanceOf(DatabaseException::class, $e);
114+
$this->dontSeeInDatabase('job', ['name' => 'Grocery Sales']);
115+
}
116+
117+
public function testTransStrictTrue()
118+
{
119+
$builder = $this->db->table('job');
120+
121+
// The first transaction group
122+
$this->db->transStart();
123+
124+
$jobData = [
125+
'name' => 'Grocery Sales',
126+
'description' => 'Discount!',
127+
];
128+
$builder->insert($jobData);
129+
130+
$this->assertTrue($this->db->transStatus());
131+
132+
// Duplicate entry '1' for key 'PRIMARY'
133+
$jobData = [
134+
'id' => 1,
135+
'name' => 'Comedian',
136+
'description' => 'Theres something in your teeth',
137+
];
138+
$builder->insert($jobData);
139+
140+
$this->assertFalse($this->db->transStatus());
141+
142+
$this->db->transComplete();
143+
144+
$this->dontSeeInDatabase('job', ['name' => 'Grocery Sales']);
145+
146+
// The second transaction group
147+
$this->db->transStart();
148+
149+
$jobData = [
150+
'name' => 'Comedian',
151+
'description' => 'Theres something in your teeth',
152+
];
153+
$builder->insert($jobData);
154+
155+
$this->assertFalse($this->db->transStatus());
156+
157+
$this->db->transComplete();
158+
159+
$this->dontSeeInDatabase('job', ['name' => 'Comedian']);
160+
}
161+
162+
public function testTransStrictFalse()
163+
{
164+
$builder = $this->db->table('job');
165+
166+
$this->db->transStrict(false);
167+
168+
// The first transaction group
169+
$this->db->transStart();
170+
171+
$jobData = [
172+
'name' => 'Grocery Sales',
173+
'description' => 'Discount!',
174+
];
175+
$builder->insert($jobData);
176+
177+
$this->assertTrue($this->db->transStatus());
178+
179+
// Duplicate entry '1' for key 'PRIMARY'
180+
$jobData = [
181+
'id' => 1,
182+
'name' => 'Comedian',
183+
'description' => 'Theres something in your teeth',
184+
];
185+
$builder->insert($jobData);
186+
187+
$this->assertFalse($this->db->transStatus());
188+
189+
$this->db->transComplete();
190+
191+
$this->dontSeeInDatabase('job', ['name' => 'Grocery Sales']);
192+
193+
// The second transaction group
194+
$this->db->transStart();
195+
196+
$jobData = [
197+
'name' => 'Comedian',
198+
'description' => 'Theres something in your teeth',
199+
];
200+
$builder->insert($jobData);
201+
202+
$this->assertTrue($this->db->transStatus());
203+
204+
$this->db->transComplete();
205+
206+
$this->seeInDatabase('job', ['name' => 'Comedian']);
207+
}
208+
209+
public function testTransBegin()
210+
{
211+
$builder = $this->db->table('job');
212+
213+
$this->db->transBegin();
214+
215+
$jobData = [
216+
'name' => 'Grocery Sales',
217+
'description' => 'Discount!',
218+
];
219+
$builder->insert($jobData);
220+
221+
// Duplicate entry '1' for key 'PRIMARY'
222+
$jobData = [
223+
'id' => 1,
224+
'name' => 'Comedian',
225+
'description' => 'Theres something in your teeth',
226+
];
227+
$builder->insert($jobData);
228+
229+
$this->assertFalse($this->db->transStatus());
230+
231+
$this->db->transRollback();
232+
233+
$this->dontSeeInDatabase('job', ['name' => 'Grocery Sales']);
234+
}
235+
}

user_guide_src/source/changelogs/v4.3.0.rst

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,22 @@ Exceptions when Database Errors Occur
2727

2828
- The exceptions thrown by the database connection classes have been changed to ``CodeIgniter\Database\Exceptions\DatabaseException``. Previously, different database drivers threw different exception classes, but these have been unified into ``DatabaseException``.
2929
- The exceptions thrown by the ``execute()`` method of Prepared Queries have been changed to ``DatabaseException``. Previously, different database drivers might throw different exception classes or did not throw exceptions, but these have been unified into ``DatabaseException``.
30-
- ``DBDebug`` and ``CI_DEBUG``
30+
- During transactions, exceptions are not thrown by default even if ``DBDebug`` is true.
31+
- ``DBDebug`` and ``CI_DEBUG`` Changes
3132

3233
- To be consistent in behavior regardless of environments, ``Config\Database::$default['DBDebug']``
3334
and ``Config\Database::$tests['DBDebug']`` has been changed to ``true`` by default. With these
34-
settings, an exception is always thrown when a database error occurs.
35+
settings, an exception is always thrown when a database error occurs. Previously, it is ``false``
36+
**only in the production environment**.
3537
- Now ``DatabaseException`` thrown in ``BaseBuilder`` is thrown if ``$DBDebug`` is true.
3638
Previously, it is thrown if ``CI_DEBUG`` is true.
3739
- The default value of ``BaseConnection::$DBDebug`` has been changed to ``true``.
3840
- With these changes, ``DBDebug`` **now means whether or not to throw an exception when an error occurs**.
3941
Although unrelated to debugging, the name has not been changed.
42+
- When running transactions with ``DBDebug`` is ``true``, even if a query error occurs, exceptions
43+
are not thrown by default. Previously, if a query error occurs, all the queries
44+
will be rolled backed, and an exception will be thrown, so :ref:`transactions-managing-errors` or
45+
:ref:`transactions-manual-transactions` won't work.
4046
- Now when you delete without WHERE clause in ``Model``, ``DatabaseException`` is thrown even if
4147
``CI_DEBUG`` is false. Previously, it is thrown if ``CI_DEBUG`` is true.
4248

@@ -233,6 +239,8 @@ Others
233239
- Improved data returned by :ref:`BaseConnection::getForeignKeyData() <metadata-getforeignkeydata>`. All DBMS returns the same structure.
234240
- SQLite :ref:`BaseConnection::getIndexData() <db-metadata-getindexdata>` now can return pseudo index named ``PRIMARY`` for `AUTOINCREMENT` column, and each returned index data has ``type`` property.
235241
- ``BasePreparedQuery::close()`` now deallocates the prepared statement in all DBMS. Previously, they were not deallocated in Postgre, SQLSRV and OCI8. See :ref:`database-queries-stmt-close`.
242+
- Added ``BaseConnection::transException()`` to throw exceptinons during transactions.
243+
See :ref:`transactions-throwing-exceptions`
236244

237245
Model
238246
=====

0 commit comments

Comments
 (0)