Passed
Pull Request — master (#372)
by Wilmer
02:53
created

testNestedTransactionNotSupported()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 11
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Tests;
6
7
use PDO;
8
use PHPUnit\Framework\TestCase;
9
use Psr\Log\NullLogger;
10
use Yiisoft\Db\Connection\ConnectionInterface;
11
use Yiisoft\Db\Driver\DriverInterface;
12
use Yiisoft\Db\Exception\Exception;
13
use Yiisoft\Db\Exception\NotSupportedException;
14
use Yiisoft\Db\Tests\Support\Assert;
15
use Yiisoft\Log\Logger;
16
use Yiisoft\Profiler\Profiler;
17
18
use function serialize;
19
use function unserialize;
20
21
abstract class AbstractConnectionTest extends TestCase
22
{
23
    public function testCacheKey(): void
24
    {
25
        $db = $this->getConnection();
0 ignored issues
show
Bug introduced by
The method getConnection() does not exist on Yiisoft\Db\Tests\AbstractConnectionTest. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

25
        /** @scrutinizer ignore-call */ 
26
        $db = $this->getConnection();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
26
27
        $driver = $db->getDriver();
28
        $dsn = $driver->getDsn();
29
        $username = $driver->getUsername();
30
31
        $this->assertSame([$dsn, $username], $db->getCacheKey());
32
    }
33
34
    /**
35
     * Ensure database connection is reset on when a connection is cloned.
36
     *
37
     * Make sure each connection element has its own PDO instance i.e. own connection to the DB.
38
     * Also transaction elements should not be shared between two connections.
39
     */
40
    public function testClone(): void
41
    {
42
        $db = $this->getConnection();
43
44
        $this->assertNull($db->getTransaction());
45
        $this->assertNull($db->getPDO());
46
47
        $db->open();
48
49
        $this->assertNull($db->getTransaction());
50
        $this->assertNotNull($db->getPDO());
51
52
        $conn2 = clone $db;
53
54
        $this->assertNull($db->getTransaction());
55
        $this->assertNotNull($db->getPDO());
56
57
        $this->assertNull($conn2->getTransaction());
58
        $this->assertNull($conn2->getPDO());
59
60
        $db->beginTransaction();
61
62
        $this->assertNotNull($db->getTransaction());
63
        $this->assertNotNull($db->getPDO());
64
65
        $this->assertNull($conn2->getTransaction());
66
        $this->assertNull($conn2->getPDO());
67
68
        $conn3 = clone $db;
69
70
        $this->assertNotNull($db->getTransaction());
71
        $this->assertNotNull($db->getPDO());
72
        $this->assertNull($conn3->getTransaction());
73
        $this->assertNull($conn3->getPDO());
74
    }
75
76
    public function testCommitTransactionsWithSavepoints(): void
77
    {
78
        $db = $this->getConnection();
79
80
        $db->createCommand('DELETE FROM {{profile}}')->execute();
81
        $transaction = $db->beginTransaction();
82
83
        $this->assertSame(1, $transaction->getLevel());
84
85
        $db->createCommand()->insert('profile', ['description' => 'test transaction1'])->execute();
86
        $transaction->begin();
87
88
        $this->assertSame(2, $transaction->getLevel());
89
90
        $db->createCommand()->insert('profile', ['description' => 'test transaction2'])->execute();
91
        $transaction->commit();
92
93
        $this->assertSame(1, $transaction->getLevel());
94
95
        $db->createCommand()->insert('profile', ['description' => 'test transaction3'])->execute();
96
        $transaction->commit();
97
98
        $this->assertSame(0, $transaction->getLevel());
99
        $this->assertFalse($transaction->isActive());
100
        $this->assertNull($db->getTransaction());
101
        $this->assertSame(
102
            '1',
103
            $db->createCommand(
104
                "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction1'"
105
            )->queryScalar(),
106
        );
107
        $this->assertSame(
108
            '1',
109
            $db->createCommand(
110
                "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction2'"
111
            )->queryScalar(),
112
        );
113
        $this->assertSame(
114
            '1',
115
            $db->createCommand(
116
                "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction3'"
117
            )->queryScalar(),
118
        );
119
    }
120
121
    public function testEnableQueryLog(): void
122
    {
123
        $db = $this->getConnection();
124
125
        foreach (['qlog1', 'qlog2', 'qlog3', 'qlog4'] as $table) {
126
            if ($db->getTableSchema($table, true) !== null) {
127
                $db->createCommand()->dropTable($table)->execute();
128
            }
129
        }
130
131
        $logger = new Logger();
132
        $profiler = new Profiler($logger);
133
134
        /* profiling and logging */
135
        $db->setLogger($logger);
136
        $db->setProfiler($profiler);
137
        $logger->flush();
138
        $profiler->flush();
139
        $db->createCommand()->createTable('qlog1', ['id' => 'pk'])->execute();
140
141
        $this->assertCount(1, Assert::getInaccessibleProperty($logger, 'messages'));
142
        $this->assertCount(1, Assert::getInaccessibleProperty($profiler, 'messages'));
143
        $this->assertNotNull($db->getTableSchema('qlog1', true));
144
145
        $logger->flush();
146
        $profiler->flush();
147
        $db->createCommand('SELECT * FROM {{qlog1}}')->queryAll();
148
149
        $this->assertCount(1, Assert::getInaccessibleProperty($logger, 'messages'));
150
        $this->assertCount(1, Assert::getInaccessibleProperty($profiler, 'messages'));
151
152
        /* profiling only */
153
        $db->setLogger(new NullLogger());
154
        $db->setProfiler($profiler);
155
        $logger->flush();
156
        $profiler->flush();
157
        $db->createCommand()->createTable('qlog2', ['id' => 'pk'])->execute();
158
159
        $this->assertCount(0, Assert::getInaccessibleProperty($logger, 'messages'));
160
        $this->assertCount(1, Assert::getInaccessibleProperty($profiler, 'messages'));
161
        $this->assertNotNull($db->getTableSchema('qlog2', true));
162
163
        $logger->flush();
164
        $profiler->flush();
165
        $db->createCommand('SELECT * FROM {{qlog2}}')->queryAll();
166
167
        $this->assertCount(0, Assert::getInaccessibleProperty($logger, 'messages'));
168
        $this->assertCount(1, Assert::getInaccessibleProperty($profiler, 'messages'));
169
170
        /* logging only */
171
        $db->setLogger($logger);
172
        $db->notProfiler();
173
        $logger->flush();
174
        $profiler->flush();
175
        $db->createCommand()->createTable('qlog3', ['id' => 'pk'])->execute();
176
177
        $this->assertCount(1, Assert::getInaccessibleProperty($logger, 'messages'));
178
        $this->assertCount(0, Assert::getInaccessibleProperty($profiler, 'messages'));
179
        $this->assertNotNull($db->getTableSchema('qlog3', true));
180
181
        $logger->flush();
182
        $profiler->flush();
183
        $db->createCommand('SELECT * FROM {{qlog3}}')->queryAll();
184
185
        $this->assertCount(1, Assert::getInaccessibleProperty($logger, 'messages'));
186
        $this->assertCount(0, Assert::getInaccessibleProperty($profiler, 'messages'));
187
188
        /* disabled */
189
        $db->setLogger(new NullLogger());
190
        $db->notProfiler();
191
192
        $logger->flush();
193
        $profiler->flush();
194
195
        $db->createCommand()->createTable('qlog4', ['id' => 'pk'])->execute();
196
197
        $this->assertNotNull($db->getTableSchema('qlog4', true));
198
        $this->assertCount(0, Assert::getInaccessibleProperty($logger, 'messages'));
199
        $this->assertCount(0, Assert::getInaccessibleProperty($profiler, 'messages'));
200
201
        $db->createCommand('SELECT * FROM {{qlog4}}')->queryAll();
202
203
        $this->assertCount(0, Assert::getInaccessibleProperty($logger, 'messages'));
204
        $this->assertCount(0, Assert::getInaccessibleProperty($profiler, 'messages'));
205
    }
206
207
    public function testExceptionContainsRawQuery(): void
208
    {
209
        $db = $this->getConnection();
210
211
        if ($db->getTableSchema('qlog1', true) === null) {
212
            $db->createCommand()->createTable('qlog1', ['id' => 'pk'])->execute();
213
        }
214
215
        $db->setEmulatePrepare(true);
216
        $logger = new Logger();
217
        $profiler = new Profiler($logger);
218
219
        /* profiling and logging */
220
        $db->setLogger($logger);
221
        $db->setProfiler($profiler);
222
223
        $this->runExceptionTest($db);
224
225
        /* profiling only */
226
        $db->setLogger(new NullLogger());
227
        $db->setProfiler($profiler);
228
229
        $this->runExceptionTest($db);
230
231
        /* logging only */
232
        $db->setLogger($logger);
233
        $db->notProfiler();
234
235
        $this->runExceptionTest($db);
236
237
        /* disabled */
238
        $db->setLogger(new NullLogger());
239
        $db->notProfiler();
240
241
        $this->runExceptionTest($db);
242
    }
243
244
    public function testGetDriver(): void
245
    {
246
        $db = $this->getConnection();
247
248
        $this->assertInstanceOf(DriverInterface::class, $db->getDriver());
249
    }
250
251
    /**
252
     * Tests nested transactions with partial rollback.
253
     *
254
     * {@see https://github.com/yiisoft/yii2/issues/9851}
255
     */
256
    public function testNestedTransaction(): void
257
    {
258
        $db = $this->getConnection();
259
260
        $db->transaction(
261
            static function (ConnectionInterface $db): void {
262
                self::assertNotNull($db->getTransaction());
263
264
                $db->transaction(
265
                    static function (ConnectionInterface $db): void {
266
                        $transaction = $db->getTransaction();
267
                        self::assertNotNull($transaction);
268
                        $transaction->rollBack();
269
                    }
270
                );
271
272
                self::assertNotNull($db->getTransaction());
273
            }
274
        );
275
    }
276
277
    public function testNestedTransactionNotSupported(): void
278
    {
279
        $db = $this->getConnection();
280
281
        $db->setEnableSavepoint(false);
282
283
        $db->transaction(
284
            function (ConnectionInterface $db): void {
285
                $this->assertNotNull($db->getTransaction());
286
                $this->expectException(NotSupportedException::class);
287
                $db->beginTransaction();
288
            }
289
        );
290
    }
291
292
    public function testOpenClose(): void
293
    {
294
        $db = $this->getConnection();
295
296
        $this->assertFalse($db->isActive());
297
        $this->assertNull($db->getPDO());
298
299
        $db->open();
300
301
        $this->assertTrue($db->isActive());
302
        $this->assertInstanceOf(PDO::class, $db->getPDO());
303
304
        $db->close();
305
306
        $this->assertFalse($db->isActive());
307
        $this->assertNull($db->getPDO());
308
309
        $db = $this->getConnection(false, 'unknown::memory:');
310
311
        $this->expectException(Exception::class);
312
        $this->expectExceptionMessage('could not find driver');
313
        $db->open();
314
    }
315
316
    public function testPartialRollbackTransactionsWithSavePoints(): void
317
    {
318
        $db = $this->getConnection();
319
320
        $db->createCommand('DELETE FROM {{profile}}')->execute();
321
        $db->open();
322
        $transaction = $db->beginTransaction();
323
324
        $this->assertSame(1, $transaction->getLevel());
325
326
        $db->createCommand()->insert('profile', ['description' => 'test transaction1'])->execute();
327
        $transaction->begin();
328
329
        $this->assertSame(2, $transaction->getLevel());
330
331
        $db->createCommand()->insert('profile', ['description' => 'test transaction2'])->execute();
332
        $transaction->rollBack();
333
334
        $this->assertSame(1, $transaction->getLevel());
335
        $this->assertTrue($transaction->isActive());
336
337
        $db->createCommand()->insert('profile', ['description' => 'test transaction3'])->execute();
338
        $transaction->commit();
339
340
        $this->assertSame(0, $transaction->getLevel());
341
        $this->assertFalse($transaction->isActive());
342
        $this->assertNull($db->getTransaction());
343
        $this->assertSame(
344
            '1',
345
            $db->createCommand(
346
                "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction1'"
347
            )->queryScalar(),
348
        );
349
        $this->assertSame(
350
            '0',
351
            $db->createCommand(
352
                "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction2'"
353
            )->queryScalar(),
354
        );
355
        $this->assertSame(
356
            '1',
357
            $db->createCommand(
358
                "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction3'"
359
            )->queryScalar(),
360
        );
361
    }
362
363
    public function testRollbackTransactionsWithSavePoints(): void
364
    {
365
        $db = $this->getConnection();
366
367
        $db->createCommand('DELETE FROM {{profile}}')->execute();
368
        $db->open();
369
        $transaction = $db->beginTransaction();
370
371
        $this->assertSame(1, $transaction->getLevel());
372
373
        $db->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
374
        $transaction->begin();
375
376
        $this->assertSame(2, $transaction->getLevel());
377
378
        $db->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
379
        $transaction->rollBack();
380
381
        $this->assertSame(1, $transaction->getLevel());
382
        $this->assertTrue($transaction->isActive());
383
384
        $db->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
385
        $transaction->rollBack();
386
387
        $this->assertSame(0, $transaction->getLevel());
388
        $this->assertFalse($transaction->isActive());
389
        $this->assertNull($db->getTransaction());
390
        $this->assertSame(
391
            '0',
392
            $db->createCommand(
393
                "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction'"
394
            )->queryScalar(),
395
        );
396
    }
397
398
    public function testSerialize(): void
399
    {
400
        $db = $this->getConnection();
401
402
        $db->open();
403
        $serialized = serialize($db);
404
405
        $this->assertNotNull($db->getPDO());
406
407
        $unserialized = unserialize($serialized);
408
409
        $this->assertInstanceOf(ConnectionInterface::class, $unserialized);
410
        $this->assertNull($unserialized->getPDO());
411
        $this->assertSame('123', $unserialized->createCommand('SELECT 123')->queryScalar());
412
    }
413
414
    public function testTransaction(): void
415
    {
416
        $db = $this->getConnection();
417
418
        $db->createCommand('DELETE FROM {{profile}}')->execute();
419
420
        $this->assertNull($db->getTransaction());
421
422
        $transaction = $db->beginTransaction();
423
424
        $this->assertNotNull($db->getTransaction());
425
        $this->assertTrue($transaction->isActive());
426
427
        $db->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
428
429
        $transaction->rollBack();
430
431
        $this->assertFalse($transaction->isActive());
432
        $this->assertNull($db->getTransaction());
433
        $this->assertSame(
434
            '0',
435
            $db->createCommand(
436
                "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction'"
437
            )->queryScalar(),
438
        );
439
440
        $transaction = $db->beginTransaction();
441
        $db->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
442
        $transaction->commit();
443
444
        $this->assertFalse($transaction->isActive());
445
        $this->assertNull($db->getTransaction());
446
        $this->assertSame(
447
            '1',
448
            $db->createCommand(
449
                "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction'"
450
            )->queryScalar()
451
        );
452
    }
453
454
    public function testTransactionShortcutCorrect(): void
455
    {
456
        $db = $this->getConnection();
457
458
        $result = $db->transaction(
459
            static function (ConnectionInterface $db): bool {
460
                $db->createCommand()->insert('profile', ['description' => 'test transaction shortcut'])->execute();
461
                return true;
462
            }
463
        );
464
465
        $this->assertTrue($result, 'transaction shortcut valid value should be returned from callback');
466
467
        $profilesCount = $db->createCommand(
468
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction shortcut'"
469
        )->queryScalar();
470
471
        $this->assertSame('1', $profilesCount, 'profile should be inserted in transaction shortcut');
472
    }
473
474
    public function testTransactionShortcutException(): void
475
    {
476
        $db = $this->getConnection();
477
478
        $this->expectException(Exception::class);
479
        $this->expectExceptionMessage('Exception in transaction shortcut');
480
        $db->transaction(
481
            static function (ConnectionInterface $db): void {
482
                $db->createCommand()->insert('profile', ['description' => 'test transaction shortcut'])->execute();
483
                throw new Exception('Exception in transaction shortcut');
484
            }
485
        );
486
    }
487
488
    private function runExceptionTest(ConnectionInterface $db): void
489
    {
490
        $thrown = false;
491
492
        try {
493
            $db->createCommand('INSERT INTO qlog1(a) VALUES(:a);', [':a' => 1])->execute();
494
        } catch (Exception $e) {
495
            $this->assertStringContainsString(
496
                'INSERT INTO qlog1(a) VALUES(1);',
497
                $e->getMessage(),
498
                'Exceptions message should contain raw SQL query: ' . (string) $e
499
            );
500
501
            $thrown = true;
502
        }
503
504
        $this->assertTrue($thrown, 'An exception should have been thrown by the command.');
505
506
        $thrown = false;
507
508
        try {
509
            $db->createCommand(
510
                'SELECT * FROM qlog1 WHERE id=:a ORDER BY nonexistingcolumn;',
511
                [':a' => 1]
512
            )->queryAll();
513
        } catch (Exception $e) {
514
            $this->assertStringContainsString(
515
                'SELECT * FROM qlog1 WHERE id=1 ORDER BY nonexistingcolumn;',
516
                $e->getMessage(),
517
                'Exceptions message should contain raw SQL query: ' . (string) $e
518
            );
519
520
            $thrown = true;
521
        }
522
523
        $this->assertTrue($thrown, 'An exception should have been thrown by the command.');
524
    }
525
}
526