Passed
Push — master ( 4bc1c4...2778f9 )
by William
03:23 queued 11s
created

CreateStatementTest::testBuilderWithComments()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 8
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpMyAdmin\SqlParser\Tests\Builder;
6
7
use PhpMyAdmin\SqlParser\Components\CreateDefinition;
8
use PhpMyAdmin\SqlParser\Components\DataType;
9
use PhpMyAdmin\SqlParser\Components\Expression;
10
use PhpMyAdmin\SqlParser\Components\Key;
11
use PhpMyAdmin\SqlParser\Components\OptionsArray;
12
use PhpMyAdmin\SqlParser\Components\ParameterDefinition;
13
use PhpMyAdmin\SqlParser\Parser;
14
use PhpMyAdmin\SqlParser\Statements\CreateStatement;
15
use PhpMyAdmin\SqlParser\Tests\TestCase;
16
use PhpMyAdmin\SqlParser\TokensList;
17
18
class CreateStatementTest extends TestCase
19
{
20
    public function testBuilder(): void
21
    {
22
        $parser = new Parser('CREATE USER "jeffrey"@"localhost" IDENTIFIED BY "mypass"');
23
        $stmt = $parser->statements[0];
24
        $this->assertEquals(
25
            'CREATE USER "jeffrey"@"localhost" IDENTIFIED BY "mypass"',
26
            $stmt->build()
27
        );
28
    }
29
30
    public function testBuilderDatabase(): void
31
    {
32
        // CREATE DATABASE ...
33
        $parser = new Parser(
34
            'CREATE DATABASE `mydb` ' .
35
            'DEFAULT CHARACTER SET = utf8 DEFAULT COLLATE = utf8_general_ci'
36
        );
37
        $stmt = $parser->statements[0];
38
39
        $this->assertEquals(
40
            'CREATE DATABASE `mydb` ' .
41
            'DEFAULT CHARACTER SET=utf8 DEFAULT COLLATE=utf8_general_ci',
42
            $stmt->build()
43
        );
44
45
        // CREATE SCHEMA ...
46
        $parser = new Parser('CREATE SCHEMA `mydb` DEFAULT CHARACTER SET = utf8 DEFAULT COLLATE = utf8_general_ci');
47
        $stmt = $parser->statements[0];
48
49
        $this->assertEquals(
50
            'CREATE SCHEMA `mydb` ' .
51
            'DEFAULT CHARACTER SET=utf8 DEFAULT COLLATE=utf8_general_ci',
52
            $stmt->build()
53
        );
54
    }
55
56
    public function testBuilderDefaultInt(): void
57
    {
58
        $parser = new Parser(
59
            'CREATE TABLE IF NOT EXISTS t1 (' .
60
            " c1 int(11) NOT NULL DEFAULT '0' COMMENT 'xxx'" .
61
            ') ENGINE=MyISAM'
62
        );
63
        $stmt = $parser->statements[0];
64
65
        $this->assertEquals(
66
            "CREATE TABLE IF NOT EXISTS t1 (\n" .
67
            "  `c1` int(11) NOT NULL DEFAULT '0' COMMENT 'xxx'\n" .
68
            ') ENGINE=MyISAM',
69
            $stmt->build()
70
        );
71
    }
72
73
    public function testBuilderWithComments(): void
74
    {
75
        $parser = new Parser('CREATE TABLE tab1 (`col1` TIMESTAMP /*!40100 DEFAULT NULL */)');
76
        $stmt = $parser->statements[0];
77
        $this->assertEquals(
78
            // TODO: fix with https://github.com/phpmyadmin/sql-parser/issues/256
79
            "CREATE TABLE tab1 (\n  `col1` timestamp DEFAULT NULL\n) ",
80
            $stmt->build()
81
        );
82
    }
83
84
    public function testBuilderCompressed(): void
85
    {
86
        $parser = new Parser('CREATE TABLE users ( user_id int ) PAGE_COMPRESSED=1 PAGE_COMPRESSION_LEVEL=9;');
87
        $stmt = $parser->statements[0];
88
        $this->assertEquals(
89
            "CREATE TABLE users (\n  `user_id` int\n) PAGE_COMPRESSED=1 PAGE_COMPRESSION_LEVEL=9",
90
            $stmt->build()
91
        );
92
    }
93
94
    public function testBuilderCollate(): void
95
    {
96
        $parser = new Parser(
97
            'CREATE TABLE IF NOT EXISTS t1 (' .
98
            " c1 varchar(11) NOT NULL DEFAULT '0' COLLATE 'utf8_czech_ci' COMMENT 'xxx'" .
99
            ') ENGINE=MyISAM'
100
        );
101
        $stmt = $parser->statements[0];
102
103
        $this->assertEquals(
104
            "CREATE TABLE IF NOT EXISTS t1 (\n" .
105
            "  `c1` varchar(11) NOT NULL DEFAULT '0' COLLATE 'utf8_czech_ci' COMMENT 'xxx'\n" .
106
            ') ENGINE=MyISAM',
107
            $stmt->build()
108
        );
109
    }
110
111
    public function testBuilderDefaultComment(): void
112
    {
113
        $parser = new Parser(
114
            'CREATE TABLE `wp_audio` (' .
115
            " `somedata` int(11) DEFAULT NULL COMMENT 'ma data', " .
116
            " `someinfo` int(11) DEFAULT NULL COMMENT 'ma info' " .
117
            ' )'
118
        );
119
        $stmt = $parser->statements[0];
120
121
        $this->assertEquals(
122
            "CREATE TABLE `wp_audio` (\n" .
123
            "  `somedata` int(11) DEFAULT NULL COMMENT 'ma data',\n" .
124
            "  `someinfo` int(11) DEFAULT NULL COMMENT 'ma info'\n" .
125
            ') ',
126
            $stmt->build()
127
        );
128
    }
129
130
    public function testBuilderTable(): void
131
    {
132
        /* Assertion 1 */
133
        $stmt = new CreateStatement();
134
135
        $stmt->name = new Expression('', 'test', '');
136
        $stmt->options = new OptionsArray(['TABLE']);
137
        $stmt->fields = [
138
            new CreateDefinition(
139
                'id',
140
                new OptionsArray(['NOT NULL', 'AUTO_INCREMENT']),
141
                new DataType('INT', [11], new OptionsArray(['UNSIGNED']))
142
            ),
143
            new CreateDefinition(
144
                '',
145
                null,
146
                new Key('', [['name' => 'id']], 'PRIMARY KEY')
147
            ),
148
        ];
149
150
        $this->assertEquals(
151
            "CREATE TABLE `test` (\n" .
152
            "  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,\n" .
153
            "  PRIMARY KEY (`id`)\n" .
154
            ') ',
155
            $stmt->build()
156
        );
157
158
        /* Assertion 2 */
159
        $query =
160
            "CREATE TABLE `jos_core_acl_aro` (\n" .
161
            "  `id` int(11) NOT NULL,\n" .
162
            "  `section_value` varchar(240) NOT NULL DEFAULT '0',\n" .
163
            "  `value` varchar(240) NOT NULL DEFAULT '',\n" .
164
            "  `order_value` int(11) NOT NULL DEFAULT '0',\n" .
165
            "  `name` varchar(255) NOT NULL DEFAULT '',\n" .
166
            "  `hidden` int(11) NOT NULL DEFAULT '0',\n" .
167
            "  PRIMARY KEY (`id`),\n" .
168
            "  UNIQUE KEY `jos_section_value_value_aro` (`section_value`(100),`value`(15)) USING BTREE,\n" .
169
            "  KEY `jos_gacl_hidden_aro` (`hidden`)\n" .
170
            ') ENGINE=InnoDB DEFAULT CHARSET=latin1';
171
        $parser = new Parser($query);
172
        $this->assertEquals($query, $parser->statements[0]->build());
173
174
        /* Assertion 3 */
175
        $query = 'CREATE TABLE `table_copy` LIKE `old_table`';
176
        $parser = new Parser($query);
177
        $this->assertEquals($query, $parser->statements[0]->build());
178
179
        /* Assertion 4 */
180
        $query =
181
            "CREATE TABLE `aa` (\n" .
182
            "  `id` int(11) NOT NULL,\n" .
183
            "  `rTime` timestamp(3) NOT NULL DEFAULT '0000-00-00 00:00:00.000' ON UPDATE CURRENT_TIMESTAMP(3),\n" .
184
            "  PRIMARY KEY (`id`)\n" .
185
            ') ENGINE=InnoDB DEFAULT CHARSET=latin1';
186
        $parser = new Parser($query);
187
        $this->assertEquals($query, $parser->statements[0]->build());
188
189
        /* Assertion 5 */
190
        $parser = new Parser(
191
            'CREATE table table_name WITH' .
192
            ' cte (col1) AS ( SELECT 1 UNION ALL SELECT 2 )' .
193
            ' SELECT col1 FROM cte'
194
        );
195
        $stmt = $parser->statements[0];
196
197
        $this->assertEquals(
198
            'CREATE TABLE table_name WITH' .
199
            ' cte(col1) AS (SELECT 1 UNION ALL SELECT 2)' .
200
            ' SELECT col1 FROM cte',
201
            $stmt->build()
202
        );
203
    }
204
205
    public function testBuilderPartitions(): void
206
    {
207
        /* Assertion 1 */
208
        $query = 'CREATE TABLE ts (' . "\n"
209
            . '  `id` int,' . "\n"
210
            . '  `purchased` date' . "\n"
211
            . ') ' . "\n"
212
            . 'PARTITION BY RANGE(YEAR(purchased))' . "\n"
213
            . 'PARTITIONS 3' . "\n"
214
            . 'SUBPARTITION BY HASH(TO_DAYS(purchased))' . "\n"
215
            . 'SUBPARTITIONS 2' . "\n"
216
            . '(' . "\n"
217
            . 'PARTITION p0 VALUES LESS THAN (1990)  (' . "\n"
218
            . 'SUBPARTITION s0,' . "\n"
219
            . 'SUBPARTITION s1' . "\n"
220
            . '),' . "\n"
221
            . 'PARTITION p1 VALUES LESS THAN (2000)  (' . "\n"
222
            . 'SUBPARTITION s2,' . "\n"
223
            . 'SUBPARTITION s3' . "\n"
224
            . '),' . "\n"
225
            . 'PARTITION p2 VALUES LESS THAN MAXVALUE  (' . "\n"
226
            . 'SUBPARTITION s4,' . "\n"
227
            . 'SUBPARTITION s5' . "\n"
228
            . ')' . "\n"
229
            . ')';
230
        $parser = new Parser($query);
231
        $this->assertEquals($query, $parser->statements[0]->build());
232
233
        /* Assertion 2 */
234
        $query = 'CREATE TABLE `pma_test` (' . "\n"
235
            . '  `test_id` int(32) NOT NULL,' . "\n"
236
            . '  `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP' . "\n"
237
            . ') ENGINE=InnoDB DEFAULT CHARSET=utf8' . "\n"
238
            . 'PARTITION BY RANGE (test_id)' . "\n"
239
            . '(' . "\n"
240
            . 'PARTITION p0 VALUES LESS THAN (250000) ENGINE=InnoDB,' . "\n"
241
            . 'PARTITION p1 VALUES LESS THAN (500000) ENGINE=InnoDB,' . "\n"
242
            . 'PARTITION p2 VALUES LESS THAN (750000) ENGINE=InnoDB,' . "\n"
243
            . 'PARTITION p3 VALUES LESS THAN (1000000) ENGINE=InnoDB,' . "\n"
244
            . 'PARTITION p4 VALUES LESS THAN (1250000) ENGINE=InnoDB,' . "\n"
245
            . 'PARTITION p5 VALUES LESS THAN (1500000) ENGINE=InnoDB,' . "\n"
246
            . 'PARTITION p6 VALUES LESS THAN MAXVALUE ENGINE=InnoDB' . "\n"
247
            . ')';
248
        $parser = new Parser($query);
249
        $this->assertEquals($query, $parser->statements[0]->build());
250
    }
251
252
    /**
253
     * @return string[][]
254
     */
255
    public static function partitionQueriesProvider(): array
256
    {
257
        return [
258
            [
259
                'subparts' => <<<'EOT'
260
CREATE TABLE `ts` (
261
  `id` int(11) DEFAULT NULL,
262
  `purchased` date DEFAULT NULL
263
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
264
PARTITION BY RANGE (YEAR(purchased))
265
SUBPARTITION BY HASH (TO_DAYS(purchased))
266
(
267
PARTITION p0 VALUES LESS THAN (1990)  (
268
SUBPARTITION s0 ENGINE=InnoDB,
269
SUBPARTITION s1 ENGINE=InnoDB
270
),
271
PARTITION p1 VALUES LESS THAN (2000)  (
272
SUBPARTITION s2 ENGINE=InnoDB,
273
SUBPARTITION s3 ENGINE=InnoDB
274
),
275
PARTITION p2 VALUES LESS THAN MAXVALUE  (
276
SUBPARTITION s4 ENGINE=InnoDB,
277
SUBPARTITION s5 ENGINE=InnoDB
278
)
279
)
280
EOT
281
            ,
282
            ],
283
            [
284
                'parts' => <<<'EOT'
285
CREATE TABLE ptest (
286
  `event_date` date NOT NULL
287
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC
288
PARTITION BY HASH (TO_DAYS(event_date))
289
(
290
PARTITION p0 ENGINE=InnoDB,
291
PARTITION p1 ENGINE=InnoDB,
292
PARTITION p2 ENGINE=InnoDB,
293
PARTITION p3 ENGINE=InnoDB,
294
PARTITION p4 ENGINE=InnoDB
295
)
296
EOT
297
            ,
298
            ],
299
        ];
300
    }
301
302
    /**
303
     * @dataProvider partitionQueriesProvider
304
     */
305
    public function testBuilderPartitionsEngine(string $query): void
306
    {
307
        $parser = new Parser($query);
308
        $stmt = $parser->statements[0];
309
310
        $this->assertEquals($query, $stmt->build());
311
    }
312
313
    public function testBuilderView(): void
314
    {
315
        $parser = new Parser(
316
            'CREATE OR REPLACE VIEW xviewmytable  AS SELECT mytable.id '
317
            . 'AS id, mytable.personid AS personid FROM mytable '
318
            . 'WHERE (mytable.birth > \'1990-01-19\') GROUP BY mytable.personid  ;'
319
        );
320
        $stmt = $parser->statements[0];
321
322
        $this->assertEquals(
323
            'CREATE OR REPLACE VIEW xviewmytable  AS SELECT mytable.id '
324
            . 'AS `id`, mytable.personid AS `personid` FROM mytable '
325
            . 'WHERE (mytable.birth > \'1990-01-19\') GROUP BY mytable.personid ',
326
            $stmt->build()
327
        );
328
329
        $parser = new Parser(
330
            'CREATE VIEW myView (vid, vfirstname) AS ' .
331
            'SELECT id, first_name FROM employee WHERE id = 1'
332
        );
333
        $stmt = $parser->statements[0];
334
335
        $this->assertEquals(
336
            'CREATE VIEW myView (vid, vfirstname) AS  ' .
337
            'SELECT id, first_name FROM employee WHERE id = 1 ',
338
            $stmt->build()
339
        );
340
341
        $parser = new Parser(
342
            'CREATE OR REPLACE VIEW myView (vid, vfirstname) AS ' .
343
            'SELECT id, first_name FROM employee WHERE id = 1'
344
        );
345
        $stmt = $parser->statements[0];
346
347
        $this->assertEquals(
348
            'CREATE OR REPLACE VIEW myView (vid, vfirstname) AS  ' .
349
            'SELECT id, first_name FROM employee WHERE id = 1 ',
350
            $stmt->build()
351
        );
352
353
        // Assert the builder can build wrong syntax select expressions
354
        $parser = new Parser(
355
            'CREATE OR REPLACE VIEW myView (vid, vfirstname) AS ' .
356
            'SELECT id, first_name, FROMzz employee WHERE id = 1'
357
        );
358
        $stmt = $parser->statements[0];
359
        $this->assertEquals(
360
            'CREATE OR REPLACE VIEW myView (vid, vfirstname) AS  ' .
361
            'SELECT id, first_name, FROMzz employee WHERE id = 1 ',
362
            $stmt->build()
363
        );
364
365
        $parser = new Parser(
366
            'CREATE OR REPLACE VIEW myView (vid, vfirstname) AS ' .
367
            'SELECT id, first_name, FROMzz employee WHERE id = 1 ' .
368
            'UNION ' .
369
            'SELECT id, first_name, FROMzz employee WHERE id = 2 '
370
        );
371
        $stmt = $parser->statements[0];
372
373
        $this->assertEquals(
374
            'CREATE OR REPLACE VIEW myView (vid, vfirstname) AS  ' .
375
            'SELECT id, first_name, FROMzz employee WHERE id = 1 ' .
376
            'UNION ' .
377
            'SELECT id, first_name, FROMzz employee WHERE id = 2  ',
378
            $stmt->build()
379
        );
380
381
        $parser = new Parser('CREATE VIEW `view_programlevelpartner`  AS SELECT `p`.`country_id`'
382
        . 'AS `country_id` FROM `program_level_partner` `p` ORDER BY `p`.`id` asc');
383
        $stmt = $parser->statements[0];
384
        $this->assertEquals(
385
            'CREATE VIEW `view_programlevelpartner`  AS SELECT `p`.`country_id`'
386
            . ' AS `country_id` FROM `program_level_partner` AS `p` ORDER BY `p`.`id` ASC ',
387
            $stmt->build()
388
        );
389
390
        $parser = new Parser('CREATE VIEW `view_zg_bycountry`  AS '
391
        . 'SELECT `d`.`zg_id` FROM `view_zg_value` AS `d` GROUP BY `d`.`ind_id`;');
392
        $stmt = $parser->statements[0];
393
        $this->assertEquals(
394
            'CREATE VIEW `view_zg_bycountry`  AS '
395
            . 'SELECT `d`.`zg_id` FROM `view_zg_value` AS `d` GROUP BY `d`.`ind_id` ',
396
            $stmt->build()
397
        );
398
399
        $parser = new Parser('CREATE view view_name AS WITH  aa(col1)'
400
        . ' AS ( SELECT 1 UNION ALL SELECT 2 ) SELECT col1 FROM cte AS `d` ');
401
        $stmt = $parser->statements[0];
402
        $this->assertEquals(
403
            'CREATE view view_name  AS WITH aa(col1)'
404
            . ' AS (SELECT 1 UNION ALL SELECT 2) SELECT col1 FROM cte AS `d`  ',
405
            $stmt->build()
406
        );
407
    }
408
409
    public function testBuilderViewComplex(): void
410
    {
411
        $parser = new Parser(
412
            'CREATE VIEW withclause AS' . "\n"
413
            . "\n"
414
            . 'WITH cte AS (' . "\n"
415
                . 'SELECT p.name, p.shape' . "\n"
416
                . 'FROM gis_all as p' . "\n"
417
            . ')' . "\n"
418
            . "\n"
419
            . 'SELECT cte.*' . "\n"
420
            . 'FROM cte' . "\n"
421
            . 'CROSS JOIN gis_all;'
422
        );
423
        $stmt = $parser->statements[0];
424
425
        $this->assertEquals(
426
            'CREATE VIEW withclause  AS '
427
            . 'WITH cte AS ('
428
                . 'SELECT p.name, p.shape '
429
                . 'FROM gis_all AS `p`'
430
            . ') '
431
            . 'SELECT cte.* '
432
            . 'FROM cte '
433
            . 'CROSS JOIN gis_all ',
434
            $stmt->build()
435
        );
436
        $parser = new Parser(
437
            'CREATE VIEW withclause2 AS' . "\n"
438
            . "\n"
439
            . 'WITH cte AS (' . "\n"
440
                . "\t" . 'SELECT p.name, p.shape' . "\n"
441
                . "\t" . 'FROM gis_all as p' . "\n"
442
            . '), cte2 AS (' . "\n"
443
                . "\t" . 'SELECT p.name as n2, p.shape as sh2' . "\n"
444
                . "\t" . 'FROM gis_all as p' . "\n"
445
            . ')' . "\n"
446
            . "\n"
447
            . 'SELECT cte.*,cte2.*' . "\n"
448
            . 'FROM cte,cte2' . "\n"
449
            . 'CROSS JOIN gis_all;'
450
        );
451
        $stmt = $parser->statements[0];
452
453
        $this->assertEquals(
454
            'CREATE VIEW withclause2  AS '
455
            . 'WITH cte AS ('
456
                . 'SELECT p.name, p.shape'
457
                . ' FROM gis_all AS `p`'
458
            . '), cte2 AS ('
459
                . 'SELECT p.name AS `n2`, p.shape AS `sh2`'
460
                . ' FROM gis_all AS `p`'
461
            . ')'
462
            . ' SELECT cte.*, cte2.* '
463
            . 'FROM cte, cte2'
464
            . ' CROSS JOIN gis_all ',
465
            $stmt->build()
466
        );
467
    }
468
469
    public function testBuilderCreateProcedure(): void
470
    {
471
        $parser = new Parser(
472
            'CREATE DEFINER=`root`@`%`'
473
            . ' PROCEDURE `test2`(IN `_var` INT) DETERMINISTIC'
474
            . ' MODIFIES SQL DATA SELECT _var'
475
        );
476
477
        /** @var CreateStatement $stmt */
478
        $stmt = $parser->statements[0];
479
480
        $this->assertSame(
481
            'CREATE DEFINER=`root`@`%`'
482
            . ' PROCEDURE `test2` (IN `_var` INT)  DETERMINISTIC'
483
            . ' MODIFIES SQL DATA SELECT _var',
484
            $stmt->build()
485
        );
486
487
        $parser = new Parser(
488
            'CREATE DEFINER=`root`@`%`'
489
            . ' PROCEDURE `test2`(IN `_var` INT) NOT DETERMINISTIC NO SQL'
490
            . ' SQL SECURITY INVOKER NO SQL SQL SECURITY INVOKER SELECT _var'
491
        );
492
493
        /** @var CreateStatement $stmt */
494
        $stmt = $parser->statements[0];
495
496
        $this->assertSame(
497
            'CREATE DEFINER=`root`@`%`'
498
            . ' PROCEDURE `test2` (IN `_var` INT)  NOT DETERMINISTIC NO SQL'
499
            . ' SQL SECURITY INVOKER NO SQL SQL SECURITY INVOKER SELECT _var',
500
            $stmt->build()
501
        );
502
503
        $this->assertFalse($stmt->entityOptions->isEmpty());
0 ignored issues
show
Bug introduced by
The method isEmpty() does not exist on null. ( Ignorable by Annotation )

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

503
        $this->assertFalse($stmt->entityOptions->/** @scrutinizer ignore-call */ isEmpty());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
504
        $this->assertFalse($stmt->options->isEmpty());
0 ignored issues
show
Bug introduced by
The method isEmpty() does not exist on null. ( Ignorable by Annotation )

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

504
        $this->assertFalse($stmt->options->/** @scrutinizer ignore-call */ isEmpty());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
505
        $this->assertSame(
506
            'DEFINER=`root`@`%` PROCEDURE',
507
            $stmt->options->__toString()
508
        );
509
510
        $this->assertSame(
511
            '`test2`',
512
            $stmt->name->__toString()
0 ignored issues
show
Bug introduced by
The method __toString() does not exist on null. ( Ignorable by Annotation )

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

512
            $stmt->name->/** @scrutinizer ignore-call */ 
513
                         __toString()

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
513
        );
514
515
        $this->assertSame(
516
            '(IN `_var` INT)',
517
            ParameterDefinition::build($stmt->parameters)
518
        );
519
520
        $this->assertSame(
521
            'NOT DETERMINISTIC NO SQL SQL SECURITY INVOKER NO SQL SQL SECURITY INVOKER',
522
            $stmt->entityOptions->__toString()
523
        );
524
525
        $this->assertSame(
526
            'SELECT _var',
527
            TokensList::build($stmt->body)
528
        );
529
    }
530
531
    public function testBuilderCreateFunction(): void
532
    {
533
        $parser = new Parser(
534
            'DELIMITER $$' . "\n"
535
            . 'CREATE DEFINER=`root`@`localhost`'
536
            . ' FUNCTION `inventory_in_stock`(`p_inventory_id` INT) RETURNS tinyint(1)'
537
            . ' READS SQL DATA'
538
            . ' COMMENT \'My best function written by a friend\'\'s friend\''
539
            . ' BEGIN' . "\n"
540
            . '    DECLARE v_rentals INT;' . "\n"
541
            . '    DECLARE v_out     INT;' . "\n"
542
            . "\n"
543
            . '    ' . "\n"
544
            . '    ' . "\n"
545
            . "\n"
546
            . '    SELECT COUNT(*) INTO v_rentals' . "\n"
547
            . '    FROM rental' . "\n"
548
            . '    WHERE inventory_id = p_inventory_id;' . "\n"
549
            . "\n"
550
            . '    IF v_rentals = 0 THEN' . "\n"
551
            . '      RETURN TRUE;' . "\n"
552
            . '    END IF;' . "\n"
553
            . "\n"
554
            . '    SELECT COUNT(rental_id) INTO v_out' . "\n"
555
            . '    FROM inventory LEFT JOIN rental USING(inventory_id)' . "\n"
556
            . '    WHERE inventory.inventory_id = p_inventory_id' . "\n"
557
            . '    AND rental.return_date IS NULL;' . "\n"
558
            . "\n"
559
            . '    IF v_out > 0 THEN' . "\n"
560
            . '      RETURN FALSE;' . "\n"
561
            . '    ELSE' . "\n"
562
            . '      RETURN TRUE;' . "\n"
563
            . '    END IF;' . "\n"
564
            . 'END'
565
        );
566
567
        /** @var CreateStatement $stmt */
568
        $stmt = $parser->statements[0];
569
570
        $this->assertSame(
571
            'CREATE DEFINER=`root`@`localhost`'
572
            . ' FUNCTION `inventory_in_stock` (`p_inventory_id` INT) RETURNS TINYINT(1)'
573
            . ' READS SQL DATA'
574
            . ' COMMENT \'My best function written by a friend\'\'s friend\''
575
            . ' BEGIN' . "\n"
576
            . '    DECLARE v_rentals INT;' . "\n"
577
            . '    DECLARE v_out     INT;' . "\n"
578
            . "\n"
579
            . '    ' . "\n"
580
            . '    ' . "\n"
581
            . "\n"
582
            . '    SELECT COUNT(*) INTO v_rentals' . "\n"
583
            . '    FROM rental' . "\n"
584
            . '    WHERE inventory_id = p_inventory_id;' . "\n"
585
            . "\n"
586
            . '    IF v_rentals = 0 THEN' . "\n"
587
            . '      RETURN TRUE;' . "\n"
588
            . '    END IF;' . "\n"
589
            . "\n"
590
            . '    SELECT COUNT(rental_id) INTO v_out' . "\n"
591
            . '    FROM inventory LEFT JOIN rental USING(inventory_id)' . "\n"
592
            . '    WHERE inventory.inventory_id = p_inventory_id' . "\n"
593
            . '    AND rental.return_date IS NULL;' . "\n"
594
            . "\n"
595
            . '    IF v_out > 0 THEN' . "\n"
596
            . '      RETURN FALSE;' . "\n"
597
            . '    ELSE' . "\n"
598
            . '      RETURN TRUE;' . "\n"
599
            . '    END IF;' . "\n"
600
            . 'END',
601
            $stmt->build()
602
        );
603
604
        $this->assertFalse($stmt->entityOptions->isEmpty());
605
        $this->assertFalse($stmt->options->isEmpty());
606
607
        $this->assertSame(
608
            'DEFINER=`root`@`localhost` FUNCTION',
609
            $stmt->options->__toString()
610
        );
611
612
        $this->assertSame(
613
            '`inventory_in_stock`',
614
            $stmt->name->__toString()
615
        );
616
617
        $this->assertSame(
618
            '(`p_inventory_id` INT)',
619
            ParameterDefinition::build($stmt->parameters)
620
        );
621
622
        $this->assertSame(
623
            'READS SQL DATA COMMENT \'My best function written by a friend\'\'s friend\'',
624
            $stmt->entityOptions->__toString()
625
        );
626
627
        $this->assertSame(
628
            'BEGIN' . "\n"
629
            . '    DECLARE v_rentals INT;' . "\n"
630
            . '    DECLARE v_out     INT;' . "\n"
631
            . "\n"
632
            . '    ' . "\n"
633
            . '    ' . "\n"
634
            . "\n"
635
            . '    SELECT COUNT(*) INTO v_rentals' . "\n"
636
            . '    FROM rental' . "\n"
637
            . '    WHERE inventory_id = p_inventory_id;' . "\n"
638
            . "\n"
639
            . '    IF v_rentals = 0 THEN' . "\n"
640
            . '      RETURN TRUE;' . "\n"
641
            . '    END IF;' . "\n"
642
            . "\n"
643
            . '    SELECT COUNT(rental_id) INTO v_out' . "\n"
644
            . '    FROM inventory LEFT JOIN rental USING(inventory_id)' . "\n"
645
            . '    WHERE inventory.inventory_id = p_inventory_id' . "\n"
646
            . '    AND rental.return_date IS NULL;' . "\n"
647
            . "\n"
648
            . '    IF v_out > 0 THEN' . "\n"
649
            . '      RETURN FALSE;' . "\n"
650
            . '    ELSE' . "\n"
651
            . '      RETURN TRUE;' . "\n"
652
            . '    END IF;' . "\n"
653
            . 'END',
654
            TokensList::build($stmt->body)
655
        );
656
    }
657
658
    public function testBuilderTrigger(): void
659
    {
660
        $stmt = new CreateStatement();
661
662
        $stmt->options = new OptionsArray(['TRIGGER']);
663
        $stmt->name = new Expression('ins_sum');
664
        $stmt->entityOptions = new OptionsArray(['BEFORE', 'INSERT']);
665
        $stmt->table = new Expression('account');
666
        $stmt->body = 'SET @sum = @sum + NEW.amount';
667
668
        $this->assertEquals(
669
            'CREATE TRIGGER ins_sum BEFORE INSERT ON account ' .
670
            'FOR EACH ROW SET @sum = @sum + NEW.amount',
671
            $stmt->build()
672
        );
673
    }
674
675
    public function testBuilderRoutine(): void
676
    {
677
        $parser = new Parser(
678
            'DELIMITER $$' . "\n" .
679
            'CREATE FUNCTION test (IN `i` INT) RETURNS VARCHAR ' .
680
            'BEGIN ' .
681
            'DECLARE name VARCHAR DEFAULT ""; ' .
682
            'SELECT name INTO name FROM employees WHERE id = i; ' .
683
            'RETURN name; ' .
684
            'END'
685
        );
686
        $stmt = $parser->statements[0];
687
688
        $this->assertEquals(
689
            'CREATE FUNCTION test (IN `i` INT) RETURNS VARCHAR ' .
690
            ' BEGIN ' .
691
            'DECLARE name VARCHAR DEFAULT ""; ' .
692
            'SELECT name INTO name FROM employees WHERE id = i; ' .
693
            'RETURN name; ' .
694
            'END',
695
            $stmt->build()
696
        );
697
    }
698
699
    public function testBuildSelect(): void
700
    {
701
        $parser = new Parser('CREATE TABLE new_tbl SELECT * FROM orig_tbl');
702
        $this->assertEquals(
703
            'CREATE TABLE new_tbl SELECT * FROM orig_tbl',
704
            $parser->statements[0]->build()
705
        );
706
    }
707
708
    public function testBuildCreateTableSortedIndex(): void
709
    {
710
        $parser = new Parser(
711
            <<<'SQL'
712
CREATE TABLE `entries` (
713
    `id` int(11) NOT NULL AUTO_INCREMENT,
714
    `fk_ug_id` int(11) DEFAULT NULL,
715
    `amount` decimal(10,2) DEFAULT NULL,
716
    PRIMARY KEY (`id`),
717
    KEY `entries__ug` (`fk_ug_id` DESC),
718
    KEY `entries__ug2` (`fk_ug_id` ASC),
719
    KEY `33` (`id` ASC, `fk_ug_id` DESC)
720
) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=4465 DEFAULT CHARSET=utf8
721
SQL
722
        );
723
724
        /** @var CreateStatement $stmt */
725
        $stmt = $parser->statements[0];
726
727
        $tableBody = <<<'SQL'
728
(
729
  `id` int(11) NOT NULL AUTO_INCREMENT,
730
  `fk_ug_id` int(11) DEFAULT NULL,
731
  `amount` decimal(10,2) DEFAULT NULL,
732
  PRIMARY KEY (`id`),
733
  KEY `entries__ug` (`fk_ug_id` DESC),
734
  KEY `entries__ug2` (`fk_ug_id` ASC),
735
  KEY `33` (`id` ASC,`fk_ug_id` DESC)
736
)
737
SQL;
738
739
        $this->assertEquals(
740
            $tableBody,
741
            CreateDefinition::build($stmt->fields)
0 ignored issues
show
Bug introduced by
It seems like $stmt->fields can also be of type PhpMyAdmin\SqlParser\Components\ArrayObj; however, parameter $component of PhpMyAdmin\SqlParser\Com...eateDefinition::build() does only seem to accept PhpMyAdmin\SqlParser\Com...onents\CreateDefinition, maybe add an additional type check? ( Ignorable by Annotation )

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

741
            CreateDefinition::build(/** @scrutinizer ignore-type */ $stmt->fields)
Loading history...
742
        );
743
744
        $this->assertEquals(
745
            'CREATE TABLE `entries` '
746
            . $tableBody
747
            . ' ENGINE=InnoDB AUTO_INCREMENT=4465 DEFAULT CHARSET=utf8 TABLESPACE `innodb_system`',
748
            $stmt->build()
749
        );
750
    }
751
752
    public function testBuildCreateTableComplexIndexes(): void
753
    {
754
        // phpcs:disable Generic.Files.LineLength.TooLong
755
        $parser = new Parser(
756
            <<<'SQL'
757
CREATE TABLE `page_rebuild_control` (
758
    `proc_row_number` int DEFAULT NULL,
759
    `place_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
760
    `place_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
761
    `place_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
762
    `waterway_id` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
763
    `cache_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
764
    `place_active` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
765
    `alias_type` int NOT NULL DEFAULT '0',
766
    `status` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
767
    `time_taken` float DEFAULT NULL,
768
    PRIMARY KEY (`place_id`,`place_type`) USING BTREE,
769
    KEY `place_type_idx` (`place_type`(10)),
770
    KEY `cached_time_idx` (`cache_updated`),
771
    KEY `active_idx` (`place_active`),
772
    KEY `status_idx` (`status`),
773
    KEY `waterway_idx` (`waterway_id`),
774
    KEY `time_taken_idx` (`time_taken`),
775
    KEY `updated_tz_ind3` (
776
        -- my expression
777
		(convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB'))
778
    ) COMMENT 'foo\'s',
779
    KEY `updated_tz_ind_two_indexes_commented` (
780
		-- first expression
781
		(
782
			convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB')
783
		)
784
		,
785
		-- second expression
786
		(
787
			convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'FR')
788
		)
789
	)
790
	-- and now some options
791
	COMMENT 'haha, this is a complex and indented case',
792
    KEY `alias_type_idx` (`alias_type`),
793
    KEY `updated_tz_ind2` ((convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB'))) COMMENT 'foo\'s',
794
    KEY `updated_tz_ind_two_indexes` ((convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB')), (convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'FR'))) COMMENT 'bar\'s',
795
    KEY `updated_tz_ind` ((convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB')))
796
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
797
SQL
798
        );
799
        // phpcs:enable
800
801
        /** @var CreateStatement $stmt */
802
        $stmt = $parser->statements[0];
803
804
        // phpcs:disable Generic.Files.LineLength.TooLong
805
        $tableBody = <<<'SQL'
806
(
807
  `proc_row_number` int DEFAULT NULL,
808
  `place_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
809
  `place_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
810
  `place_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
811
  `waterway_id` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
812
  `cache_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
813
  `place_active` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
814
  `alias_type` int NOT NULL DEFAULT '0',
815
  `status` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
816
  `time_taken` float DEFAULT NULL,
817
  PRIMARY KEY (`place_id`,`place_type`) USING BTREE,
818
  KEY `place_type_idx` (`place_type`(10)),
819
  KEY `cached_time_idx` (`cache_updated`),
820
  KEY `active_idx` (`place_active`),
821
  KEY `status_idx` (`status`),
822
  KEY `waterway_idx` (`waterway_id`),
823
  KEY `time_taken_idx` (`time_taken`),
824
  KEY `updated_tz_ind3` ((convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB'))) COMMENT 'foo\'s',
825
  KEY `updated_tz_ind_two_indexes_commented` ((
826
			convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB')
827
		), (
828
			convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'FR')
829
		)) COMMENT 'haha, this is a complex and indented case',
830
  KEY `alias_type_idx` (`alias_type`),
831
  KEY `updated_tz_ind2` ((convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB'))) COMMENT 'foo\'s',
832
  KEY `updated_tz_ind_two_indexes` ((convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB')), (convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'FR'))) COMMENT 'bar\'s',
833
  KEY `updated_tz_ind` ((convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB')))
834
)
835
SQL;
836
        // phpcs:enable
837
838
        $this->assertEquals(
839
            $tableBody,
840
            CreateDefinition::build($stmt->fields)
0 ignored issues
show
Bug introduced by
It seems like $stmt->fields can also be of type PhpMyAdmin\SqlParser\Components\ArrayObj; however, parameter $component of PhpMyAdmin\SqlParser\Com...eateDefinition::build() does only seem to accept PhpMyAdmin\SqlParser\Com...onents\CreateDefinition, maybe add an additional type check? ( Ignorable by Annotation )

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

840
            CreateDefinition::build(/** @scrutinizer ignore-type */ $stmt->fields)
Loading history...
841
        );
842
843
        $this->assertEquals(
844
            'CREATE TABLE `page_rebuild_control` '
845
            . $tableBody
846
            . ' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci',
847
            $stmt->build()
848
        );
849
    }
850
}
851