Passed
Pull Request — master (#380)
by Wilmer
02:37
created

BaseQueryBuilderProvider::buildWhereExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 13
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 19
rs 9.8333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Tests\Provider;
6
7
use Yiisoft\Db\Expression\Expression;
8
use Yiisoft\Db\QueryBuilder\Conditions\BetweenColumnsCondition;
9
use Yiisoft\Db\QueryBuilder\Conditions\InCondition;
10
use Yiisoft\Db\QueryBuilder\QueryBuilder;
11
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
12
use Yiisoft\Db\Schema\SchemaBuilderTrait;
13
use Yiisoft\Db\Tests\Support\DbHelper;
14
use Yiisoft\Db\Tests\Support\Mock;
15
use Yiisoft\Db\Tests\Support\TraversableObject;
16
17
final class BaseQueryBuilderProvider
18
{
19
    use SchemaBuilderTrait;
20
21
    public function __construct(private Mock $mock)
22
    {
23
        $this->db = $this->mock->connection();
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
24
    }
25
26
    public function addDropChecks(): array
27
    {
28
        $tableName = 'T_constraints_1';
29
        $name = 'CN_check';
30
31
        return [
32
            'drop' => [
33
                "ALTER TABLE {{{$tableName}}} DROP CONSTRAINT [[$name]]",
34
                static fn (QueryBuilderInterface $qb) => $qb->dropCheck($name, $tableName),
35
            ],
36
            'add' => [
37
                "ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] CHECK ([[C_not_null]] > 100)",
38
                static fn (QueryBuilderInterface $qb) => $qb->addCheck($name, $tableName, '[[C_not_null]] > 100'),
39
            ],
40
        ];
41
    }
42
43
    public function addDropForeignKeys(): array
44
    {
45
        $name = 'CN_constraints_3';
46
        $pkTableName = 'T_constraints_2';
47
        $tableName = 'T_constraints_3';
48
49
        return [
50
            'drop' => [
51
                "ALTER TABLE {{{$tableName}}} DROP CONSTRAINT [[$name]]",
52
                static fn (QueryBuilderInterface $qb) => $qb->dropForeignKey($name, $tableName),
53
            ],
54
            'add' => [
55
                "ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] FOREIGN KEY ([[C_fk_id_1]])"
56
                . " REFERENCES {{{$pkTableName}}} ([[C_id_1]]) ON DELETE CASCADE ON UPDATE CASCADE",
57
                static fn (QueryBuilderInterface $qb) => $qb->addForeignKey(
58
                    $name,
59
                    $tableName,
60
                    'C_fk_id_1',
61
                    $pkTableName,
62
                    'C_id_1',
63
                    'CASCADE',
64
                    'CASCADE'
65
                ),
66
            ],
67
            'add (2 columns)' => [
68
                "ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] FOREIGN KEY ([[C_fk_id_1]], [[C_fk_id_2]])"
69
                . " REFERENCES {{{$pkTableName}}} ([[C_id_1]], [[C_id_2]]) ON DELETE CASCADE ON UPDATE CASCADE",
70
                static fn (QueryBuilderInterface $qb) => $qb->addForeignKey(
71
                    $name,
72
                    $tableName,
73
                    'C_fk_id_1, C_fk_id_2',
74
                    $pkTableName,
75
                    'C_id_1, C_id_2',
76
                    'CASCADE',
77
                    'CASCADE'
78
                ),
79
            ],
80
        ];
81
    }
82
83
    public function addDropPrimaryKeys(): array
84
    {
85
        $tableName = 'T_constraints_1';
86
        $name = 'CN_pk';
87
88
        return [
89
            'drop' => [
90
                "ALTER TABLE {{{$tableName}}} DROP CONSTRAINT [[$name]]",
91
                static fn (QueryBuilderInterface $qb) => $qb->dropPrimaryKey($name, $tableName),
92
            ],
93
            'add' => [
94
                "ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] PRIMARY KEY ([[C_id_1]])",
95
                static fn (QueryBuilderInterface $qb) => $qb->addPrimaryKey($name, $tableName, 'C_id_1'),
96
            ],
97
            'add (2 columns)' => [
98
                "ALTER TABLE {{{$tableName}}} ADD CONSTRAINT [[$name]] PRIMARY KEY ([[C_id_1]], [[C_id_2]])",
99
                static fn (QueryBuilderInterface $qb) => $qb->addPrimaryKey($name, $tableName, 'C_id_1, C_id_2'),
100
            ],
101
        ];
102
    }
103
104
    public function addDropUniques(): array
105
    {
106
        $tableName1 = 'T_constraints_1';
107
        $name1 = 'CN_unique';
108
        $tableName2 = 'T_constraints_2';
109
        $name2 = 'CN_constraints_2_multi';
110
111
        return [
112
            'drop' => [
113
                "ALTER TABLE {{{$tableName1}}} DROP CONSTRAINT [[$name1]]",
114
                static fn (QueryBuilderInterface $qb) => $qb->dropUnique($name1, $tableName1),
115
            ],
116
            'add' => [
117
                "ALTER TABLE {{{$tableName1}}} ADD CONSTRAINT [[$name1]] UNIQUE ([[C_unique]])",
118
                static fn (QueryBuilderInterface $qb) => $qb->addUnique($name1, $tableName1, 'C_unique'),
119
            ],
120
            'add (2 columns)' => [
121
                "ALTER TABLE {{{$tableName2}}} ADD CONSTRAINT [[$name2]] UNIQUE ([[C_index_2_1]], [[C_index_2_2]])",
122
                static fn (QueryBuilderInterface $qb) => $qb->addUnique($name2, $tableName2, 'C_index_2_1, C_index_2_2'),
123
            ],
124
        ];
125
    }
126
127
    public function alterColumn(): array
128
    {
129
        return [
130
            [
131
                'foo1',
132
                'bar',
133
                'varchar(255)',
134
                <<<SQL
135
                ALTER TABLE `foo1` CHANGE `bar` `bar` varchar(255)
136
                SQL,
137
            ],
138
            [
139
                'foo1',
140
                'bar',
141
                'SET NOT null',
142
                <<<SQL
143
                ALTER TABLE `foo1` CHANGE `bar` `bar` SET NOT null
144
                SQL,
145
            ],
146
            [
147
                'foo1',
148
                'bar',
149
                'drop default',
150
                <<<SQL
151
                ALTER TABLE `foo1` CHANGE `bar` `bar` drop default
152
                SQL,
153
            ],
154
            [
155
                'foo1',
156
                'bar',
157
                'reset xyz',
158
                <<<SQL
159
                ALTER TABLE `foo1` CHANGE `bar` `bar` reset xyz
160
                SQL,
161
            ],
162
            [
163
                'foo1',
164
                'bar',
165
                $this->string(255),
166
                <<<SQL
167
                ALTER TABLE `foo1` CHANGE `bar` `bar` string(255)
168
                SQL,
169
            ],
170
            [
171
                'foo1',
172
                'bar',
173
                'varchar(255) USING bar::varchar',
174
                <<<SQL
175
                ALTER TABLE `foo1` CHANGE `bar` `bar` varchar(255) USING bar::varchar
176
                SQL,
177
            ],
178
            [
179
                'foo1',
180
                'bar',
181
                'varchar(255) using cast("bar" as varchar)',
182
                <<<SQL
183
                ALTER TABLE `foo1` CHANGE `bar` `bar` varchar(255) using cast("bar" as varchar)
184
                SQL,
185
            ],
186
            [
187
                'foo1',
188
                'bar',
189
                $this->string(255)->notNull(),
190
                <<<SQL
191
                ALTER TABLE `foo1` CHANGE `bar` `bar` string(255) NOT NULL
192
                SQL,
193
            ],
194
            [
195
                'foo1',
196
                'bar',
197
                $this->string(255)->null(),
198
                <<<SQL
199
                ALTER TABLE `foo1` CHANGE `bar` `bar` string(255) NULL DEFAULT NULL
200
                SQL,
201
            ],
202
            [
203
                'foo1',
204
                'bar',
205
                $this->string(255)->null()->defaultValue('xxx'),
206
                <<<SQL
207
                ALTER TABLE `foo1` CHANGE `bar` `bar` string(255) NULL DEFAULT 'xxx'
208
                SQL,
209
            ],
210
            [
211
                'foo1',
212
                'bar',
213
                $this->string(255)->check('char_length(bar) > 5'),
214
                <<<SQL
215
                ALTER TABLE `foo1` CHANGE `bar` `bar` string(255) CHECK (char_length(bar) > 5)
216
                SQL,
217
            ],
218
            [
219
                'foo1',
220
                'bar',
221
                $this->string(255)->defaultValue(''),
222
                <<<SQL
223
                ALTER TABLE `foo1` CHANGE `bar` `bar` string(255) DEFAULT ''
224
                SQL,
225
            ],
226
            [
227
                'foo1',
228
                'bar',
229
                $this->string(255)->defaultValue('AbCdE'),
230
                <<<SQL
231
                ALTER TABLE `foo1` CHANGE `bar` `bar` string(255) DEFAULT 'AbCdE'
232
                SQL,
233
            ],
234
            [
235
                'foo1',
236
                'bar',
237
                $this->timestamp()->defaultExpression('CURRENT_TIMESTAMP'),
238
                <<<SQL
239
                ALTER TABLE `foo1` CHANGE `bar` `bar` timestamp DEFAULT CURRENT_TIMESTAMP
240
                SQL,
241
            ],
242
            [
243
                'foo1',
244
                'bar',
245
                $this->string(30)->unique(),
246
                <<<SQL
247
                ALTER TABLE `foo1` CHANGE `bar` `bar` string(30) UNIQUE
248
                SQL,
249
            ],
250
        ];
251
    }
252
253
    public function batchInsert(): array
254
    {
255
        return [
256
            'simple' => [
257
                'customer',
258
                ['email', 'name', 'address'],
259
                [['[email protected]', 'silverfire', 'Kyiv {{city}}, Ukraine']],
260
                'expected' => DbHelper::replaceQuotes(
261
                    <<<SQL
262
                    INSERT INTO [[customer]] ([[email]], [[name]], [[address]]) VALUES (:qp0, :qp1, :qp2)
263
                    SQL,
264
                    $this->mock->getDriverName(),
265
                ),
266
                [
267
                    ':qp0' => '[email protected]',
268
                    ':qp1' => 'silverfire',
269
                    ':qp2' => 'Kyiv {{city}}, Ukraine',
270
                ],
271
            ],
272
            'escape-danger-chars' => [
273
                'customer',
274
                ['address'],
275
                [["SQL-danger chars are escaped: '); --"]],
276
                'expected' => DbHelper::replaceQuotes(
277
                    <<<SQL
278
                    INSERT INTO [[customer]] ([[address]]) VALUES (:qp0)
279
                    SQL,
280
                    $this->mock->getDriverName(),
281
                ),
282
                [
283
                    ':qp0' => "SQL-danger chars are escaped: '); --",
284
                ],
285
            ],
286
            'customer2' => [
287
                'customer',
288
                ['address'],
289
                [],
290
                '',
291
            ],
292
            'customer3' => [
293
                'customer',
294
                [],
295
                [['no columns passed']],
296
                'expected' => DbHelper::replaceQuotes(
297
                    <<<SQL
298
                    INSERT INTO [[customer]] () VALUES (:qp0)
299
                    SQL,
300
                    $this->mock->getDriverName(),
301
                ),
302
                [
303
                    ':qp0' => 'no columns passed',
304
                ],
305
            ],
306
            'bool-false, bool2-null' => [
307
                'type',
308
                ['bool_col', 'bool_col2'],
309
                [[false, null]],
310
                'expected' => DbHelper::replaceQuotes(
311
                    <<<SQL
312
                    INSERT INTO [[type]] ([[bool_col]], [[bool_col2]]) VALUES (:qp0, :qp1)
313
                    SQL,
314
                    $this->mock->getDriverName(),
315
                ),
316
                [
317
                    ':qp0' => false,
318
                    ':qp1' => null,
319
                ],
320
            ],
321
            'wrong' => [
322
                '{{%type}}',
323
                ['{{%type}}.[[float_col]]', '[[time]]'],
324
                [[null, new Expression('now()')], [null, new Expression('now()')]],
325
                'expected' => 'INSERT INTO {{%type}} ({{%type}}.[[float_col]], [[time]]) VALUES (:qp0, now()), (:qp1, now())',
326
                [
327
                    ':qp0' => null,
328
                    ':qp1' => null,
329
                ],
330
            ],
331
            'bool-false, time-now()' => [
332
                '{{%type}}',
333
                ['{{%type}}.[[bool_col]]', '[[time]]'],
334
                [[false, new Expression('now()')]],
335
                'expected' => 'INSERT INTO {{%type}} ({{%type}}.[[bool_col]], [[time]]) VALUES (:qp0, now())',
336
                [
337
                    ':qp0' => false,
338
                ],
339
            ],
340
        ];
341
    }
342
343
    public function buildConditions(): array
344
    {
345
        $conditions = [
346
            /* empty values */
347
            [['like', 'name', []], '0=1', []],
348
            [['not like', 'name', []], '', []],
349
            [['or like', 'name', []], '0=1', []],
350
            [['or not like', 'name', []], '', []],
351
352
            /* not */
353
            [['not', ''], '', []],
354
            [['not', 'name'], 'NOT (name)', []],
355
            [
356
                [
357
                    'not',
358
                    $this->mock->query()->select('exists')->from('some_table'),
359
                ],
360
                'NOT ((SELECT [[exists]] FROM [[some_table]]))', [],
361
            ],
362
363
            /* and */
364
            [['and', '', ''], '', []],
365
            [['and', '', 'id=2'], 'id=2', []],
366
            [['and', 'id=1', 'id=2'], '(id=1) AND (id=2)', []],
367
            [['and', 'type=1', ['or', 'id=1', 'id=2']], '(type=1) AND ((id=1) OR (id=2))', []],
368
            [['and', 'id=1', new Expression('id=:qp0', [':qp0' => 2])], '(id=1) AND (id=:qp0)', [':qp0' => 2]],
369
            [
370
                [
371
                    'and',
372
                    ['expired' => false],
373
                    $this->mock->query()->select('count(*) > 1')->from('queue'),
374
                ],
375
                '([[expired]]=:qp0) AND ((SELECT count(*) > 1 FROM [[queue]]))',
376
                [':qp0' => false],
377
            ],
378
379
            /* or */
380
            [['or', 'id=1', 'id=2'], '(id=1) OR (id=2)', []],
381
            [['or', 'type=1', ['or', 'id=1', 'id=2']], '(type=1) OR ((id=1) OR (id=2))', []],
382
            [['or', 'type=1', new Expression('id=:qp0', [':qp0' => 1])], '(type=1) OR (id=:qp0)', [':qp0' => 1]],
383
384
            /* between */
385
            [['between', 'id', 1, 10], '[[id]] BETWEEN :qp0 AND :qp1', [':qp0' => 1, ':qp1' => 10]],
386
            [['not between', 'id', 1, 10], '[[id]] NOT BETWEEN :qp0 AND :qp1', [':qp0' => 1, ':qp1' => 10]],
387
            [
388
                ['between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), new Expression('NOW()')],
389
                '[[date]] BETWEEN (NOW() - INTERVAL 1 MONTH) AND NOW()',
390
                [],
391
            ],
392
            [
393
                ['between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), 123],
394
                '[[date]] BETWEEN (NOW() - INTERVAL 1 MONTH) AND :qp0',
395
                [':qp0' => 123],
396
            ],
397
            [
398
                ['not between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), new Expression('NOW()')],
399
                '[[date]] NOT BETWEEN (NOW() - INTERVAL 1 MONTH) AND NOW()',
400
                [],
401
            ],
402
            [
403
                ['not between', 'date', new Expression('(NOW() - INTERVAL 1 MONTH)'), 123],
404
                '[[date]] NOT BETWEEN (NOW() - INTERVAL 1 MONTH) AND :qp0',
405
                [':qp0' => 123],
406
            ],
407
            [
408
                new BetweenColumnsCondition('2018-02-11', 'BETWEEN', 'create_time', 'update_time'),
409
                ':qp0 BETWEEN [[create_time]] AND [[update_time]]',
410
                [':qp0' => '2018-02-11'],
411
            ],
412
            [
413
                new BetweenColumnsCondition('2018-02-11', 'NOT BETWEEN', 'NOW()', 'update_time'),
414
                ':qp0 NOT BETWEEN NOW() AND [[update_time]]',
415
                [':qp0' => '2018-02-11'],
416
            ],
417
            [
418
                new BetweenColumnsCondition(new Expression('NOW()'), 'BETWEEN', 'create_time', 'update_time'),
419
                'NOW() BETWEEN [[create_time]] AND [[update_time]]',
420
                [],
421
            ],
422
            [
423
                new BetweenColumnsCondition(new Expression('NOW()'), 'NOT BETWEEN', 'create_time', 'update_time'),
424
                'NOW() NOT BETWEEN [[create_time]] AND [[update_time]]',
425
                [],
426
            ],
427
            [
428
                new BetweenColumnsCondition(
429
                    new Expression('NOW()'),
430
                    'NOT BETWEEN',
431
                    $this->mock->query()->select('min_date')->from('some_table'),
432
                    'max_date'
433
                ),
434
                'NOW() NOT BETWEEN (SELECT [[min_date]] FROM [[some_table]]) AND [[max_date]]',
435
                [],
436
            ],
437
            [
438
                new BetweenColumnsCondition(
439
                    new Expression('NOW()'),
440
                    'NOT BETWEEN',
441
                    new Expression('min_date'),
442
                    $this->mock->query()->select('max_date')->from('some_table'),
443
                ),
444
                'NOW() NOT BETWEEN min_date AND (SELECT [[max_date]] FROM [[some_table]])',
445
                [],
446
            ],
447
448
            /* in */
449
            [
450
                ['in', 'id', [1, 2, $this->mock->query()->select('three')->from('digits')]],
451
                '[[id]] IN (:qp0, :qp1, (SELECT [[three]] FROM [[digits]]))',
452
                [':qp0' => 1, ':qp1' => 2],
453
            ],
454
            [
455
                ['not in', 'id', [1, 2, 3]],
456
                '[[id]] NOT IN (:qp0, :qp1, :qp2)',
457
                [':qp0' => 1, ':qp1' => 2, ':qp2' => 3],
458
            ],
459
            [
460
                ['in', 'id', $this->mock->query()->select('id')->from('users')->where(['active' => 1])],
461
                '[[id]] IN (SELECT [[id]] FROM [[users]] WHERE [[active]]=:qp0)',
462
                [':qp0' => 1],
463
            ],
464
            [
465
                ['not in', 'id', $this->mock->query()->select('id')->from('users')->where(['active' => 1])],
466
                '[[id]] NOT IN (SELECT [[id]] FROM [[users]] WHERE [[active]]=:qp0)',
467
                [':qp0' => 1],
468
            ],
469
            [['in', 'id', 1], '[[id]]=:qp0', [':qp0' => 1]],
470
            [['in', 'id', [1]], '[[id]]=:qp0', [':qp0' => 1]],
471
            [['in', 'id', new TraversableObject([1])], '[[id]]=:qp0', [':qp0' => 1]],
472
            'composite in' => [
473
                ['in', ['id', 'name'], [['id' => 1, 'name' => 'oy']]],
474
                '([[id]], [[name]]) IN ((:qp0, :qp1))',
475
                [':qp0' => 1, ':qp1' => 'oy'],
476
            ],
477
            'composite in (just one column)' => [
478
                ['in', ['id'], [['id' => 1, 'name' => 'Name1'], ['id' => 2, 'name' => 'Name2']]],
479
                '[[id]] IN (:qp0, :qp1)',
480
                [':qp0' => 1, ':qp1' => 2],
481
            ],
482
            'composite in using array objects (just one column)' => [
483
                [
484
                    'in',
485
                    new TraversableObject(['id']),
486
                    new TraversableObject([['id' => 1, 'name' => 'Name1'], ['id' => 2, 'name' => 'Name2']]),
487
                ],
488
                '[[id]] IN (:qp0, :qp1)',
489
                [':qp0' => 1, ':qp1' => 2],
490
            ],
491
492
            /* in using array objects. */
493
            [['id' => new TraversableObject([1, 2])], '[[id]] IN (:qp0, :qp1)', [':qp0' => 1, ':qp1' => 2]],
494
            [
495
                ['in', 'id', new TraversableObject([1, 2, 3])],
496
                '[[id]] IN (:qp0, :qp1, :qp2)',
497
                [':qp0' => 1, ':qp1' => 2, ':qp2' => 3],
498
            ],
499
500
            /* in using array objects containing null value */
501
            [['in', 'id', new TraversableObject([1, null])], '[[id]]=:qp0 OR [[id]] IS NULL', [':qp0' => 1]],
502
            [
503
                ['in', 'id', new TraversableObject([1, 2, null])],
504
                '[[id]] IN (:qp0, :qp1) OR [[id]] IS NULL', [':qp0' => 1, ':qp1' => 2],
505
            ],
506
507
            /* not in using array object containing null value */
508
            [
509
                ['not in', 'id', new TraversableObject([1, null])],
510
                '[[id]]<>:qp0 AND [[id]] IS NOT NULL', [':qp0' => 1],
511
            ],
512
            [
513
                ['not in', 'id', new TraversableObject([1, 2, null])],
514
                '[[id]] NOT IN (:qp0, :qp1) AND [[id]] IS NOT NULL',
515
                [':qp0' => 1, ':qp1' => 2],
516
            ],
517
518
            /* in using array object containing only null value */
519
            [['in', 'id', new TraversableObject([null])], '[[id]] IS NULL', []],
520
            [['not in', 'id', new TraversableObject([null])], '[[id]] IS NOT NULL', []],
521
            'composite in using array objects' => [
522
                [
523
                    'in',
524
                    new TraversableObject(['id', 'name']),
525
                    new TraversableObject([['id' => 1, 'name' => 'oy'], ['id' => 2, 'name' => 'yo']]),
526
                ],
527
                '([[id]], [[name]]) IN ((:qp0, :qp1), (:qp2, :qp3))',
528
                [':qp0' => 1, ':qp1' => 'oy', ':qp2' => 2, ':qp3' => 'yo'],
529
            ],
530
531
            /* in object conditions */
532
            [new InCondition('id', 'in', 1), '[[id]]=:qp0', [':qp0' => 1]],
533
            [new InCondition('id', 'in', [1]), '[[id]]=:qp0', [':qp0' => 1]],
534
            [new InCondition('id', 'not in', 1), '[[id]]<>:qp0', [':qp0' => 1]],
535
            [new InCondition('id', 'not in', [1]), '[[id]]<>:qp0', [':qp0' => 1]],
536
            [new InCondition('id', 'in', [1, 2]), '[[id]] IN (:qp0, :qp1)', [':qp0' => 1, ':qp1' => 2]],
537
            [new InCondition('id', 'not in', [1, 2]), '[[id]] NOT IN (:qp0, :qp1)', [':qp0' => 1, ':qp1' => 2]],
538
            [new InCondition([], 'in', 1), '0=1', []],
539
            [new InCondition([], 'in', [1]), '0=1', []],
540
            [new InCondition(['id', 'name'], 'in', []), '0=1', []],
541
            [
542
                new InCondition(
543
                    ['id'],
544
                    'in',
545
                    $this->mock->query()->select('id')->from('users')->where(['active' => 1]),
546
                ),
547
                '([[id]]) IN (SELECT [[id]] FROM [[users]] WHERE [[active]]=:qp0)',
548
                [':qp0' => 1],
549
            ],
550
            [
551
                new InCondition(['id', 'name'], 'in', [['id' => 1]]),
552
                '([[id]], [[name]]) IN ((:qp0, NULL))',
553
                [':qp0' => 1],
554
            ],
555
            [
556
                new InCondition(['id', 'name'], 'in', [['name' => 'oy']]),
557
                '([[id]], [[name]]) IN ((NULL, :qp0))',
558
                [':qp0' => 'oy'],
559
            ],
560
            [
561
                new InCondition(['id', 'name'], 'in', [['id' => 1, 'name' => 'oy']]),
562
                '([[id]], [[name]]) IN ((:qp0, :qp1))',
563
                [':qp0' => 1, ':qp1' => 'oy'],
564
            ],
565
566
            /* exists */
567
            [
568
                [
569
                    'exists',
570
                    $this->mock->query()->select('id')->from('users')->where(['active' => 1]),
571
                ],
572
                'EXISTS (SELECT [[id]] FROM [[users]] WHERE [[active]]=:qp0)',
573
                [':qp0' => 1],
574
            ],
575
            [
576
                ['not exists', $this->mock->query()->select('id')->from('users')->where(['active' => 1])],
577
                'NOT EXISTS (SELECT [[id]] FROM [[users]] WHERE [[active]]=:qp0)', [':qp0' => 1],
578
            ],
579
580
            /* simple conditions */
581
            [['=', 'a', 'b'], '[[a]] = :qp0', [':qp0' => 'b']],
582
            [['>', 'a', 1], '[[a]] > :qp0', [':qp0' => 1]],
583
            [['>=', 'a', 'b'], '[[a]] >= :qp0', [':qp0' => 'b']],
584
            [['<', 'a', 2], '[[a]] < :qp0', [':qp0' => 2]],
585
            [['<=', 'a', 'b'], '[[a]] <= :qp0', [':qp0' => 'b']],
586
            [['<>', 'a', 3], '[[a]] <> :qp0', [':qp0' => 3]],
587
            [['!=', 'a', 'b'], '[[a]] != :qp0', [':qp0' => 'b']],
588
            [
589
                ['>=', 'date', new Expression('DATE_SUB(NOW(), INTERVAL 1 MONTH)')],
590
                '[[date]] >= DATE_SUB(NOW(), INTERVAL 1 MONTH)',
591
                [],
592
            ],
593
            [
594
                ['>=', 'date', new Expression('DATE_SUB(NOW(), INTERVAL :month MONTH)', [':month' => 2])],
595
                '[[date]] >= DATE_SUB(NOW(), INTERVAL :month MONTH)',
596
                [':month' => 2],
597
            ],
598
            [
599
                ['=', 'date', $this->mock->query()->select('max(date)')->from('test')->where(['id' => 5])],
600
                '[[date]] = (SELECT max(date) FROM [[test]] WHERE [[id]]=:qp0)',
601
                [':qp0' => 5],
602
            ],
603
            [['=', 'a', null], '[[a]] = NULL', []],
604
605
            /* operand1 is Expression */
606
            [
607
                ['=', new Expression('date'), '2019-08-01'],
608
                'date = :qp0',
609
                [':qp0' => '2019-08-01'],
610
            ],
611
            [
612
                ['=', $this->mock->query()->select('COUNT(*)')->from('test')->where(['id' => 6]), 0],
613
                '(SELECT COUNT(*) FROM [[test]] WHERE [[id]]=:qp0) = :qp1',
614
                [':qp0' => 6, ':qp1' => 0],
615
            ],
616
617
            /* hash condition */
618
            [['a' => 1, 'b' => 2], '([[a]]=:qp0) AND ([[b]]=:qp1)', [':qp0' => 1, ':qp1' => 2]],
619
            [
620
                ['a' => new Expression('CONCAT(col1, col2)'), 'b' => 2],
621
                '([[a]]=CONCAT(col1, col2)) AND ([[b]]=:qp0)',
622
                [':qp0' => 2],
623
            ],
624
            [['a' => null], '[[a]] IS NULL', []],
625
626
            /* direct conditions */
627
            ['a = CONCAT(col1, col2)', 'a = CONCAT(col1, col2)', []],
628
            [
629
                new Expression('a = CONCAT(col1, :param1)', ['param1' => 'value1']),
630
                'a = CONCAT(col1, :param1)',
631
                ['param1' => 'value1'],
632
            ],
633
634
            /* Expression with params as operand of 'not' */
635
            [
636
                ['not', new Expression('any_expression(:a)', [':a' => 1])],
637
                'NOT (any_expression(:a))', [':a' => 1],
638
            ],
639
            [new Expression('NOT (any_expression(:a))', [':a' => 1]), 'NOT (any_expression(:a))', [':a' => 1]],
640
641
            /* like */
642
            [['like', 'a', 'b'], '[[a]] LIKE :qp0', [':qp0' => '%b%']],
643
            [
644
                ['like', 'a', new Expression(':qp0', [':qp0' => '%b%'])],
645
                '[[a]] LIKE :qp0',
646
                [':qp0' => '%b%'],
647
            ],
648
            [['like', new Expression('CONCAT(col1, col2)'), 'b'], 'CONCAT(col1, col2) LIKE :qp0', [':qp0' => '%b%']],
649
        ];
650
651
        return $this->replaceQuotes($conditions);
652
    }
653
654
    public function buildFilterCondition(): array
655
    {
656
        $conditions = [
657
            /* like */
658
            [['like', 'name', []], '', []],
659
            [['not like', 'name', []], '', []],
660
            [['or like', 'name', []], '', []],
661
            [['or not like', 'name', []], '', []],
662
663
            /* not */
664
            [['not', ''], '', []],
665
666
            /* and */
667
            [['and', '', ''], '', []],
668
            [['and', '', 'id=2'], 'id=2', []],
669
            [['and', 'id=1', ''], 'id=1', []],
670
            [['and', 'type=1', ['or', '', 'id=2']], '(type=1) AND (id=2)', []],
671
672
            /* or */
673
            [['or', 'id=1', ''], 'id=1', []],
674
            [['or', 'type=1', ['or', '', 'id=2']], '(type=1) OR (id=2)', []],
675
676
            /* between */
677
            [['between', 'id', 1, null], '', []],
678
            [['not between', 'id', null, 10], '', []],
679
680
            /* in */
681
            [['in', 'id', []], '', []],
682
            [['not in', 'id', []], '', []],
683
684
            /* simple conditions */
685
            [['=', 'a', ''], '', []],
686
            [['>', 'a', ''], '', []],
687
            [['>=', 'a', ''], '', []],
688
            [['<', 'a', ''], '', []],
689
            [['<=', 'a', ''], '', []],
690
            [['<>', 'a', ''], '', []],
691
            [['!=', 'a', ''], '', []],
692
        ];
693
694
        return $this->replaceQuotes($conditions);
695
    }
696
697
    public function buildFrom(): array
698
    {
699
        return [
700
            ['test t1', '[[test]] [[t1]]'],
701
            ['test as t1', '[[test]] [[t1]]'],
702
            ['test AS t1', '[[test]] [[t1]]'],
703
            ['test', '[[test]]'],
704
        ];
705
    }
706
707
    public function buildWhereExists(): array
708
    {
709
        return [
710
            [
711
                'exists',
712
                DbHelper::replaceQuotes(
713
                    <<<SQL
714
                    SELECT [[id]] FROM [[TotalExample]] [[t]] WHERE EXISTS (SELECT [[1]] FROM [[Website]] [[w]])
715
                    SQL,
716
                    $this->mock->getDriverName(),
717
                ),
718
            ],
719
            [
720
                'not exists',
721
                DbHelper::replaceQuotes(
722
                    <<<SQL
723
                    SELECT [[id]] FROM [[TotalExample]] [[t]] WHERE NOT EXISTS (SELECT [[1]] FROM [[Website]] [[w]])
724
                    SQL,
725
                    $this->mock->getDriverName(),
726
                ),
727
            ],
728
        ];
729
    }
730
731
    public function createDropIndex(): array
732
    {
733
        $tableName = 'T_constraints_2';
734
        $name1 = 'CN_constraints_2_single';
735
        $name2 = 'CN_constraints_2_multi';
736
737
        return [
738
            'drop' => [
739
                <<<SQL
740
                DROP INDEX [[$name1]] ON {{{$tableName}}}
741
                SQL,
742
                static fn (QueryBuilderInterface $qb) => $qb->dropIndex($name1, $tableName),
743
            ],
744
            'create' => [
745
                <<<SQL
746
                CREATE INDEX [[$name1]] ON {{{$tableName}}} ([[C_index_1]])
747
                SQL,
748
                static fn (QueryBuilderInterface $qb) => $qb->createIndex($name1, $tableName, 'C_index_1'),
749
            ],
750
            'create (2 columns)' => [
751
                <<<SQL
752
                CREATE INDEX [[$name2]] ON {{{$tableName}}} ([[C_index_2_1]], [[C_index_2_2]])
753
                SQL,
754
                static fn (QueryBuilderInterface $qb) => $qb->createIndex(
755
                    $name2,
756
                    $tableName,
757
                    'C_index_2_1,
758
                    C_index_2_2',
759
                ),
760
            ],
761
            'create unique' => [
762
                <<<SQL
763
                CREATE UNIQUE INDEX [[$name1]] ON {{{$tableName}}} ([[C_index_1]])
764
                SQL,
765
                static fn (QueryBuilderInterface $qb) => $qb->createIndex(
766
                    $name1,
767
                    $tableName,
768
                    'C_index_1',
769
                    QueryBuilder::INDEX_UNIQUE,
770
                ),
771
            ],
772
            'create unique (2 columns)' => [
773
                <<<SQL
774
                CREATE UNIQUE INDEX [[$name2]] ON {{{$tableName}}} ([[C_index_2_1]], [[C_index_2_2]])
775
                SQL,
776
                static fn (QueryBuilderInterface $qb) => $qb->createIndex(
777
                    $name2,
778
                    $tableName,
779
                    'C_index_2_1,
780
                    C_index_2_2',
781
                    QueryBuilder::INDEX_UNIQUE,
782
                ),
783
            ],
784
        ];
785
    }
786
787
    public function delete(): array
788
    {
789
        return [
790
            [
791
                'user',
792
                [
793
                    'is_enabled' => false,
794
                    'power' => new Expression('WRONG_POWER()'),
795
                ],
796
                DbHelper::replaceQuotes(
797
                    <<<SQL
798
                    DELETE FROM [[user]] WHERE ([[is_enabled]]=:qp0) AND ([[power]]=WRONG_POWER())
799
                    SQL,
800
                    $this->mock->getDriverName(),
801
                ),
802
                [
803
                    ':qp0' => false,
804
                ],
805
            ],
806
        ];
807
    }
808
809
    public function insert(): array
810
    {
811
        return [
812
            'regular-values' => [
813
                'customer',
814
                [
815
                    'email' => '[email protected]',
816
                    'name' => 'silverfire',
817
                    'address' => 'Kyiv {{city}}, Ukraine',
818
                    'is_active' => false,
819
                    'related_id' => null,
820
                ],
821
                [],
822
                <<<SQL
823
                INSERT INTO `customer` (`email`, `name`, `address`, `is_active`, `related_id`) VALUES (:qp0, :qp1, :qp2, :qp3, :qp4)
824
                SQL,
825
                [
826
                    ':qp0' => '[email protected]',
827
                    ':qp1' => 'silverfire',
828
                    ':qp2' => 'Kyiv {{city}}, Ukraine',
829
                    ':qp3' => false,
830
                    ':qp4' => null,
831
                ],
832
            ],
833
            'params-and-expressions' => [
834
                '{{%type}}',
835
                [
836
                    '{{%type}}.[[related_id]]' => null,
837
                    '[[time]]' => new Expression('now()'),
838
                ],
839
                [],
840
                <<<SQL
841
                INSERT INTO {{%type}} ({{%type}}.[[related_id]], [[time]]) VALUES (:qp0, now())
842
                SQL,
843
                [
844
                    ':qp0' => null,
845
                ],
846
            ],
847
            'carry passed params' => [
848
                'customer',
849
                [
850
                    'email' => '[email protected]',
851
                    'name' => 'sergeymakinen',
852
                    'address' => '{{city}}',
853
                    'is_active' => false,
854
                    'related_id' => null,
855
                    'col' => new Expression('CONCAT(:phFoo, :phBar)', [':phFoo' => 'foo']),
856
                ],
857
                [':phBar' => 'bar'],
858
                <<<SQL
859
                INSERT INTO `customer` (`email`, `name`, `address`, `is_active`, `related_id`, `col`) VALUES (:qp1, :qp2, :qp3, :qp4, :qp5, CONCAT(:phFoo, :phBar))
860
                SQL,
861
                [
862
                    ':phBar' => 'bar',
863
                    ':qp1' => '[email protected]',
864
                    ':qp2' => 'sergeymakinen',
865
                    ':qp3' => '{{city}}',
866
                    ':qp4' => false,
867
                    ':qp5' => null,
868
                    ':phFoo' => 'foo',
869
                ],
870
            ],
871
            'carry passed params (query)' => [
872
                'customer',
873
                $this->mock
874
                    ->query()
875
                    ->select([
876
                        'email',
877
                        'name',
878
                        'address',
879
                        'is_active',
880
                        'related_id',
881
                    ])
882
                    ->from('customer')
883
                    ->where([
884
                        'email' => '[email protected]',
885
                        'name' => 'sergeymakinen',
886
                        'address' => '{{city}}',
887
                        'is_active' => false,
888
                        'related_id' => null,
889
                        'col' => new Expression('CONCAT(:phFoo, :phBar)', [':phFoo' => 'foo']),
890
                    ]),
891
                [':phBar' => 'bar'],
892
                <<<SQL
893
                INSERT INTO `customer` (`email`, `name`, `address`, `is_active`, `related_id`) SELECT `email`, `name`, `address`, `is_active`, `related_id` FROM `customer` WHERE (`email`=:qp1) AND (`name`=:qp2) AND (`address`=:qp3) AND (`is_active`=:qp4) AND (`related_id` IS NULL) AND (`col`=CONCAT(:phFoo, :phBar))
894
                SQL,
895
                [
896
                    ':phBar' => 'bar',
897
                    ':qp1' => '[email protected]',
898
                    ':qp2' => 'sergeymakinen',
899
                    ':qp3' => '{{city}}',
900
                    ':qp4' => false,
901
                    ':phFoo' => 'foo',
902
                ],
903
            ],
904
        ];
905
    }
906
907
    public function insertEx(): array
908
    {
909
        return [
910
            'regular-values' => [
911
                'customer',
912
                [
913
                    'email' => '[email protected]',
914
                    'name' => 'silverfire',
915
                    'address' => 'Kyiv {{city}}, Ukraine',
916
                    'is_active' => false,
917
                    'related_id' => null,
918
                ],
919
                [],
920
                DbHelper::replaceQuotes(
921
                    <<<SQL
922
                    INSERT INTO `customer` (`email`, `name`, `address`, `is_active`, `related_id`) VALUES (:qp0, :qp1, :qp2, :qp3, :qp4)
923
                    SQL,
924
                    $this->mock->getDriverName(),
925
                ),
926
                [
927
                    ':qp0' => '[email protected]',
928
                    ':qp1' => 'silverfire',
929
                    ':qp2' => 'Kyiv {{city}}, Ukraine',
930
                    ':qp3' => false,
931
                    ':qp4' => null,
932
                ],
933
            ],
934
            'params-and-expressions' => [
935
                '{{%type}}',
936
                [
937
                    '{{%type}}.[[related_id]]' => null,
938
                    '[[time]]' => new Expression('now()'),
939
                ],
940
                [],
941
                <<<SQL
942
                INSERT INTO {{%type}} ({{%type}}.[[related_id]], [[time]]) VALUES (:qp0, now())
943
                SQL,
944
                [
945
                    ':qp0' => null,
946
                ],
947
            ],
948
            'carry passed params' => [
949
                'customer',
950
                [
951
                    'email' => '[email protected]',
952
                    'name' => 'sergeymakinen',
953
                    'address' => '{{city}}',
954
                    'is_active' => false,
955
                    'related_id' => null,
956
                    'col' => new Expression('CONCAT(:phFoo, :phBar)', [':phFoo' => 'foo']),
957
                ],
958
                [':phBar' => 'bar'],
959
                DbHelper::replaceQuotes(
960
                    <<<SQL
961
                    INSERT INTO `customer` (`email`, `name`, `address`, `is_active`, `related_id`, `col`) VALUES (:qp1, :qp2, :qp3, :qp4, :qp5, CONCAT(:phFoo, :phBar))
962
                    SQL,
963
                    $this->mock->getDriverName(),
964
                ),
965
                [
966
                    ':phBar' => 'bar',
967
                    ':qp1' => '[email protected]',
968
                    ':qp2' => 'sergeymakinen',
969
                    ':qp3' => '{{city}}',
970
                    ':qp4' => false,
971
                    ':qp5' => null,
972
                    ':phFoo' => 'foo',
973
                ],
974
            ],
975
            'carry passed params (query)' => [
976
                'customer',
977
                $this->mock->query()
978
                    ->select([
979
                        'email',
980
                        'name',
981
                        'address',
982
                        'is_active',
983
                        'related_id',
984
                    ])
985
                    ->from('customer')
986
                    ->where([
987
                        'email' => '[email protected]',
988
                        'name' => 'sergeymakinen',
989
                        'address' => '{{city}}',
990
                        'is_active' => false,
991
                        'related_id' => null,
992
                        'col' => new Expression('CONCAT(:phFoo, :phBar)', [':phFoo' => 'foo']),
993
                    ]),
994
                [':phBar' => 'bar'],
995
                DbHelper::replaceQuotes(
996
                    <<<SQL
997
                    INSERT INTO `customer` (`email`, `name`, `address`, `is_active`, `related_id`) SELECT `email`, `name`, `address`, `is_active`, `related_id` FROM `customer` WHERE (`email`=:qp1) AND (`name`=:qp2) AND (`address`=:qp3) AND (`is_active`=:qp4) AND (`related_id` IS NULL) AND (`col`=CONCAT(:phFoo, :phBar))
998
                    SQL,
999
                    $this->mock->getDriverName(),
1000
                ),
1001
                [
1002
                    ':phBar' => 'bar',
1003
                    ':qp1' => '[email protected]',
1004
                    ':qp2' => 'sergeymakinen',
1005
                    ':qp3' => '{{city}}',
1006
                    ':qp4' => false,
1007
                    ':phFoo' => 'foo',
1008
                ],
1009
            ],
1010
        ];
1011
    }
1012
1013
    public function update(): array
1014
    {
1015
        return [
1016
            [
1017
                'customer',
1018
                [
1019
                    'status' => 1,
1020
                    'updated_at' => new Expression('now()'),
1021
                ],
1022
                [
1023
                    'id' => 100,
1024
                ],
1025
                DbHelper::replaceQuotes(
1026
                    <<<SQL
1027
                    UPDATE [[customer]] SET [[status]]=:qp0, [[updated_at]]=now() WHERE [[id]]=:qp1
1028
                    SQL,
1029
                    $this->mock->getDriverName(),
1030
                ),
1031
                [
1032
                    ':qp0' => 1,
1033
                    ':qp1' => 100,
1034
                ],
1035
            ],
1036
        ];
1037
    }
1038
1039
    private function replaceQuotes(array $conditions): array
1040
    {
1041
        /* adjust dbms specific escaping */
1042
        foreach ($conditions as $i => $condition) {
1043
            $conditions[$i][1] = DbHelper::replaceQuotes($condition[1], $this->mock->getDriverName());
1044
        }
1045
1046
        return $conditions;
1047
    }
1048
}
1049