Passed
Pull Request — master (#380)
by Wilmer
04:10 queued 01:20
created

BaseQueryBuilderProvider::replaceQuotes()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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