Passed
Branch master (bbd9d5)
by Wilmer
04:13 queued 01:59
created

TestCommandTrait::testBatchInsertFailsOld()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 18
c 1
b 0
f 0
dl 0
loc 28
rs 9.6666
cc 1
nc 1
nop 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
24
use function call_user_func_array;
25
use function date;
26
use function is_array;
27
use function range;
28
use function rtrim;
29
use function setlocale;
30
use function time;
31
32
trait TestCommandTrait
33
{
34
    /**
35
     * @throws Exception|InvalidConfigException
36
     */
37
    public function testConstruct(): void
38
    {
39
        $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

39
        /** @scrutinizer ignore-call */ 
40
        $db = $this->getConnection();
Loading history...
40
41
        /* null */
42
        $command = $db->createCommand();
43
        $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

43
        $this->/** @scrutinizer ignore-call */ 
44
               assertEmpty($command->getSql());
Loading history...
44
45
        /* string */
46
        $sql = 'SELECT * FROM customer';
47
        $command = $db->createCommand($sql);
48
        $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

48
        $this->/** @scrutinizer ignore-call */ 
49
               assertEquals($sql, $command->getSql());
Loading history...
49
    }
50
51
    /**
52
     * @throws Exception|InvalidConfigException
53
     */
54
    public function testGetSetSql(): void
55
    {
56
        $db = $this->getConnection();
57
58
        $sql = 'SELECT * FROM customer';
59
        $command = $db->createCommand($sql);
60
        $this->assertEquals($sql, $command->getSql());
61
62
        $sql2 = 'SELECT * FROM order';
63
        $command->setSql($sql2);
64
        $this->assertEquals($sql2, $command->getSql());
65
    }
66
67
    /**
68
     * @throws Exception|InvalidConfigException
69
     */
70
    public function testPrepareCancel(): void
71
    {
72
        $db = $this->getConnection();
73
74
        $command = $db->createCommand('SELECT * FROM {{customer}}');
75
        $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

75
        $this->/** @scrutinizer ignore-call */ 
76
               assertNull($command->getPdoStatement());
Loading history...
76
77
        $command->prepare();
78
        $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

78
        $this->/** @scrutinizer ignore-call */ 
79
               assertNotNull($command->getPdoStatement());
Loading history...
79
80
        $command->cancel();
81
        $this->assertNull($command->getPdoStatement());
82
    }
83
84
    /**
85
     * @throws Exception|InvalidConfigException|Throwable
86
     */
87
    public function testExecute(): void
88
    {
89
        $db = $this->getConnection(true);
90
91
        $sql = 'INSERT INTO {{customer}}([[email]], [[name]], [[address]])'
92
            . ' VALUES (\'[email protected]\', \'user4\', \'address4\')';
93
94
        $command = $db->createCommand($sql);
95
        $this->assertEquals(1, $command->execute());
96
97
        $sql = 'SELECT COUNT(*) FROM {{customer}} WHERE [[name]] = \'user4\'';
98
        $command = $db->createCommand($sql);
99
        $this->assertEquals(1, $command->queryScalar());
100
101
        $command = $db->createCommand('bad SQL');
102
        $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

102
        $this->/** @scrutinizer ignore-call */ 
103
               expectException(Exception::class);
Loading history...
103
        $command->execute();
104
    }
105
106
    public function testDataReaderCreationException(): void
107
    {
108
        $db = $this->getConnection(true);
109
110
        $this->expectException(InvalidParamException::class);
111
        $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

111
        $this->/** @scrutinizer ignore-call */ 
112
               expectExceptionMessage('The PDOStatement cannot be null.');
Loading history...
112
113
        $sql = 'SELECT * FROM {{customer}}';
114
        new DataReader($db->createCommand($sql));
115
    }
116
117
    public function testDataReaderRewindException(): void
118
    {
119
        $db = $this->getConnection(true);
120
121
        $this->expectException(InvalidCallException::class);
122
        $this->expectExceptionMessage('DataReader cannot rewind. It is a forward-only reader.');
123
124
        $sql = 'SELECT * FROM {{customer}}';
125
        $reader = $db->createCommand($sql)->query();
126
        $reader->next();
127
        $reader->rewind();
128
    }
129
130
    /**
131
     * @throws Exception|InvalidConfigException|Throwable
132
     */
133
    public function testQuery(): void
134
    {
135
        $db = $this->getConnection(true);
136
137
        $sql = 'SELECT * FROM {{customer}}';
138
        $reader = $db->createCommand($sql)->query();
139
        $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

139
        $this->/** @scrutinizer ignore-call */ 
140
               assertInstanceOf(DataReaderInterface::class, $reader);
Loading history...
140
141
        // Next line is commented by reason:: For sqlite & pgsql result may be incorrect
142
        // $this->assertEquals(3, $reader->count());
143
        $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

143
        $this->/** @scrutinizer ignore-call */ 
144
               assertIsInt($reader->count());
Loading history...
144
        foreach ($reader as $row) {
145
            $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

145
            $this->/** @scrutinizer ignore-call */ 
146
                   assertIsArray($row);
Loading history...
146
            $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

146
            $this->/** @scrutinizer ignore-call */ 
147
                   assertTrue(count($row) >= 6);
Loading history...
147
        }
148
149
        $command = $db->createCommand('bad SQL');
150
        $this->expectException(Exception::class);
151
        $command->query();
152
    }
153
154
    public function testQyeryScalar(): void
155
    {
156
        $db = $this->getConnection();
157
158
        $sql = 'SELECT * FROM {{customer}} ORDER BY [[id]]';
159
        $this->assertEquals($db->createCommand($sql)->queryScalar(), 1);
160
161
        $sql = 'SELECT [[id]] FROM {{customer}} ORDER BY [[id]]';
162
        $command = $db->createCommand($sql);
163
164
        $command->prepare();
165
        $this->assertEquals(1, $command->queryScalar());
166
167
        $command = $db->createCommand('SELECT [[id]] FROM {{customer}} WHERE [[id]] = 10');
168
        $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

168
        $this->/** @scrutinizer ignore-call */ 
169
               assertFalse($command->queryScalar());
Loading history...
169
    }
170
171
    public function testQueryOne(): void
172
    {
173
        $db = $this->getConnection();
174
175
        $sql = 'SELECT * FROM {{customer}} ORDER BY [[id]]';
176
        $row = $db->createCommand($sql)->queryOne();
177
        $this->assertIsArray($row);
178
        $this->assertEquals(1, $row['id']);
179
        $this->assertEquals('user1', $row['name']);
180
181
        $sql = 'SELECT * FROM {{customer}} ORDER BY [[id]]';
182
        $command = $db->createCommand($sql);
183
        $command->prepare();
184
        $row = $command->queryOne();
185
186
        $this->assertIsArray($row);
187
        $this->assertEquals(1, $row['id']);
188
        $this->assertEquals('user1', $row['name']);
189
190
        $sql = 'SELECT * FROM {{customer}} WHERE [[id]] = 10';
191
        $command = $db->createCommand($sql);
192
        $this->assertFalse($command->queryOne());
193
    }
194
195
    public function testQueryColumn(): void
196
    {
197
        $db = $this->getConnection();
198
199
        $sql = 'SELECT * FROM {{customer}}';
200
        $column = $db->createCommand($sql)->queryColumn();
201
        $this->assertEquals(range(1, 3), $column);
202
        $this->assertIsArray($column);
203
204
        $command = $db->createCommand('SELECT [[id]] FROM {{customer}} WHERE [[id]] = 10');
205
        $this->assertFalse($command->queryColumn());
206
    }
207
208
    public function testQueryAll(): void
209
    {
210
        $db = $this->getConnection();
211
212
        $rows = $db->createCommand('SELECT [[id]],[[name]] FROM {{customer}}')->queryAll();
213
        /** @psalm-suppress RedundantCondition */
214
        $this->assertIsArray($rows);
215
        $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

215
        $this->/** @scrutinizer ignore-call */ 
216
               assertCount(3, $rows);
Loading history...
216
217
        $row = $rows[2];
218
        $this->assertEquals(3, $row['id']);
219
        $this->assertEquals('user3', $row['name']);
220
        $this->assertTrue(is_array($rows) && count($rows)>1 && count($rows[0]) === 2);
221
222
        $rows = $db->createCommand('SELECT * FROM {{customer}} WHERE [[id]] = 10')->queryAll();
223
        $this->assertEquals([], $rows);
224
    }
225
226
    /**
227
     * @throws Exception|InvalidConfigException|Throwable
228
     */
229
    public function testBatchInsert(): void
230
    {
231
        $db = $this->getConnection();
232
233
        $command = $db->createCommand();
234
        $command->batchInsert(
235
            '{{customer}}',
236
            ['email', 'name', 'address'],
237
            [
238
                ['[email protected]', 't1', 't1 address'],
239
                ['[email protected]', null, false],
240
            ]
241
        );
242
        $this->assertEquals(2, $command->execute());
243
244
        /**
245
         * @link https://github.com/yiisoft/yii2/issues/11693
246
         */
247
        $command = $db->createCommand();
248
        $command->batchInsert(
249
            '{{customer}}',
250
            ['email', 'name', 'address'],
251
            []
252
        );
253
        $this->assertEquals(0, $command->execute());
254
    }
255
256
    /**
257
     * @throws Exception|InvalidConfigException|Throwable
258
     */
259
    public function testBatchInsertFailsOld(): void
260
    {
261
        $db = $this->getConnection(true);
262
263
        $command = $db->createCommand();
264
        $command->batchInsert(
265
            '{{customer}}',
266
            ['email', 'name', 'address'],
267
            [
268
                ['[email protected]', 'test_name', 'test_address'],
269
            ]
270
        );
271
        $this->assertEquals(1, $command->execute());
272
273
        $result = (new Query($db))
274
            ->select(['email', 'name', 'address'])
275
            ->from('{{customer}}')
276
            ->where(['=', '[[email]]', '[email protected]'])
277
            ->one();
278
279
        $this->assertCount(3, $result);
280
        $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

280
        $this->/** @scrutinizer ignore-call */ 
281
               assertSame(
Loading history...
281
            [
282
                'email' => '[email protected]',
283
                'name' => 'test_name',
284
                'address' => 'test_address',
285
            ],
286
            $result,
287
        );
288
    }
289
290
    /**
291
     * @throws Exception|InvalidConfigException|Throwable
292
     */
293
    public function testBatchInsertWithYield(): void
294
    {
295
        $rows = (static function () {
296
            foreach ([['[email protected]', 'test name', 'test address']] as $row) {
297
                yield $row;
298
            }
299
        })();
300
301
        $command = $this->getConnection()->createCommand();
302
        $command->batchInsert(
303
            '{{customer}}',
304
            ['email', 'name', 'address'],
305
            $rows
306
        );
307
        $this->assertEquals(1, $command->execute());
308
    }
309
310
    /**
311
     * Test batch insert with different data types.
312
     *
313
     * Ensure double is inserted with `.` decimal separator.
314
     *
315
     * @link https://github.com/yiisoft/yii2/issues/6526
316
     */
317
    public function testBatchInsertDataTypesLocale(): void
318
    {
319
        $locale = setlocale(LC_NUMERIC, 0);
320
321
        if (false === $locale) {
322
            $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

322
            $this->/** @scrutinizer ignore-call */ 
323
                   markTestSkipped('Your platform does not support locales.');
Loading history...
323
        }
324
325
        $db = $this->getConnection(true);
326
327
        try {
328
            /* This one sets decimal mark to comma sign */
329
            setlocale(LC_NUMERIC, 'ru_RU.utf8');
330
            $cols = ['int_col', 'char_col', 'float_col', 'bool_col'];
331
            $data = [
332
                [1, 'A', 9.735, true],
333
                [2, 'B', -2.123, false],
334
                [3, 'C', 2.123, false],
335
            ];
336
337
            /* clear data in "type" table */
338
            $db->createCommand()->delete('type')->execute();
339
340
            /* change, for point oracle. */
341
            if ($db->getDriver()->getDriverName() === 'oci') {
342
                $db->createCommand("ALTER SESSION SET NLS_NUMERIC_CHARACTERS='.,'")->execute();
343
            }
344
345
            /* batch insert on "type" table */
346
            $db->createCommand()->batchInsert('type', $cols, $data)->execute();
347
            $data = $db->createCommand(
348
                'SELECT [[int_col]], [[char_col]], [[float_col]], [[bool_col]] ' .
349
                'FROM {{type}} WHERE [[int_col]] IN (1,2,3) ORDER BY [[int_col]]'
350
            )->queryAll();
351
            $this->assertCount(3, $data);
352
            $this->assertEquals(1, $data[0]['int_col']);
353
            $this->assertEquals(2, $data[1]['int_col']);
354
            $this->assertEquals(3, $data[2]['int_col']);
355
356
            /* rtrim because Postgres padds the column with whitespace */
357
            $this->assertEquals('A', rtrim($data[0]['char_col']));
358
            $this->assertEquals('B', rtrim($data[1]['char_col']));
359
            $this->assertEquals('C', rtrim($data[2]['char_col']));
360
            $this->assertEquals('9.735', $data[0]['float_col']);
361
            $this->assertEquals('-2.123', $data[1]['float_col']);
362
            $this->assertEquals('2.123', $data[2]['float_col']);
363
            $this->assertEquals('1', $data[0]['bool_col']);
364
            $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

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

852
        $this->/** @scrutinizer ignore-call */ 
853
               assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues);
Loading history...
853
        $this->assertCount(2, $bindedValues);
854
855
        $param = new Param('str', 99);
856
        $command->bindValues(['param' => $param]);
857
        $bindedValues = $command->getParams(false);
858
859
        $this->assertIsArray($bindedValues);
860
        $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues);
861
        $this->assertCount(3, $bindedValues);
862
        $this->assertEquals($param, $bindedValues['param']);
863
        $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

863
        $this->/** @scrutinizer ignore-call */ 
864
               assertNotEquals($param, $bindedValues['int']);
Loading history...
864
865
        // Replace test
866
        $command->bindValues(['int' => $param]);
867
        $bindedValues = $command->getParams(false);
868
869
        $this->assertIsArray($bindedValues);
870
        $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues);
871
        $this->assertCount(3, $bindedValues);
872
        $this->assertEquals($param, $bindedValues['int']);
873
    }
874
875
    public function testIntegrityViolation(): void
876
    {
877
        $this->expectException(IntegrityException::class);
878
879
        $db = $this->getConnection();
880
881
        $sql = 'INSERT INTO {{profile}}([[id]], [[description]]) VALUES (123, \'duplicate\')';
882
        $command = $db->createCommand($sql);
883
        $command->execute();
884
        $command->execute();
885
    }
886
887
    public function testLastInsertId(): void
888
    {
889
        $db = $this->getConnection(true);
890
891
        $sql = 'INSERT INTO {{profile}}([[description]]) VALUES (\'non duplicate\')';
892
        $command = $db->createCommand($sql);
893
        $command->execute();
894
        $this->assertEquals(3, $db->getLastInsertID());
895
    }
896
897
    public function testLastInsertIdException(): void
898
    {
899
        $db = $this->getConnection();
900
        $db->close();
901
902
        $this->expectException(InvalidCallException::class);
903
        $db->getLastInsertID();
904
    }
905
906
    public function testQueryCache(): void
907
    {
908
        $db = $this->getConnection(true);
909
910
        /** @psalm-suppress PossiblyNullReference */
911
        $this->queryCache->setEnable(true);
912
        $command = $db->createCommand('SELECT [[name]] FROM {{customer}} WHERE [[id]] = :id');
913
        $this->assertEquals('user1', $command->bindValue(':id', 1)->queryScalar());
914
915
        $update = $db->createCommand('UPDATE {{customer}} SET [[name]] = :name WHERE [[id]] = :id');
916
        $update->bindValues([':id' => 1, ':name' => 'user11'])->execute();
917
        $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
918
919
        $db->cache(function (ConnectionInterface $db) use ($command, $update) {
920
            $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
921
922
            $update->bindValues([':id' => 2, ':name' => 'user22'])->execute();
923
924
            $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
925
926
            $db->noCache(function () use ($command) {
927
                $this->assertEquals('user22', $command->bindValue(':id', 2)->queryScalar());
928
            });
929
930
            $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
931
        }, 10);
932
933
        /** @psalm-suppress PossiblyNullReference */
934
        $this->queryCache->setEnable(false);
935
936
        $db->cache(function () use ($command, $update) {
937
            $this->assertEquals('user22', $command->bindValue(':id', 2)->queryScalar());
938
            $update->bindValues([':id' => 2, ':name' => 'user2'])->execute();
939
            $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
940
        }, 10);
941
942
        /** @psalm-suppress PossiblyNullReference */
943
        $this->queryCache->setEnable(true);
944
        $command = $db->createCommand('SELECT [[name]] FROM {{customer}} WHERE [[id]] = :id')->cache();
945
        $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
946
947
        $update->bindValues([':id' => 1, ':name' => 'user1'])->execute();
948
        $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
949
        $this->assertEquals('user1', $command->noCache()->bindValue(':id', 1)->queryScalar());
950
951
        $command = $db->createCommand('SELECT [[name]] FROM {{customer}} WHERE [[id]] = :id');
952
953
        $db->cache(function () use ($command) {
954
            $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
955
            $this->assertEquals('user1', $command->noCache()->bindValue(':id', 1)->queryScalar());
956
        }, 10);
957
    }
958
959
    public function testColumnCase(): void
960
    {
961
        $db = $this->getConnection();
962
963
        $this->assertEquals(PDO::CASE_NATURAL, $db->getActivePDO()->getAttribute(PDO::ATTR_CASE));
964
965
        $sql = 'SELECT [[customer_id]], [[total]] FROM {{order}}';
966
        $rows = $db->createCommand($sql)->queryAll();
967
        $this->assertTrue(isset($rows[0]));
968
        $this->assertTrue(isset($rows[0]['customer_id']));
969
        $this->assertTrue(isset($rows[0]['total']));
970
971
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
972
        $rows = $db->createCommand($sql)->queryAll();
973
        $this->assertTrue(isset($rows[0]));
974
        $this->assertTrue(isset($rows[0]['customer_id']));
975
        $this->assertTrue(isset($rows[0]['total']));
976
977
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
978
        $rows = $db->createCommand($sql)->queryAll();
979
        $this->assertTrue(isset($rows[0]));
980
        $this->assertTrue(isset($rows[0]['CUSTOMER_ID']));
981
        $this->assertTrue(isset($rows[0]['TOTAL']));
982
    }
983
984
    public function testTransaction(): void
985
    {
986
        $db = $this->getConnection();
987
988
        $this->assertNull($db->getTransaction());
989
990
        $command = $db->createCommand("INSERT INTO {{profile}}([[description]]) VALUES('command transaction')");
991
        $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

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