Passed
Pull Request — master (#163)
by Wilmer
10:25
created

TestQueryBuilderTrait::filterConditionProvider()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 46
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 26
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 46
rs 9.504
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\TestUtility;
6
7
use Closure;
8
use Yiisoft\Arrays\ArrayHelper;
9
use Yiisoft\Db\Connection\Connection;
10
use Yiisoft\Db\Exception\Exception;
11
use Yiisoft\Db\Expression\Expression;
12
use Yiisoft\Db\Expression\ExpressionInterface;
13
use Yiisoft\Db\Query\Conditions\BetweenColumnsCondition;
14
use Yiisoft\Db\Query\Query;
15
use Yiisoft\Db\Query\QueryBuilder;
16
use Yiisoft\Db\Schema\ColumnSchema;
17
use Yiisoft\Db\Schema\Schema;
18
use Yiisoft\Db\Schema\SchemaBuilderTrait;
19
20
use function array_key_exists;
21
use function array_merge;
22
use function array_values;
23
use function is_array;
24
use function is_string;
25
use function preg_match_all;
26
use function str_replace;
27
use function strncmp;
28
use function substr;
29
30
trait TestQueryBuilderTrait
31
{
32
    use SchemaBuilderTrait;
33
34
    public function getDb(): Connection
35
    {
36
        return $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

36
        return $this->/** @scrutinizer ignore-call */ getConnection();
Loading history...
37
    }
38
39
    /**
40
     * This is not used as a dataprovider for testGetColumnType to speed up the test when used as dataprovider every
41
     * single line will cause a reconnect with the database which is not needed here.
42
     */
43
    public function columnTypes(): array
44
    {
45
        $items = [
46
            [
47
                Schema::TYPE_BIGINT,
48
                $this->bigInteger(),
49
                [
50
                    'mysql' => 'bigint(20)',
51
                    'postgres' => 'bigint',
52
                    'sqlite' => 'bigint',
53
                    'oci' => 'NUMBER(20)',
54
                    'sqlsrv' => 'bigint',
55
                ],
56
            ],
57
            [
58
                Schema::TYPE_BIGINT . ' NOT NULL',
59
                $this->bigInteger()->notNull(),
60
                [
61
                    'mysql' => 'bigint(20) NOT NULL',
62
                    'postgres' => 'bigint NOT NULL',
63
                    'sqlite' => 'bigint NOT NULL',
64
                    'oci' => 'NUMBER(20) NOT NULL',
65
                    'sqlsrv' => 'bigint NOT NULL',
66
                ],
67
            ],
68
            [
69
                Schema::TYPE_BIGINT . ' CHECK (value > 5)',
70
                $this->bigInteger()->check('value > 5'),
71
                [
72
                    'mysql' => 'bigint(20) CHECK (value > 5)',
73
                    'postgres' => 'bigint CHECK (value > 5)',
74
                    'sqlite' => 'bigint CHECK (value > 5)',
75
                    'oci' => 'NUMBER(20) CHECK (value > 5)',
76
                    'sqlsrv' => 'bigint CHECK (value > 5)',
77
                ],
78
            ],
79
            [
80
                Schema::TYPE_BIGINT . '(8)',
81
                $this->bigInteger(8),
82
                [
83
                    'mysql' => 'bigint(8)',
84
                    'postgres' => 'bigint',
85
                    'sqlite' => 'bigint',
86
                    'oci' => 'NUMBER(8)',
87
                    'sqlsrv' => 'bigint',
88
                ],
89
            ],
90
            [
91
                Schema::TYPE_BIGINT . '(8) CHECK (value > 5)',
92
                $this->bigInteger(8)->check('value > 5'),
93
                [
94
                    'mysql' => 'bigint(8) CHECK (value > 5)',
95
                    'postgres' => 'bigint CHECK (value > 5)',
96
                    'sqlite' => 'bigint CHECK (value > 5)',
97
                    'oci' => 'NUMBER(8) CHECK (value > 5)',
98
                    'sqlsrv' => 'bigint CHECK (value > 5)',
99
                ],
100
            ],
101
            [
102
                Schema::TYPE_BIGPK,
103
                $this->bigPrimaryKey(),
104
                [
105
                    'mysql' => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY',
106
                    'postgres' => 'bigserial NOT NULL PRIMARY KEY',
107
                    'sqlite' => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
108
                ],
109
            ],
110
            [
111
                Schema::TYPE_BINARY,
112
                $this->binary(),
113
                [
114
                    'mysql' => 'blob',
115
                    'postgres' => 'bytea',
116
                    'sqlite' => 'blob',
117
                    'oci' => 'BLOB',
118
                    'sqlsrv' => 'varbinary(max)',
119
                ],
120
            ],
121
            [
122
                Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1',
123
                $this->boolean()->notNull()->defaultValue(1),
124
                [
125
                    'mysql' => 'tinyint(1) NOT NULL DEFAULT 1',
126
                    'sqlite' => 'boolean NOT NULL DEFAULT 1',
127
                    'sqlsrv' => 'bit NOT NULL DEFAULT 1',
128
                ],
129
            ],
130
            [
131
                Schema::TYPE_BOOLEAN,
132
                $this->boolean(),
133
                [
134
                    'mysql' => 'tinyint(1)',
135
                    'postgres' => 'boolean',
136
                    'sqlite' => 'boolean',
137
                    'oci' => 'NUMBER(1)',
138
                    'sqlsrv' => 'bit',
139
                ],
140
            ],
141
            [
142
                Schema::TYPE_CHAR . ' CHECK (value LIKE "test%")',
143
                $this->char()->check('value LIKE "test%"'),
144
                [
145
                    'mysql' => 'char(1) CHECK (value LIKE "test%")',
146
                    'sqlite' => 'char(1) CHECK (value LIKE "test%")',
147
                ],
148
            ],
149
            [
150
                Schema::TYPE_CHAR . ' NOT NULL',
151
                $this->char()->notNull(),
152
                [
153
                    'mysql' => 'char(1) NOT NULL',
154
                    'postgres' => 'char(1) NOT NULL',
155
                    'sqlite' => 'char(1) NOT NULL',
156
                    'oci' => 'CHAR(1) NOT NULL',
157
                ],
158
            ],
159
            [
160
                Schema::TYPE_CHAR . '(6) CHECK (value LIKE "test%")',
161
                $this->char(6)->check('value LIKE "test%"'),
162
                [
163
                    'mysql' => 'char(6) CHECK (value LIKE "test%")',
164
                    'sqlite' => 'char(6) CHECK (value LIKE "test%")',
165
                ],
166
            ],
167
            [
168
                Schema::TYPE_CHAR . '(6)',
169
                $this->char(6),
170
                [
171
                    'mysql' => 'char(6)',
172
                    'postgres' => 'char(6)',
173
                    'sqlite' => 'char(6)',
174
                    'oci' => 'CHAR(6)',
175
                ],
176
            ],
177
            [
178
                Schema::TYPE_CHAR,
179
                $this->char(),
180
                [
181
                    'mysql' => 'char(1)',
182
                    'postgres' => 'char(1)',
183
                    'sqlite' => 'char(1)',
184
                    'oci' => 'CHAR(1)',
185
                ],
186
            ],
187
            [
188
                Schema::TYPE_DATE . ' NOT NULL',
189
                $this->date()->notNull(),
190
                [
191
                    'mysql' => 'date NOT NULL',
192
                    'postgres' => 'date NOT NULL',
193
                    'sqlite' => 'date NOT NULL',
194
                    'oci' => 'DATE NOT NULL',
195
                    'sqlsrv' => 'date NOT NULL',
196
                ],
197
            ],
198
            [
199
                Schema::TYPE_DATE,
200
                $this->date(),
201
                [
202
                    'mysql' => 'date',
203
                    'postgres' => 'date',
204
                    'sqlite' => 'date',
205
                    'oci' => 'DATE',
206
                    'sqlsrv' => 'date',
207
                ],
208
            ],
209
            [
210
                Schema::TYPE_DATETIME . ' NOT NULL',
211
                $this->dateTime()->notNull(),
212
                [
213
                    'postgres' => 'timestamp(0) NOT NULL',
214
                    'sqlite' => 'datetime NOT NULL',
215
                    'oci' => 'TIMESTAMP NOT NULL',
216
                    'sqlsrv' => 'datetime NOT NULL',
217
                ],
218
            ],
219
            [
220
                Schema::TYPE_DATETIME,
221
                $this->dateTime(),
222
                [
223
                    'postgres' => 'timestamp(0)',
224
                    'sqlite' => 'datetime',
225
                    'oci' => 'TIMESTAMP',
226
                    'sqlsrv' => 'datetime',
227
                ],
228
            ],
229
            [
230
                Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)',
231
                $this->decimal()->check('value > 5.6'),
232
                [
233
                    'mysql' => 'decimal(10,0) CHECK (value > 5.6)',
234
                    'postgres' => 'numeric(10,0) CHECK (value > 5.6)',
235
                    'sqlite' => 'decimal(10,0) CHECK (value > 5.6)',
236
                    'oci' => 'NUMBER CHECK (value > 5.6)',
237
                    'sqlsrv' => 'decimal(18,0) CHECK (value > 5.6)',
238
                ],
239
            ],
240
            [
241
                Schema::TYPE_DECIMAL . ' NOT NULL',
242
                $this->decimal()->notNull(),
243
                [
244
                    'mysql' => 'decimal(10,0) NOT NULL',
245
                    'postgres' => 'numeric(10,0) NOT NULL',
246
                    'sqlite' => 'decimal(10,0) NOT NULL',
247
                    'oci' => 'NUMBER NOT NULL',
248
                    'sqlsrv' => 'decimal(18,0) NOT NULL',
249
                ],
250
            ],
251
            [
252
                Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)',
253
                $this->decimal(12, 4)->check('value > 5.6'),
254
                [
255
                    'mysql' => 'decimal(12,4) CHECK (value > 5.6)',
256
                    'postgres' => 'numeric(12,4) CHECK (value > 5.6)',
257
                    'sqlite' => 'decimal(12,4) CHECK (value > 5.6)',
258
                    'oci' => 'NUMBER CHECK (value > 5.6)',
259
                    'sqlsrv' => 'decimal(12,4) CHECK (value > 5.6)',
260
                ],
261
            ],
262
            [
263
                Schema::TYPE_DECIMAL . '(12,4)',
264
                $this->decimal(12, 4),
265
                [
266
                    'mysql' => 'decimal(12,4)',
267
                    'postgres' => 'numeric(12,4)',
268
                    'sqlite' => 'decimal(12,4)',
269
                    'oci' => 'NUMBER',
270
                    'sqlsrv' => 'decimal(12,4)',
271
                ],
272
            ],
273
            [
274
                Schema::TYPE_DECIMAL,
275
                $this->decimal(),
276
                [
277
                    'mysql' => 'decimal(10,0)',
278
                    'postgres' => 'numeric(10,0)',
279
                    'sqlite' => 'decimal(10,0)',
280
                    'oci' => 'NUMBER',
281
                    'sqlsrv' => 'decimal(18,0)',
282
                ],
283
            ],
284
            [
285
                Schema::TYPE_DOUBLE . ' CHECK (value > 5.6)',
286
                $this->double()->check('value > 5.6'),
287
                [
288
                    'mysql' => 'double CHECK (value > 5.6)',
289
                    'postgres' => 'double precision CHECK (value > 5.6)',
290
                    'sqlite' => 'double CHECK (value > 5.6)',
291
                    'oci' => 'NUMBER CHECK (value > 5.6)',
292
                    'sqlsrv' => 'float CHECK (value > 5.6)',
293
                ],
294
            ],
295
            [
296
                Schema::TYPE_DOUBLE . ' NOT NULL',
297
                $this->double()->notNull(),
298
                [
299
                    'mysql' => 'double NOT NULL',
300
                    'postgres' => 'double precision NOT NULL',
301
                    'sqlite' => 'double NOT NULL',
302
                    'oci' => 'NUMBER NOT NULL',
303
                    'sqlsrv' => 'float NOT NULL',
304
                ],
305
            ],
306
            [
307
                Schema::TYPE_DOUBLE . '(16) CHECK (value > 5.6)',
308
                $this->double(16)->check('value > 5.6'),
309
                [
310
                    'mysql' => 'double CHECK (value > 5.6)',
311
                    'postgres' => 'double precision CHECK (value > 5.6)',
312
                    'sqlite' => 'double CHECK (value > 5.6)',
313
                    'oci' => 'NUMBER CHECK (value > 5.6)',
314
                    'sqlsrv' => 'float CHECK (value > 5.6)',
315
                ],
316
            ],
317
            [
318
                Schema::TYPE_DOUBLE . '(16)',
319
                $this->double(16),
320
                [
321
                    'mysql' => 'double',
322
                    'sqlite' => 'double',
323
                    'oci' => 'NUMBER',
324
                    'sqlsrv' => 'float',
325
                ],
326
            ],
327
            [
328
                Schema::TYPE_DOUBLE,
329
                $this->double(),
330
                [
331
                    'mysql' => 'double',
332
                    'postgres' => 'double precision',
333
                    'sqlite' => 'double',
334
                    'oci' => 'NUMBER',
335
                    'sqlsrv' => 'float',
336
                ],
337
            ],
338
            [
339
                Schema::TYPE_FLOAT . ' CHECK (value > 5.6)',
340
                $this->float()->check('value > 5.6'),
341
                [
342
                    'mysql' => 'float CHECK (value > 5.6)',
343
                    'postgres' => 'double precision CHECK (value > 5.6)',
344
                    'sqlite' => 'float CHECK (value > 5.6)',
345
                    'oci' => 'NUMBER CHECK (value > 5.6)',
346
                    'sqlsrv' => 'float CHECK (value > 5.6)',
347
                ],
348
            ],
349
            [
350
                Schema::TYPE_FLOAT . ' NOT NULL',
351
                $this->float()->notNull(),
352
                [
353
                    'mysql' => 'float NOT NULL',
354
                    'postgres' => 'double precision NOT NULL',
355
                    'sqlite' => 'float NOT NULL',
356
                    'oci' => 'NUMBER NOT NULL',
357
                    'sqlsrv' => 'float NOT NULL',
358
                ],
359
            ],
360
            [
361
                Schema::TYPE_FLOAT . '(16) CHECK (value > 5.6)',
362
                $this->float(16)->check('value > 5.6'),
363
                [
364
                    'mysql' => 'float CHECK (value > 5.6)',
365
                    'postgres' => 'double precision CHECK (value > 5.6)',
366
                    'sqlite' => 'float CHECK (value > 5.6)',
367
                    'oci' => 'NUMBER CHECK (value > 5.6)',
368
                    'sqlsrv' => 'float CHECK (value > 5.6)',
369
                ],
370
            ],
371
            [
372
                Schema::TYPE_FLOAT . '(16)',
373
                $this->float(16),
374
                [
375
                    'mysql' => 'float',
376
                    'sqlite' => 'float',
377
                    'oci' => 'NUMBER',
378
                    'sqlsrv' => 'float',
379
                ],
380
            ],
381
            [
382
                Schema::TYPE_FLOAT,
383
                $this->float(),
384
                [
385
                    'mysql' => 'float',
386
                    'postgres' => 'double precision',
387
                    'sqlite' => 'float',
388
                    'oci' => 'NUMBER',
389
                    'sqlsrv' => 'float',
390
                ],
391
            ],
392
            [
393
                Schema::TYPE_INTEGER . ' CHECK (value > 5)',
394
                $this->integer()->check('value > 5'),
395
                [
396
                    'mysql' => 'int(11) CHECK (value > 5)',
397
                    'postgres' => 'integer CHECK (value > 5)',
398
                    'sqlite' => 'integer CHECK (value > 5)',
399
                    'oci' => 'NUMBER(10) CHECK (value > 5)',
400
                    'sqlsrv' => 'int CHECK (value > 5)',
401
                ],
402
            ],
403
            [
404
                Schema::TYPE_INTEGER . ' NOT NULL',
405
                $this->integer()->notNull(),
406
                [
407
                    'mysql' => 'int(11) NOT NULL',
408
                    'postgres' => 'integer NOT NULL',
409
                    'sqlite' => 'integer NOT NULL',
410
                    'oci' => 'NUMBER(10) NOT NULL',
411
                    'sqlsrv' => 'int NOT NULL',
412
                ],
413
            ],
414
            [
415
                Schema::TYPE_INTEGER . '(8) CHECK (value > 5)',
416
                $this->integer(8)->check('value > 5'),
417
                [
418
                    'mysql' => 'int(8) CHECK (value > 5)',
419
                    'postgres' => 'integer CHECK (value > 5)',
420
                    'sqlite' => 'integer CHECK (value > 5)',
421
                    'oci' => 'NUMBER(8) CHECK (value > 5)',
422
                    'sqlsrv' => 'int CHECK (value > 5)',
423
                ],
424
            ],
425
            [
426
                Schema::TYPE_INTEGER . '(8)',
427
                $this->integer(8),
428
                [
429
                    'mysql' => 'int(8)',
430
                    'postgres' => 'integer',
431
                    'sqlite' => 'integer',
432
                    'oci' => 'NUMBER(8)',
433
                    'sqlsrv' => 'int',
434
                ],
435
            ],
436
            [
437
                Schema::TYPE_INTEGER,
438
                $this->integer(),
439
                [
440
                    'mysql' => 'int(11)',
441
                    'postgres' => 'integer',
442
                    'sqlite' => 'integer',
443
                    'oci' => 'NUMBER(10)',
444
                    'sqlsrv' => 'int',
445
                ],
446
            ],
447
            [
448
                Schema::TYPE_MONEY . ' CHECK (value > 0.0)',
449
                $this->money()->check('value > 0.0'),
450
                [
451
                    'mysql' => 'decimal(19,4) CHECK (value > 0.0)',
452
                    'postgres' => 'numeric(19,4) CHECK (value > 0.0)',
453
                    'sqlite' => 'decimal(19,4) CHECK (value > 0.0)',
454
                    'oci' => 'NUMBER(19,4) CHECK (value > 0.0)',
455
                    'sqlsrv' => 'decimal(19,4) CHECK (value > 0.0)',
456
                ]
457
            ],
458
            [
459
                Schema::TYPE_MONEY . ' NOT NULL',
460
                $this->money()->notNull(),
461
                [
462
                    'mysql' => 'decimal(19,4) NOT NULL',
463
                    'postgres' => 'numeric(19,4) NOT NULL',
464
                    'sqlite' => 'decimal(19,4) NOT NULL',
465
                    'oci' => 'NUMBER(19,4) NOT NULL',
466
                    'sqlsrv' => 'decimal(19,4) NOT NULL',
467
                ],
468
            ],
469
            [
470
                Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)',
471
                $this->money(16, 2)->check('value > 0.0'),
472
                [
473
                    'mysql' => 'decimal(16,2) CHECK (value > 0.0)',
474
                    'postgres' => 'numeric(16,2) CHECK (value > 0.0)',
475
                    'sqlite' => 'decimal(16,2) CHECK (value > 0.0)',
476
                    'oci' => 'NUMBER(16,2) CHECK (value > 0.0)',
477
                    'sqlsrv' => 'decimal(16,2) CHECK (value > 0.0)',
478
                ],
479
            ],
480
            [
481
                Schema::TYPE_MONEY . '(16,2)',
482
                $this->money(16, 2),
483
                [
484
                    'mysql' => 'decimal(16,2)',
485
                    'postgres' => 'numeric(16,2)',
486
                    'sqlite' => 'decimal(16,2)',
487
                    'oci' => 'NUMBER(16,2)',
488
                    'sqlsrv' => 'decimal(16,2)',
489
                ],
490
            ],
491
            [
492
                Schema::TYPE_MONEY,
493
                $this->money(),
494
                [
495
                    'mysql' => 'decimal(19,4)',
496
                    'postgres' => 'numeric(19,4)',
497
                    'sqlite' => 'decimal(19,4)',
498
                    'oci' => 'NUMBER(19,4)',
499
                    'sqlsrv' => 'decimal(19,4)',
500
                ],
501
            ],
502
            [
503
                Schema::TYPE_PK . ' CHECK (value > 5)',
504
                $this->primaryKey()->check('value > 5'),
505
                [
506
                    'mysql' => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)',
507
                    'postgres' => 'serial NOT NULL PRIMARY KEY CHECK (value > 5)',
508
                    'sqlite' => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (value > 5)',
509
                    'oci' => 'NUMBER(10) NOT NULL PRIMARY KEY CHECK (value > 5)',
510
                    'sqlsrv' => 'int IDENTITY PRIMARY KEY CHECK (value > 5)',
511
                ],
512
            ],
513
            [
514
                Schema::TYPE_PK . '(8) CHECK (value > 5)',
515
                $this->primaryKey(8)->check('value > 5'),
516
                [
517
                    'mysql' => 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)',
518
                    'oci' => 'NUMBER(8) NOT NULL PRIMARY KEY CHECK (value > 5)',
519
                ],
520
            ],
521
            [
522
                Schema::TYPE_PK . '(8)',
523
                $this->primaryKey(8),
524
                [
525
                    'mysql' => 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY',
526
                    'oci' => 'NUMBER(8) NOT NULL PRIMARY KEY',
527
                ],
528
            ],
529
            [
530
                Schema::TYPE_PK,
531
                $this->primaryKey(),
532
                [
533
                    'mysql' => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY',
534
                    'postgres' => 'serial NOT NULL PRIMARY KEY',
535
                    'sqlite' => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
536
                    'oci' => 'NUMBER(10) NOT NULL PRIMARY KEY',
537
                    'sqlsrv' => 'int IDENTITY PRIMARY KEY',
538
                ],
539
            ],
540
            [
541
                Schema::TYPE_TINYINT . '(2)',
542
                $this->tinyInteger(2),
543
                [
544
                    'mysql' => 'tinyint(2)',
545
                    'postgres' => 'smallint',
546
                    'sqlite' => 'tinyint',
547
                    'oci' => 'NUMBER(2)',
548
                    'sqlsrv' => 'tinyint',
549
                ],
550
            ],
551
            [
552
                Schema::TYPE_TINYINT . ' UNSIGNED',
553
                $this->tinyInteger()->unsigned(),
554
                [
555
                    'mysql' => 'tinyint(3) UNSIGNED',
556
                    'postgres' => 'smallint UNSIGNED',
557
                    'sqlite' => 'tinyint UNSIGNED',
558
                ],
559
            ],
560
            [
561
                Schema::TYPE_TINYINT,
562
                $this->tinyInteger(),
563
                [
564
                    'mysql' => 'tinyint(3)',
565
                    'postgres' => 'smallint',
566
                    'sqlite' => 'tinyint',
567
                    'oci' => 'NUMBER(3)',
568
                    'sqlsrv' => 'tinyint',
569
                ],
570
            ],
571
            [
572
                Schema::TYPE_SMALLINT . '(8)',
573
                $this->smallInteger(8),
574
                [
575
                    'mysql' => 'smallint(8)',
576
                    'postgres' => 'smallint',
577
                    'sqlite' => 'smallint',
578
                    'oci' => 'NUMBER(8)',
579
                    'sqlsrv' => 'smallint',
580
                ],
581
            ],
582
            [
583
                Schema::TYPE_SMALLINT,
584
                $this->smallInteger(),
585
                [
586
                    'mysql' => 'smallint(6)',
587
                    'postgres' => 'smallint',
588
                    'sqlite' => 'smallint',
589
                    'oci' => 'NUMBER(5)',
590
                    'sqlsrv' => 'smallint',
591
                ],
592
            ],
593
            [
594
                Schema::TYPE_STRING . " CHECK (value LIKE 'test%')",
595
                $this->string()->check("value LIKE 'test%'"),
596
                [
597
                    'mysql' => "varchar(255) CHECK (value LIKE 'test%')",
598
                    'sqlite' => "varchar(255) CHECK (value LIKE 'test%')",
599
                    'sqlsrv' => "nvarchar(255) CHECK (value LIKE 'test%')",
600
                ],
601
            ],
602
            [
603
                Schema::TYPE_STRING . ' CHECK (value LIKE \'test%\')',
604
                $this->string()->check('value LIKE \'test%\''),
605
                [
606
                    'postgres' => 'varchar(255) CHECK (value LIKE \'test%\')',
607
                    'oci' => 'VARCHAR2(255) CHECK (value LIKE \'test%\')',
608
                ],
609
            ],
610
            [
611
                Schema::TYPE_STRING . ' NOT NULL',
612
                $this->string()->notNull(),
613
                [
614
                    'mysql' => 'varchar(255) NOT NULL',
615
                    'postgres' => 'varchar(255) NOT NULL',
616
                    'sqlite' => 'varchar(255) NOT NULL',
617
                    'oci' => 'VARCHAR2(255) NOT NULL',
618
                    'sqlsrv' => 'nvarchar(255) NOT NULL',
619
                ],
620
            ],
621
            [
622
                Schema::TYPE_STRING . "(32) CHECK (value LIKE 'test%')",
623
                $this->string(32)->check("value LIKE 'test%'"),
624
                [
625
                    'mysql' => "varchar(32) CHECK (value LIKE 'test%')",
626
                    'sqlite' => "varchar(32) CHECK (value LIKE 'test%')",
627
                    'sqlsrv' => "nvarchar(32) CHECK (value LIKE 'test%')",
628
                ],
629
            ],
630
            [
631
                Schema::TYPE_STRING . '(32) CHECK (value LIKE \'test%\')',
632
                $this->string(32)->check('value LIKE \'test%\''),
633
                [
634
                    'postgres' => 'varchar(32) CHECK (value LIKE \'test%\')',
635
                    'oci' => 'VARCHAR2(32) CHECK (value LIKE \'test%\')',
636
                ],
637
            ],
638
            [
639
                Schema::TYPE_STRING . '(32)',
640
                $this->string(32),
641
                [
642
                    'mysql' => 'varchar(32)',
643
                    'postgres' => 'varchar(32)',
644
                    'sqlite' => 'varchar(32)',
645
                    'oci' => 'VARCHAR2(32)',
646
                    'sqlsrv' => 'nvarchar(32)',
647
                ],
648
            ],
649
            [
650
                Schema::TYPE_STRING,
651
                $this->string(),
652
                [
653
                    'mysql' => 'varchar(255)',
654
                    'postgres' => 'varchar(255)',
655
                    'sqlite' => 'varchar(255)',
656
                    'oci' => 'VARCHAR2(255)',
657
                    'sqlsrv' => 'nvarchar(255)',
658
                ],
659
            ],
660
            [
661
                Schema::TYPE_TEXT . " CHECK (value LIKE 'test%')",
662
                $this->text()->check("value LIKE 'test%'"),
663
                [
664
                    'mysql' => "text CHECK (value LIKE 'test%')",
665
                    'sqlite' => "text CHECK (value LIKE 'test%')",
666
                    'sqlsrv' => "nvarchar(max) CHECK (value LIKE 'test%')",
667
                ],
668
            ],
669
            [
670
                Schema::TYPE_TEXT . ' CHECK (value LIKE \'test%\')',
671
                $this->text()->check('value LIKE \'test%\''),
672
                [
673
                    'postgres' => 'text CHECK (value LIKE \'test%\')',
674
                    'oci' => 'CLOB CHECK (value LIKE \'test%\')',
675
                ],
676
            ],
677
            [
678
                Schema::TYPE_TEXT . ' NOT NULL',
679
                $this->text()->notNull(),
680
                [
681
                    'mysql' => 'text NOT NULL',
682
                    'postgres' => 'text NOT NULL',
683
                    'sqlite' => 'text NOT NULL',
684
                    'oci' => 'CLOB NOT NULL',
685
                    'sqlsrv' => 'nvarchar(max) NOT NULL',
686
                ],
687
            ],
688
            [
689
                Schema::TYPE_TEXT . " CHECK (value LIKE 'test%')",
690
                $this->text()->check("value LIKE 'test%'"),
691
                [
692
                    'mysql' => "text CHECK (value LIKE 'test%')",
693
                    'sqlite' => "text CHECK (value LIKE 'test%')",
694
                    'sqlsrv' => "nvarchar(max) CHECK (value LIKE 'test%')",
695
                ],
696
                Schema::TYPE_TEXT . " CHECK (value LIKE 'test%')",
697
            ],
698
            [
699
                Schema::TYPE_TEXT . ' CHECK (value LIKE \'test%\')',
700
                $this->text()->check('value LIKE \'test%\''),
701
                [
702
                    'postgres' => 'text CHECK (value LIKE \'test%\')',
703
                    'oci' => 'CLOB CHECK (value LIKE \'test%\')',
704
                ],
705
                Schema::TYPE_TEXT . ' CHECK (value LIKE \'test%\')',
706
            ],
707
            [
708
                Schema::TYPE_TEXT . ' NOT NULL',
709
                $this->text()->notNull(),
710
                [
711
                    'mysql' => 'text NOT NULL',
712
                    'postgres' => 'text NOT NULL',
713
                    'sqlite' => 'text NOT NULL',
714
                    'oci' => 'CLOB NOT NULL',
715
                    'sqlsrv' => 'nvarchar(max) NOT NULL',
716
                ],
717
                Schema::TYPE_TEXT . ' NOT NULL',
718
            ],
719
            [
720
                Schema::TYPE_TEXT,
721
                $this->text(),
722
                [
723
                    'mysql' => 'text',
724
                    'postgres' => 'text',
725
                    'sqlite' => 'text',
726
                    'oci' => 'CLOB',
727
                    'sqlsrv' => 'nvarchar(max)',
728
                ],
729
                Schema::TYPE_TEXT,
730
            ],
731
            [
732
                Schema::TYPE_TEXT,
733
                $this->text(),
734
                [
735
                    'mysql' => 'text',
736
                    'postgres' => 'text',
737
                    'sqlite' => 'text',
738
                    'oci' => 'CLOB',
739
                    'sqlsrv' => 'nvarchar(max)',
740
                ],
741
            ],
742
            [
743
                Schema::TYPE_TIME . ' NOT NULL',
744
                $this->time()->notNull(),
745
                [
746
                    'postgres' => 'time(0) NOT NULL',
747
                    'sqlite' => 'time NOT NULL',
748
                    'oci' => 'TIMESTAMP NOT NULL',
749
                    'sqlsrv' => 'time NOT NULL',
750
                ],
751
            ],
752
            [
753
                Schema::TYPE_TIME,
754
                $this->time(),
755
                [
756
                    'postgres' => 'time(0)',
757
                    'sqlite' => 'time',
758
                    'oci' => 'TIMESTAMP',
759
                    'sqlsrv' => 'time',
760
                ],
761
            ],
762
            [
763
                Schema::TYPE_TIMESTAMP . ' NOT NULL',
764
                $this->timestamp()->notNull(),
765
                [
766
                    'postgres' => 'timestamp(0) NOT NULL',
767
                    'sqlite' => 'timestamp NOT NULL',
768
                    'oci' => 'TIMESTAMP NOT NULL',
769
                    'sqlsrv' => 'datetime NOT NULL',
770
                ],
771
            ],
772
            [
773
                Schema::TYPE_TIMESTAMP,
774
                $this->timestamp(),
775
                [
776
                    /**
777
                     * MySQL has its own TIMESTAMP test realization.
778
                     *
779
                     * {@see QueryBuilderTest::columnTypes()}
780
                     */
781
                    'postgres' => 'timestamp(0)',
782
                    'sqlite' => 'timestamp',
783
                    'oci' => 'TIMESTAMP',
784
                    'sqlsrv' => 'datetime',
785
                ],
786
            ],
787
            [
788
                Schema::TYPE_TIMESTAMP . ' NULL DEFAULT NULL',
789
                $this->timestamp()->defaultValue(null),
790
                [
791
                    'postgres' => 'timestamp(0) NULL DEFAULT NULL',
792
                    'sqlite' => 'timestamp NULL DEFAULT NULL',
793
                    'sqlsrv' => 'datetime NULL DEFAULT NULL',
794
                ],
795
            ],
796
            [
797
                Schema::TYPE_UPK,
798
                $this->primaryKey()->unsigned(),
799
                [
800
                    'mysql' => 'int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY',
801
                    'postgres' => 'serial NOT NULL PRIMARY KEY',
802
                    'sqlite' => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
803
                ],
804
            ],
805
            [
806
                Schema::TYPE_UBIGPK,
807
                $this->bigPrimaryKey()->unsigned(),
808
                [
809
                    'mysql' => 'bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY',
810
                    'postgres' => 'bigserial NOT NULL PRIMARY KEY',
811
                    'sqlite' => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
812
                ],
813
            ],
814
            [
815
                Schema::TYPE_INTEGER . " COMMENT 'test comment'",
816
                $this->integer()->comment('test comment'),
817
                [
818
                    'mysql' => "int(11) COMMENT 'test comment'",
819
                    'postgres' => 'integer',
820
                    'sqlsrv' => 'int',
821
                ],
822
                [
823
                    'sqlsrv' => 'integer',
824
                ]
825
            ],
826
            [
827
                Schema::TYPE_PK . " COMMENT 'test comment'",
828
                $this->primaryKey()->comment('test comment'),
829
                [
830
                    'mysql' => "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'test comment'",
831
                    'postgres' => 'serial NOT NULL PRIMARY KEY',
832
                    'sqlsrv' => 'int IDENTITY PRIMARY KEY',
833
                ],
834
                [
835
                    'sqlsrv' => 'pk',
836
                ]
837
            ],
838
            [
839
                Schema::TYPE_PK . ' FIRST',
840
                $this->primaryKey()->first(),
841
                [
842
                    'mysql' => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST',
843
                    'postgres' => 'serial NOT NULL PRIMARY KEY',
844
                    'oci' => 'NUMBER(10) NOT NULL PRIMARY KEY',
845
                    'sqlsrv' => 'int IDENTITY PRIMARY KEY',
846
                ],
847
                [
848
                    'sqlsrv' => 'pk',
849
                ]
850
            ],
851
            [
852
                Schema::TYPE_INTEGER . ' FIRST',
853
                $this->integer()->first(),
854
                [
855
                    'mysql' => 'int(11) FIRST',
856
                    'postgres' => 'integer',
857
                    'oci' => 'NUMBER(10)',
858
                    'sqlsrv' => 'int',
859
                ],
860
                [
861
                    'sqlsrv' => 'integer',
862
                ]
863
            ],
864
            [
865
                Schema::TYPE_STRING . ' FIRST',
866
                $this->string()->first(),
867
                [
868
                    'mysql' => 'varchar(255) FIRST',
869
                    'postgres' => 'varchar(255)',
870
                    'oci' => 'VARCHAR2(255)',
871
                    'sqlsrv' => 'nvarchar(255)',
872
                ],
873
                [
874
                    'sqlsrv' => 'string',
875
                ]
876
            ],
877
            [
878
                Schema::TYPE_INTEGER . ' NOT NULL FIRST',
879
                $this->integer()->append('NOT NULL')->first(),
880
                [
881
                    'mysql' => 'int(11) NOT NULL FIRST',
882
                    'postgres' => 'integer NOT NULL',
883
                    'oci' => 'NUMBER(10) NOT NULL',
884
                    'sqlsrv' => 'int NOT NULL',
885
                ],
886
                [
887
                    'sqlsrv' => 'integer NOT NULL',
888
                ]
889
            ],
890
            [
891
                Schema::TYPE_STRING . ' NOT NULL FIRST',
892
                $this->string()->append('NOT NULL')->first(),
893
                [
894
                    'mysql' => 'varchar(255) NOT NULL FIRST',
895
                    'postgres' => 'varchar(255) NOT NULL',
896
                    'oci' => 'VARCHAR2(255) NOT NULL',
897
                    'sqlsrv' => 'nvarchar(255) NOT NULL',
898
                ],
899
                [
900
                    'sqlsrv' => 'string NOT NULL',
901
                ]
902
            ],
903
        ];
904
905
        $db =  $this->getConnection();
906
907
        foreach ($items as $i => $item) {
908
            if (array_key_exists($db->getDriverName(), $item[2])) {
909
                $item[2] = $item[2][$db->getDriverName()];
910
                $items[$i] = $item;
911
            } else {
912
                unset($items[$i]);
913
            }
914
        }
915
916
        return array_values($items);
917
    }
918
919
    public function testGetColumnType(): void
920
    {
921
        $db =  $this->getConnection();
922
923
        $qb = $this->getQueryBuilder();
0 ignored issues
show
Bug introduced by
It seems like getQueryBuilder() 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

923
        /** @scrutinizer ignore-call */ 
924
        $qb = $this->getQueryBuilder();
Loading history...
924
925
        foreach ($this->columnTypes() as $item) {
926
            [$column, $builder, $expected] = $item;
927
928
            if (isset($item[3][$db->getDriverName()])) {
929
                $expectedColumnSchemaBuilder = $item[3][$db->getDriverName()];
930
            } elseif (isset($item[3]) && !is_array($item[3])) {
931
                $expectedColumnSchemaBuilder = $item[3];
932
            } else {
933
                $expectedColumnSchemaBuilder = $column;
934
            }
935
936
            $this->assertEquals($expected, $qb->getColumnType($column));
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

936
            $this->/** @scrutinizer ignore-call */ 
937
                   assertEquals($expected, $qb->getColumnType($column));
Loading history...
937
            $this->assertEquals($expected, $qb->getColumnType($builder));
938
            $this->assertEquals($expectedColumnSchemaBuilder, $builder->__toString());
939
        }
940
    }
941
942
    public function testCreateTableColumnTypes(): void
943
    {
944
        $qb = $this->getQueryBuilder();
945
946
        if ($qb->getDb()->getTableSchema('column_type_table', true) !== null) {
947
            $this->getConnection(false)->createCommand($qb->dropTable('column_type_table'))->execute();
948
        }
949
950
        $columns = [];
951
        $i = 0;
952
953
        foreach ($this->columnTypes() as [$column, $builder, $expected]) {
954
            if (
955
                !(
956
                    strncmp($column, Schema::TYPE_PK, 2) === 0 ||
957
                    strncmp($column, Schema::TYPE_UPK, 3) === 0 ||
958
                    strncmp($column, Schema::TYPE_BIGPK, 5) === 0 ||
959
                    strncmp($column, Schema::TYPE_UBIGPK, 6) === 0 ||
960
                    strncmp(substr($column, -5), 'FIRST', 5) === 0
961
                )
962
            ) {
963
                $columns['col' . ++$i] = str_replace('CHECK (value', 'CHECK ([[col' . $i . ']]', $column);
964
            }
965
        }
966
967
        $this->getConnection(false)->createCommand($qb->createTable('column_type_table', $columns))->execute();
968
969
        $this->assertNotEmpty($qb->getDb()->getTableSchema('column_type_table', true));
0 ignored issues
show
Bug introduced by
It seems like assertNotEmpty() 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

969
        $this->/** @scrutinizer ignore-call */ 
970
               assertNotEmpty($qb->getDb()->getTableSchema('column_type_table', true));
Loading history...
970
    }
971
972
    public function conditionProviderTrait(): array
973
    {
974
        $db = $this->getConnection();
975
976
        $conditions = [
977
            /* empty values */
978
            [['like', 'name', []], '0=1', []],
979
            [['not like', 'name', []], '', []],
980
            [['or like', 'name', []], '0=1', []],
981
            [['or not like', 'name', []], '', []],
982
983
            /* not */
984
            [['not', 'name'], 'NOT (name)', []],
985
            [
986
                ['not', (new Query($db))->select('exists')->from('some_table')],
987
                'NOT ((SELECT [[exists]] FROM [[some_table]]))', []
988
            ],
989
990
            /* and */
991
            [['and', 'id=1', 'id=2'], '(id=1) AND (id=2)', []],
992
            [['and', 'type=1', ['or', 'id=1', 'id=2']], '(type=1) AND ((id=1) OR (id=2))', []],
993
            [['and', 'id=1', new Expression('id=:qp0', [':qp0' => 2])], '(id=1) AND (id=:qp0)', [':qp0' => 2]],
994
            [
995
                ['and', ['expired' => false], (new Query($db))->select('count(*) > 1')->from('queue')],
996
                '([[expired]]=:qp0) AND ((SELECT count(*) > 1 FROM [[queue]]))', [':qp0' => false]
997
            ],
998
999
            /* or */
1000
            [['or', 'id=1', 'id=2'], '(id=1) OR (id=2)', []],
1001
            [['or', 'type=1', ['or', 'id=1', 'id=2']], '(type=1) OR ((id=1) OR (id=2))', []],
1002
            [['or', 'type=1', new Expression('id=:qp0', [':qp0' => 1])], '(type=1) OR (id=:qp0)', [':qp0' => 1]],
1003
1004
            /* between */
1005
            [['between', 'id', 1, 10], '[[id]] BETWEEN :qp0 AND :qp1', [':qp0' => 1, ':qp1' => 10]],
1006
            [['not between', 'id', 1, 10], '[[id]] NOT BETWEEN :qp0 AND :qp1', [':qp0' => 1, ':qp1' => 10]],
1007
            [
1008
                ['between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), new Expression('NOW()')],
1009
                '[[date]] BETWEEN (NOW() - INTERVAL 1 MONTH) AND NOW()',
1010
                []
1011
            ],
1012
            [
1013
                ['between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), 123],
1014
                '[[date]] BETWEEN (NOW() - INTERVAL 1 MONTH) AND :qp0',
1015
                [':qp0' => 123]
1016
            ],
1017
            [
1018
                ['not between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), new Expression('NOW()')],
1019
                '[[date]] NOT BETWEEN (NOW() - INTERVAL 1 MONTH) AND NOW()',
1020
                []
1021
            ],
1022
            [
1023
                ['not between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), 123],
1024
                '[[date]] NOT BETWEEN (NOW() - INTERVAL 1 MONTH) AND :qp0',
1025
                [':qp0' => 123]
1026
            ],
1027
            [
1028
                new BetweenColumnsCondition('2018-02-11', 'BETWEEN', 'create_time', 'update_time'),
1029
                ':qp0 BETWEEN [[create_time]] AND [[update_time]]',
1030
                [':qp0' => '2018-02-11']
1031
            ],
1032
            [
1033
                new BetweenColumnsCondition('2018-02-11', 'NOT BETWEEN', 'NOW()', 'update_time'),
1034
                ':qp0 NOT BETWEEN NOW() AND [[update_time]]',
1035
                [':qp0' => '2018-02-11']
1036
            ],
1037
            [
1038
                new BetweenColumnsCondition(new Expression('NOW()'), 'BETWEEN', 'create_time', 'update_time'),
1039
                'NOW() BETWEEN [[create_time]] AND [[update_time]]',
1040
                []
1041
            ],
1042
            [
1043
                new BetweenColumnsCondition(new Expression('NOW()'), 'NOT BETWEEN', 'create_time', 'update_time'),
1044
                'NOW() NOT BETWEEN [[create_time]] AND [[update_time]]',
1045
                []
1046
            ],
1047
            [
1048
                new BetweenColumnsCondition(
1049
                    new Expression('NOW()'),
1050
                    'NOT BETWEEN',
1051
                    (new Query($db))->select('min_date')->from('some_table'),
1052
                    'max_date'
1053
                ),
1054
                'NOW() NOT BETWEEN (SELECT [[min_date]] FROM [[some_table]]) AND [[max_date]]',
1055
                []
1056
            ],
1057
1058
            /* in */
1059
            [
1060
                ['in', 'id', [1, 2, (new Query($db))->select('three')->from('digits')]],
1061
                '[[id]] IN (:qp0, :qp1, (SELECT [[three]] FROM [[digits]]))', [':qp0' => 1, ':qp1' => 2]
1062
            ],
1063
            [['not in', 'id', [1, 2, 3]], '[[id]] NOT IN (:qp0, :qp1, :qp2)', [':qp0' => 1, ':qp1' => 2, ':qp2' => 3]],
1064
            [
1065
                ['in', 'id', (new Query($db))->select('id')->from('users')->where(['active' => 1])],
1066
                '[[id]] IN (SELECT [[id]] FROM [[users]] WHERE [[active]]=:qp0)', [':qp0' => 1]
1067
            ],
1068
            [
1069
                ['not in', 'id', (new Query($db))->select('id')->from('users')->where(['active' => 1])],
1070
                '[[id]] NOT IN (SELECT [[id]] FROM [[users]] WHERE [[active]]=:qp0)', [':qp0' => 1]
1071
            ],
1072
1073
            [['in', 'id', 1],   '[[id]]=:qp0', [':qp0' => 1]],
1074
            [['in', 'id', [1]], '[[id]]=:qp0', [':qp0' => 1]],
1075
            [['in', 'id', new TraversableObject([1])], '[[id]]=:qp0', [':qp0' => 1]],
1076
            'composite in' => [
1077
                ['in', ['id', 'name'], [['id' => 1, 'name' => 'oy']]],
1078
                '([[id]], [[name]]) IN ((:qp0, :qp1))',
1079
                [':qp0' => 1, ':qp1' => 'oy'],
1080
            ],
1081
1082
            /* in using array objects. */
1083
            [['id' => new TraversableObject([1, 2])], '[[id]] IN (:qp0, :qp1)', [':qp0' => 1, ':qp1' => 2]],
1084
1085
            [
1086
                ['in', 'id', new TraversableObject([1, 2, 3])],
1087
                '[[id]] IN (:qp0, :qp1, :qp2)', [':qp0' => 1, ':qp1' => 2, ':qp2' => 3]
1088
            ],
1089
1090
            'composite in using array objects' => [
1091
                ['in', new TraversableObject(['id', 'name']), new TraversableObject([
1092
                    ['id' => 1, 'name' => 'oy'],
1093
                    ['id' => 2, 'name' => 'yo'],
1094
                ])],
1095
                '([[id]], [[name]]) IN ((:qp0, :qp1), (:qp2, :qp3))',
1096
                [':qp0' => 1, ':qp1' => 'oy', ':qp2' => 2, ':qp3' => 'yo'],
1097
            ],
1098
1099
            /* exists */
1100
            [
1101
                ['exists', (new Query($db))->select('id')->from('users')->where(['active' => 1])],
1102
                'EXISTS (SELECT [[id]] FROM [[users]] WHERE [[active]]=:qp0)', [':qp0' => 1]
1103
            ],
1104
            [
1105
                ['not exists', (new Query($db))->select('id')->from('users')->where(['active' => 1])],
1106
                'NOT EXISTS (SELECT [[id]] FROM [[users]] WHERE [[active]]=:qp0)', [':qp0' => 1]
1107
            ],
1108
1109
            /* simple conditions */
1110
            [['=', 'a', 'b'], '[[a]] = :qp0', [':qp0' => 'b']],
1111
            [['>', 'a', 1], '[[a]] > :qp0', [':qp0' => 1]],
1112
            [['>=', 'a', 'b'], '[[a]] >= :qp0', [':qp0' => 'b']],
1113
            [['<', 'a', 2], '[[a]] < :qp0', [':qp0' => 2]],
1114
            [['<=', 'a', 'b'], '[[a]] <= :qp0', [':qp0' => 'b']],
1115
            [['<>', 'a', 3], '[[a]] <> :qp0', [':qp0' => 3]],
1116
            [['!=', 'a', 'b'], '[[a]] != :qp0', [':qp0' => 'b']],
1117
            [
1118
                ['>=', 'date', new Expression('DATE_SUB(NOW(), INTERVAL 1 MONTH)')],
1119
                '[[date]] >= DATE_SUB(NOW(), INTERVAL 1 MONTH)', []
1120
            ],
1121
            [
1122
                ['>=', 'date', new Expression('DATE_SUB(NOW(), INTERVAL :month MONTH)', [':month' => 2])],
1123
                '[[date]] >= DATE_SUB(NOW(), INTERVAL :month MONTH)', [':month' => 2]
1124
            ],
1125
            [
1126
                ['=', 'date', (new Query($db))->select('max(date)')->from('test')->where(['id' => 5])],
1127
                '[[date]] = (SELECT max(date) FROM [[test]] WHERE [[id]]=:qp0)', [':qp0' => 5]
1128
            ],
1129
1130
            /* hash condition */
1131
            [['a' => 1, 'b' => 2], '([[a]]=:qp0) AND ([[b]]=:qp1)', [':qp0' => 1, ':qp1' => 2]],
1132
            [
1133
                ['a' => new Expression('CONCAT(col1, col2)'), 'b' => 2],
1134
                '([[a]]=CONCAT(col1, col2)) AND ([[b]]=:qp0)', [':qp0' => 2]
1135
            ],
1136
1137
            /* direct conditions */
1138
            ['a = CONCAT(col1, col2)', 'a = CONCAT(col1, col2)', []],
1139
            [
1140
                new Expression('a = CONCAT(col1, :param1)', ['param1' => 'value1']),
1141
                'a = CONCAT(col1, :param1)', ['param1' => 'value1']
1142
            ],
1143
1144
            /* Expression with params as operand of 'not' */
1145
            [['not', new Expression('any_expression(:a)', [':a' => 1])], 'NOT (any_expression(:a))', [':a' => 1]],
1146
            [new Expression('NOT (any_expression(:a))', [':a' => 1]), 'NOT (any_expression(:a))', [':a' => 1]],
1147
        ];
1148
1149
        switch ($db->getDriverName()) {
1150
            case 'sqlsrv':
1151
            case 'sqlite':
1152
                $conditions = array_merge($conditions, [
1153
                    [
1154
                        ['in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]],
1155
                        '(([[id]] = :qp0 AND [[name]] = :qp1) OR ([[id]] = :qp2 AND [[name]] = :qp3))',
1156
                        [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar']
1157
                    ],
1158
                    [
1159
                        ['not in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]],
1160
                        '(([[id]] != :qp0 OR [[name]] != :qp1) AND ([[id]] != :qp2 OR [[name]] != :qp3))',
1161
                        [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar']
1162
                    ],
1163
                    //[['in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], 'EXISTS (SELECT 1 FROM (SELECT [[id]], [[name]] FROM [[users]] WHERE [[active]]=:qp0) AS a WHERE a.[[id]] = [[id AND a.]]name[[ = ]]name`)', [':qp0' => 1] ],
1164
                    //[['not in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], 'NOT EXISTS (SELECT 1 FROM (SELECT [[id]], [[name]] FROM [[users]] WHERE [[active]]=:qp0) AS a WHERE a.[[id]] = [[id]] AND a.[[name = ]]name`)', [':qp0' => 1] ],
1165
                ]);
1166
1167
                break;
1168
            default:
1169
                $conditions = array_merge($conditions, [
1170
                    [
1171
                        ['in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]],
1172
                        '([[id]], [[name]]) IN ((:qp0, :qp1), (:qp2, :qp3))',
1173
                        [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar']
1174
                    ],
1175
                    [
1176
                        ['not in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]],
1177
                        '([[id]], [[name]]) NOT IN ((:qp0, :qp1), (:qp2, :qp3))',
1178
                        [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar']
1179
                    ],
1180
                    [
1181
                        [
1182
                            'in',
1183
                            ['id', 'name'],
1184
                            (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])
1185
                        ],
1186
                        '([[id]], [[name]]) IN (SELECT [[id]], [[name]] FROM [[users]] WHERE [[active]]=:qp0)', [':qp0' => 1]
1187
                    ],
1188
                    [
1189
                        [
1190
                            'not in',
1191
                            ['id', 'name'],
1192
                            (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])
1193
                        ],
1194
                        '([[id]], [[name]]) NOT IN (SELECT [[id]], [[name]] FROM [[users]] WHERE [[active]]=:qp0)',
1195
                        [':qp0' => 1]
1196
                    ],
1197
                ]);
1198
1199
                break;
1200
        }
1201
1202
        /* adjust dbms specific escaping */
1203
        foreach ($conditions as $i => $condition) {
1204
            $conditions[$i][1] = $this->replaceQuotes($condition[1]);
0 ignored issues
show
Bug introduced by
It seems like replaceQuotes() 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

1204
            /** @scrutinizer ignore-call */ 
1205
            $conditions[$i][1] = $this->replaceQuotes($condition[1]);
Loading history...
1205
        }
1206
1207
        return $conditions;
1208
    }
1209
1210
    /**
1211
     * @dataProvider conditionProvider
1212
     *
1213
     * @param ExpressionInterface|array $condition
1214
     * @param string $expected
1215
     * @param array $expectedParams
1216
     */
1217
    public function testBuildCondition($condition, string $expected, array $expectedParams): void
1218
    {
1219
        $db = $this->getConnection();
1220
1221
        $query = (new Query($db))->where($condition);
1222
1223
        [$sql, $params] = $this->getQueryBuilder()->build($query);
1224
1225
        $this->assertEquals('SELECT *' . (empty($expected) ? '' : ' WHERE ' . $this->replaceQuotes($expected)), $sql);
1226
        $this->assertEquals($expectedParams, $params);
1227
    }
1228
1229
    public function filterConditionProvider(): array
1230
    {
1231
        $conditions = [
1232
            /* like */
1233
            [['like', 'name', []], '', []],
1234
            [['not like', 'name', []], '', []],
1235
            [['or like', 'name', []], '', []],
1236
            [['or not like', 'name', []], '', []],
1237
1238
            /* not */
1239
            [['not', ''], '', []],
1240
1241
            /* and */
1242
            [['and', '', ''], '', []],
1243
            [['and', '', 'id=2'], 'id=2', []],
1244
            [['and', 'id=1', ''], 'id=1', []],
1245
            [['and', 'type=1', ['or', '', 'id=2']], '(type=1) AND (id=2)', []],
1246
1247
            /* or */
1248
            [['or', 'id=1', ''], 'id=1', []],
1249
            [['or', 'type=1', ['or', '', 'id=2']], '(type=1) OR (id=2)', []],
1250
1251
            /* between */
1252
            [['between', 'id', 1, null], '', []],
1253
            [['not between', 'id', null, 10], '', []],
1254
1255
            /* in */
1256
            [['in', 'id', []], '', []],
1257
            [['not in', 'id', []], '', []],
1258
1259
            /* simple conditions */
1260
            [['=', 'a', ''], '', []],
1261
            [['>', 'a', ''], '', []],
1262
            [['>=', 'a', ''], '', []],
1263
            [['<', 'a', ''], '', []],
1264
            [['<=', 'a', ''], '', []],
1265
            [['<>', 'a', ''], '', []],
1266
            [['!=', 'a', ''], '', []],
1267
        ];
1268
1269
        /* adjust dbms specific escaping */
1270
        foreach ($conditions as $i => $condition) {
1271
            $conditions[$i][1] = $this->replaceQuotes($condition[1]);
1272
        }
1273
1274
        return $conditions;
1275
    }
1276
1277
    /**
1278
     * @dataProvider filterConditionProvider
1279
     *
1280
     * @param array $condition
1281
     * @param string $expected
1282
     * @param array $expectedParams
1283
     */
1284
    public function testBuildFilterCondition(array $condition, string $expected, array $expectedParams): void
1285
    {
1286
        $query = (new Query($this->getConnection()))->filterWhere($condition);
1287
1288
        [$sql, $params] = $this->getQueryBuilder()->build($query);
1289
1290
        $this->assertEquals('SELECT *' . (empty($expected) ? '' : ' WHERE ' . $this->replaceQuotes($expected)), $sql);
1291
        $this->assertEquals($expectedParams, $params);
1292
    }
1293
1294
    public function buildFromDataProviderTrait(): array
1295
    {
1296
        return [
1297
            ['test t1', '[[test]] [[t1]]'],
1298
            ['test as t1', '[[test]] [[t1]]'],
1299
            ['test AS t1', '[[test]] [[t1]]'],
1300
            ['test', '[[test]]'],
1301
        ];
1302
    }
1303
1304
    /**
1305
     * @dataProvider buildFromDataProvider
1306
     *
1307
     * @param string $table
1308
     * @param string $expected
1309
     *
1310
     * @throws Exception
1311
     */
1312
    public function testBuildFrom(string $table, string $expected): void
1313
    {
1314
        $params = [];
1315
1316
        $sql = $this->getQueryBuilder()->buildFrom([$table], $params);
1317
1318
        $this->assertEquals('FROM ' . $this->replaceQuotes($expected), $sql);
1319
    }
1320
1321
    public function primaryKeysProvider(): array
1322
    {
1323
        $tableName = 'T_constraints_1';
1324
        $name = 'CN_pk';
1325
1326
        return [
1327
            'drop' => [
1328
                "ALTER TABLE {{{$tableName}}} DROP CONSTRAINT [[$name]]",
1329
                static function (QueryBuilder $qb) use ($tableName, $name) {
1330
                    return $qb->dropPrimaryKey($name, $tableName);
1331
                },
1332
            ],
1333
            'add' => [
1334
                "ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] PRIMARY KEY ([[C_id_1]])",
1335
                static function (QueryBuilder $qb) use ($tableName, $name) {
1336
                    return $qb->addPrimaryKey($name, $tableName, 'C_id_1');
1337
                },
1338
            ],
1339
            'add (2 columns)' => [
1340
                "ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] PRIMARY KEY ([[C_id_1]], [[C_id_2]])",
1341
                static function (QueryBuilder $qb) use ($tableName, $name) {
1342
                    return $qb->addPrimaryKey($name, $tableName, 'C_id_1, C_id_2');
1343
                },
1344
            ],
1345
        ];
1346
    }
1347
1348
    /**
1349
     * @dataProvider primaryKeysProvider
1350
     *
1351
     * @param string $sql
1352
     * @param Closure $builder
1353
     */
1354
    public function testAddDropPrimaryKey(string $sql, Closure $builder): void
1355
    {
1356
        $this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false)));
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

1356
        $this->/** @scrutinizer ignore-call */ 
1357
               assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false)));
Loading history...
1357
    }
1358
1359
    public function foreignKeysProvider(): array
1360
    {
1361
        $tableName = 'T_constraints_3';
1362
        $name = 'CN_constraints_3';
1363
        $pkTableName = 'T_constraints_2';
1364
1365
        return [
1366
            'drop' => [
1367
                "ALTER TABLE {{{$tableName}}} DROP CONSTRAINT [[$name]]",
1368
                static function (QueryBuilder $qb) use ($tableName, $name) {
1369
                    return $qb->dropForeignKey($name, $tableName);
1370
                },
1371
            ],
1372
            'add' => [
1373
                "ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] FOREIGN KEY ([[C_fk_id_1]])"
1374
                . " REFERENCES {{{$pkTableName}}} ([[C_id_1]]) ON DELETE CASCADE ON UPDATE CASCADE",
1375
                static function (QueryBuilder $qb) use ($tableName, $name, $pkTableName) {
1376
                    return $qb->addForeignKey(
1377
                        $name,
1378
                        $tableName,
1379
                        'C_fk_id_1',
1380
                        $pkTableName,
1381
                        'C_id_1',
1382
                        'CASCADE',
1383
                        'CASCADE'
1384
                    );
1385
                },
1386
            ],
1387
            'add (2 columns)' => [
1388
                "ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] FOREIGN KEY ([[C_fk_id_1]], [[C_fk_id_2]])"
1389
                . " REFERENCES {{{$pkTableName}}} ([[C_id_1]], [[C_id_2]]) ON DELETE CASCADE ON UPDATE CASCADE",
1390
                static function (QueryBuilder $qb) use ($tableName, $name, $pkTableName) {
1391
                    return $qb->addForeignKey(
1392
                        $name,
1393
                        $tableName,
1394
                        'C_fk_id_1, C_fk_id_2',
1395
                        $pkTableName,
1396
                        'C_id_1, C_id_2',
1397
                        'CASCADE',
1398
                        'CASCADE'
1399
                    );
1400
                },
1401
            ],
1402
        ];
1403
    }
1404
1405
    /**
1406
     * @dataProvider foreignKeysProvider
1407
     *
1408
     * @param string $sql
1409
     * @param Closure $builder
1410
     */
1411
    public function testAddDropForeignKey(string $sql, Closure $builder): void
1412
    {
1413
        $this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false)));
1414
    }
1415
1416
    public function indexesProvider(): array
1417
    {
1418
        $tableName = 'T_constraints_2';
1419
        $name1 = 'CN_constraints_2_single';
1420
        $name2 = 'CN_constraints_2_multi';
1421
1422
        return [
1423
            'drop' => [
1424
                "DROP INDEX [[$name1]] ON {{{$tableName}}}",
1425
                static function (QueryBuilder $qb) use ($tableName, $name1) {
1426
                    return $qb->dropIndex($name1, $tableName);
1427
                },
1428
            ],
1429
            'create' => [
1430
                "CREATE INDEX [[$name1]] ON {{{$tableName}}} ([[C_index_1]])",
1431
                static function (QueryBuilder $qb) use ($tableName, $name1) {
1432
                    return $qb->createIndex($name1, $tableName, 'C_index_1');
1433
                },
1434
            ],
1435
            'create (2 columns)' => [
1436
                "CREATE INDEX [[$name2]] ON {{{$tableName}}} ([[C_index_2_1]], [[C_index_2_2]])",
1437
                static function (QueryBuilder $qb) use ($tableName, $name2) {
1438
                    return $qb->createIndex($name2, $tableName, 'C_index_2_1, C_index_2_2');
1439
                },
1440
            ],
1441
            'create unique' => [
1442
                "CREATE UNIQUE INDEX [[$name1]] ON {{{$tableName}}} ([[C_index_1]])",
1443
                static function (QueryBuilder $qb) use ($tableName, $name1) {
1444
                    return $qb->createIndex($name1, $tableName, 'C_index_1', true);
1445
                },
1446
            ],
1447
            'create unique (2 columns)' => [
1448
                "CREATE UNIQUE INDEX [[$name2]] ON {{{$tableName}}} ([[C_index_2_1]], [[C_index_2_2]])",
1449
                static function (QueryBuilder $qb) use ($tableName, $name2) {
1450
                    return $qb->createIndex($name2, $tableName, 'C_index_2_1, C_index_2_2', true);
1451
                },
1452
            ],
1453
        ];
1454
    }
1455
1456
    /**
1457
     * @dataProvider indexesProvider
1458
     *
1459
     * @param string $sql
1460
     */
1461
    public function testCreateDropIndex(string $sql, Closure $builder): void
1462
    {
1463
        $this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false)));
1464
    }
1465
1466
    public function uniquesProvider(): array
1467
    {
1468
        $tableName1 = 'T_constraints_1';
1469
        $name1 = 'CN_unique';
1470
        $tableName2 = 'T_constraints_2';
1471
        $name2 = 'CN_constraints_2_multi';
1472
1473
        return [
1474
            'drop' => [
1475
                "ALTER TABLE {{{$tableName1}}} DROP CONSTRAINT [[$name1]]",
1476
                static function (QueryBuilder $qb) use ($tableName1, $name1) {
1477
                    return $qb->dropUnique($name1, $tableName1);
1478
                },
1479
            ],
1480
            'add' => [
1481
                "ALTER TABLE {{{$tableName1}}} ADD CONSTRAINT [[$name1]] UNIQUE ([[C_unique]])",
1482
                static function (QueryBuilder $qb) use ($tableName1, $name1) {
1483
                    return $qb->addUnique($name1, $tableName1, 'C_unique');
1484
                },
1485
            ],
1486
            'add (2 columns)' => [
1487
                "ALTER TABLE {{{$tableName2}}} ADD CONSTRAINT [[$name2]] UNIQUE ([[C_index_2_1]], [[C_index_2_2]])",
1488
                static function (QueryBuilder $qb) use ($tableName2, $name2) {
1489
                    return $qb->addUnique($name2, $tableName2, 'C_index_2_1, C_index_2_2');
1490
                },
1491
            ],
1492
        ];
1493
    }
1494
1495
    /**
1496
     * @dataProvider uniquesProvider
1497
     *
1498
     * @param string $sql
1499
     * @param Closure $builder
1500
     */
1501
    public function testAddDropUnique(string $sql, Closure $builder): void
1502
    {
1503
        $this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false)));
1504
    }
1505
1506
    public function checksProvider(): array
1507
    {
1508
        $tableName = 'T_constraints_1';
1509
        $name = 'CN_check';
1510
1511
        return [
1512
            'drop' => [
1513
                "ALTER TABLE {{{$tableName}}} DROP CONSTRAINT [[$name]]",
1514
                static function (QueryBuilder $qb) use ($tableName, $name) {
1515
                    return $qb->dropCheck($name, $tableName);
1516
                },
1517
            ],
1518
            'add' => [
1519
                "ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] CHECK ([[C_not_null]] > 100)",
1520
                static function (QueryBuilder $qb) use ($tableName, $name) {
1521
                    return $qb->addCheck($name, $tableName, '[[C_not_null]] > 100');
1522
                },
1523
            ],
1524
        ];
1525
    }
1526
1527
    /**
1528
     * @dataProvider checksProvider
1529
     *
1530
     * @param string $sql
1531
     * @param Closure $builder
1532
     */
1533
    public function testAddDropCheck(string $sql, Closure $builder): void
1534
    {
1535
        $this->assertSame($this->getConnection(false)->quoteSql($sql), $builder($this->getQueryBuilder(false)));
1536
    }
1537
1538
    public function defaultValuesProvider(): array
1539
    {
1540
        $tableName = 'T_constraints_1';
1541
        $name = 'CN_default';
1542
1543
        return [
1544
            'drop' => [
1545
                "ALTER TABLE {{{$tableName}}} DROP CONSTRAINT [[$name]]",
1546
                static function (QueryBuilder $qb) use ($tableName, $name) {
1547
                    return $qb->dropDefaultValue($name, $tableName);
1548
                },
1549
            ],
1550
            'add' => [
1551
                "ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] DEFAULT 0 FOR [[C_default]]",
1552
                static function (QueryBuilder $qb) use ($tableName, $name) {
1553
                    return $qb->addDefaultValue($name, $tableName, 'C_default', 0);
1554
                },
1555
            ],
1556
        ];
1557
    }
1558
1559
    /**
1560
     * @dataProvider defaultValuesProvider
1561
     *
1562
     * @param string $sql
1563
     * @param Closure $builder
1564
     */
1565
    public function testAddDropDefaultValue(string $sql, Closure $builder): void
1566
    {
1567
        $this->assertSame(
1568
            $this->getConnection(false)->quoteSql($sql),
1569
            $builder($this->getQueryBuilder(false))
1570
        );
1571
    }
1572
1573
    public function existsParamsProvider(): array
1574
    {
1575
        return [
1576
            [
1577
                'exists',
1578
                $this->replaceQuotes(
1579
                    'SELECT [[id]] FROM [[TotalExample]] [[t]] WHERE EXISTS (SELECT [[1]] FROM [[Website]] [[w]])'
1580
                )
1581
            ],
1582
            [
1583
                'not exists',
1584
                $this->replaceQuotes(
1585
                    'SELECT [[id]] FROM [[TotalExample]] [[t]] WHERE NOT EXISTS (SELECT [[1]] FROM [[Website]] [[w]])'
1586
                )
1587
            ],
1588
        ];
1589
    }
1590
1591
    /**
1592
     * @dataProvider existsParamsProvider
1593
     *
1594
     * @param string $cond
1595
     * @param string $expectedQuerySql
1596
     */
1597
    public function testBuildWhereExists(string $cond, string $expectedQuerySql): void
1598
    {
1599
        $db = $this->getConnection();
1600
1601
        $expectedQueryParams = [];
1602
1603
        $subQuery = new Query($db);
1604
1605
        $subQuery->select('1')
1606
            ->from('Website w');
1607
1608
        $query = new Query($db);
1609
1610
        $query->select('id')
1611
            ->from('TotalExample t')
1612
            ->where([$cond, $subQuery]);
1613
1614
        [$actualQuerySql, $actualQueryParams] = $this->getQueryBuilder()->build($query);
1615
1616
        $this->assertEquals($expectedQuerySql, $actualQuerySql);
1617
        $this->assertEquals($expectedQueryParams, $actualQueryParams);
1618
    }
1619
1620
    public function testBuildWhereExistsWithParameters(): void
1621
    {
1622
        $db = $this->getConnection();
1623
1624
        $expectedQuerySql = $this->replaceQuotes(
1625
            'SELECT [[id]] FROM [[TotalExample]] [[t]] WHERE (EXISTS (SELECT [[1]] FROM [[Website]] [[w]]'
1626
            . ' WHERE (w.id = t.website_id) AND (w.merchant_id = :merchant_id))) AND (t.some_column = :some_value)'
1627
        );
1628
1629
        $expectedQueryParams = [':some_value' => 'asd', ':merchant_id' => 6];
1630
1631
        $subQuery = new Query($db);
1632
1633
        $subQuery->select('1')
1634
            ->from('Website w')
1635
            ->where('w.id = t.website_id')
1636
            ->andWhere('w.merchant_id = :merchant_id', [':merchant_id' => 6]);
1637
1638
        $query = new Query($db);
1639
1640
        $query->select('id')
1641
            ->from('TotalExample t')
1642
            ->where(['exists', $subQuery])
1643
            ->andWhere('t.some_column = :some_value', [':some_value' => 'asd']);
1644
1645
        [$actualQuerySql, $queryParams] = $this->getQueryBuilder()->build($query);
1646
1647
        $this->assertEquals($expectedQuerySql, $actualQuerySql);
1648
        $this->assertEquals($expectedQueryParams, $queryParams);
1649
    }
1650
1651
    public function testBuildWhereExistsWithArrayParameters(): void
1652
    {
1653
        $db = $this->getConnection();
1654
1655
        $expectedQuerySql = $this->replaceQuotes(
1656
            'SELECT [[id]] FROM [[TotalExample]] [[t]] WHERE (EXISTS (SELECT [[1]] FROM [[Website]] [[w]]'
1657
            . ' WHERE (w.id = t.website_id) AND (([[w]].[[merchant_id]]=:qp0) AND ([[w]].[[user_id]]=:qp1))))'
1658
            . ' AND ([[t]].[[some_column]]=:qp2)'
1659
        );
1660
1661
        $expectedQueryParams = [':qp0' => 6, ':qp1' => 210, ':qp2' => 'asd'];
1662
1663
        $subQuery = new Query($db);
1664
1665
        $subQuery->select('1')
1666
            ->from('Website w')
1667
            ->where('w.id = t.website_id')
1668
            ->andWhere(['w.merchant_id' => 6, 'w.user_id' => '210']);
1669
1670
        $query = new Query($db);
1671
1672
        $query->select('id')
1673
            ->from('TotalExample t')
1674
            ->where(['exists', $subQuery])
1675
            ->andWhere(['t.some_column' => 'asd']);
1676
1677
        [$actualQuerySql, $queryParams] = $this->getQueryBuilder()->build($query);
1678
1679
        $this->assertEquals($expectedQuerySql, $actualQuerySql);
1680
        $this->assertEquals($expectedQueryParams, $queryParams);
1681
    }
1682
1683
    /**
1684
     * This test contains three select queries connected with UNION and UNION ALL constructions.
1685
     * It could be useful to use "phpunit --group=db --filter testBuildUnion" command for run it.
1686
     */
1687
    public function testBuildUnion(): void
1688
    {
1689
        $db = $this->getConnection();
1690
1691
        $expectedQuerySql = $this->replaceQuotes(
1692
            '(SELECT [[id]] FROM [[TotalExample]] [[t1]] WHERE (w > 0) AND (x < 2)) UNION ( SELECT [[id]]'
1693
            . ' FROM [[TotalTotalExample]] [[t2]] WHERE w > 5 ) UNION ALL ( SELECT [[id]] FROM [[TotalTotalExample]]'
1694
            . ' [[t3]] WHERE w = 3 )'
1695
        );
1696
1697
        $query = new Query($db);
1698
        $secondQuery = new Query($db);
1699
1700
        $secondQuery->select('id')
1701
              ->from('TotalTotalExample t2')
1702
              ->where('w > 5');
1703
1704
        $thirdQuery = new Query($db);
1705
1706
        $thirdQuery->select('id')
1707
              ->from('TotalTotalExample t3')
1708
              ->where('w = 3');
1709
1710
        $query->select('id')
1711
              ->from('TotalExample t1')
1712
              ->where(['and', 'w > 0', 'x < 2'])
1713
              ->union($secondQuery)
1714
              ->union($thirdQuery, true);
1715
1716
        [$actualQuerySql, $queryParams] = $this->getQueryBuilder()->build($query);
1717
1718
        $this->assertEquals($expectedQuerySql, $actualQuerySql);
1719
        $this->assertEquals([], $queryParams);
1720
    }
1721
1722
    public function testBuildWithQuery(): void
1723
    {
1724
        $db = $this->getConnection();
1725
1726
        $expectedQuerySql = $this->replaceQuotes(
1727
            'WITH a1 AS (SELECT [[id]] FROM [[t1]] WHERE expr = 1), a2 AS ((SELECT [[id]] FROM [[t2]]'
1728
            . ' INNER JOIN [[a1]] ON t2.id = a1.id WHERE expr = 2) UNION ( SELECT [[id]] FROM [[t3]] WHERE expr = 3 ))'
1729
            . ' SELECT * FROM [[a2]]'
1730
        );
1731
1732
        $with1Query = (new Query($db))
1733
            ->select('id')
1734
            ->from('t1')
1735
            ->where('expr = 1');
1736
1737
        $with2Query = (new Query($db))
1738
            ->select('id')
1739
            ->from('t2')
1740
            ->innerJoin('a1', 't2.id = a1.id')
1741
            ->where('expr = 2');
1742
1743
        $with3Query = (new Query($db))
1744
            ->select('id')
1745
            ->from('t3')
1746
            ->where('expr = 3');
1747
1748
        $query = (new Query($db))
1749
            ->withQuery($with1Query, 'a1')
1750
            ->withQuery($with2Query->union($with3Query), 'a2')
1751
            ->from('a2');
1752
1753
        [$actualQuerySql, $queryParams] = $this->getQueryBuilder()->build($query);
1754
1755
        $this->assertEquals($expectedQuerySql, $actualQuerySql);
1756
        $this->assertEquals([], $queryParams);
1757
    }
1758
1759
    public function testBuildWithQueryRecursive(): void
1760
    {
1761
        $db = $this->getConnection();
1762
1763
        $expectedQuerySql = $this->replaceQuotes(
1764
            'WITH RECURSIVE a1 AS (SELECT [[id]] FROM [[t1]] WHERE expr = 1) SELECT * FROM [[a1]]'
1765
        );
1766
1767
        $with1Query = (new Query($db))
1768
            ->select('id')
1769
            ->from('t1')
1770
            ->where('expr = 1');
1771
1772
        $query = (new Query($db))
1773
            ->withQuery($with1Query, 'a1', true)
1774
            ->from('a1');
1775
1776
        [$actualQuerySql, $queryParams] = $this->getQueryBuilder()->build($query);
1777
1778
        $this->assertEquals($expectedQuerySql, $actualQuerySql);
1779
        $this->assertEquals([], $queryParams);
1780
    }
1781
1782
    public function testSelectSubquery(): void
1783
    {
1784
        $db = $this->getConnection();
1785
1786
        $subquery = (new Query($db))
1787
            ->select('COUNT(*)')
1788
            ->from('operations')
1789
            ->where('account_id = accounts.id');
1790
1791
        $query = (new Query($db))
1792
            ->select('*')
1793
            ->from('accounts')
1794
            ->addSelect(['operations_count' => $subquery]);
1795
1796
        [$sql, $params] = $this->getQueryBuilder()->build($query);
1797
1798
        $expected = $this->replaceQuotes(
1799
            'SELECT *, (SELECT COUNT(*) FROM [[operations]] WHERE account_id = accounts.id) AS [[operations_count]]'
1800
            . ' FROM [[accounts]]'
1801
        );
1802
1803
        $this->assertEquals($expected, $sql);
1804
        $this->assertEmpty($params);
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

1804
        $this->/** @scrutinizer ignore-call */ 
1805
               assertEmpty($params);
Loading history...
1805
    }
1806
1807
    public function testComplexSelect(): void
1808
    {
1809
        $db = $this->getConnection();
1810
1811
        $query = (new Query($db))
1812
            ->select([
1813
                'ID' => 't.id',
1814
                'gsm.username as GSM',
1815
                'part.Part',
1816
                'Part Cost' => 't.Part_Cost',
1817
                'st_x(location::geometry) as lon',
1818
                new Expression(
1819
                    $this->replaceQuotes(
1820
                        "case t.Status_Id when 1 then 'Acknowledge' when 2 then 'No Action' else 'Unknown Action'"
1821
                        . " END as [[Next Action]]"
1822
                    )
1823
                ),
1824
            ])
1825
            ->from('tablename');
1826
1827
        [$sql, $params] = $this->getQueryBuilder()->build($query);
1828
1829
        $expected = $this->replaceQuotes(
1830
            'SELECT [[t]].[[id]] AS [[ID]], [[gsm]].[[username]] AS [[GSM]], [[part]].[[Part]], [[t]].[[Part_Cost]]'
1831
            . ' AS [[Part Cost]], st_x(location::geometry) AS [[lon]], case t.Status_Id when 1 then \'Acknowledge\''
1832
            . ' when 2 then \'No Action\' else \'Unknown Action\' END as [[Next Action]] FROM [[tablename]]'
1833
        );
1834
1835
        $this->assertEquals($expected, $sql);
1836
        $this->assertEmpty($params);
1837
    }
1838
1839
    public function testSelectExpression(): void
1840
    {
1841
        $db = $this->getConnection();
1842
1843
        $query = (new Query($db))
1844
            ->select(new Expression('1 AS ab'))
1845
            ->from('tablename');
1846
1847
        [$sql, $params] = $this->getQueryBuilder()->build($query);
1848
1849
        $expected = $this->replaceQuotes('SELECT 1 AS ab FROM [[tablename]]');
1850
1851
        $this->assertEquals($expected, $sql);
1852
        $this->assertEmpty($params);
1853
1854
        $query = (new Query($db))
1855
            ->select(new Expression('1 AS ab'))
1856
            ->addSelect(new Expression('2 AS cd'))
1857
            ->addSelect(['ef' => new Expression('3')])
1858
            ->from('tablename');
1859
1860
        [$sql, $params] = $this->getQueryBuilder()->build($query);
1861
1862
        $expected = $this->replaceQuotes('SELECT 1 AS ab, 2 AS cd, 3 AS [[ef]] FROM [[tablename]]');
1863
1864
        $this->assertEquals($expected, $sql);
1865
        $this->assertEmpty($params);
1866
1867
        $query = (new Query($db))
1868
            ->select(new Expression('SUBSTR(name, 0, :len)', [':len' => 4]))
1869
            ->from('tablename');
1870
1871
        [$sql, $params] = $this->getQueryBuilder()->build($query);
1872
1873
        $expected = $this->replaceQuotes('SELECT SUBSTR(name, 0, :len) FROM [[tablename]]');
1874
1875
        $this->assertEquals($expected, $sql);
1876
        $this->assertEquals([':len' => 4], $params);
1877
    }
1878
1879
    /**
1880
     * {@see https://github.com/yiisoft/yii2/issues/10869}
1881
     */
1882
    public function testFromIndexHint(): void
1883
    {
1884
        $db = $this->getConnection();
1885
1886
        $query = (new Query($db))->from([new Expression('{{%user}} USE INDEX (primary)')]);
1887
1888
        [$sql, $params] = $this->getQueryBuilder()->build($query);
1889
1890
        $expected = $this->replaceQuotes('SELECT * FROM {{%user}} USE INDEX (primary)');
1891
1892
        $this->assertEquals($expected, $sql);
1893
        $this->assertEmpty($params);
1894
1895
        $query = (new Query($db))
1896
            ->from([new Expression('{{user}} {{t}} FORCE INDEX (primary) IGNORE INDEX FOR ORDER BY (i1)')])
1897
            ->leftJoin(['p' => 'profile'], 'user.id = profile.user_id USE INDEX (i2)');
1898
1899
        [$sql, $params] = $this->getQueryBuilder()->build($query);
1900
1901
        $expected = $this->replaceQuotes(
1902
            'SELECT * FROM {{user}} {{t}} FORCE INDEX (primary) IGNORE INDEX FOR ORDER BY (i1)'
1903
            . ' LEFT JOIN [[profile]] [[p]] ON user.id = profile.user_id USE INDEX (i2)'
1904
        );
1905
1906
        $this->assertEquals($expected, $sql);
1907
        $this->assertEmpty($params);
1908
    }
1909
1910
    public function testFromSubquery(): void
1911
    {
1912
        $db = $this->getConnection();
1913
1914
        /* query subquery */
1915
        $subquery = (new Query($db))->from('user')->where('account_id = accounts.id');
1916
1917
        $query = (new Query($db))->from(['activeusers' => $subquery]);
1918
1919
        /* SELECT * FROM (SELECT * FROM [[user]] WHERE [[active]] = 1) [[activeusers]]; */
1920
        [$sql, $params] = $this->getQueryBuilder()->build($query);
1921
1922
        $expected = $this->replaceQuotes(
1923
            'SELECT * FROM (SELECT * FROM [[user]] WHERE account_id = accounts.id) [[activeusers]]'
1924
        );
1925
1926
        $this->assertEquals($expected, $sql);
1927
        $this->assertEmpty($params);
1928
1929
        /* query subquery with params */
1930
        $subquery = (new Query($db))->from('user')->where('account_id = :id', ['id' => 1]);
1931
1932
        $query = (new Query($db))->from(['activeusers' => $subquery])->where('abc = :abc', ['abc' => 'abc']);
1933
1934
        /* SELECT * FROM (SELECT * FROM [[user]] WHERE [[active]] = 1) [[activeusers]]; */
1935
        [$sql, $params] = $this->getQueryBuilder()->build($query);
1936
1937
        $expected = $this->replaceQuotes(
1938
            'SELECT * FROM (SELECT * FROM [[user]] WHERE account_id = :id) [[activeusers]] WHERE abc = :abc'
1939
        );
1940
1941
        $this->assertEquals($expected, $sql);
1942
        $this->assertEquals(['id'  => 1, 'abc' => 'abc'], $params);
1943
1944
        /* simple subquery */
1945
        $subquery = '(SELECT * FROM user WHERE account_id = accounts.id)';
1946
1947
        $query = (new Query($db))->from(['activeusers' => $subquery]);
1948
1949
        /* SELECT * FROM (SELECT * FROM [[user]] WHERE [[active]] = 1) [[activeusers]]; */
1950
        [$sql, $params] = $this->getQueryBuilder()->build($query);
1951
1952
        $expected = $this->replaceQuotes(
1953
            'SELECT * FROM (SELECT * FROM user WHERE account_id = accounts.id) [[activeusers]]'
1954
        );
1955
1956
        $this->assertEquals($expected, $sql);
1957
        $this->assertEmpty($params);
1958
    }
1959
1960
    public function testOrderBy(): void
1961
    {
1962
        $db = $this->getConnection();
1963
1964
        /* simple string */
1965
        $query = (new Query($db))
1966
            ->select('*')
1967
            ->from('operations')
1968
            ->orderBy('name ASC, date DESC');
1969
1970
        [$sql, $params] = $this->getQueryBuilder()->build($query);
1971
1972
        $expected = $this->replaceQuotes('SELECT * FROM [[operations]] ORDER BY [[name]], [[date]] DESC');
1973
1974
        $this->assertEquals($expected, $sql);
1975
        $this->assertEmpty($params);
1976
1977
        /* array syntax */
1978
        $query = (new Query($db))
1979
            ->select('*')
1980
            ->from('operations')
1981
            ->orderBy(['name' => SORT_ASC, 'date' => SORT_DESC]);
1982
1983
        [$sql, $params] = $this->getQueryBuilder()->build($query);
1984
1985
        $expected = $this->replaceQuotes('SELECT * FROM [[operations]] ORDER BY [[name]], [[date]] DESC');
1986
1987
        $this->assertEquals($expected, $sql);
1988
        $this->assertEmpty($params);
1989
1990
        /* expression */
1991
        $query = (new Query($db))
1992
            ->select('*')
1993
            ->from('operations')
1994
            ->where('account_id = accounts.id')
1995
            ->orderBy(new Expression('SUBSTR(name, 3, 4) DESC, x ASC'));
1996
1997
        [$sql, $params] = $this->getQueryBuilder()->build($query);
1998
1999
        $expected = $this->replaceQuotes(
2000
            'SELECT * FROM [[operations]] WHERE account_id = accounts.id ORDER BY SUBSTR(name, 3, 4) DESC, x ASC'
2001
        );
2002
2003
        $this->assertEquals($expected, $sql);
2004
        $this->assertEmpty($params);
2005
2006
        /* expression with params */
2007
        $query = (new Query($db))
2008
            ->select('*')
2009
            ->from('operations')
2010
            ->orderBy(new Expression('SUBSTR(name, 3, :to) DESC, x ASC', [':to' => 4]));
2011
2012
        [$sql, $params] = $this->getQueryBuilder()->build($query);
2013
2014
        $expected = $this->replaceQuotes('SELECT * FROM [[operations]] ORDER BY SUBSTR(name, 3, :to) DESC, x ASC');
2015
2016
        $this->assertEquals($expected, $sql);
2017
        $this->assertEquals([':to' => 4], $params);
2018
    }
2019
2020
    public function testGroupBy(): void
2021
    {
2022
        $db = $this->getConnection();
2023
2024
        /* simple string */
2025
        $query = (new Query($db))
2026
            ->select('*')
2027
            ->from('operations')
2028
            ->groupBy('name, date');
2029
2030
        [$sql, $params] = $this->getQueryBuilder()->build($query);
2031
2032
        $expected = $this->replaceQuotes('SELECT * FROM [[operations]] GROUP BY [[name]], [[date]]');
2033
2034
        $this->assertEquals($expected, $sql);
2035
        $this->assertEmpty($params);
2036
2037
        /* array syntax */
2038
        $query = (new Query($db))
2039
            ->select('*')
2040
            ->from('operations')
2041
            ->groupBy(['name', 'date']);
2042
2043
        [$sql, $params] = $this->getQueryBuilder()->build($query);
2044
2045
        $expected = $this->replaceQuotes('SELECT * FROM [[operations]] GROUP BY [[name]], [[date]]');
2046
2047
        $this->assertEquals($expected, $sql);
2048
        $this->assertEmpty($params);
2049
2050
        /* expression */
2051
        $query = (new Query($db))
2052
            ->select('*')
2053
            ->from('operations')
2054
            ->where('account_id = accounts.id')
2055
            ->groupBy(new Expression('SUBSTR(name, 0, 1), x'));
2056
2057
        [$sql, $params] = $this->getQueryBuilder()->build($query);
2058
2059
        $expected = $this->replaceQuotes(
2060
            'SELECT * FROM [[operations]] WHERE account_id = accounts.id GROUP BY SUBSTR(name, 0, 1), x'
2061
        );
2062
2063
        $this->assertEquals($expected, $sql);
2064
        $this->assertEmpty($params);
2065
2066
        /* expression with params */
2067
        $query = (new Query($db))
2068
            ->select('*')
2069
            ->from('operations')
2070
            ->groupBy(new Expression('SUBSTR(name, 0, :to), x', [':to' => 4]));
2071
2072
        [$sql, $params] = $this->getQueryBuilder()->build($query);
2073
2074
        $expected = $this->replaceQuotes('SELECT * FROM [[operations]] GROUP BY SUBSTR(name, 0, :to), x');
2075
2076
        $this->assertEquals($expected, $sql);
2077
        $this->assertEquals([':to' => 4], $params);
2078
    }
2079
2080
    /**
2081
     * @dataProvider insertProvider
2082
     *
2083
     * @param string $table
2084
     * @param ColumnSchema|array $columns
2085
     * @param array $params
2086
     * @param string $expectedSQL
2087
     * @param array $expectedParams
2088
     */
2089
    public function testInsert(string $table, $columns, array $params, string $expectedSQL, array $expectedParams): void
2090
    {
2091
        $actualParams = $params;
2092
2093
        $actualSQL = $this->getQueryBuilder()->insert($table, $columns, $actualParams);
2094
2095
        $this->assertSame($expectedSQL, $actualSQL);
2096
        $this->assertSame($expectedParams, $actualParams);
2097
    }
2098
2099
    /**
2100
     * Dummy test to speed up QB's tests which rely on DB schema.
2101
     */
2102
    public function testInitFixtures(): void
2103
    {
2104
        $this->assertInstanceOf(QueryBuilder::class, $this->getQueryBuilder(true, true));
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

2104
        $this->/** @scrutinizer ignore-call */ 
2105
               assertInstanceOf(QueryBuilder::class, $this->getQueryBuilder(true, true));
Loading history...
2105
    }
2106
2107
    public function upsertProviderTrait(): array
2108
    {
2109
        $db = $this->getConnection();
2110
2111
        return [
2112
            'regular values' => [
2113
                'T_upsert',
2114
                [
2115
                    'email' => '[email protected]',
2116
                    'address' => 'bar {{city}}',
2117
                    'status' => 1,
2118
                    'profile_id' => null,
2119
                ],
2120
                true,
2121
                null,
2122
                [
2123
                    ':qp0' => '[email protected]',
2124
                    ':qp1' => 'bar {{city}}',
2125
                    ':qp2' => 1,
2126
                    ':qp3' => null,
2127
                ],
2128
            ],
2129
            'regular values with update part' => [
2130
                'T_upsert',
2131
                [
2132
                    'email' => '[email protected]',
2133
                    'address' => 'bar {{city}}',
2134
                    'status' => 1,
2135
                    'profile_id' => null,
2136
                ],
2137
                [
2138
                    'address' => 'foo {{city}}',
2139
                    'status' => 2,
2140
                    'orders' => new Expression('T_upsert.orders + 1'),
2141
                ],
2142
                null,
2143
                [
2144
                    ':qp0' => '[email protected]',
2145
                    ':qp1' => 'bar {{city}}',
2146
                    ':qp2' => 1,
2147
                    ':qp3' => null,
2148
                    ':qp4' => 'foo {{city}}',
2149
                    ':qp5' => 2,
2150
                ],
2151
            ],
2152
            'regular values without update part' => [
2153
                'T_upsert',
2154
                [
2155
                    'email' => '[email protected]',
2156
                    'address' => 'bar {{city}}',
2157
                    'status' => 1,
2158
                    'profile_id' => null,
2159
                ],
2160
                false,
2161
                null,
2162
                [
2163
                    ':qp0' => '[email protected]',
2164
                    ':qp1' => 'bar {{city}}',
2165
                    ':qp2' => 1,
2166
                    ':qp3' => null,
2167
                ],
2168
            ],
2169
            'query' => [
2170
                'T_upsert',
2171
                (new Query($db))
2172
                    ->select([
2173
                        'email',
2174
                        'status' => new Expression('2'),
2175
                    ])
2176
                    ->from('customer')
2177
                    ->where(['name' => 'user1'])
2178
                    ->limit(1),
2179
                true,
2180
                null,
2181
                [
2182
                    ':qp0' => 'user1',
2183
                ],
2184
            ],
2185
            'query with update part' => [
2186
                'T_upsert',
2187
                (new Query($db))
2188
                    ->select([
2189
                        'email',
2190
                        'status' => new Expression('2'),
2191
                    ])
2192
                    ->from('customer')
2193
                    ->where(['name' => 'user1'])
2194
                    ->limit(1),
2195
                [
2196
                    'address' => 'foo {{city}}',
2197
                    'status' => 2,
2198
                    'orders' => new Expression('T_upsert.orders + 1'),
2199
                ],
2200
                null,
2201
                [
2202
                    ':qp0' => 'user1',
2203
                    ':qp1' => 'foo {{city}}',
2204
                    ':qp2' => 2,
2205
                ],
2206
            ],
2207
            'query without update part' => [
2208
                'T_upsert',
2209
                (new Query($db))
2210
                    ->select([
2211
                        'email',
2212
                        'status' => new Expression('2'),
2213
                    ])
2214
                    ->from('customer')
2215
                    ->where(['name' => 'user1'])
2216
                    ->limit(1),
2217
                false,
2218
                null,
2219
                [
2220
                    ':qp0' => 'user1',
2221
                ],
2222
            ],
2223
            'values and expressions' => [
2224
                '{{%T_upsert}}',
2225
                [
2226
                    '{{%T_upsert}}.[[email]]' => '[email protected]',
2227
                    '[[ts]]' => new Expression('now()'),
2228
                ],
2229
                true,
2230
                null,
2231
                [
2232
                    ':qp0' => '[email protected]',
2233
                ],
2234
            ],
2235
            'values and expressions with update part' => [
2236
                '{{%T_upsert}}',
2237
                [
2238
                    '{{%T_upsert}}.[[email]]' => '[email protected]',
2239
                    '[[ts]]' => new Expression('now()'),
2240
                ],
2241
                [
2242
                    '[[orders]]' => new Expression('T_upsert.orders + 1'),
2243
                ],
2244
                null,
2245
                [
2246
                    ':qp0' => '[email protected]',
2247
                ],
2248
            ],
2249
            'values and expressions without update part' => [
2250
                '{{%T_upsert}}',
2251
                [
2252
                    '{{%T_upsert}}.[[email]]' => '[email protected]',
2253
                    '[[ts]]' => new Expression('now()'),
2254
                ],
2255
                false,
2256
                null,
2257
                [
2258
                    ':qp0' => '[email protected]',
2259
                ],
2260
            ],
2261
            'query, values and expressions with update part' => [
2262
                '{{%T_upsert}}',
2263
                (new Query($db))
2264
                    ->select([
2265
                        'email' => new Expression(':phEmail', [':phEmail' => '[email protected]']),
2266
                        '[[time]]' => new Expression('now()'),
2267
                    ]),
2268
                [
2269
                    'ts' => 0,
2270
                    '[[orders]]' => new Expression('T_upsert.orders + 1'),
2271
                ],
2272
                null,
2273
                [
2274
                    ':phEmail' => '[email protected]',
2275
                    ':qp1' => 0,
2276
                ],
2277
            ],
2278
            'query, values and expressions without update part' => [
2279
                '{{%T_upsert}}',
2280
                (new Query($db))
2281
                    ->select([
2282
                        'email' => new Expression(':phEmail', [':phEmail' => '[email protected]']),
2283
                        '[[time]]' => new Expression('now()'),
2284
                    ]),
2285
                [
2286
                    'ts' => 0,
2287
                    '[[orders]]' => new Expression('T_upsert.orders + 1'),
2288
                ],
2289
                null,
2290
                [
2291
                    ':phEmail' => '[email protected]',
2292
                    ':qp1' => 0,
2293
                ],
2294
            ],
2295
            'no columns to update' => [
2296
                'T_upsert_1',
2297
                [
2298
                    'a' => 1,
2299
                ],
2300
                false,
2301
                null,
2302
                [
2303
                    ':qp0' => 1,
2304
                ],
2305
            ],
2306
        ];
2307
    }
2308
2309
    /**
2310
     * @depends testInitFixtures
2311
     *
2312
     * @dataProvider upsertProvider
2313
     *
2314
     * @param string $table
2315
     * @param ColumnSchema|array $insertColumns
2316
     * @param array|bool|null $updateColumns
2317
     * @param string|string[] $expectedSQL
2318
     * @param array $expectedParams
2319
     *
2320
     * @throws NotSupportedException
2321
     * @throws Exception
2322
     */
2323
    public function testUpsert(string $table, $insertColumns, $updateColumns, $expectedSQL, array $expectedParams): void
2324
    {
2325
        $db = $this->getConnection();
2326
2327
        $actualParams = [];
2328
2329
        $actualSQL = $this->getQueryBuilder(
2330
            true,
2331
            $db->getDriverName() === 'sqlite'
2332
        )->upsert($table, $insertColumns, $updateColumns, $actualParams);
2333
2334
        if (is_string($expectedSQL)) {
2335
            $this->assertSame($expectedSQL, $actualSQL);
2336
        } else {
2337
            $this->assertContains($actualSQL, $expectedSQL);
0 ignored issues
show
Bug introduced by
It seems like assertContains() 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

2337
            $this->/** @scrutinizer ignore-call */ 
2338
                   assertContains($actualSQL, $expectedSQL);
Loading history...
2338
        }
2339
2340
        if (ArrayHelper::isAssociative($expectedParams)) {
2341
            $this->assertSame($expectedParams, $actualParams);
2342
        } else {
2343
            $this->assertIsOneOf($actualParams, $expectedParams);
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

2343
            $this->/** @scrutinizer ignore-call */ 
2344
                   assertIsOneOf($actualParams, $expectedParams);
Loading history...
2344
        }
2345
    }
2346
2347
    public function batchInsertProviderTrait(): array
2348
    {
2349
        return [
2350
            [
2351
                'customer',
2352
                ['email', 'name', 'address'],
2353
                [['[email protected]', 'silverfire', 'Kyiv {{city}}, Ukraine']],
2354
                $this->replaceQuotes(
2355
                    "INSERT INTO [[customer]] ([[email]], [[name]], [[address]])"
2356
                    . " VALUES ('[email protected]', 'silverfire', 'Kyiv {{city}}, Ukraine')"
2357
                ),
2358
            ],
2359
            'escape-danger-chars' => [
2360
                'customer',
2361
                ['address'],
2362
                [["SQL-danger chars are escaped: '); --"]],
2363
                'expected' => $this->replaceQuotes(
2364
                    "INSERT INTO [[customer]] ([[address]]) VALUES ('SQL-danger chars are escaped: \'); --')"
2365
                ),
2366
            ],
2367
            [
2368
                'customer',
2369
                ['address'],
2370
                [],
2371
                '',
2372
            ],
2373
            [
2374
                'customer',
2375
                [],
2376
                [['no columns passed']],
2377
                $this->replaceQuotes("INSERT INTO [[customer]] () VALUES ('no columns passed')"),
2378
            ],
2379
            'bool-false, bool2-null' => [
2380
                'type',
2381
                ['bool_col', 'bool_col2'],
2382
                [[false, null]],
2383
                'expected' => $this->replaceQuotes(
2384
                    'INSERT INTO [[type]] ([[bool_col]], [[bool_col2]]) VALUES (0, NULL)'
2385
                ),
2386
            ],
2387
            [
2388
                '{{%type}}',
2389
                ['{{%type}}.[[float_col]]', '[[time]]'],
2390
                [[null, new Expression('now()')]],
2391
                'INSERT INTO {{%type}} ({{%type}}.[[float_col]], [[time]]) VALUES (NULL, now())',
2392
            ],
2393
            'bool-false, time-now()' => [
2394
                '{{%type}}',
2395
                ['{{%type}}.[[bool_col]]', '[[time]]'],
2396
                [[false, new Expression('now()')]],
2397
                'expected' => 'INSERT INTO {{%type}} ({{%type}}.[[bool_col]], [[time]]) VALUES (0, now())',
2398
            ],
2399
        ];
2400
    }
2401
2402
    /**
2403
     * @dataProvider batchInsertProvider
2404
     *
2405
     * @param string $table
2406
     * @param array $columns
2407
     * @param array $value
2408
     * @param string $expected
2409
     */
2410
    public function testBatchInsert(string $table, array $columns, array $value, string $expected): void
2411
    {
2412
        $queryBuilder = $this->getQueryBuilder();
2413
2414
        $sql = $queryBuilder->batchInsert($table, $columns, $value);
2415
2416
        $this->assertEquals($expected, $sql);
2417
    }
2418
2419
    public function updateProvider(): array
2420
    {
2421
        return [
2422
            [
2423
                'customer',
2424
                [
2425
                    'status'     => 1,
2426
                    'updated_at' => new Expression('now()'),
2427
                ],
2428
                [
2429
                    'id' => 100,
2430
                ],
2431
                $this->replaceQuotes(
2432
                    'UPDATE [[customer]] SET [[status]]=:qp0, [[updated_at]]=now() WHERE [[id]]=:qp1'
2433
                ),
2434
                [
2435
                    ':qp0' => 1,
2436
                    ':qp1' => 100,
2437
                ],
2438
            ],
2439
        ];
2440
    }
2441
2442
    /**
2443
     * @dataProvider updateProvider
2444
     *
2445
     * @param string $table
2446
     * @param array $columns
2447
     * @param array|string $condition
2448
     * @param string $expectedSQL
2449
     * @param array $expectedParams
2450
     */
2451
    public function testUpdate(
2452
        string $table,
2453
        array $columns,
2454
        $condition,
2455
        string $expectedSQL,
2456
        array $expectedParams
2457
    ): void {
2458
        $actualParams = [];
2459
2460
        $actualSQL = $this->getQueryBuilder()->update($table, $columns, $condition, $actualParams);
2461
2462
        $this->assertSame($expectedSQL, $actualSQL);
2463
        $this->assertSame($expectedParams, $actualParams);
2464
    }
2465
2466
    public function deleteProvider(): array
2467
    {
2468
        return [
2469
            [
2470
                'user',
2471
                [
2472
                    'is_enabled' => false,
2473
                    'power'      => new Expression('WRONG_POWER()'),
2474
                ],
2475
                $this->replaceQuotes('DELETE FROM [[user]] WHERE ([[is_enabled]]=:qp0) AND ([[power]]=WRONG_POWER())'),
2476
                [
2477
                    ':qp0' => false,
2478
                ],
2479
            ],
2480
        ];
2481
    }
2482
2483
    /**
2484
     * @dataProvider deleteProvider
2485
     *
2486
     * @param string $table
2487
     * @param array|string $condition
2488
     * @param string $expectedSQL
2489
     * @param array $expectedParams
2490
     */
2491
    public function testDelete(string $table, $condition, string $expectedSQL, array $expectedParams): void
2492
    {
2493
        $actualParams = [];
2494
2495
        $actualSQL = $this->getQueryBuilder()->delete($table, $condition, $actualParams);
2496
2497
        $this->assertSame($expectedSQL, $actualSQL);
2498
        $this->assertSame($expectedParams, $actualParams);
2499
    }
2500
2501
    public function likeConditionProvider(): array
2502
    {
2503
        $conditions = [
2504
            /* simple like */
2505
            [['like', 'name', 'foo%'], '[[name]] LIKE :qp0', [':qp0' => '%foo\%%']],
2506
            [['not like', 'name', 'foo%'], '[[name]] NOT LIKE :qp0', [':qp0' => '%foo\%%']],
2507
            [['or like', 'name', 'foo%'], '[[name]] LIKE :qp0', [':qp0' => '%foo\%%']],
2508
            [['or not like', 'name', 'foo%'], '[[name]] NOT LIKE :qp0', [':qp0' => '%foo\%%']],
2509
2510
            /* like for many values */
2511
            [
2512
                ['like', 'name', ['foo%', '[abc]']],
2513
                '[[name]] LIKE :qp0 AND [[name]] LIKE :qp1',
2514
                [':qp0' => '%foo\%%', ':qp1' => '%[abc]%']
2515
            ],
2516
            [
2517
                ['not like', 'name', ['foo%', '[abc]']],
2518
                '[[name]] NOT LIKE :qp0 AND [[name]] NOT LIKE :qp1',
2519
                [':qp0' => '%foo\%%', ':qp1' => '%[abc]%']
2520
            ],
2521
            [
2522
                ['or like', 'name', ['foo%', '[abc]']],
2523
                '[[name]] LIKE :qp0 OR [[name]] LIKE :qp1',
2524
                [':qp0' => '%foo\%%', ':qp1' => '%[abc]%']
2525
            ],
2526
            [
2527
                ['or not like', 'name', ['foo%', '[abc]']],
2528
                '[[name]] NOT LIKE :qp0 OR [[name]] NOT LIKE :qp1',
2529
                [':qp0' => '%foo\%%', ':qp1' => '%[abc]%']
2530
            ],
2531
2532
            /* like with Expression */
2533
            [
2534
                ['like', 'name', new Expression('CONCAT("test", name, "%")')],
2535
                '[[name]] LIKE CONCAT("test", name, "%")',
2536
                []
2537
            ],
2538
            [
2539
                ['not like', 'name', new Expression('CONCAT("test", name, "%")')],
2540
                '[[name]] NOT LIKE CONCAT("test", name, "%")',
2541
                []
2542
            ],
2543
            [
2544
                ['or like', 'name', new Expression('CONCAT("test", name, "%")')],
2545
                '[[name]] LIKE CONCAT("test", name, "%")',
2546
                []
2547
            ],
2548
            [
2549
                ['or not like', 'name', new Expression('CONCAT("test", name, "%")')],
2550
                '[[name]] NOT LIKE CONCAT("test", name, "%")',
2551
                []
2552
            ],
2553
            [
2554
                ['like', 'name', [new Expression('CONCAT("test", name, "%")'), '\ab_c']],
2555
                '[[name]] LIKE CONCAT("test", name, "%") AND [[name]] LIKE :qp0',
2556
                [':qp0' => '%\\\ab\_c%']
2557
            ],
2558
            [
2559
                ['not like', 'name', [new Expression('CONCAT("test", name, "%")'), '\ab_c']],
2560
                '[[name]] NOT LIKE CONCAT("test", name, "%") AND [[name]] NOT LIKE :qp0',
2561
                [':qp0' => '%\\\ab\_c%']
2562
            ],
2563
            [
2564
                ['or like', 'name', [new Expression('CONCAT("test", name, "%")'), '\ab_c']],
2565
                '[[name]] LIKE CONCAT("test", name, "%") OR [[name]] LIKE :qp0',
2566
                [':qp0' => '%\\\ab\_c%']
2567
            ],
2568
            [
2569
                ['or not like', 'name', [new Expression('CONCAT("test", name, "%")'), '\ab_c']],
2570
                '[[name]] NOT LIKE CONCAT("test", name, "%") OR [[name]] NOT LIKE :qp0',
2571
                [':qp0' => '%\\\ab\_c%']
2572
            ],
2573
2574
            /**
2575
             * {@see https://github.com/yiisoft/yii2/issues/15630}
2576
             */
2577
            [
2578
                ['like', 'location.title_ru', 'vi%', false],
2579
                '[[location]].[[title_ru]] LIKE :qp0',
2580
                [':qp0' => 'vi%'],
2581
            ],
2582
        ];
2583
2584
        /* adjust dbms specific escaping */
2585
        foreach ($conditions as $i => $condition) {
2586
            $conditions[$i][1] = $this->replaceQuotes($condition[1]);
2587
            if (!empty($this->likeEscapeCharSql)) {
2588
                preg_match_all('/(?P<condition>LIKE.+?)( AND| OR|$)/', $conditions[$i][1], $matches, PREG_SET_ORDER);
2589
                foreach ($matches as $match) {
2590
                    $conditions[$i][1] = str_replace(
2591
                        $match['condition'],
2592
                        $match['condition'] . $this->likeEscapeCharSql,
2593
                        $conditions[$i][1]
2594
                    );
2595
                }
2596
            }
2597
2598
            foreach ($conditions[$i][2] as $name => $value) {
2599
                $conditions[$i][2][$name] = strtr($conditions[$i][2][$name], $this->likeParameterReplacements);
2600
            }
2601
        }
2602
2603
        return $conditions;
2604
    }
2605
2606
    /**
2607
     * @dataProvider likeConditionProvider
2608
     *
2609
     * @param array $condition
2610
     * @param string $expected
2611
     * @param array $expectedParams
2612
     */
2613
    public function testBuildLikeCondition(array $condition, string $expected, array $expectedParams): void
2614
    {
2615
        $db = $this->getConnection();
2616
2617
        $query = (new Query($db))->where($condition);
2618
2619
        [$sql, $params] = $this->getQueryBuilder()->build($query);
2620
2621
        $this->assertEquals('SELECT *' . (empty($expected) ? '' : ' WHERE ' . $this->replaceQuotes($expected)), $sql);
2622
        $this->assertEquals($expectedParams, $params);
2623
    }
2624
2625
    /**
2626
     * {@see https://github.com/yiisoft/yii2/issues/15653}
2627
     */
2628
    public function testIssue15653(): void
2629
    {
2630
        $db = $this->getConnection();
2631
2632
        $query = (new Query($db))
2633
            ->from('admin_user')
2634
            ->where(['is_deleted' => false]);
2635
2636
        $query
2637
            ->where([])
2638
            ->andWhere(['in', 'id', ['1', '0']]);
2639
2640
        [$sql, $params] = $this->getQueryBuilder()->build($query);
2641
2642
        $this->assertSame($this->replaceQuotes('SELECT * FROM [[admin_user]] WHERE [[id]] IN (:qp0, :qp1)'), $sql);
2643
        $this->assertSame([':qp0' => '1', ':qp1' => '0'], $params);
2644
    }
2645
}
2646