Passed
Push — master ( 914087...c6011d )
by Alexander
11:22
created

testDataReaderCreationException()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 9
rs 10
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 = $this->getConnection()->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 testBatchInsertWithYield(): void
260
    {
261
        $rows = (static function () {
262
            foreach ([['[email protected]', 'test name', 'test address']] as $row) {
263
                yield $row;
264
            }
265
        })();
266
267
        $command = $this->getConnection()->createCommand();
268
        $command->batchInsert(
269
            '{{customer}}',
270
            ['email', 'name', 'address'],
271
            $rows
272
        );
273
        $this->assertEquals(1, $command->execute());
274
    }
275
276
    /**
277
     * Test batch insert with different data types.
278
     *
279
     * Ensure double is inserted with `.` decimal separator.
280
     *
281
     * @link https://github.com/yiisoft/yii2/issues/6526
282
     */
283
    public function testBatchInsertDataTypesLocale(): void
284
    {
285
        $locale = setlocale(LC_NUMERIC, 0);
286
287
        if (false === $locale) {
288
            $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

288
            $this->/** @scrutinizer ignore-call */ 
289
                   markTestSkipped('Your platform does not support locales.');
Loading history...
289
        }
290
291
        $db = $this->getConnection(true);
292
293
        try {
294
            /* This one sets decimal mark to comma sign */
295
            setlocale(LC_NUMERIC, 'ru_RU.utf8');
296
            $cols = ['int_col', 'char_col', 'float_col', 'bool_col'];
297
            $data = [
298
                [1, 'A', 9.735, true],
299
                [2, 'B', -2.123, false],
300
                [3, 'C', 2.123, false],
301
            ];
302
303
            /* clear data in "type" table */
304
            $db->createCommand()->delete('type')->execute();
305
306
            /* change, for point oracle. */
307
            if ($db->getDriverName() === 'oci') {
308
                $db->createCommand("ALTER SESSION SET NLS_NUMERIC_CHARACTERS='.,'")->execute();
309
            }
310
311
            /* batch insert on "type" table */
312
            $db->createCommand()->batchInsert('type', $cols, $data)->execute();
313
            $data = $db->createCommand(
314
                'SELECT [[int_col]], [[char_col]], [[float_col]], [[bool_col]] ' .
315
                'FROM {{type}} WHERE [[int_col]] IN (1,2,3) ORDER BY [[int_col]]'
316
            )->queryAll();
317
            $this->assertCount(3, $data);
318
            $this->assertEquals(1, $data[0]['int_col']);
319
            $this->assertEquals(2, $data[1]['int_col']);
320
            $this->assertEquals(3, $data[2]['int_col']);
321
322
            /* rtrim because Postgres padds the column with whitespace */
323
            $this->assertEquals('A', rtrim($data[0]['char_col']));
324
            $this->assertEquals('B', rtrim($data[1]['char_col']));
325
            $this->assertEquals('C', rtrim($data[2]['char_col']));
326
            $this->assertEquals('9.735', $data[0]['float_col']);
327
            $this->assertEquals('-2.123', $data[1]['float_col']);
328
            $this->assertEquals('2.123', $data[2]['float_col']);
329
            $this->assertEquals('1', $data[0]['bool_col']);
330
            $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

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

818
        $this->/** @scrutinizer ignore-call */ 
819
               assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues);
Loading history...
819
        $this->assertCount(2, $bindedValues);
820
821
        $param = new Param('str', 99);
822
        $command->bindValues(['param' => $param]);
823
        $bindedValues = $command->getParams(false);
824
825
        $this->assertIsArray($bindedValues);
826
        $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues);
827
        $this->assertCount(3, $bindedValues);
828
        $this->assertEquals($param, $bindedValues['param']);
829
        $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

829
        $this->/** @scrutinizer ignore-call */ 
830
               assertNotEquals($param, $bindedValues['int']);
Loading history...
830
831
        // Replace test
832
        $command->bindValues(['int' => $param]);
833
        $bindedValues = $command->getParams(false);
834
835
        $this->assertIsArray($bindedValues);
836
        $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues);
837
        $this->assertCount(3, $bindedValues);
838
        $this->assertEquals($param, $bindedValues['int']);
839
    }
840
841
    public function testIntegrityViolation(): void
842
    {
843
        $this->expectException(IntegrityException::class);
844
845
        $db = $this->getConnection();
846
847
        $sql = 'INSERT INTO {{profile}}([[id]], [[description]]) VALUES (123, \'duplicate\')';
848
        $command = $db->createCommand($sql);
849
        $command->execute();
850
        $command->execute();
851
    }
852
853
    public function testLastInsertId(): void
854
    {
855
        $db = $this->getConnection(true);
856
857
        $sql = 'INSERT INTO {{profile}}([[description]]) VALUES (\'non duplicate\')';
858
        $command = $db->createCommand($sql);
859
        $command->execute();
860
        $this->assertEquals(3, $db->getLastInsertID());
861
    }
862
863
    public function testLastInsertIdException(): void
864
    {
865
        $db = $this->getConnection();
866
        $db->close();
867
868
        $this->expectException(InvalidCallException::class);
869
        $db->getLastInsertID();
870
    }
871
872
    public function testQueryCache(): void
873
    {
874
        $db = $this->getConnection(true);
875
876
        /** @psalm-suppress PossiblyNullReference */
877
        $this->queryCache->setEnable(true);
878
        $command = $db->createCommand('SELECT [[name]] FROM {{customer}} WHERE [[id]] = :id');
879
        $this->assertEquals('user1', $command->bindValue(':id', 1)->queryScalar());
880
881
        $update = $db->createCommand('UPDATE {{customer}} SET [[name]] = :name WHERE [[id]] = :id');
882
        $update->bindValues([':id' => 1, ':name' => 'user11'])->execute();
883
        $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
884
885
        $db->cache(function (ConnectionInterface $db) use ($command, $update) {
886
            $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
887
888
            $update->bindValues([':id' => 2, ':name' => 'user22'])->execute();
889
890
            $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
891
892
            $db->noCache(function () use ($command) {
893
                $this->assertEquals('user22', $command->bindValue(':id', 2)->queryScalar());
894
            });
895
896
            $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
897
        }, 10);
898
899
        /** @psalm-suppress PossiblyNullReference */
900
        $this->queryCache->setEnable(false);
901
902
        $db->cache(function () use ($command, $update) {
903
            $this->assertEquals('user22', $command->bindValue(':id', 2)->queryScalar());
904
            $update->bindValues([':id' => 2, ':name' => 'user2'])->execute();
905
            $this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
906
        }, 10);
907
908
        /** @psalm-suppress PossiblyNullReference */
909
        $this->queryCache->setEnable(true);
910
        $command = $db->createCommand('SELECT [[name]] FROM {{customer}} WHERE [[id]] = :id')->cache();
911
        $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
912
913
        $update->bindValues([':id' => 1, ':name' => 'user1'])->execute();
914
        $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
915
        $this->assertEquals('user1', $command->noCache()->bindValue(':id', 1)->queryScalar());
916
917
        $command = $db->createCommand('SELECT [[name]] FROM {{customer}} WHERE [[id]] = :id');
918
919
        $db->cache(function () use ($command) {
920
            $this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
921
            $this->assertEquals('user1', $command->noCache()->bindValue(':id', 1)->queryScalar());
922
        }, 10);
923
    }
924
925
    public function testColumnCase(): void
926
    {
927
        $db = $this->getConnection();
928
929
        $this->assertEquals(PDO::CASE_NATURAL, $db->getActivePDO()->getAttribute(PDO::ATTR_CASE));
930
931
        $sql = 'SELECT [[customer_id]], [[total]] FROM {{order}}';
932
        $rows = $db->createCommand($sql)->queryAll();
933
        $this->assertTrue(isset($rows[0]));
934
        $this->assertTrue(isset($rows[0]['customer_id']));
935
        $this->assertTrue(isset($rows[0]['total']));
936
937
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
938
        $rows = $db->createCommand($sql)->queryAll();
939
        $this->assertTrue(isset($rows[0]));
940
        $this->assertTrue(isset($rows[0]['customer_id']));
941
        $this->assertTrue(isset($rows[0]['total']));
942
943
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
944
        $rows = $db->createCommand($sql)->queryAll();
945
        $this->assertTrue(isset($rows[0]));
946
        $this->assertTrue(isset($rows[0]['CUSTOMER_ID']));
947
        $this->assertTrue(isset($rows[0]['TOTAL']));
948
    }
949
950
    public function testTransaction(): void
951
    {
952
        $db = $this->getConnection();
953
954
        $this->assertNull($db->getTransaction());
955
956
        $command = $db->createCommand("INSERT INTO {{profile}}([[description]]) VALUES('command transaction')");
957
        $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

957
        $this->/** @scrutinizer ignore-call */ 
958
               invokeMethod($command, 'requireTransaction');
Loading history...
958
        $command->execute();
959
        $this->assertNull($db->getTransaction());
960
        $this->assertEquals(
961
            1,
962
            $db->createCommand(
963
                "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'command transaction'"
964
            )->queryScalar()
965
        );
966
    }
967
968
    public function testRetryHandler(): void
969
    {
970
        $db = $this->getConnection();
971
972
        $this->assertNull($db->getTransaction());
973
974
        $db->createCommand("INSERT INTO {{profile}}([[description]]) VALUES('command retry')")->execute();
975
        $this->assertNull($db->getTransaction());
976
        $this->assertEquals(
977
            1,
978
            $db->createCommand(
979
                "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'command retry'"
980
            )->queryScalar()
981
        );
982
983
        $attempts = null;
984
        $hitHandler = false;
985
        $hitCatch = false;
986
987
        $command = $db->createCommand(
988
            "INSERT INTO {{profile}}([[id]], [[description]]) VALUES(1, 'command retry')"
989
        );
990
        $this->invokeMethod(
991
            $command,
992
            'setRetryHandler',
993
            [static function ($exception, $attempt) use (&$attempts, &$hitHandler) {
994
                $attempts = $attempt;
995
                $hitHandler = true;
996
997
                return $attempt <= 2;
998
            }]
999
        );
1000
1001
        try {
1002
            $command->execute();
1003
        } catch (Exception $e) {
1004
            $hitCatch = true;
1005
            $this->assertInstanceOf(IntegrityException::class, $e);
1006
        }
1007
1008
        $this->assertNull($db->getTransaction());
1009
        $this->assertSame(3, $attempts);
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

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