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