Skip to content

Commit 09faf92

Browse files
committed
Add support for LOCK and UNLOCK Statements
Signed-off-by: Deven Bansod <[email protected]>
1 parent e07403c commit 09faf92

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+568
-0
lines changed

src/Components/LockExpression.php

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
<?php
2+
3+
/**
4+
* Parses a reference to a LOCK expression.
5+
*/
6+
7+
namespace PhpMyAdmin\SqlParser\Components;
8+
9+
use PhpMyAdmin\SqlParser\Component;
10+
use PhpMyAdmin\SqlParser\Parser;
11+
use PhpMyAdmin\SqlParser\Token;
12+
use PhpMyAdmin\SqlParser\TokensList;
13+
14+
/**
15+
* Parses a reference to a LOCK expression.
16+
*
17+
* @category Components
18+
*
19+
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
20+
*/
21+
class LockExpression extends Component
22+
{
23+
/**
24+
* The table to be locked.
25+
*
26+
* @var Expression
27+
*/
28+
public $table;
29+
30+
/**
31+
* The type of lock to be applied.
32+
*
33+
* @var string
34+
*/
35+
public $type;
36+
37+
/**
38+
* @param Parser $parser the parser that serves as context
39+
* @param TokensList $list the list of tokens that are being parsed
40+
* @param array $options parameters for parsing
41+
*
42+
* @return CaseExpression
43+
*/
44+
public static function parse(Parser $parser, TokensList $list, array $options = array())
45+
{
46+
$ret = new self();
47+
48+
/**
49+
* The state of the parser.
50+
*
51+
* Below are the states of the parser.
52+
*
53+
* 0 ---------------- [ tbl_name ] -----------------> 1
54+
* 1 ---------------- [ lock_type ] ----------------> 2
55+
* 2 -------------------- [ , ] --------------------> break
56+
*
57+
* @var int
58+
*/
59+
$state = 0;
60+
61+
$prevToken = null;
62+
63+
for (; $list->idx < $list->count; ++$list->idx) {
64+
/**
65+
* Token parsed at this moment.
66+
*
67+
* @var Token
68+
*/
69+
$token = $list->tokens[$list->idx];
70+
71+
// End of statement.
72+
if ($token->type === Token::TYPE_DELIMITER
73+
|| ($token->type === Token::TYPE_OPERATOR
74+
&& $token->value === ',')
75+
) {
76+
break;
77+
}
78+
79+
if ($state === 0) {
80+
$ret->table = Expression::parse($parser, $list, array('parseField' => 'table'));
81+
$state = 1;
82+
} elseif ($state === 1) {
83+
// parse lock type
84+
$ret->type = self::parseLockType($parser, $list);
85+
$state = 2;
86+
}
87+
$prevToken = $token;
88+
}
89+
90+
// 2 is the only valid end state
91+
if ($state !== 2) {
92+
$parser->error('Unexpected end of LOCK expression.', $prevToken);
93+
}
94+
95+
--$list->idx;
96+
97+
return $ret;
98+
}
99+
100+
/**
101+
* @param LockExpression|LockExpression[] $component the component to be built
102+
* @param array $options parameters for building
103+
*
104+
* @return string
105+
*/
106+
public static function build($component, array $options = array())
107+
{
108+
if (is_array($component)) {
109+
return implode(', ', $component);
110+
}
111+
112+
return $component->table . ' ' . $component->type;
113+
}
114+
115+
private static function parseLockType(Parser $parser, TokensList $list) {
116+
$lockType = '';
117+
118+
/**
119+
* The state of the parser while parsing for lock type.
120+
*
121+
* Below are the states of the parser.
122+
*
123+
* 0 ---------------- [ READ ] -----------------> 1
124+
* 0 ------------- [ LOW_PRIORITY ] ------------> 2
125+
* 0 ---------------- [ WRITE ] ----------------> 3
126+
* 1 ---------------- [ LOCAL ] ----------------> 3
127+
* 2 ---------------- [ WRITE ] ----------------> 3
128+
*
129+
* @var int
130+
*/
131+
$state = 0;
132+
133+
$prevToken = null;
134+
135+
for (; $list->idx < $list->count; ++$list->idx) {
136+
/**
137+
* Token parsed at this moment.
138+
*
139+
* @var Token
140+
*/
141+
$token = $list->tokens[$list->idx];
142+
143+
// End of statement.
144+
if ($token->type === Token::TYPE_DELIMITER
145+
|| ($token->type === Token::TYPE_OPERATOR
146+
&& $token->value === ',')
147+
) {
148+
--$list->idx;
149+
break;
150+
}
151+
152+
// Skipping whitespaces and comments.
153+
if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
154+
continue;
155+
}
156+
157+
// We only expect keywords
158+
if ($token->type !== Token::TYPE_KEYWORD) {
159+
$parser->error('Unexpected token.', $token);
160+
break;
161+
}
162+
163+
if ($state === 0) {
164+
if ($token->keyword === 'READ') {
165+
$state = 1;
166+
} elseif ($token->keyword === 'LOW_PRIORITY') {
167+
$state = 2;
168+
} elseif ($token->keyword === 'WRITE') {
169+
$state = 3;
170+
} else {
171+
$parser->error('Unexpected keyword.', $token);
172+
break;
173+
}
174+
$lockType .= $token->keyword;
175+
} elseif ($state === 1) {
176+
if ($token->keyword === 'LOCAL') {
177+
$lockType .= ' ' . $token->keyword;
178+
$state = 3;
179+
} else {
180+
$parser->error('Unexpected keyword.', $token);
181+
break;
182+
}
183+
} elseif ($state === 2) {
184+
if ($token->keyword === 'WRITE') {
185+
$lockType .= ' ' . $token->keyword;
186+
$state = 3; // parsing over
187+
} else {
188+
$parser->error('Unexpected keyword.', $token);
189+
break;
190+
}
191+
}
192+
193+
$prevToken = $token;
194+
}
195+
196+
// Only two possible end states
197+
if ($state !== 1 && $state !== 3) {
198+
$parser->error('Unexpected end of Lock expression.', $prevToken);
199+
}
200+
201+
return $lockType;
202+
}
203+
}

src/Parser.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ class Parser extends Core
8888
'START TRANSACTION' => 'PhpMyAdmin\\SqlParser\\Statements\\TransactionStatement',
8989

9090
'PURGE' => 'PhpMyAdmin\\SqlParser\\Statements\\PurgeStatement',
91+
92+
// Lock statements
93+
// https://dev.mysql.com/doc/refman/5.7/en/lock-tables.html
94+
'LOCK' => 'PhpMyAdmin\\SqlParser\\Statements\\LockStatement',
95+
'UNLOCK' => 'PhpMyAdmin\\SqlParser\\Statements\\LockStatement',
9196
);
9297

9398
/**

src/Statements/LockStatement.php

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
3+
/**
4+
* `LOCK` statement.
5+
*/
6+
7+
namespace PhpMyAdmin\SqlParser\Statements;
8+
9+
use PhpMyAdmin\SqlParser\Components\LockExpression;
10+
use PhpMyAdmin\SqlParser\Parser;
11+
use PhpMyAdmin\SqlParser\Statement;
12+
use PhpMyAdmin\SqlParser\Token;
13+
use PhpMyAdmin\SqlParser\TokensList;
14+
15+
/**
16+
* `LOCK` statement.
17+
*
18+
* @category Statements
19+
*
20+
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
21+
*/
22+
class LockStatement extends Statement
23+
{
24+
/**
25+
* Tables with their Lock expressions.
26+
*
27+
* @var LockExpression[]
28+
*/
29+
public $locked = array();
30+
31+
/**
32+
* Whether it's a LOCK statement
33+
* if false, it's an UNLOCK statement
34+
*/
35+
public $isLock = true;
36+
37+
/**
38+
* @param Parser $parser the instance that requests parsing
39+
* @param TokensList $list the list of tokens to be parsed
40+
*/
41+
public function parse(Parser $parser, TokensList $list)
42+
{
43+
if ($list->tokens[$list->idx]->value === 'UNLOCK') {
44+
// this is in fact an UNLOCK statement
45+
$this->isLock = false;
46+
}
47+
++$list->idx; // Skipping `LOCK`.
48+
49+
/**
50+
* The state of the parser.
51+
*
52+
* Below are the states of the parser.
53+
*
54+
* 0 ---------------- [ TABLES ] -----------------> 1
55+
* 1 -------------- [ lock_expr ] ----------------> 2
56+
* 2 ------------------ [ , ] --------------------> 1
57+
*
58+
* @var int
59+
*/
60+
$state = 0;
61+
62+
/**
63+
* Previous parsed token
64+
*/
65+
$prevToken = null;
66+
67+
for (; $list->idx < $list->count; ++$list->idx) {
68+
/**
69+
* Token parsed at this moment.
70+
*
71+
* @var Token
72+
*/
73+
$token = $list->tokens[$list->idx];
74+
75+
// End of statement.
76+
if ($token->type === Token::TYPE_DELIMITER) {
77+
break;
78+
}
79+
80+
// Skipping whitespaces and comments.
81+
if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
82+
continue;
83+
}
84+
85+
if ($state === 0) {
86+
if ($token->type === Token::TYPE_KEYWORD) {
87+
if ($token->keyword !== 'TABLES') {
88+
$parser->error('Unexpected keyword.', $token);
89+
break;
90+
}
91+
$state = 1;
92+
continue;
93+
} else {
94+
$parser->error('Unexpected token.', $token);
95+
break;
96+
}
97+
} elseif ($state === 1) {
98+
if (!$this->isLock) {
99+
// UNLOCK statement should not have any more tokens
100+
$parser->error('Unexpected token.', $token);
101+
break;
102+
}
103+
$this->locked[] = LockExpression::parse($parser, $list);
104+
$state = 2;
105+
} elseif ($state === 2) {
106+
if ($token->value === ',') {
107+
// move over to parsing next lock expression
108+
$state = 1;
109+
}
110+
}
111+
112+
$prevToken = $token;
113+
}
114+
115+
if ($state !== 2 && $prevToken != null) {
116+
$parser->error('Unexpected end of LOCK statement.', $prevToken);
117+
}
118+
}
119+
120+
/**
121+
* @return string
122+
*/
123+
public function build()
124+
{
125+
return trim(($this->isLock ? 'LOCK' : 'UNLOCK')
126+
. ' TABLES ' . LockExpression::build($this->locked));
127+
}
128+
}

0 commit comments

Comments
 (0)