4
4
5
5
namespace PhpMyAdmin \SqlParser \Statements ;
6
6
7
- use PhpMyAdmin \SqlParser \Components \Expression ;
8
7
use PhpMyAdmin \SqlParser \Components \OptionsArray ;
8
+ use PhpMyAdmin \SqlParser \Exceptions \ParserException ;
9
+ use PhpMyAdmin \SqlParser \Parser ;
9
10
use PhpMyAdmin \SqlParser \Statement ;
11
+ use PhpMyAdmin \SqlParser \Token ;
12
+ use PhpMyAdmin \SqlParser \TokensList ;
10
13
11
- use function trim ;
14
+ use function array_slice ;
15
+ use function count ;
16
+ use function is_int ;
12
17
13
- /**
14
- * `KILL` statement.
15
- *
16
- * KILL [CONNECTION | QUERY] processlist_id
18
+ /** KILL [HARD|SOFT]
19
+ * {
20
+ * {CONNECTION|QUERY} id |
21
+ * QUERY ID query_id | USER user_name
22
+ * }
17
23
*/
18
24
class KillStatement extends Statement
19
25
{
@@ -24,20 +30,153 @@ class KillStatement extends Statement
24
30
* @psalm-var array<string, (positive-int|array{positive-int, ('var'|'var='|'expr'|'expr=')})>
25
31
*/
26
32
public static $ OPTIONS = [
27
- 'CONNECTION ' => 1 ,
28
- 'QUERY ' => 1 ,
33
+ 'HARD ' => 1 ,
34
+ 'SOFT ' => 1 ,
35
+ 'CONNECTION ' => 2 ,
36
+ 'QUERY ' => 2 ,
37
+ 'USER ' => 2 ,
29
38
];
30
39
31
- /** @var Expression|null */
32
- public $ processListId = null ;
40
+ /**
41
+ * Holds the identifier if explicitly set
42
+ *
43
+ * @psalm-var Statement|int|null
44
+ */
45
+ public $ identifier = null ;
46
+
47
+ /**
48
+ * Whether MariaDB ID keyword is used or not.
49
+ *
50
+ * @psalm-var bool
51
+ */
52
+ public $ IDKeywordUsed = false ;
53
+
54
+ /**
55
+ * Whether parenthesis used around the identifier or not
56
+ *
57
+ * @psalm-var bool
58
+ */
59
+ public $ parenthesisUsed = false ;
60
+
61
+ /** @throws ParserException */
62
+ public function parse (Parser $ parser , TokensList $ list ): void
63
+ {
64
+ /**
65
+ * The state of the parser.
66
+ *
67
+ * Below are the states of the parser.
68
+ *
69
+ * 0 --------------------- [ OPTIONS PARSED ] --------------------------> 0
70
+ *
71
+ * 0 -------------------- [ number ] -----------------------------------> 2
72
+ *
73
+ * 0 -------------------- [ ( ] ----------------------------------------> 3
74
+ *
75
+ * 0 -------------------- [ QUERY ID ] ---------------------------------> 0
76
+ *
77
+ * 3 -------------------- [ number ] -----------------------------------> 3
78
+ *
79
+ * 3 -------------------- [ SELECT STATEMENT ] -------------------------> 2
80
+ *
81
+ * 3 -------------------- [ ) ] ----------------------------------------> 2
82
+ *
83
+ * 2 ----------------------------------------------------------> Final state
84
+ */
85
+ $ state = 0 ;
86
+
87
+ ++$ list ->idx ; // Skipping `KILL`.
88
+ $ this ->options = OptionsArray::parse ($ parser , $ list , static ::$ OPTIONS );
89
+ ++$ list ->idx ;
90
+ for (; $ list ->idx < $ list ->count ; ++$ list ->idx ) {
91
+ $ token = $ list ->tokens [$ list ->idx ];
92
+
93
+ if ($ token ->type === Token::TYPE_WHITESPACE || $ token ->type === Token::TYPE_COMMENT ) {
94
+ continue ;
95
+ }
33
96
34
- public function build (): string
97
+ switch ($ state ) {
98
+ case 0 :
99
+ $ currIdx = $ list ->idx ;
100
+ $ prev = $ list ->getPreviousOfType (Token::TYPE_KEYWORD );
101
+ $ list ->idx = $ currIdx ;
102
+ if ($ token ->type === Token::TYPE_NUMBER && is_int ($ token ->value )) {
103
+ $ this ->identifier = $ token ->value ;
104
+ $ state = 2 ;
105
+ } elseif ($ token ->type === Token::TYPE_OPERATOR && $ token ->value === '( ' ) {
106
+ $ this ->parenthesisUsed = true ;
107
+ $ state = 3 ;
108
+ } elseif ($ prev && $ token ->value === 'ID ' && $ prev ->value === 'QUERY ' ) {
109
+ $ this ->IDKeywordUsed = true ;
110
+ $ state = 0 ;
111
+ } else {
112
+ $ parser ->error ('Unexpected token. ' , $ token );
113
+ break 2 ;
114
+ }
115
+
116
+ break ;
117
+
118
+ case 3 :
119
+ if ($ token ->type === Token::TYPE_KEYWORD && $ token ->value === 'SELECT ' ) {
120
+ $ subList = new TokensList (array_slice ($ list ->tokens , $ list ->idx - 1 ));
121
+ $ subParser = new Parser ($ subList );
122
+ if (count ($ subParser ->errors )) {
123
+ foreach ($ subParser ->errors as $ error ) {
124
+ $ parser ->errors [] = $ error ;
125
+ }
126
+
127
+ break ;
128
+ }
129
+
130
+ $ this ->identifier = $ subParser ->statements [0 ];
131
+ $ state = 2 ;
132
+ } elseif ($ token ->type === Token::TYPE_OPERATOR && $ token ->value === ') ' ) {
133
+ $ state = 2 ;
134
+ } elseif ($ token ->type === Token::TYPE_NUMBER && is_int ($ token ->value )) {
135
+ $ this ->identifier = $ token ->value ;
136
+ $ state = 3 ;
137
+ } else {
138
+ $ parser ->error ('Unexpected token. ' , $ token );
139
+ break 2 ;
140
+ }
141
+
142
+ break ;
143
+ }
144
+ }
145
+
146
+ if ($ state !== 2 ) {
147
+ $ token = $ list ->tokens [$ list ->idx ];
148
+ $ parser ->error ('Unexpected end of the KILL statement. ' , $ token );
149
+ }
150
+
151
+ --$ list ->idx ;
152
+ }
153
+
154
+ /**
155
+ * {@inheritdoc}
156
+ */
157
+ public function build ()
35
158
{
36
- $ option = $ this ->options === null || $ this ->options ->isEmpty ()
37
- ? ''
38
- : ' ' . OptionsArray::build ($ this ->options );
39
- $ expression = $ this ->processListId === null ? '' : ' ' . Expression::build ($ this ->processListId );
159
+ $ ret = 'KILL ' ;
160
+
161
+ if ($ this ->options && count ($ this ->options ->options ) > 0 ) {
162
+ $ ret .= ' ' . OptionsArray::build ($ this ->options );
163
+ }
164
+
165
+ if ($ this ->IDKeywordUsed ) {
166
+ $ ret .= ' ID ' ;
167
+ }
168
+
169
+ $ builtIdentifier = (string ) $ this ->identifier ;
170
+ if ($ this ->identifier instanceof Statement) {
171
+ $ builtIdentifier = $ this ->identifier ->build ();
172
+ }
173
+
174
+ if ($ this ->parenthesisUsed ) {
175
+ $ ret .= ' ( ' . $ builtIdentifier . ') ' ;
176
+ } elseif ($ this ->identifier !== null ) {
177
+ $ ret .= ' ' . $ builtIdentifier ;
178
+ }
40
179
41
- return trim ( ' KILL ' . $ option . $ expression ) ;
180
+ return $ ret ;
42
181
}
43
182
}
0 commit comments