Passed
Pull Request — master (#30)
by Wilmer
15:21
created

QueryBuilderTest::columnTimeTypes()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 74
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 43
c 1
b 0
f 0
nc 6
nop 0
dl 0
loc 74
rs 8.9208

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Mysql\Tests;
6
7
use Yiisoft\Db\Expression\Expression;
8
use Yiisoft\Db\Expression\JsonExpression;
9
use Yiisoft\Db\Query\Query;
10
use Yiisoft\Db\Mysql\Schema\Schema;
11
use Yiisoft\Db\Tests\QueryBuilderTest as AbstractQueryBuilderTest;
12
13
class QueryBuilderTest extends AbstractQueryBuilderTest
14
{
15
    protected ?string $driverName = 'mysql';
16
17
    /**
18
     * This is not used as a dataprovider for testGetColumnType to speed up the test when used as dataprovider every
19
     * single line will cause a reconnect with the database which is not needed here.
20
     */
21
    public function columnTypes(): array
22
    {
23
        $columns = [
24
            [
25
                Schema::TYPE_PK . ' AFTER `col_before`',
26
                $this->primaryKey()->after('col_before'),
27
                'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY AFTER `col_before`',
28
            ],
29
            [
30
                Schema::TYPE_PK . ' FIRST',
31
                $this->primaryKey()->first(),
32
                'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST',
33
            ],
34
            [
35
                Schema::TYPE_PK . ' FIRST',
36
                $this->primaryKey()->first()->after('col_before'),
37
                'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST',
38
            ],
39
            [
40
                Schema::TYPE_PK . '(8) AFTER `col_before`',
41
                $this->primaryKey(8)->after('col_before'),
42
                'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY AFTER `col_before`',
43
            ],
44
            [
45
                Schema::TYPE_PK . '(8) FIRST',
46
                $this->primaryKey(8)->first(),
47
                'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST',
48
            ],
49
            [
50
                Schema::TYPE_PK . '(8) FIRST',
51
                $this->primaryKey(8)->first()->after('col_before'),
52
                'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST',
53
            ],
54
            [
55
                Schema::TYPE_PK . " COMMENT 'test' AFTER `col_before`",
56
                $this->primaryKey()->comment('test')->after('col_before'),
57
                "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'test' AFTER `col_before`",
58
            ],
59
            [
60
                Schema::TYPE_PK . " COMMENT 'testing \'quote\'' AFTER `col_before`",
61
                $this->primaryKey()->comment('testing \'quote\'')->after('col_before'),
62
                "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'testing \'quote\'' AFTER `col_before`",
63
            ],
64
        ];
65
66
        return \array_merge(parent::columnTypes(), $this->columnTimeTypes(), $columns);
67
    }
68
69
    public function columnTimeTypes(): array
70
    {
71
        $columns = [
72
            [
73
                Schema::TYPE_DATETIME . ' NOT NULL',
74
                $this->dateTime()->notNull(),
75
                'datetime NOT NULL',
76
            ],
77
            [
78
                Schema::TYPE_DATETIME,
79
                $this->dateTime(),
80
                'datetime',
81
            ],
82
            [
83
                Schema::TYPE_TIME . ' NOT NULL',
84
                $this->time()->notNull(),
85
                'time NOT NULL',
86
            ],
87
            [
88
                Schema::TYPE_TIME,
89
                $this->time(),
90
                'time',
91
            ],
92
            [
93
                Schema::TYPE_TIMESTAMP . ' NOT NULL',
94
                $this->timestamp()->notNull(),
95
                'timestamp NOT NULL',
96
            ],
97
            [
98
                Schema::TYPE_TIMESTAMP . ' NULL DEFAULT NULL',
99
                $this->timestamp()->defaultValue(null),
100
                'timestamp NULL DEFAULT NULL',
101
            ],
102
        ];
103
104
        /**
105
         * {@see https://github.com/yiisoft/yii2/issues/14367}
106
         */
107
        $mysqlVersion = $this->getDb()->getSlavePdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
108
        $supportsFractionalSeconds = \version_compare($mysqlVersion, '5.6.4', '>=');
109
        if ($supportsFractionalSeconds) {
110
            $expectedValues = [
111
                'datetime(0) NOT NULL',
112
                'datetime(0)',
113
                'time(0) NOT NULL',
114
                'time(0)',
115
                'timestamp(0) NOT NULL',
116
                'timestamp(0) NULL DEFAULT NULL',
117
            ];
118
119
            foreach ($expectedValues as $index => $expected) {
120
                $columns[$index][2] = $expected;
121
            }
122
        }
123
124
        /**
125
         * {@see https://github.com/yiisoft/yii2/issues/14834}
126
         */
127
        $sqlModes = $this->getConnection(false)->createCommand('SELECT @@sql_mode')->queryScalar();
128
        $sqlModes = \explode(',', $sqlModes);
0 ignored issues
show
Bug introduced by
It seems like $sqlModes can also be of type false; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

128
        $sqlModes = \explode(',', /** @scrutinizer ignore-type */ $sqlModes);
Loading history...
129
        if (\in_array('NO_ZERO_DATE', $sqlModes, true)) {
130
            $this->markTestIncomplete(
131
                "MySQL doesn't allow the 'TIMESTAMP' column definition when the NO_ZERO_DATE mode enabled. " .
132
                "This definition test was skipped."
133
            );
134
        } else {
135
            $columns[] = [
136
                Schema::TYPE_TIMESTAMP,
137
                $this->timestamp(),
138
                $supportsFractionalSeconds ? 'timestamp(0)' : 'timestamp',
139
            ];
140
        }
141
142
        return $columns;
143
    }
144
145
    public function primaryKeysProvider(): array
146
    {
147
        $result = parent::primaryKeysProvider();
148
        $result['drop'][0] = 'ALTER TABLE {{T_constraints_1}} DROP PRIMARY KEY';
149
        $result['add'][0] = 'ALTER TABLE {{T_constraints_1}} ADD CONSTRAINT [[CN_pk]] PRIMARY KEY ([[C_id_1]])';
150
        $result['add (2 columns)'][0] = 'ALTER TABLE {{T_constraints_1}} ADD CONSTRAINT [[CN_pk]] PRIMARY KEY ([[C_id_1]], [[C_id_2]])';
151
152
        return $result;
153
    }
154
155
    public function foreignKeysProvider(): array
156
    {
157
        $result = parent::foreignKeysProvider();
158
        $result['drop'][0] = 'ALTER TABLE {{T_constraints_3}} DROP FOREIGN KEY [[CN_constraints_3]]';
159
160
        return $result;
161
    }
162
163
    public function indexesProvider(): array
164
    {
165
        $result = parent::indexesProvider();
166
        $result['create'][0] = 'ALTER TABLE {{T_constraints_2}} ADD INDEX [[CN_constraints_2_single]] ([[C_index_1]])';
167
        $result['create (2 columns)'][0] = 'ALTER TABLE {{T_constraints_2}} ADD INDEX [[CN_constraints_2_multi]] ([[C_index_2_1]], [[C_index_2_2]])';
168
        $result['create unique'][0] = 'ALTER TABLE {{T_constraints_2}} ADD UNIQUE INDEX [[CN_constraints_2_single]] ([[C_index_1]])';
169
        $result['create unique (2 columns)'][0] = 'ALTER TABLE {{T_constraints_2}} ADD UNIQUE INDEX [[CN_constraints_2_multi]] ([[C_index_2_1]], [[C_index_2_2]])';
170
171
        return $result;
172
    }
173
174
    public function uniquesProvider(): array
175
    {
176
        $result = parent::uniquesProvider();
177
        $result['drop'][0] = 'DROP INDEX [[CN_unique]] ON {{T_constraints_1}}';
178
179
        return $result;
180
    }
181
182
    public function checksProvider(): void
183
    {
184
        $this->markTestSkipped('Adding/dropping check constraints is not supported in MySQL.');
185
    }
186
187
    public function defaultValuesProvider()
188
    {
189
        $this->markTestSkipped('Adding/dropping default constraints is not supported in MySQL.');
190
    }
191
192
    public function testResetSequence(): void
193
    {
194
        $qb = $this->getQueryBuilder(true, true);
195
196
        $expected = 'ALTER TABLE `item` AUTO_INCREMENT=6';
197
        $sql = $qb->resetSequence('item');
198
        $this->assertEquals($expected, $sql);
199
200
        $expected = 'ALTER TABLE `item` AUTO_INCREMENT=4';
201
        $sql = $qb->resetSequence('item', 4);
202
        $this->assertEquals($expected, $sql);
203
    }
204
205
    public function upsertProvider(): array
206
    {
207
        $concreteData = [
208
            'regular values' => [
209
                3 => 'INSERT INTO `T_upsert` (`email`, `address`, `status`, `profile_id`) VALUES (:qp0, :qp1, :qp2, :qp3) ON DUPLICATE KEY UPDATE `address`=VALUES(`address`), `status`=VALUES(`status`), `profile_id`=VALUES(`profile_id`)',
210
            ],
211
            'regular values with update part' => [
212
                3 => 'INSERT INTO `T_upsert` (`email`, `address`, `status`, `profile_id`) VALUES (:qp0, :qp1, :qp2, :qp3) ON DUPLICATE KEY UPDATE `address`=:qp4, `status`=:qp5, `orders`=T_upsert.orders + 1',
213
            ],
214
            'regular values without update part' => [
215
                3 => 'INSERT INTO `T_upsert` (`email`, `address`, `status`, `profile_id`) VALUES (:qp0, :qp1, :qp2, :qp3) ON DUPLICATE KEY UPDATE `email`=`T_upsert`.`email`',
216
            ],
217
            'query' => [
218
                3 => 'INSERT INTO `T_upsert` (`email`, `status`) SELECT `email`, 2 AS `status` FROM `customer` WHERE `name`=:qp0 LIMIT 1 ON DUPLICATE KEY UPDATE `status`=VALUES(`status`)',
219
            ],
220
            'query with update part' => [
221
                3 => 'INSERT INTO `T_upsert` (`email`, `status`) SELECT `email`, 2 AS `status` FROM `customer` WHERE `name`=:qp0 LIMIT 1 ON DUPLICATE KEY UPDATE `address`=:qp1, `status`=:qp2, `orders`=T_upsert.orders + 1',
222
            ],
223
            'query without update part' => [
224
                3 => 'INSERT INTO `T_upsert` (`email`, `status`) SELECT `email`, 2 AS `status` FROM `customer` WHERE `name`=:qp0 LIMIT 1 ON DUPLICATE KEY UPDATE `email`=`T_upsert`.`email`',
225
            ],
226
            'values and expressions' => [
227
                3 => 'INSERT INTO {{%T_upsert}} ({{%T_upsert}}.[[email]], [[ts]]) VALUES (:qp0, now())',
228
            ],
229
            'values and expressions with update part' => [
230
                3 => 'INSERT INTO {{%T_upsert}} ({{%T_upsert}}.[[email]], [[ts]]) VALUES (:qp0, now())',
231
            ],
232
            'values and expressions without update part' => [
233
                3 => 'INSERT INTO {{%T_upsert}} ({{%T_upsert}}.[[email]], [[ts]]) VALUES (:qp0, now())',
234
            ],
235
            'query, values and expressions with update part' => [
236
                3 => 'INSERT INTO {{%T_upsert}} (`email`, [[time]]) SELECT :phEmail AS `email`, now() AS [[time]] ON DUPLICATE KEY UPDATE `ts`=:qp1, [[orders]]=T_upsert.orders + 1',
237
            ],
238
            'query, values and expressions without update part' => [
239
                3 => 'INSERT INTO {{%T_upsert}} (`email`, [[time]]) SELECT :phEmail AS `email`, now() AS [[time]] ON DUPLICATE KEY UPDATE `ts`=:qp1, [[orders]]=T_upsert.orders + 1',
240
            ],
241
            'no columns to update' => [
242
                3 => 'INSERT INTO `T_upsert_1` (`a`) VALUES (:qp0) ON DUPLICATE KEY UPDATE `a`=`T_upsert_1`.`a`',
243
            ],
244
        ];
245
246
        $newData = parent::upsertProvider();
247
248
        foreach ($concreteData as $testName => $data) {
249
            $newData[$testName] = \array_replace($newData[$testName], $data);
250
        }
251
252
        return $newData;
253
    }
254
255
    public function conditionProvider(): array
256
    {
257
        $db = $this->createConnection();
258
259
        return \array_merge(parent::conditionProvider(), [
260
            /** json conditions */
261
            [
262
                ['=', 'jsoncol', new JsonExpression(['lang' => 'uk', 'country' => 'UA'])],
263
                '[[jsoncol]] = CAST(:qp0 AS JSON)', [':qp0' => '{"lang":"uk","country":"UA"}'],
264
            ],
265
            [
266
                ['=', 'jsoncol', new JsonExpression([false])],
267
                '[[jsoncol]] = CAST(:qp0 AS JSON)', [':qp0' => '[false]']
268
            ],
269
            'object with type. Type is ignored for MySQL' => [
270
                ['=', 'prices', new JsonExpression(['seeds' => 15, 'apples' => 25], 'jsonb')],
271
                '[[prices]] = CAST(:qp0 AS JSON)', [':qp0' => '{"seeds":15,"apples":25}'],
272
            ],
273
            'nested json' => [
274
                ['=', 'data', new JsonExpression(['user' => ['login' => 'silverfire', 'password' => 'c4ny0ur34d17?'], 'props' => ['mood' => 'good']])],
275
                '[[data]] = CAST(:qp0 AS JSON)', [':qp0' => '{"user":{"login":"silverfire","password":"c4ny0ur34d17?"},"props":{"mood":"good"}}']
276
            ],
277
            'null value' => [
278
                ['=', 'jsoncol', new JsonExpression(null)],
279
                '[[jsoncol]] = CAST(:qp0 AS JSON)', [':qp0' => 'null']
280
            ],
281
            'null as array value' => [
282
                ['=', 'jsoncol', new JsonExpression([null])],
283
                '[[jsoncol]] = CAST(:qp0 AS JSON)', [':qp0' => '[null]']
284
            ],
285
            'null as object value' => [
286
                ['=', 'jsoncol', new JsonExpression(['nil' => null])],
287
                '[[jsoncol]] = CAST(:qp0 AS JSON)', [':qp0' => '{"nil":null}']
288
            ],
289
            'query' => [
290
                ['=', 'jsoncol', new JsonExpression((new Query($db))->select('params')->from('user')->where(['id' => 1]))],
291
                '[[jsoncol]] = (SELECT [[params]] FROM [[user]] WHERE [[id]]=:qp0)', [':qp0' => 1]
292
            ],
293
            'query with type, that is ignored in MySQL' => [
294
                ['=', 'jsoncol', new JsonExpression((new Query($db))->select('params')->from('user')->where(['id' => 1]), 'jsonb')],
295
                '[[jsoncol]] = (SELECT [[params]] FROM [[user]] WHERE [[id]]=:qp0)', [':qp0' => 1]
296
            ],
297
            'nested and combined json expression' => [
298
                ['=', 'jsoncol', new JsonExpression(new JsonExpression(['a' => 1, 'b' => 2, 'd' => new JsonExpression(['e' => 3])]))],
299
                "[[jsoncol]] = CAST(:qp0 AS JSON)", [':qp0' => '{"a":1,"b":2,"d":{"e":3}}']
300
            ],
301
            'search by property in JSON column (issue #15838)' => [
302
                ['=', new Expression("(jsoncol->>'$.someKey')"), '42'],
303
                "(jsoncol->>'$.someKey') = :qp0", [':qp0' => 42]
304
            ]
305
        ]);
306
    }
307
308
    public function updateProvider(): array
309
    {
310
        $items = parent::updateProvider();
311
312
        $items[] = [
313
            'profile',
314
            [
315
                'description' => new JsonExpression(['abc' => 'def', 123, null]),
316
            ],
317
            [
318
                'id' => 1,
319
            ],
320
            $this->replaceQuotes('UPDATE [[profile]] SET [[description]]=CAST(:qp0 AS JSON) WHERE [[id]]=:qp1'),
321
            [
322
                ':qp0' => '{"abc":"def","0":123,"1":null}',
323
                ':qp1' => 1,
324
            ],
325
        ];
326
327
        return $items;
328
    }
329
330
    public function testIssue17449(): void
331
    {
332
        $db = $this->getConnection();
333
        $pdo = $db->getPDO();
334
        $pdo->exec('DROP TABLE IF EXISTS `issue_17449`');
335
336
        $tableQuery = <<<MySqlStatement
337
CREATE TABLE `issue_17449` (
338
  `test_column` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'some comment' CHECK (json_valid(`test_column`))
339
) ENGINE=InnoDB DEFAULT CHARSET=latin1
340
MySqlStatement;
341
        $db->createCommand($tableQuery)->execute();
342
343
        $actual = $db->createCommand()->addCommentOnColumn('issue_17449', 'test_column', 'Some comment')->getRawSql();
344
345
        $checkPos = stripos($actual, 'check');
346
        if ($checkPos === false) {
347
            $this->markTestSkipped("The used MySql-Server removed or moved the CHECK from the column line, so the original bug doesn't affect it");
348
        }
349
        $commentPos = stripos($actual, 'comment');
350
        $this->assertNotFalse($commentPos);
351
        $this->assertLessThan($checkPos, $commentPos);
352
    }
353
}
354