44
55namespace PhpMyAdmin \SqlParser \Statements ;
66
7- use PhpMyAdmin \SqlParser \Components \Expression ;
87use PhpMyAdmin \SqlParser \Components \OptionsArray ;
8+ use PhpMyAdmin \SqlParser \Exceptions \ParserException ;
9+ use PhpMyAdmin \SqlParser \Parser ;
910use PhpMyAdmin \SqlParser \Statement ;
11+ use PhpMyAdmin \SqlParser \Token ;
12+ use PhpMyAdmin \SqlParser \TokensList ;
1013
11- use function trim ;
14+ use function array_slice ;
15+ use function is_int ;
1216
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+ * }
1722 */
1823class KillStatement extends Statement
1924{
@@ -24,20 +29,149 @@ class KillStatement extends Statement
2429 * @psalm-var array<string, (positive-int|array{positive-int, ('var'|'var='|'expr'|'expr=')})>
2530 */
2631 public static $ OPTIONS = [
27- 'CONNECTION ' => 1 ,
28- 'QUERY ' => 1 ,
32+ 'HARD ' => 1 ,
33+ 'SOFT ' => 1 ,
34+ 'CONNECTION ' => 2 ,
35+ 'QUERY ' => 2 ,
36+ 'USER ' => 2 ,
2937 ];
3038
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+ * @var bool
50+ */
51+ public $ idKeywordUsed = false ;
3352
34- public function build (): string
53+ /**
54+ * Whether parenthesis used around the identifier or not
55+ *
56+ * @var bool
57+ */
58+ public $ parenthesisUsed = false ;
59+
60+ /** @throws ParserException */
61+ public function parse (Parser $ parser , TokensList $ list ): void
3562 {
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+ }
40174
41- return trim ( ' KILL ' . $ option . $ expression ) ;
175+ return $ ret ;
42176 }
43177}
0 commit comments