Passed
Pull Request — master (#336)
by Def
02:24
created

TestCommandTrait::testInsertToBlob()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 13
c 1
b 1
f 0
dl 0
loc 20
rs 9.8333
cc 2
nc 2
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
        $command = $db->createCommand();
256
        $command->batchInsert(
257
            '{{customer}}',
258
            ['email', 'name', 'address'],
259
            [
260
                ['[email protected]', 'test_name', 'test_address'],
261
            ]
262
        );
263
        $this->assertEquals(1, $command->execute());
264
    }
265
266
    /**
267
     * @throws Exception|InvalidConfigException|Throwable
268
     */
269
    public function testBatchInsertFails(): void
270
    {
271
        $db = $this->getConnection(true);
272
273
        $command = $db->createCommand();
274
        $command->batchInsert(
275
            '{{customer}}',
276
            ['email', 'name', 'address'],
277
            [
278
                ['[email protected]', 'test_name', 'test_address'],
279
            ]
280
        );
281
        $this->assertEquals(1, $command->execute());
282
283
        $result = (new Query($db))
284
            ->select(['email', 'name', 'address'])
285
            ->from('{{customer}}')
286
            ->where(['=', 'email', '[email protected]'])
287
            ->one();
288
289
        $this->assertCount(3, $result);
290
        $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

290
        $this->/** @scrutinizer ignore-call */ 
291
               assertSame(
Loading history...
291
            [
292
                'email' => '[email protected]',
293
                'name' => 'test_name',
294
                'address' => 'test_address',
295
            ],
296
            $result,
297
        );
298
    }
299
300
    /**
301
     * @throws Exception|InvalidConfigException|Throwable
302
     */
303
    public function testBatchInsertFailsOld(): void
304
    {
305
        $db = $this->getConnection(true);
306
307
        $command = $db->createCommand();
308
        $command->batchInsert(
309
            '{{customer}}',
310
            ['email', 'name', 'address'],
311
            [
312
                ['[email protected]', 'test_name', 'test_address'],
313
            ]
314
        );
315
        $this->assertEquals(1, $command->execute());
316
317
        $result = (new Query($db))
318
            ->select(['email', 'name', 'address'])
319
            ->from('{{customer}}')
320
            ->where(['=', '[[email]]', '[email protected]'])
321
            ->one();
322
323
        $this->assertCount(3, $result);
324
        $this->assertSame(
325
            [
326
                'email' => '[email protected]',
327
                'name' => 'test_name',
328
                'address' => 'test_address',
329
            ],
330
            $result,
331
        );
332
    }
333
334
    /**
335
     * @throws Exception|InvalidConfigException|Throwable
336
     */
337
    public function testBatchInsertWithYield(): void
338
    {
339
        $rows = (static function () {
340
            foreach ([['[email protected]', 'test name', 'test address']] as $row) {
341
                yield $row;
342
            }
343
        })();
344
345
        $command = $this->getConnection()->createCommand();
346
        $command->batchInsert(
347
            '{{customer}}',
348
            ['email', 'name', 'address'],
349
            $rows
350
        );
351
        $this->assertEquals(1, $command->execute());
352
    }
353
354
    /**
355
     * Test batch insert with different data types.
356
     *
357
     * Ensure double is inserted with `.` decimal separator.
358
     *
359
     * @link https://github.com/yiisoft/yii2/issues/6526
360
     */
361
    public function testBatchInsertDataTypesLocale(): void
362
    {
363
        $locale = setlocale(LC_NUMERIC, 0);
364
365
        if (false === $locale) {
366
            $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

366
            $this->/** @scrutinizer ignore-call */ 
367
                   markTestSkipped('Your platform does not support locales.');
Loading history...
367
        }
368
369
        $db = $this->getConnection(true);
370
371
        try {
372
            /* This one sets decimal mark to comma sign */
373
            setlocale(LC_NUMERIC, 'ru_RU.utf8');
374
            $cols = ['int_col', 'char_col', 'float_col', 'bool_col'];
375
            $data = [
376
                [1, 'A', 9.735, true],
377
                [2, 'B', -2.123, false],
378
                [3, 'C', 2.123, false],
379
            ];
380
381
            /* clear data in "type" table */
382
            $db->createCommand()->delete('type')->execute();
383
384
            /* change, for point oracle. */
385
            if ($db->getDriver()->getDriverName() === 'oci') {
386
                $db->createCommand("ALTER SESSION SET NLS_NUMERIC_CHARACTERS='.,'")->execute();
387
            }
388
389
            /* batch insert on "type" table */
390
            $db->createCommand()->batchInsert('type', $cols, $data)->execute();
391
            $data = $db->createCommand(
392
                'SELECT [[int_col]], [[char_col]], [[float_col]], [[bool_col]] ' .
393
                'FROM {{type}} WHERE [[int_col]] IN (1,2,3) ORDER BY [[int_col]]'
394
            )->queryAll();
395
            $this->assertCount(3, $data);
396
            $this->assertEquals(1, $data[0]['int_col']);
397
            $this->assertEquals(2, $data[1]['int_col']);
398
            $this->assertEquals(3, $data[2]['int_col']);
399
400
            /* rtrim because Postgres padds the column with whitespace */
401
            $this->assertEquals('A', rtrim($data[0]['char_col']));
402
            $this->assertEquals('B', rtrim($data[1]['char_col']));
403
            $this->assertEquals('C', rtrim($data[2]['char_col']));
404
            $this->assertEquals('9.735', $data[0]['float_col']);
405
            $this->assertEquals('-2.123', $data[1]['float_col']);
406
            $this->assertEquals('2.123', $data[2]['float_col']);
407
            $this->assertEquals('1', $data[0]['bool_col']);
408
            $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

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

896
        $this->/** @scrutinizer ignore-call */ 
897
               assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues);
Loading history...
897
        $this->assertCount(2, $bindedValues);
898
899
        $param = new Param('str', 99);
900
        $command->bindValues(['param' => $param]);
901
        $bindedValues = $command->getParams(false);
902
903
        $this->assertIsArray($bindedValues);
904
        $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues);
905
        $this->assertCount(3, $bindedValues);
906
        $this->assertEquals($param, $bindedValues['param']);
907
        $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

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

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