Passed
Pull Request — master (#363)
by Def
11:52
created

TestCommandTrait::testTransaction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 10
nc 1
nop 0
dl 0
loc 15
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\TestSupport;
6
7
use PDO;
8
use Throwable;
9
use Yiisoft\Db\Command\Param;
10
use Yiisoft\Db\Command\ParamInterface;
11
use Yiisoft\Db\Connection\ConnectionInterface;
12
use Yiisoft\Db\Exception\Exception;
13
use Yiisoft\Db\Exception\IntegrityException;
14
use Yiisoft\Db\Exception\InvalidCallException;
15
use Yiisoft\Db\Exception\InvalidConfigException;
16
use Yiisoft\Db\Exception\InvalidParamException;
17
use Yiisoft\Db\Expression\Expression;
18
use Yiisoft\Db\Query\Data\DataReaderInterface;
19
use Yiisoft\Db\QueryBuilder\QueryBuilder;
20
use Yiisoft\Db\Query\Data\DataReader;
21
use Yiisoft\Db\Query\Query;
22
use Yiisoft\Db\Schema\Schema;
23
use Yiisoft\Db\TestSupport\Helper\DbHelper;
24
25
use function call_user_func_array;
26
use function date;
27
use function is_array;
28
use function range;
29
use function rtrim;
30
use function setlocale;
31
use function time;
32
33
trait TestCommandTrait
34
{
35
    /**
36
     * @throws Exception|InvalidConfigException
37
     */
38
    public function testConstruct(): void
39
    {
40
        $db = $this->getConnection();
0 ignored issues
show
Bug introduced by
It seems like getConnection() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

40
        /** @scrutinizer ignore-call */ 
41
        $db = $this->getConnection();
Loading history...
41
42
        /* null */
43
        $command = $db->createCommand();
44
        $this->assertEmpty($command->getSql());
0 ignored issues
show
Bug introduced by
It seems like assertEmpty() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

44
        $this->/** @scrutinizer ignore-call */ 
45
               assertEmpty($command->getSql());
Loading history...
45
46
        /* string */
47
        $sql = 'SELECT * FROM customer';
48
        $command = $db->createCommand($sql);
49
        $this->assertEquals($sql, $command->getSql());
0 ignored issues
show
Bug introduced by
It seems like assertEquals() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

49
        $this->/** @scrutinizer ignore-call */ 
50
               assertEquals($sql, $command->getSql());
Loading history...
50
    }
51
52
    /**
53
     * @throws Exception|InvalidConfigException
54
     */
55
    public function testGetSetSql(): void
56
    {
57
        $db = $this->getConnection();
58
59
        $sql = 'SELECT * FROM customer';
60
        $command = $db->createCommand($sql);
61
        $this->assertEquals($sql, $command->getSql());
62
63
        $sql2 = 'SELECT * FROM order';
64
        $command->setSql($sql2);
65
        $this->assertEquals($sql2, $command->getSql());
66
    }
67
68
    /**
69
     * @throws Exception|InvalidConfigException
70
     */
71
    public function testPrepareCancel(): void
72
    {
73
        $db = $this->getConnection();
74
75
        $command = $db->createCommand('SELECT * FROM {{customer}}');
76
        $this->assertNull($command->getPdoStatement());
0 ignored issues
show
Bug introduced by
It seems like assertNull() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

76
        $this->/** @scrutinizer ignore-call */ 
77
               assertNull($command->getPdoStatement());
Loading history...
77
78
        $command->prepare();
79
        $this->assertNotNull($command->getPdoStatement());
0 ignored issues
show
Bug introduced by
It seems like assertNotNull() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

79
        $this->/** @scrutinizer ignore-call */ 
80
               assertNotNull($command->getPdoStatement());
Loading history...
80
81
        $command->cancel();
82
        $this->assertNull($command->getPdoStatement());
83
    }
84
85
    /**
86
     * @throws Exception|InvalidConfigException|Throwable
87
     */
88
    public function testExecute(): void
89
    {
90
        $db = $this->getConnection(true);
91
92
        $sql = 'INSERT INTO {{customer}}([[email]], [[name]], [[address]])'
93
            . ' VALUES (\'[email protected]\', \'user4\', \'address4\')';
94
95
        $command = $db->createCommand($sql);
96
        $this->assertEquals(1, $command->execute());
97
98
        $sql = 'SELECT COUNT(*) FROM {{customer}} WHERE [[name]] = \'user4\'';
99
        $command = $db->createCommand($sql);
100
        $this->assertEquals(1, $command->queryScalar());
101
102
        $command = $db->createCommand('bad SQL');
103
        $this->expectException(Exception::class);
0 ignored issues
show
Bug introduced by
It seems like expectException() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

103
        $this->/** @scrutinizer ignore-call */ 
104
               expectException(Exception::class);
Loading history...
104
        $command->execute();
105
    }
106
107
    public function testDataReaderCreationException(): void
108
    {
109
        $db = $this->getConnection(true);
110
111
        $this->expectException(InvalidParamException::class);
112
        $this->expectExceptionMessage('The PDOStatement cannot be null.');
0 ignored issues
show
Bug introduced by
It seems like expectExceptionMessage() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

112
        $this->/** @scrutinizer ignore-call */ 
113
               expectExceptionMessage('The PDOStatement cannot be null.');
Loading history...
113
114
        $sql = 'SELECT * FROM {{customer}}';
115
        new DataReader($db->createCommand($sql));
116
    }
117
118
    public function testDataReaderRewindException(): void
119
    {
120
        $db = $this->getConnection(true);
121
122
        $this->expectException(InvalidCallException::class);
123
        $this->expectExceptionMessage('DataReader cannot rewind. It is a forward-only reader.');
124
125
        $sql = 'SELECT * FROM {{customer}}';
126
        $reader = $db->createCommand($sql)->query();
127
        $reader->next();
128
        $reader->rewind();
129
    }
130
131
    /**
132
     * @throws Exception|InvalidConfigException|Throwable
133
     */
134
    public function testQuery(): void
135
    {
136
        $db = $this->getConnection(true);
137
138
        $sql = 'SELECT * FROM {{customer}}';
139
        $reader = $db->createCommand($sql)->query();
140
        $this->assertInstanceOf(DataReaderInterface::class, $reader);
0 ignored issues
show
Bug introduced by
It seems like assertInstanceOf() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

140
        $this->/** @scrutinizer ignore-call */ 
141
               assertInstanceOf(DataReaderInterface::class, $reader);
Loading history...
141
142
        // Next line is commented by reason:: For sqlite & pgsql result may be incorrect
143
        // $this->assertEquals(3, $reader->count());
144
        $this->assertIsInt($reader->count());
0 ignored issues
show
Bug introduced by
It seems like assertIsInt() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

144
        $this->/** @scrutinizer ignore-call */ 
145
               assertIsInt($reader->count());
Loading history...
145
        foreach ($reader as $row) {
146
            $this->assertIsArray($row);
0 ignored issues
show
Bug introduced by
It seems like assertIsArray() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

146
            $this->/** @scrutinizer ignore-call */ 
147
                   assertIsArray($row);
Loading history...
147
            $this->assertTrue(count($row) >= 6);
0 ignored issues
show
Bug introduced by
It seems like assertTrue() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

147
            $this->/** @scrutinizer ignore-call */ 
148
                   assertTrue(count($row) >= 6);
Loading history...
148
        }
149
150
        $command = $db->createCommand('bad SQL');
151
        $this->expectException(Exception::class);
152
        $command->query();
153
    }
154
155
    public function testQyeryScalar(): void
156
    {
157
        $db = $this->getConnection();
158
159
        $sql = 'SELECT * FROM {{customer}} ORDER BY [[id]]';
160
        $this->assertEquals($db->createCommand($sql)->queryScalar(), 1);
161
162
        $sql = 'SELECT [[id]] FROM {{customer}} ORDER BY [[id]]';
163
        $command = $db->createCommand($sql);
164
165
        $command->prepare();
166
        $this->assertEquals(1, $command->queryScalar());
167
168
        $command = $db->createCommand('SELECT [[id]] FROM {{customer}} WHERE [[id]] = 10');
169
        $this->assertFalse($command->queryScalar());
0 ignored issues
show
Bug introduced by
It seems like assertFalse() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

169
        $this->/** @scrutinizer ignore-call */ 
170
               assertFalse($command->queryScalar());
Loading history...
170
    }
171
172
    public function testQueryOne(): void
173
    {
174
        $db = $this->getConnection();
175
176
        $sql = 'SELECT * FROM {{customer}} ORDER BY [[id]]';
177
        $row = $db->createCommand($sql)->queryOne();
178
        $this->assertIsArray($row);
179
        $this->assertEquals(1, $row['id']);
180
        $this->assertEquals('user1', $row['name']);
181
182
        $sql = 'SELECT * FROM {{customer}} ORDER BY [[id]]';
183
        $command = $db->createCommand($sql);
184
        $command->prepare();
185
        $row = $command->queryOne();
186
187
        $this->assertIsArray($row);
188
        $this->assertEquals(1, $row['id']);
189
        $this->assertEquals('user1', $row['name']);
190
191
        $sql = 'SELECT * FROM {{customer}} WHERE [[id]] = 10';
192
        $command = $db->createCommand($sql);
193
        $this->assertNull($command->queryOne());
194
    }
195
196
    public function testQueryColumn(): void
197
    {
198
        $db = $this->getConnection();
199
200
        $sql = 'SELECT * FROM {{customer}}';
201
        $column = $db->createCommand($sql)->queryColumn();
202
        $this->assertEquals(range(1, 3), $column);
203
        $this->assertIsArray($column);
204
205
        $command = $db->createCommand('SELECT [[id]] FROM {{customer}} WHERE [[id]] = 10');
206
        $this->assertEmpty($command->queryColumn());
207
    }
208
209
    public function testQueryAll(): void
210
    {
211
        $db = $this->getConnection();
212
213
        $rows = $db->createCommand('SELECT [[id]],[[name]] FROM {{customer}}')->queryAll();
214
        /** @psalm-suppress RedundantCondition */
215
        $this->assertIsArray($rows);
216
        $this->assertCount(3, $rows);
0 ignored issues
show
Bug introduced by
It seems like assertCount() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

216
        $this->/** @scrutinizer ignore-call */ 
217
               assertCount(3, $rows);
Loading history...
217
218
        $row = $rows[2];
219
        $this->assertEquals(3, $row['id']);
220
        $this->assertEquals('user3', $row['name']);
221
        $this->assertTrue(is_array($rows) && count($rows)>1 && count($rows[0]) === 2);
222
223
        $rows = $db->createCommand('SELECT * FROM {{customer}} WHERE [[id]] = 10')->queryAll();
224
        $this->assertEquals([], $rows);
225
    }
226
227
    /**
228
     * @throws Exception|InvalidConfigException|Throwable
229
     */
230
    public function testBatchInsert(): void
231
    {
232
        $db = $this->getConnection();
233
234
        $command = $db->createCommand();
235
        $command->batchInsert(
236
            '{{customer}}',
237
            ['email', 'name', 'address'],
238
            [
239
                ['[email protected]', 't1', 't1 address'],
240
                ['[email protected]', null, false],
241
            ]
242
        );
243
        $this->assertEquals(2, $command->execute());
244
        $result = (new Query($db))
245
            ->select(['email', 'name', 'address'])
246
            ->from('{{customer}}')
247
            ->where(['=', '[[email]]', '[email protected]'])
248
            ->one();
249
        $this->assertCount(3, $result);
250
        $this->assertSame(
0 ignored issues
show
Bug introduced by
It seems like assertSame() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

250
        $this->/** @scrutinizer ignore-call */ 
251
               assertSame(
Loading history...
251
            [
252
                'email' => '[email protected]',
253
                'name' => 't1',
254
                'address' => 't1 address',
255
            ],
256
            $result,
257
        );
258
        $result = (new Query($db))
259
            ->select(['email', 'name', 'address'])
260
            ->from('{{customer}}')
261
            ->where(['=', '[[email]]', '[email protected]'])
262
            ->one();
263
        $this->assertCount(3, $result);
264
        $this->assertSame(
265
            [
266
                'email' => '[email protected]',
267
                'name' => null,
268
                'address' => '0',
269
            ],
270
            $result,
271
        );
272
273
        /**
274
         * @link https://github.com/yiisoft/yii2/issues/11693
275
         */
276
        $command = $db->createCommand();
277
        $command->batchInsert(
278
            '{{customer}}',
279
            ['email', 'name', 'address'],
280
            []
281
        );
282
        $this->assertEquals(0, $command->execute());
283
    }
284
285
    public function testBatchInsertWithManyData(): void
286
    {
287
        $attemptsInsertRows = 200;
288
        $db = $this->getConnection(true);
289
290
        $command = $db->createCommand();
291
        for ($i = 0; $i < $attemptsInsertRows; $i++) {
292
            $values[$i] = ['t' . $i .'@any.com', 't'.$i, 't' . $i . ' address'];
293
        }
294
295
        $command->batchInsert('{{customer}}', ['email', 'name', 'address'], $values);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $values does not seem to be defined for all execution paths leading up to this point.
Loading history...
296
297
        $this->assertEquals($attemptsInsertRows, $command->execute());
298
299
        $insertedRowsCount = (new Query($db))->from('{{customer}}')->count();
300
        $this->assertGreaterThanOrEqual($attemptsInsertRows, $insertedRowsCount);
0 ignored issues
show
Bug introduced by
It seems like assertGreaterThanOrEqual() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

300
        $this->/** @scrutinizer ignore-call */ 
301
               assertGreaterThanOrEqual($attemptsInsertRows, $insertedRowsCount);
Loading history...
301
    }
302
303
    /**
304
     * @throws Exception|InvalidConfigException|Throwable
305
     */
306
    public function testBatchInsertFailsOld(): void
307
    {
308
        $db = $this->getConnection(true);
309
310
        $command = $db->createCommand();
311
        $command->batchInsert(
312
            '{{customer}}',
313
            ['email', 'name', 'address'],
314
            [
315
                ['[email protected]', 'test_name', 'test_address'],
316
            ]
317
        );
318
        $this->assertEquals(1, $command->execute());
319
320
        $result = (new Query($db))
321
            ->select(['email', 'name', 'address'])
322
            ->from('{{customer}}')
323
            ->where(['=', '[[email]]', '[email protected]'])
324
            ->one();
325
326
        $this->assertCount(3, $result);
327
        $this->assertSame(
328
            [
329
                'email' => '[email protected]',
330
                'name' => 'test_name',
331
                'address' => 'test_address',
332
            ],
333
            $result,
334
        );
335
    }
336
337
    /**
338
     * @throws Exception|InvalidConfigException|Throwable
339
     */
340
    public function testBatchInsertWithYield(): void
341
    {
342
        $rows = (static function () {
343
            foreach ([['[email protected]', 'test name', 'test address']] as $row) {
344
                yield $row;
345
            }
346
        })();
347
348
        $command = $this->getConnection()->createCommand();
349
        $command->batchInsert(
350
            '{{customer}}',
351
            ['email', 'name', 'address'],
352
            $rows
353
        );
354
        $this->assertEquals(1, $command->execute());
355
    }
356
357
    /**
358
     * Test batch insert with different data types.
359
     *
360
     * Ensure double is inserted with `.` decimal separator.
361
     *
362
     * @link https://github.com/yiisoft/yii2/issues/6526
363
     */
364
    public function testBatchInsertDataTypesLocale(): void
365
    {
366
        $locale = setlocale(LC_NUMERIC, 0);
367
368
        if (false === $locale) {
369
            $this->markTestSkipped('Your platform does not support locales.');
0 ignored issues
show
Bug introduced by
It seems like markTestSkipped() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

369
            $this->/** @scrutinizer ignore-call */ 
370
                   markTestSkipped('Your platform does not support locales.');
Loading history...
370
        }
371
372
        $db = $this->getConnection(true);
373
374
        try {
375
            /* This one sets decimal mark to comma sign */
376
            setlocale(LC_NUMERIC, 'ru_RU.utf8');
377
            $cols = ['int_col', 'char_col', 'float_col', 'bool_col'];
378
            $data = [
379
                [1, 'A', 9.735, true],
380
                [2, 'B', -2.123, false],
381
                [3, 'C', 2.123, false],
382
            ];
383
384
            /* clear data in "type" table */
385
            $db->createCommand()->delete('type')->execute();
386
387
            /* change, for point oracle. */
388
            if ($db->getDriver()->getDriverName() === 'oci') {
389
                $db->createCommand("ALTER SESSION SET NLS_NUMERIC_CHARACTERS='.,'")->execute();
390
            }
391
392
            /* batch insert on "type" table */
393
            $db->createCommand()->batchInsert('type', $cols, $data)->execute();
394
            $data = $db->createCommand(
395
                'SELECT [[int_col]], [[char_col]], [[float_col]], [[bool_col]] ' .
396
                'FROM {{type}} WHERE [[int_col]] IN (1,2,3) ORDER BY [[int_col]]'
397
            )->queryAll();
398
            $this->assertCount(3, $data);
399
            $this->assertEquals(1, $data[0]['int_col']);
400
            $this->assertEquals(2, $data[1]['int_col']);
401
            $this->assertEquals(3, $data[2]['int_col']);
402
403
            /* rtrim because Postgres padds the column with whitespace */
404
            $this->assertEquals('A', rtrim($data[0]['char_col']));
405
            $this->assertEquals('B', rtrim($data[1]['char_col']));
406
            $this->assertEquals('C', rtrim($data[2]['char_col']));
407
            $this->assertEquals('9.735', $data[0]['float_col']);
408
            $this->assertEquals('-2.123', $data[1]['float_col']);
409
            $this->assertEquals('2.123', $data[2]['float_col']);
410
            $this->assertEquals('1', $data[0]['bool_col']);
411
            $this->assertIsOneOf($data[1]['bool_col'], ['0', false]);
0 ignored issues
show
Bug introduced by
It seems like assertIsOneOf() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

411
            $this->/** @scrutinizer ignore-call */ 
412
                   assertIsOneOf($data[1]['bool_col'], ['0', false]);
Loading history...
412
            $this->assertIsOneOf($data[2]['bool_col'], ['0', false]);
413
        } catch (Exception|Throwable $e) {
414
            setlocale(LC_NUMERIC, $locale);
415
            throw $e;
416
        }
417
418
        setlocale(LC_NUMERIC, $locale);
419
    }
420
421
    /**
422
     * @throws Exception|InvalidConfigException|Throwable
423
     */
424
    public function testInsert(): void
425
    {
426
        $db = $this->getConnection();
427
428
        $db->createCommand('DELETE FROM {{customer}}')->execute();
429
        $command = $db->createCommand();
430
        $command->insert(
431
            '{{customer}}',
432
            [
433
                'email' => '[email protected]',
434
                'name' => 'test',
435
                'address' => 'test address',
436
            ]
437
        )->execute();
438
        $this->assertEquals(1, $db->createCommand('SELECT COUNT(*) FROM {{customer}};')->queryScalar());
439
440
        $record = $db->createCommand('SELECT [[email]], [[name]], [[address]] FROM {{customer}}')->queryOne();
441
        $this->assertEquals([
442
            'email' => '[email protected]',
443
            'name' => 'test',
444
            'address' => 'test address',
445
        ], $record);
446
    }
447
448
    public function testInsertEx(): void
449
    {
450
        $db = $this->getConnection();
451
452
        $result = $db->createCommand()->insertEx(
453
            'customer',
454
            [
455
                'name' => 'testParams',
456
                'email' => '[email protected]',
457
                'address' => '1',
458
            ]
459
        );
460
461
        $this->assertIsArray($result);
462
        $this->assertNotNull($result['id']);
463
    }
464
465
    /**
466
     * Verify that {{}} are not going to be replaced in parameters.
467
     */
468
    public function testNoTablenameReplacement(): void
469
    {
470
        $db = $this->getConnection(true);
471
472
        $db->createCommand()->insert(
473
            '{{customer}}',
474
            [
475
                'name' => 'Some {{weird}} name',
476
                'email' => '[email protected]',
477
                'address' => 'Some {{%weird}} address',
478
            ]
479
        )->execute();
480
481
        if ($db->getDriver()->getDriverName() === 'pgsql') {
482
            $customerId = $db->getLastInsertID('public.customer_id_seq');
483
        } else {
484
            $customerId = $db->getLastInsertID();
485
        }
486
487
        $customer = $db->createCommand('SELECT * FROM {{customer}} WHERE id=' . $customerId)->queryOne();
488
        $this->assertIsArray($customer);
489
        $this->assertEquals('Some {{weird}} name', $customer['name']);
490
        $this->assertEquals('Some {{%weird}} address', $customer['address']);
491
492
        $db->createCommand()->update(
493
            '{{customer}}',
494
            [
495
                'name' => 'Some {{updated}} name',
496
                'address' => 'Some {{%updated}} address',
497
            ],
498
            ['id' => $customerId]
499
        )->execute();
500
        $customer = $db->createCommand('SELECT * FROM {{customer}} WHERE id=' . $customerId)->queryOne();
501
        $this->assertIsArray($customer);
502
        $this->assertEquals('Some {{updated}} name', $customer['name']);
503
        $this->assertEquals('Some {{%updated}} address', $customer['address']);
504
    }
505
506
    /**
507
     * Test INSERT INTO ... SELECT SQL statement.
508
     */
509
    public function testInsertSelect(): void
510
    {
511
        $db = $this->getConnection();
512
513
        $db->createCommand('DELETE FROM {{customer}}')->execute();
514
        $command = $db->createCommand();
515
        $command->insert(
516
            '{{customer}}',
517
            [
518
                'email' => '[email protected]',
519
                'name' => 'test',
520
                'address' => 'test address',
521
            ]
522
        )->execute();
523
        $query = new Query($db);
524
        $query->select(
525
            [
526
                '{{customer}}.[[email]] as name',
527
                '[[name]] as email',
528
                '[[address]]',
529
            ]
530
        )
531
            ->from('{{customer}}')
532
            ->where([
533
                'and',
534
                ['<>', 'name', 'foo'],
535
                ['status' => [0, 1, 2, 3]],
536
            ]);
537
        $command = $db->createCommand();
538
        $command->insert(
539
            '{{customer}}',
540
            $query
541
        )->execute();
542
        $this->assertEquals(2, $db->createCommand('SELECT COUNT(*) FROM {{customer}}')->queryScalar());
543
544
        $record = $db->createCommand('SELECT [[email]], [[name]], [[address]] FROM {{customer}}')->queryAll();
545
        $this->assertEquals([
546
            [
547
                'email' => '[email protected]',
548
                'name' => 'test',
549
                'address' => 'test address',
550
            ],
551
            [
552
                'email' => 'test',
553
                'name' => '[email protected]',
554
                'address' => 'test address',
555
            ],
556
        ], $record);
557
    }
558
559
    /**
560
     * Test INSERT INTO ... SELECT SQL statement with alias syntax.
561
     */
562
    public function testInsertSelectAlias(): void
563
    {
564
        $db = $this->getConnection();
565
566
        $db->createCommand('DELETE FROM {{customer}}')->execute();
567
        $command = $db->createCommand();
568
        $command->insert(
569
            '{{customer}}',
570
            [
571
                'email' => '[email protected]',
572
                'name' => 'test',
573
                'address' => 'test address',
574
            ]
575
        )->execute();
576
        $query = new Query($db);
577
        $query->select(
578
            [
579
                'email' => '{{customer}}.[[email]]',
580
                'address' => 'name',
581
                'name' => 'address',
582
            ]
583
        )
584
            ->from('{{customer}}')
585
            ->where([
586
                'and',
587
                ['<>', 'name', 'foo'],
588
                ['status' => [0, 1, 2, 3]],
589
            ]);
590
        $command = $db->createCommand();
591
        $command->insert(
592
            '{{customer}}',
593
            $query
594
        )->execute();
595
        $this->assertEquals(2, $db->createCommand('SELECT COUNT(*) FROM {{customer}}')->queryScalar());
596
597
        $record = $db->createCommand('SELECT [[email]], [[name]], [[address]] FROM {{customer}}')->queryAll();
598
        $this->assertEquals([
599
            [
600
                'email' => '[email protected]',
601
                'name' => 'test',
602
                'address' => 'test address',
603
            ],
604
            [
605
                'email' => '[email protected]',
606
                'name' => 'test address',
607
                'address' => 'test',
608
            ],
609
        ], $record);
610
    }
611
612
    public function testInsertExpression(): void
613
    {
614
        $expression = '';
615
        $db = $this->getConnection();
616
617
        $db->createCommand('DELETE FROM {{order_with_null_fk}}')->execute();
618
        switch ($db->getDriver()->getDriverName()) {
619
            case 'pgsql':
620
                $expression = "EXTRACT(YEAR FROM TIMESTAMP 'now')";
621
                break;
622
            case 'mysql':
623
                $expression = 'YEAR(NOW())';
624
                break;
625
            case 'sqlite':
626
                $expression = "strftime('%Y')";
627
                break;
628
            case 'sqlsrv':
629
                $expression = 'YEAR(GETDATE())';
630
        }
631
        $command = $db->createCommand();
632
        $command->insert(
633
            '{{order_with_null_fk}}',
634
            [
635
                'created_at' => new Expression($expression),
636
                'total' => 1,
637
            ]
638
        )->execute();
639
        $this->assertEquals(1, $db->createCommand('SELECT COUNT(*) FROM {{order_with_null_fk}}')->queryScalar());
640
641
        $record = $db->createCommand('SELECT [[created_at]] FROM {{order_with_null_fk}}')->queryOne();
642
        $this->assertEquals(['created_at' => date('Y')], $record);
643
    }
644
645
    public function testsInsertQueryAsColumnValue(): void
646
    {
647
        $time = time();
648
        $db = $this->getConnection(true);
649
650
        $db->createCommand('DELETE FROM {{order_with_null_fk}}')->execute();
651
        $command = $db->createCommand();
652
        $command->insert('{{order}}', [
653
            'customer_id' => 1,
654
            'created_at' => $time,
655
            'total' => 42,
656
        ])->execute();
657
658
        if ($db->getDriver()->getDriverName() === 'pgsql') {
659
            $orderId = $db->getLastInsertID('public.order_id_seq');
660
        } else {
661
            $orderId = $db->getLastInsertID();
662
        }
663
664
        $columnValueQuery = new Query($db);
665
        $columnValueQuery->select('created_at')->from('{{order}}')->where(['id' => $orderId]);
666
        $command = $db->createCommand();
667
        $command->insert(
668
            '{{order_with_null_fk}}',
669
            [
670
                'customer_id' => $orderId,
671
                'created_at' => $columnValueQuery,
672
                'total' => 42,
673
            ]
674
        )->execute();
675
        $this->assertEquals(
676
            $time,
677
            $db->createCommand(
678
                'SELECT [[created_at]] FROM {{order_with_null_fk}} WHERE [[customer_id]] = ' . $orderId
679
            )->queryScalar()
680
        );
681
682
        $db->createCommand('DELETE FROM {{order_with_null_fk}}')->execute();
683
        $db->createCommand('DELETE FROM {{order}} WHERE [[id]] = ' . $orderId)->execute();
684
    }
685
686
    public function testCreateTable(): void
687
    {
688
        $db = $this->getConnection();
689
690
        if ($db->getSchema()->getTableSchema('testCreateTable') !== null) {
691
            $db->createCommand()->dropTable('testCreateTable')->execute();
692
        }
693
694
        $db->createCommand()->createTable(
695
            'testCreateTable',
696
            ['id' => Schema::TYPE_PK, 'bar' => Schema::TYPE_INTEGER]
697
        )->execute();
698
        $db->createCommand()->insert('testCreateTable', ['bar' => 1])->execute();
699
        $records = $db->createCommand('SELECT [[id]], [[bar]] FROM {{testCreateTable}};')->queryAll();
700
        $this->assertEquals([['id' => 1, 'bar' => 1]], $records);
701
    }
702
703
    public function testDropTable(): void
704
    {
705
        $db = $this->getConnection();
706
707
        $tableName = 'type';
708
        $this->assertNotNull($db->getSchema()->getTableSchema($tableName));
709
710
        $db->createCommand()->dropTable($tableName)->execute();
711
        $this->assertNull($db->getSchema()->getTableSchema($tableName));
712
    }
713
714
    public function testTruncateTable(): void
715
    {
716
        $db = $this->getConnection();
717
718
        $rows = $db->createCommand('SELECT * FROM {{animal}}')->queryAll();
719
        $this->assertCount(2, $rows);
720
721
        $db->createCommand()->truncateTable('animal')->execute();
722
        $rows = $db->createCommand('SELECT * FROM {{animal}}')->queryAll();
723
        $this->assertCount(0, $rows);
724
    }
725
726
    public function testRenameTable(): void
727
    {
728
        $db = $this->getConnection(true);
729
730
        $fromTableName = 'type';
731
        $toTableName = 'new_type';
732
733
        if ($db->getSchema()->getTableSchema($toTableName) !== null) {
734
            $db->createCommand()->dropTable($toTableName)->execute();
735
        }
736
737
        $this->assertNotNull($db->getSchema()->getTableSchema($fromTableName));
738
        $this->assertNull($db->getSchema()->getTableSchema($toTableName));
739
740
        $db->createCommand()->renameTable($fromTableName, $toTableName)->execute();
741
        $this->assertNull($db->getSchema()->getTableSchema($fromTableName, true));
742
        $this->assertNotNull($db->getSchema()->getTableSchema($toTableName, true));
743
    }
744
745
    protected function performAndCompareUpsertResult(ConnectionInterface $db, array $data): void
746
    {
747
        $params = $data['params'];
748
        $expected = $data['expected'] ?? $params[1];
749
750
        $command = $db->createCommand();
751
        call_user_func_array([$command, 'upsert'], $params);
752
        $command->execute();
753
754
        $actual = (new Query($db))
755
            ->select([
756
                'email',
757
                'address' => new Expression($this->upsertTestCharCast),
758
                'status',
759
            ])
760
            ->from('T_upsert')
761
            ->one();
762
        $this->assertEquals($expected, $actual, $this->upsertTestCharCast);
763
    }
764
765
    public function testAddDropForeignKey(): void
766
    {
767
        $db = $this->getConnection();
768
769
        $tableName = 'test_fk';
770
        $name = 'test_fk_constraint';
771
772
        $schema = $db->getSchema();
773
774
        if ($schema->getTableSchema($tableName) !== null) {
775
            $db->createCommand()->dropTable($tableName)->execute();
776
        }
777
778
        $db->createCommand()->createTable($tableName, [
779
            'int1' => 'integer not null unique',
780
            'int2' => 'integer not null unique',
781
            'int3' => 'integer not null unique',
782
            'int4' => 'integer not null unique',
783
            'unique ([[int1]], [[int2]])',
784
            'unique ([[int3]], [[int4]])',
785
        ])->execute();
786
        $this->assertEmpty($schema->getTableForeignKeys($tableName, true));
787
788
        $db->createCommand()->addForeignKey($name, $tableName, ['int1'], $tableName, ['int3'])->execute();
789
        $this->assertEquals(['int1'], $schema->getTableForeignKeys($tableName, true)[0]->getColumnNames());
790
        $this->assertEquals(['int3'], $schema->getTableForeignKeys($tableName, true)[0]->getForeignColumnNames());
791
792
        $db->createCommand()->dropForeignKey($name, $tableName)->execute();
793
        $this->assertEmpty($schema->getTableForeignKeys($tableName, true));
794
795
        $db->createCommand()->addForeignKey(
796
            $name,
797
            $tableName,
798
            ['int1', 'int2'],
799
            $tableName,
800
            ['int3', 'int4']
801
        )->execute();
802
        $this->assertEquals(
803
            ['int1', 'int2'],
804
            $schema->getTableForeignKeys($tableName, true)[0]->getColumnNames()
805
        );
806
        $this->assertEquals(
807
            ['int3', 'int4'],
808
            $schema->getTableForeignKeys($tableName, true)[0]->getForeignColumnNames()
809
        );
810
    }
811
812
    public function testCreateDropIndex(): void
813
    {
814
        $db = $this->getConnection();
815
816
        $tableName = 'test_idx';
817
        $name = 'test_idx_constraint';
818
819
        $schema = $db->getSchema();
820
821
        if ($schema->getTableSchema($tableName) !== null) {
822
            $db->createCommand()->dropTable($tableName)->execute();
823
        }
824
825
        $db->createCommand()->createTable($tableName, [
826
            'int1' => 'integer not null',
827
            'int2' => 'integer not null',
828
        ])->execute();
829
        $this->assertEmpty($schema->getTableIndexes($tableName, true));
830
831
        $db->createCommand()->createIndex($name, $tableName, ['int1'])->execute();
832
        $this->assertEquals(['int1'], $schema->getTableIndexes($tableName, true)[0]->getColumnNames());
833
        $this->assertFalse($schema->getTableIndexes($tableName, true)[0]->isUnique());
834
835
        $db->createCommand()->dropIndex($name, $tableName)->execute();
836
        $this->assertEmpty($schema->getTableIndexes($tableName, true));
837
838
        $db->createCommand()->createIndex($name, $tableName, ['int1', 'int2'])->execute();
839
        $this->assertEquals(['int1', 'int2'], $schema->getTableIndexes($tableName, true)[0]->getColumnNames());
840
        $this->assertFalse($schema->getTableIndexes($tableName, true)[0]->isUnique());
841
842
        $db->createCommand()->dropIndex($name, $tableName)->execute();
843
        $this->assertEmpty($schema->getTableIndexes($tableName, true));
844
        $this->assertEmpty($schema->getTableIndexes($tableName, true));
845
846
        $db->createCommand()->createIndex($name, $tableName, ['int1'], QueryBuilder::INDEX_UNIQUE)->execute();
847
        $this->assertEquals(['int1'], $schema->getTableIndexes($tableName, true)[0]->getColumnNames());
848
        $this->assertTrue($schema->getTableIndexes($tableName, true)[0]->isUnique());
849
850
        $db->createCommand()->dropIndex($name, $tableName)->execute();
851
        $this->assertEmpty($schema->getTableIndexes($tableName, true));
852
853
        $db->createCommand()->createIndex($name, $tableName, ['int1', 'int2'], QueryBuilder::INDEX_UNIQUE)->execute();
854
        $this->assertEquals(['int1', 'int2'], $schema->getTableIndexes($tableName, true)[0]->getColumnNames());
855
        $this->assertTrue($schema->getTableIndexes($tableName, true)[0]->isUnique());
856
    }
857
858
    public function testAddDropUnique(): void
859
    {
860
        $db = $this->getConnection();
861
862
        $tableName = 'test_uq';
863
        $name = 'test_uq_constraint';
864
865
        $schema = $db->getSchema();
866
867
        if ($schema->getTableSchema($tableName) !== null) {
868
            $db->createCommand()->dropTable($tableName)->execute();
869
        }
870
871
        $db->createCommand()->createTable($tableName, [
872
            'int1' => 'integer not null',
873
            'int2' => 'integer not null',
874
        ])->execute();
875
        $this->assertEmpty($schema->getTableUniques($tableName, true));
876
877
        $db->createCommand()->addUnique($name, $tableName, ['int1'])->execute();
878
        $this->assertEquals(['int1'], $schema->getTableUniques($tableName, true)[0]->getColumnNames());
879
880
        $db->createCommand()->dropUnique($name, $tableName)->execute();
881
        $this->assertEmpty($schema->getTableUniques($tableName, true));
882
883
        $db->createCommand()->addUnique($name, $tableName, ['int1', 'int2'])->execute();
884
        $this->assertEquals(['int1', 'int2'], $schema->getTableUniques($tableName, true)[0]->getColumnNames());
885
    }
886
887
    public function testBindValues(): void
888
    {
889
        $command = $this->getConnection()->createCommand();
890
891
        $values = [
892
            'int' => 1,
893
            'string' => 'str',
894
        ];
895
        $command->bindValues($values);
896
        $bindedValues = $command->getParams(false);
897
898
        $this->assertIsArray($bindedValues);
899
        $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues);
0 ignored issues
show
Bug introduced by
It seems like assertContainsOnlyInstancesOf() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

899
        $this->/** @scrutinizer ignore-call */ 
900
               assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues);
Loading history...
900
        $this->assertCount(2, $bindedValues);
901
902
        $param = new Param('str', 99);
903
        $command->bindValues(['param' => $param]);
904
        $bindedValues = $command->getParams(false);
905
906
        $this->assertIsArray($bindedValues);
907
        $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues);
908
        $this->assertCount(3, $bindedValues);
909
        $this->assertEquals($param, $bindedValues['param']);
910
        $this->assertNotEquals($param, $bindedValues['int']);
0 ignored issues
show
Bug introduced by
It seems like assertNotEquals() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

910
        $this->/** @scrutinizer ignore-call */ 
911
               assertNotEquals($param, $bindedValues['int']);
Loading history...
911
912
        // Replace test
913
        $command->bindValues(['int' => $param]);
914
        $bindedValues = $command->getParams(false);
915
916
        $this->assertIsArray($bindedValues);
917
        $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues);
918
        $this->assertCount(3, $bindedValues);
919
        $this->assertEquals($param, $bindedValues['int']);
920
    }
921
922
    public function testIntegrityViolation(): void
923
    {
924
        $this->expectException(IntegrityException::class);
925
926
        $db = $this->getConnection();
927
928
        $sql = 'INSERT INTO {{profile}}([[id]], [[description]]) VALUES (123, \'duplicate\')';
929
        $command = $db->createCommand($sql);
930
        $command->execute();
931
        $command->execute();
932
    }
933
934
    public function testLastInsertId(): void
935
    {
936
        $db = $this->getConnection(true);
937
938
        $sql = 'INSERT INTO {{profile}}([[description]]) VALUES (\'non duplicate\')';
939
        $command = $db->createCommand($sql);
940
        $command->execute();
941
        $this->assertEquals(3, $db->getLastInsertID());
942
    }
943
944
    public function testLastInsertIdException(): void
945
    {
946
        $db = $this->getConnection();
947
        $db->close();
948
949
        $this->expectException(InvalidCallException::class);
950
        $db->getLastInsertID();
951
    }
952
953
    public function testQueryCache(): void
954
    {
955
        $db = $this->getConnection(true);
956
957
        /** @psalm-suppress PossiblyNullReference */
958
        $this->queryCache->setEnable(true);
959
        $command = $db->createCommand('SELECT [[name]] FROM {{customer}} WHERE [[id]] = :id');
960
        $this->assertEquals('user1', $command->bindValue(':id', 1)->queryScalar());
961
962
        $update = $db->createCommand('UPDATE {{customer}} SET [[name]] = :name WHERE [[id]] = :id');
963
        $update->bindValues([':id' => 1, ':name' => 'user11'])->execute();
964
        $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
965
966
        $db->cache(function (ConnectionInterface $db) use ($command, $update) {
967
            $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
968
969
            $update->bindValues([':id' => 2, ':name' => 'user22'])->execute();
970
971
            $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
972
973
            $db->noCache(function () use ($command) {
974
                $this->assertEquals('user22', $command->bindValue(':id', 2)->queryScalar());
975
            });
976
977
            $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
978
        }, 10);
979
980
        /** @psalm-suppress PossiblyNullReference */
981
        $this->queryCache->setEnable(false);
982
983
        $db->cache(function () use ($command, $update) {
984
            $this->assertEquals('user22', $command->bindValue(':id', 2)->queryScalar());
985
            $update->bindValues([':id' => 2, ':name' => 'user2'])->execute();
986
            $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
987
        }, 10);
988
989
        /** @psalm-suppress PossiblyNullReference */
990
        $this->queryCache->setEnable(true);
991
        $command = $db->createCommand('SELECT [[name]] FROM {{customer}} WHERE [[id]] = :id')->cache();
992
        $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
993
994
        $update->bindValues([':id' => 1, ':name' => 'user1'])->execute();
995
        $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
996
        $this->assertEquals('user1', $command->noCache()->bindValue(':id', 1)->queryScalar());
997
998
        $command = $db->createCommand('SELECT [[name]] FROM {{customer}} WHERE [[id]] = :id');
999
1000
        $db->cache(function () use ($command) {
1001
            $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
1002
            $this->assertEquals('user1', $command->noCache()->bindValue(':id', 1)->queryScalar());
1003
        }, 10);
1004
    }
1005
1006
    public function testColumnCase(): void
1007
    {
1008
        $db = $this->getConnection();
1009
1010
        $this->assertEquals(PDO::CASE_NATURAL, $db->getActivePDO()->getAttribute(PDO::ATTR_CASE));
1011
1012
        $sql = 'SELECT [[customer_id]], [[total]] FROM {{order}}';
1013
        $rows = $db->createCommand($sql)->queryAll();
1014
        $this->assertTrue(isset($rows[0]));
1015
        $this->assertTrue(isset($rows[0]['customer_id']));
1016
        $this->assertTrue(isset($rows[0]['total']));
1017
1018
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
1019
        $rows = $db->createCommand($sql)->queryAll();
1020
        $this->assertTrue(isset($rows[0]));
1021
        $this->assertTrue(isset($rows[0]['customer_id']));
1022
        $this->assertTrue(isset($rows[0]['total']));
1023
1024
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
1025
        $rows = $db->createCommand($sql)->queryAll();
1026
        $this->assertTrue(isset($rows[0]));
1027
        $this->assertTrue(isset($rows[0]['CUSTOMER_ID']));
1028
        $this->assertTrue(isset($rows[0]['TOTAL']));
1029
    }
1030
1031
    public function testTransaction(): void
1032
    {
1033
        $db = $this->getConnection();
1034
1035
        $this->assertNull($db->getTransaction());
1036
1037
        $command = $db->createCommand("INSERT INTO {{profile}}([[description]]) VALUES('command transaction')");
1038
        $this->invokeMethod($command, 'requireTransaction');
0 ignored issues
show
Bug introduced by
It seems like invokeMethod() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

1038
        $this->/** @scrutinizer ignore-call */ 
1039
               invokeMethod($command, 'requireTransaction');
Loading history...
1039
        $command->execute();
1040
        $this->assertNull($db->getTransaction());
1041
        $this->assertEquals(
1042
            1,
1043
            $db->createCommand(
1044
                "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'command transaction'"
1045
            )->queryScalar()
1046
        );
1047
    }
1048
1049
    public function testRetryHandler(): void
1050
    {
1051
        $db = $this->getConnection();
1052
1053
        $this->assertNull($db->getTransaction());
1054
1055
        $db->createCommand("INSERT INTO {{profile}}([[description]]) VALUES('command retry')")->execute();
1056
        $this->assertNull($db->getTransaction());
1057
        $this->assertEquals(
1058
            1,
1059
            $db->createCommand(
1060
                "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'command retry'"
1061
            )->queryScalar()
1062
        );
1063
1064
        $attempts = null;
1065
        $hitHandler = false;
1066
        $hitCatch = false;
1067
1068
        $command = $db->createCommand(
1069
            "INSERT INTO {{profile}}([[id]], [[description]]) VALUES(1, 'command retry')"
1070
        );
1071
        $this->invokeMethod(
1072
            $command,
1073
            'setRetryHandler',
1074
            [static function ($exception, $attempt) use (&$attempts, &$hitHandler) {
1075
                $attempts = $attempt;
1076
                $hitHandler = true;
1077
1078
                return $attempt <= 2;
1079
            }]
1080
        );
1081
1082
        try {
1083
            $command->execute();
1084
        } catch (Exception $e) {
1085
            $hitCatch = true;
1086
            $this->assertInstanceOf(IntegrityException::class, $e);
1087
        }
1088
1089
        $this->assertNull($db->getTransaction());
1090
        $this->assertSame(3, $attempts);
1091
        $this->assertTrue($hitHandler);
1092
        $this->assertTrue($hitCatch);
1093
    }
1094
1095
    public function testCreateView(): void
1096
    {
1097
        $db = $this->getConnection();
1098
1099
        $subquery = (new Query($db))
1100
            ->select('bar')
1101
            ->from('testCreateViewTable')
1102
            ->where(['>', 'bar', '5']);
1103
1104
        if ($db->getSchema()->getTableSchema('testCreateView') !== null) {
1105
            $db->createCommand()->dropView('testCreateView')->execute();
1106
        }
1107
1108
        if ($db->getSchema()->getTableSchema('testCreateViewTable')) {
1109
            $db->createCommand()->dropTable('testCreateViewTable')->execute();
1110
        }
1111
1112
        $db->createCommand()->createTable('testCreateViewTable', [
1113
            'id' => Schema::TYPE_PK,
1114
            'bar' => Schema::TYPE_INTEGER,
1115
        ])->execute();
1116
        $db->createCommand()->insert('testCreateViewTable', ['bar' => 1])->execute();
1117
        $db->createCommand()->insert('testCreateViewTable', ['bar' => 6])->execute();
1118
        $db->createCommand()->createView('testCreateView', $subquery)->execute();
1119
        $records = $db->createCommand('SELECT [[bar]] FROM {{testCreateView}};')->queryAll();
1120
        $this->assertEquals([['bar' => 6]], $records);
1121
    }
1122
1123
    public function testDropView(): void
1124
    {
1125
        $db = $this->getConnection();
1126
1127
        /* since it already exists in the fixtures */
1128
        $viewName = 'animal_view';
1129
1130
        $this->assertNotNull($db->getSchema()->getTableSchema($viewName));
1131
1132
        $db->createCommand()->dropView($viewName)->execute();
1133
        $this->assertNull($db->getSchema()->getTableSchema($viewName));
1134
    }
1135
1136
    public function batchInsertSqlProviderTrait(): array
1137
    {
1138
        $db = $this->getConnection();
1139
1140
        return [
1141
            'multirow' => [
1142
                'type',
1143
                ['int_col', 'float_col', 'char_col', 'bool_col'],
1144
                'values' => [
1145
                    ['0', '0.0', 'test string', true,],
1146
                    [false, 0, 'test string2', false,],
1147
                ],
1148
                'expected' => DbHelper::replaceQuotes(
1149
                    'INSERT INTO [[type]] ([[int_col]], [[float_col]], [[char_col]], [[bool_col]])'
1150
                    . " VALUES (:qp0, :qp1, :qp2, :qp3), (:qp4, :qp5, :qp6, :qp7)",
1151
                    $db->getDriver()->getDriverName(),
1152
                ),
1153
                'expectedParams' => [
1154
                    ':qp0' => 0,
1155
                    ':qp1' => 0.0,
1156
                    ':qp2' => 'test string',
1157
                    ':qp3' => true,
1158
                    ':qp4' => 0,
1159
                    ':qp5' => 0.0,
1160
                    ':qp6' => 'test string2',
1161
                    ':qp7' => false,
1162
                ],
1163
                2,
1164
            ],
1165
            'issue11242' => [
1166
                'type',
1167
                ['int_col', 'float_col', 'char_col', 'bool_col'],
1168
                'values' => [[1.0, 1.1, 'Kyiv {{city}}, Ukraine', true]],
1169
                /**
1170
                 * {@see https://github.com/yiisoft/yii2/issues/11242}
1171
                 *
1172
                 * Make sure curly bracelets (`{{..}}`) in values will not be escaped
1173
                 */
1174
                'expected' => DbHelper::replaceQuotes(
1175
                    'INSERT INTO [[type]] ([[int_col]], [[float_col]], [[char_col]], [[bool_col]])'
1176
                    . " VALUES (:qp0, :qp1, :qp2, :qp3)",
1177
                    $db->getDriver()->getDriverName(),
1178
                ),
1179
                'expectedParams' => [
1180
                    ':qp0' => 1,
1181
                    ':qp1' => 1.1,
1182
                    ':qp2' => 'Kyiv {{city}}, Ukraine',
1183
                    ':qp3' => true,
1184
                ],
1185
            ],
1186
            'wrongBehavior' => [
1187
                '{{%type}}',
1188
                ['{{%type}}.[[int_col]]', '[[float_col]]', 'char_col', 'bool_col'],
1189
                'values' => [['0', '0.0', 'Kyiv {{city}}, Ukraine', false]],
1190
                /**
1191
                 * Test covers potentially wrong behavior and marks it as expected!.
1192
                 *
1193
                 * In case table name or table column is passed with curly or square bracelets, QueryBuilder can not
1194
                 * determine the table schema and typecast values properly.
1195
                 * TODO: make it work. Impossible without BC breaking for public methods.
1196
                 */
1197
                'expected' => DbHelper::replaceQuotes(
1198
                    'INSERT INTO [[type]] ([[type]].[[int_col]], [[float_col]], [[char_col]], [[bool_col]])'
1199
                    . " VALUES (:qp0, :qp1, :qp2, :qp3)",
1200
                    $db->getDriver()->getDriverName(),
1201
                ),
1202
                'expectedParams' => [
1203
                    ':qp0' => '0',
1204
                    ':qp1' => '0.0',
1205
                    ':qp2' => 'Kyiv {{city}}, Ukraine',
1206
                    ':qp3' => false,
1207
                ],
1208
            ],
1209
            'batchInsert binds params from expression' => [
1210
                '{{%type}}',
1211
                ['int_col', 'float_col', 'char_col', 'bool_col'],
1212
                /**
1213
                 * This example is completely useless. This feature of batchInsert is intended to be used with complex
1214
                 * expression objects, such as JsonExpression.
1215
                 */
1216
                'values' => [[new Expression(':exp1', [':exp1' => 42]), 1, 'test', false]],
1217
                'expected' => DbHelper::replaceQuotes(
1218
                    'INSERT INTO [[type]] ([[int_col]], [[float_col]], [[char_col]], [[bool_col]])'
1219
                    . " VALUES (:exp1, :qp1, :qp2, :qp3)",
1220
                    $db->getDriver()->getDriverName(),
1221
                ),
1222
                'expectedParams' => [
1223
                    ':exp1' => 42,
1224
                    ':qp1' => 1.0,
1225
                    ':qp2' => 'test',
1226
                    ':qp3' => false,
1227
                ],
1228
            ],
1229
        ];
1230
    }
1231
1232
    public function bindParamsNonWhereProviderTrait(): array
1233
    {
1234
        return [
1235
            ['SELECT SUBSTR(name, :len) FROM {{customer}} WHERE [[email]] = :email GROUP BY SUBSTR(name, :len)'],
1236
            ['SELECT SUBSTR(name, :len) FROM {{customer}} WHERE [[email]] = :email ORDER BY SUBSTR(name, :len)'],
1237
            ['SELECT SUBSTR(name, :len) FROM {{customer}} WHERE [[email]] = :email'],
1238
        ];
1239
    }
1240
1241
    public function getRawSqlProviderTrait(): array
1242
    {
1243
        return [
1244
            [
1245
                'SELECT * FROM customer WHERE id = :id',
1246
                [':id' => 1],
1247
                'SELECT * FROM customer WHERE id = 1',
1248
            ],
1249
            [
1250
                'SELECT * FROM customer WHERE id = :id',
1251
                ['id' => 1],
1252
                'SELECT * FROM customer WHERE id = 1',
1253
            ],
1254
            [
1255
                'SELECT * FROM customer WHERE id = :id',
1256
                ['id' => null],
1257
                'SELECT * FROM customer WHERE id = NULL',
1258
            ],
1259
            [
1260
                'SELECT * FROM customer WHERE id = :base OR id = :basePrefix',
1261
                [
1262
                    'base' => 1,
1263
                    'basePrefix' => 2,
1264
                ],
1265
                'SELECT * FROM customer WHERE id = 1 OR id = 2',
1266
            ],
1267
            /**
1268
             * {@see https://github.com/yiisoft/yii2/issues/9268}
1269
             */
1270
            [
1271
                'SELECT * FROM customer WHERE active = :active',
1272
                [':active' => false],
1273
                'SELECT * FROM customer WHERE active = FALSE',
1274
            ],
1275
            /**
1276
             * {@see https://github.com/yiisoft/yii2/issues/15122}
1277
             */
1278
            [
1279
                'SELECT * FROM customer WHERE id IN (:ids)',
1280
                [':ids' => new Expression(implode(', ', [1, 2]))],
1281
                'SELECT * FROM customer WHERE id IN (1, 2)',
1282
            ],
1283
        ];
1284
    }
1285
1286
    public function invalidSelectColumnsProviderTrait(): array
1287
    {
1288
        return [
1289
            [[]],
1290
            ['*'],
1291
            [['*']],
1292
        ];
1293
    }
1294
1295
    public function upsertProviderTrait(): array
1296
    {
1297
        return [
1298
            'regular values' => [
1299
                [
1300
                    'params' => [
1301
                        'T_upsert',
1302
                        [
1303
                            'email' => '[email protected]',
1304
                            'address' => 'Earth',
1305
                            'status' => 3,
1306
                        ],
1307
                    ],
1308
                ],
1309
                [
1310
                    'params' => [
1311
                        'T_upsert',
1312
                        [
1313
                            'email' => '[email protected]',
1314
                            'address' => 'Universe',
1315
                            'status' => 1,
1316
                        ],
1317
                    ],
1318
                ],
1319
            ],
1320
            'regular values with update part' => [
1321
                [
1322
                    'params' => [
1323
                        'T_upsert',
1324
                        [
1325
                            'email' => '[email protected]',
1326
                            'address' => 'Earth',
1327
                            'status' => 3,
1328
                        ],
1329
                        [
1330
                            'address' => 'Moon',
1331
                            'status' => 2,
1332
                        ],
1333
                    ],
1334
                ],
1335
                [
1336
                    'params' => [
1337
                        'T_upsert',
1338
                        [
1339
                            'email' => '[email protected]',
1340
                            'address' => 'Universe',
1341
                            'status' => 1,
1342
                        ],
1343
                        [
1344
                            'address' => 'Moon',
1345
                            'status' => 2,
1346
                        ],
1347
                    ],
1348
                    'expected' => [
1349
                        'email' => '[email protected]',
1350
                        'address' => 'Moon',
1351
                        'status' => 2,
1352
                    ],
1353
                ],
1354
            ],
1355
            'regular values without update part' => [
1356
                [
1357
                    'params' => [
1358
                        'T_upsert',
1359
                        [
1360
                            'email' => '[email protected]',
1361
                            'address' => 'Earth',
1362
                            'status' => 3,
1363
                        ],
1364
                        false,
1365
                    ],
1366
                ],
1367
                [
1368
                    'params' => [
1369
                        'T_upsert',
1370
                        [
1371
                            'email' => '[email protected]',
1372
                            'address' => 'Universe',
1373
                            'status' => 1,
1374
                        ],
1375
                        false,
1376
                    ],
1377
                    'expected' => [
1378
                        'email' => '[email protected]',
1379
                        'address' => 'Earth',
1380
                        'status' => 3,
1381
                    ],
1382
                ],
1383
            ],
1384
            'query' => [
1385
                [
1386
                    'params' => [
1387
                        'T_upsert',
1388
                        (new Query($this->getConnection()))
1389
                            ->select([
1390
                                'email',
1391
                                'address',
1392
                                'status' => new Expression('1'),
1393
                            ])
1394
                            ->from('customer')
1395
                            ->where(['name' => 'user1'])
1396
                            ->limit(1),
1397
                    ],
1398
                    'expected' => [
1399
                        'email' => '[email protected]',
1400
                        'address' => 'address1',
1401
                        'status' => 1,
1402
                    ],
1403
                ],
1404
                [
1405
                    'params' => [
1406
                        'T_upsert',
1407
                        (new Query($this->getConnection()))
1408
                            ->select([
1409
                                'email',
1410
                                'address',
1411
                                'status' => new Expression('2'),
1412
                            ])
1413
                            ->from('customer')
1414
                            ->where(['name' => 'user1'])
1415
                            ->limit(1),
1416
                    ],
1417
                    'expected' => [
1418
                        'email' => '[email protected]',
1419
                        'address' => 'address1',
1420
                        'status' => 2,
1421
                    ],
1422
                ],
1423
            ],
1424
            'query with update part' => [
1425
                [
1426
                    'params' => [
1427
                        'T_upsert',
1428
                        (new Query($this->getConnection()))
1429
                            ->select([
1430
                                'email',
1431
                                'address',
1432
                                'status' => new Expression('1'),
1433
                            ])
1434
                            ->from('customer')
1435
                            ->where(['name' => 'user1'])
1436
                            ->limit(1),
1437
                        [
1438
                            'address' => 'Moon',
1439
                            'status' => 2,
1440
                        ],
1441
                    ],
1442
                    'expected' => [
1443
                        'email' => '[email protected]',
1444
                        'address' => 'address1',
1445
                        'status' => 1,
1446
                    ],
1447
                ],
1448
                [
1449
                    'params' => [
1450
                        'T_upsert',
1451
                        (new Query($this->getConnection()))
1452
                            ->select([
1453
                                'email',
1454
                                'address',
1455
                                'status' => new Expression('3'),
1456
                            ])
1457
                            ->from('customer')
1458
                            ->where(['name' => 'user1'])
1459
                            ->limit(1),
1460
                        [
1461
                            'address' => 'Moon',
1462
                            'status' => 2,
1463
                        ],
1464
                    ],
1465
                    'expected' => [
1466
                        'email' => '[email protected]',
1467
                        'address' => 'Moon',
1468
                        'status' => 2,
1469
                    ],
1470
                ],
1471
            ],
1472
            'query without update part' => [
1473
                [
1474
                    'params' => [
1475
                        'T_upsert',
1476
                        (new Query($this->getConnection()))
1477
                            ->select([
1478
                                'email',
1479
                                'address',
1480
                                'status' => new Expression('1'),
1481
                            ])
1482
                            ->from('customer')
1483
                            ->where(['name' => 'user1'])
1484
                            ->limit(1),
1485
                        false,
1486
                    ],
1487
                    'expected' => [
1488
                        'email' => '[email protected]',
1489
                        'address' => 'address1',
1490
                        'status' => 1,
1491
                    ],
1492
                ],
1493
                [
1494
                    'params' => [
1495
                        'T_upsert',
1496
                        (new Query($this->getConnection()))
1497
                            ->select([
1498
                                'email',
1499
                                'address',
1500
                                'status' => new Expression('2'),
1501
                            ])
1502
                            ->from('customer')
1503
                            ->where(['name' => 'user1'])
1504
                            ->limit(1),
1505
                        false,
1506
                    ],
1507
                    'expected' => [
1508
                        'email' => '[email protected]',
1509
                        'address' => 'address1',
1510
                        'status' => 1,
1511
                    ],
1512
                ],
1513
            ],
1514
        ];
1515
    }
1516
1517
    public function testAlterTable(): void
1518
    {
1519
        $db = $this->getConnection();
1520
1521
        if ($db->getDriver()->getDriverName() === 'sqlite') {
1522
            $this->markTestSkipped('Sqlite does not support alterTable');
1523
        }
1524
1525
        if ($db->getSchema()->getTableSchema('testAlterTable', true) !== null) {
1526
            $db->createCommand()->dropTable('testAlterTable')->execute();
1527
        }
1528
1529
        $db->createCommand()->createTable(
1530
            'testAlterTable',
1531
            [
1532
                'id' => Schema::TYPE_PK,
1533
                'bar' => Schema::TYPE_INTEGER,
1534
            ]
1535
        )->execute();
1536
1537
        $db->createCommand()->insert('testAlterTable', ['bar' => 1])->execute();
1538
        $db->createCommand()->alterColumn('testAlterTable', 'bar', Schema::TYPE_STRING)->execute();
1539
        $db->createCommand()->insert('testAlterTable', ['bar' => 'hello'])->execute();
1540
1541
        $records = $db->createCommand('SELECT [[id]], [[bar]] FROM {{testAlterTable}}')->queryAll();
1542
        $this->assertEquals([
1543
            ['id' => 1, 'bar' => 1],
1544
            ['id' => 2, 'bar' => 'hello'],
1545
        ], $records);
1546
    }
1547
1548
    public function testInsertToBlob(): void
1549
    {
1550
        $db = $this->getConnection(true);
1551
1552
        $db->createCommand()->delete('type')->execute();
1553
1554
        $columns = [
1555
            'int_col' => 1,
1556
            'char_col' => 'test',
1557
            'float_col' => 3.14,
1558
            'bool_col' => true,
1559
            'blob_col' => serialize(['test' => 'data', 'num' => 222]),
1560
        ];
1561
        $db->createCommand()->insert('type', $columns)->execute();
1562
        $result = $db->createCommand('SELECT [[blob_col]] FROM {{type}}')->queryOne();
1563
1564
        $this->assertIsArray($result);
1565
        $resultBlob = is_resource($result['blob_col']) ? stream_get_contents($result['blob_col']) : $result['blob_col'];
1566
1567
        $this->assertEquals($columns['blob_col'], $resultBlob);
1568
    }
1569
}
1570