Passed
Pull Request — master (#483)
by
unknown
03:01
created

JoinKeyword::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 4
dl 0
loc 6
ccs 5
cts 5
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpMyAdmin\SqlParser\Components;
6
7
use PhpMyAdmin\SqlParser\Component;
8
use PhpMyAdmin\SqlParser\Parser;
9
use PhpMyAdmin\SqlParser\Token;
10
use PhpMyAdmin\SqlParser\TokensList;
11
use PhpMyAdmin\SqlParser\Translator;
12
use RuntimeException;
13
14
use function array_search;
15
use function implode;
16
17
/**
18
 * `JOIN` keyword parser.
19
 */
20
final class JoinKeyword implements Component
21
{
22
    /**
23
     * Types of join.
24
     *
25
     * @var array<string, string>
26
     */
27
    public static $joins = [
28
        'CROSS JOIN' => 'CROSS',
29
        'FULL JOIN' => 'FULL',
30
        'FULL OUTER JOIN' => 'FULL',
31
        'INNER JOIN' => 'INNER',
32
        'JOIN' => 'JOIN',
33
        'LEFT JOIN' => 'LEFT',
34
        'LEFT OUTER JOIN' => 'LEFT',
35
        'RIGHT JOIN' => 'RIGHT',
36
        'RIGHT OUTER JOIN' => 'RIGHT',
37
        'NATURAL JOIN' => 'NATURAL',
38
        'NATURAL LEFT JOIN' => 'NATURAL LEFT',
39
        'NATURAL RIGHT JOIN' => 'NATURAL RIGHT',
40
        'NATURAL LEFT OUTER JOIN' => 'NATURAL LEFT OUTER',
41
        'NATURAL RIGHT OUTER JOIN' => 'NATURAL RIGHT OUTER',
42
        'STRAIGHT_JOIN' => 'STRAIGHT',
43
    ];
44
45
    /**
46
     * Type of this join.
47
     *
48
     * @see JoinKeyword::$joins
49
     *
50
     * @var string
51
     */
52
    public $type;
53
54
    /**
55
     * Join expression.
56
     *
57
     * @var Expression
58
     */
59
    public $expr;
60
61
    /**
62
     * Join conditions.
63
     *
64
     * @var Condition[]
65
     */
66
    public $on;
67
68
    /**
69
     * Columns in Using clause.
70
     *
71
     * @var ArrayObj
72
     */
73
    public $using;
74
75
    /**
76
     * @see JoinKeyword::$joins
77
     *
78
     * @param string      $type  Join type
79
     * @param Expression  $expr  join expression
80
     * @param Condition[] $on    join conditions
81
     * @param ArrayObj    $using columns joined
82
     */
83 74
    public function __construct($type = null, $expr = null, $on = null, $using = null)
84
    {
85 74
        $this->type = $type;
86 74
        $this->expr = $expr;
87 74
        $this->on = $on;
88 74
        $this->using = $using;
89
    }
90
91
    /**
92
     * @param Parser               $parser  the parser that serves as context
93
     * @param TokensList           $list    the list of tokens that are being parsed
94
     * @param array<string, mixed> $options parameters for parsing
95
     *
96
     * @return JoinKeyword[]
97
     */
98 74
    public static function parse(Parser $parser, TokensList $list, array $options = []): array
99
    {
100 74
        $ret = [];
101
102 74
        $expr = new static();
103
104
        /**
105
         * The state of the parser.
106
         *
107
         * Below are the states of the parser.
108
         *
109
         *      0 -----------------------[ JOIN ]----------------------> 1
110
         *
111
         *      1 -----------------------[ expr ]----------------------> 2
112
         *
113
         *      2 ------------------------[ ON ]-----------------------> 3
114
         *      2 -----------------------[ USING ]---------------------> 4
115
         *
116
         *      3 --------------------[ conditions ]-------------------> 0
117
         *
118
         *      4 ----------------------[ columns ]--------------------> 0
119
         *
120
         * @var int
121
         */
122 74
        $state = 0;
123
124
        // By design, the parser will parse first token after the keyword.
125
        // In this case, the keyword must be analyzed too, in order to determine
126
        // the type of this join.
127 74
        if ($list->idx > 0) {
128 68
            --$list->idx;
129
        }
130
131 74
        for (; $list->idx < $list->count; ++$list->idx) {
132
            /**
133
             * Token parsed at this moment.
134
             */
135 74
            $token = $list->tokens[$list->idx];
136
137
            // End of statement.
138 74
            if ($token->type === Token::TYPE_DELIMITER) {
139 36
                break;
140
            }
141
142
            // Skipping whitespaces and comments.
143 74
            if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
144 74
                continue;
145
            }
146
147 74
            if ($state === 0) {
148 74
                if (($token->type !== Token::TYPE_KEYWORD) || empty(static::$joins[$token->keyword])) {
149 32
                    break;
150
                }
151
152 74
                $expr->type = static::$joins[$token->keyword];
153 74
                $state = 1;
154 74
            } elseif ($state === 1) {
155 74
                $expr->expr = Expression::parse($parser, $list, ['field' => 'table']);
156 74
                $state = 2;
157 56
            } elseif ($state === 2) {
158 56
                if ($token->type === Token::TYPE_KEYWORD) {
159 56
                    switch ($token->keyword) {
160 56
                        case 'ON':
161 46
                            $state = 3;
162 46
                            break;
163 12
                        case 'USING':
164 2
                            $state = 4;
165 2
                            break;
166
                        default:
167 10
                            if (empty(static::$joins[$token->keyword])) {
168
                                /* Next clause is starting */
169 8
                                break 2;
170
                            }
171
172 8
                            $ret[] = $expr;
173 8
                            $expr = new static();
174 8
                            $expr->type = static::$joins[$token->keyword];
175 8
                            $state = 1;
176
177 31
                            break;
178
                    }
179
                }
180 48
            } elseif ($state === 3) {
181 46
                $expr->on = Condition::parse($parser, $list);
182 46
                $ret[] = $expr;
183 46
                $expr = new static();
184 46
                $state = 0;
185 2
            } elseif ($state === 4) {
186 2
                $expr->using = ArrayObj::parse($parser, $list);
187 2
                $ret[] = $expr;
188 2
                $expr = new static();
189 2
                $state = 0;
190
            }
191
        }
192
193 74
        if (! empty($expr->type)) {
194 28
            $ret[] = $expr;
195
        }
196
197 74
        --$list->idx;
198
199 74
        return $ret;
200
    }
201
202
    /**
203
     * @param JoinKeyword $component
204
     */
205
    public static function build($component): string
206
    {
207
        throw new RuntimeException(Translator::gettext('Not implemented yet.'));
208
    }
209
210
    /**
211
     * @param JoinKeyword[] $component the component to be built
212
     */
213 22
    public static function buildAll(array $component): string
214
    {
215 22
        $ret = [];
216 22
        foreach ($component as $c) {
217 22
            $ret[] = array_search($c->type, static::$joins) . ' ' . $c->expr
218 22
                . (! empty($c->on)
219 22
                    ? ' ON ' . Condition::buildAll($c->on) : '')
220 22
                . (! empty($c->using)
221 22
                    ? ' USING ' . ArrayObj::build($c->using) : '');
222
        }
223
224 22
        return implode(' ', $ret);
225
    }
226
227
    public function __toString(): string
228
    {
229
        return static::build($this);
230
    }
231
}
232