Skip to content

Commit 3544708

Browse files
author
Andrew Svirin
committed
Add handling of command SHOW FULL COLUMNS FOR {table_name}
Handling multiple databases for create queries, then sql is CREATE TABLE `database`.`table` and in same script is `information_schema`.`table` Fixing PDO::getAttribute() missing method Handling Multiple insert sql.
1 parent 5724968 commit 3544708

File tree

8 files changed

+327
-11
lines changed

8 files changed

+327
-11
lines changed

src/FakePdoStatementTrait.php

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,19 +137,41 @@ public function universalExecute(?array $params = null)
137137
$create_queries = (new Parser\CreateTableParser())->parse($sql);
138138

139139
foreach ($create_queries as $create_query) {
140+
if (strpos($create_query->name, '.')) {
141+
list($databaseName, $tableName) = explode('.', $create_query->name, 2);
142+
} else {
143+
$databaseName = $this->conn->getDatabaseName();
144+
$tableName = $create_query->name;
145+
}
140146
$this->conn->getServer()->addTableDefinition(
141-
$this->conn->getDatabaseName(),
142-
$create_query->name,
147+
$databaseName,
148+
$tableName,
143149
Processor\CreateProcessor::makeTableDefinition(
144150
$create_query,
145-
$this->conn->getDatabaseName()
151+
$databaseName
146152
)
147153
);
148154
}
149155

150156
return true;
151157
}
152158

159+
// Check that there are multiple INSERT commands in the sql.
160+
$insertPos1 = stripos($sql, 'INSERT INTO');
161+
$insertPos2 = strripos($sql, 'INSERT INTO');
162+
if (false !== $insertPos1 && $insertPos1 !== $insertPos2) {
163+
$insert_queries = (new Parser\InsertMultipleParser())->parse($sql);
164+
foreach ($insert_queries as $insert_query) {
165+
$this->affectedRows += Processor\InsertProcessor::process(
166+
$this->conn,
167+
new Processor\Scope($this->boundValues),
168+
$insert_query
169+
);
170+
}
171+
172+
return true;
173+
}
174+
153175
//echo "\n" . $sql . "\n";
154176

155177
try {
@@ -284,6 +306,17 @@ function ($row) {
284306
);
285307
break;
286308

309+
case Query\ShowColumnsQuery::class:
310+
$this->result = self::processResult(
311+
$this->conn,
312+
Processor\ShowColumnsProcessor::process(
313+
$this->conn,
314+
new Processor\Scope(array_merge($params ?? [], $this->boundValues)),
315+
$parsed_query
316+
)
317+
);
318+
break;
319+
287320
default:
288321
throw new \UnexpectedValueException('Unsupported operation type ' . $sql);
289322
}

src/FakePdoTrait.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public function __construct(string $dsn, string $username = '', string $passwd =
5252
$dsn = \Nyholm\Dsn\DsnParser::parse($dsn);
5353
$host = $dsn->getHost();
5454

55-
if (preg_match('/dbname=([a-zA-Z0-9_]+);/', $host, $matches)) {
55+
if (preg_match('/dbname=([a-zA-Z0-9_]+)(?:;|$)/', $host, $matches)) {
5656
$this->databaseName = $matches[1];
5757
}
5858

@@ -87,6 +87,16 @@ public function setAttribute($key, $value)
8787
return true;
8888
}
8989

90+
public function getAttribute($key)
91+
{
92+
switch ($key) {
93+
case \PDO::ATTR_CASE:
94+
$value = $this->lowercaseResultKeys ? \PDO::CASE_LOWER : \PDO::CASE_UPPER;
95+
}
96+
97+
return $value;
98+
}
99+
90100
public function getServer() : Server
91101
{
92102
return $this->server;

src/Parser/CreateTableParser.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,13 @@ private static function parseCreateTable(array $tokens, string $sql) : CreateQue
218218
\array_shift($tokens);
219219
}
220220

221-
$t = \array_shift($tokens);
221+
// Extract [{database}.]{table}
222+
if ($tokens[1] === '.') {
223+
$t = \array_shift($tokens) . \array_shift($tokens) . \array_shift($tokens);
224+
} else {
225+
$t = \array_shift($tokens);
226+
}
227+
222228
$name = static::decodeIdentifier($t);
223229

224230
if (static::nextTokenIs($tokens, 'LIKE')) {

src/Parser/InsertMultipleParser.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace Vimeo\MysqlEngine\Parser;
4+
5+
use Vimeo\MysqlEngine\Query\CreateQuery;
6+
use Vimeo\MysqlEngine\Query\Expression\Expression;
7+
use Vimeo\MysqlEngine\TokenType;
8+
use Vimeo\MysqlEngine\Query\InsertQuery;
9+
10+
final class InsertMultipleParser
11+
{
12+
/**
13+
* @return array<string, CreateQuery>
14+
*/
15+
public function parse(string $sql): array
16+
{
17+
return self::walk($this->splitStatements($sql));
18+
}
19+
20+
/**
21+
* @var list<string>
22+
*/
23+
private $tokens = [];
24+
25+
/**
26+
* @var array<int, array{0:int, 1:int}>
27+
*/
28+
private $sourceMap = [];
29+
30+
/**
31+
* @return non-empty-list<string>
32+
*/
33+
private function splitStatements(string $sql): array
34+
{
35+
$re_split_sql = '%
36+
# Match an SQL record ending with ";"
37+
\s* # Discard leading whitespace.
38+
( # $1: Trimmed non-empty SQL record.
39+
(?: # Group for content alternatives.
40+
\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\' # Either a single quoted string,
41+
| "[^"\\\\]*(?:\\\\.[^"\\\\]*)*" # or a double quoted string,
42+
| /\*[^*]*\*+(?:[^*/][^*]*\*+)*/ # or a multi-line comment,
43+
| \#.* # or a # single line comment,
44+
| --.* # or a -- single line comment,
45+
| [^"\';#] # or one non-["\';#-]
46+
)+ # One or more content alternatives
47+
(?:;|$) # Record end is a ; or string end.
48+
) # End $1: Trimmed SQL record.
49+
%xs';
50+
51+
if (preg_match_all($re_split_sql, $sql, $matches)) {
52+
$statements = $matches[1];
53+
}
54+
55+
return $statements ?? [];
56+
}
57+
58+
/**
59+
* @param array<string> $statements
60+
*
61+
* @return array<string, InsertQuery>
62+
*/
63+
private static function walk(array $statements)
64+
{
65+
$result = [];
66+
67+
foreach ($statements as $statement) {
68+
$statement = trim($statement);
69+
if (false === stripos($statement, 'INSERT INTO')) {
70+
continue;
71+
}
72+
$statement = rtrim($statement, ';');
73+
74+
$result[] = SQLParser::parse($statement);
75+
}
76+
77+
return $result;
78+
}
79+
}

src/Parser/SQLParser.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
InsertQuery,
1010
UpdateQuery,
1111
DropTableQuery,
12-
ShowTablesQuery};
12+
ShowTablesQuery,
13+
ShowColumnsQuery
14+
};
1315

1416
final class SQLParser
1517
{
@@ -141,11 +143,11 @@ final class SQLParser
141143
'TABLES' => true,
142144
];
143145

144-
/** @var array<SelectQuery|InsertQuery|UpdateQuery|TruncateQuery|DeleteQuery|DropTableQuery|ShowTablesQuery|ShowIndexQuery> */
146+
/** @var array<SelectQuery|InsertQuery|UpdateQuery|TruncateQuery|DeleteQuery|DropTableQuery|ShowTablesQuery|ShowIndexQuery|ShowColumnsQuery> */
145147
private static $cache = [];
146148

147149
/**
148-
* @return SelectQuery|InsertQuery|UpdateQuery|TruncateQuery|DeleteQuery|DropTableQuery|ShowTablesQuery|ShowIndexQuery
150+
* @return SelectQuery|InsertQuery|UpdateQuery|TruncateQuery|DeleteQuery|DropTableQuery|ShowTablesQuery|ShowIndexQuery|ShowColumnsQuery
149151
*/
150152
public static function parse(string $sql)
151153
{
@@ -157,7 +159,7 @@ public static function parse(string $sql)
157159
}
158160

159161
/**
160-
* @return SelectQuery|InsertQuery|UpdateQuery|TruncateQuery|DeleteQuery|DropTableQuery|ShowTablesQuery|ShowIndexQuery
162+
* @return SelectQuery|InsertQuery|UpdateQuery|TruncateQuery|DeleteQuery|DropTableQuery|ShowTablesQuery|ShowIndexQuery|ShowColumnsQuery
161163
*/
162164
private static function parseImpl(string $sql)
163165
{

src/Parser/ShowParser.php

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
namespace Vimeo\MysqlEngine\Parser;
44

5+
use Vimeo\MysqlEngine\Query\ShowColumnsQuery;
56
use Vimeo\MysqlEngine\Query\ShowIndexQuery;
6-
use Vimeo\MysqlEngine\TokenType;
77
use Vimeo\MysqlEngine\Query\ShowTablesQuery;
8+
use Vimeo\MysqlEngine\TokenType;
89

910
/**
1011
* Very limited parser for SHOW TABLES LIKE 'foo'
@@ -47,13 +48,21 @@ public function parse()
4748

4849
$this->pointer++;
4950

51+
// For case with TABLES and COLUMNS could be optinaly used argument FULL.
52+
if ($this->tokens[$this->pointer]->value === 'FULL') {
53+
$isFull = true;
54+
$this->pointer++;
55+
}
56+
5057
switch ($this->tokens[$this->pointer]->value) {
5158
case 'TABLES':
5259
return $this->parseShowTables();
5360
case 'INDEX':
5461
case 'INDEXES':
5562
case 'KEYS':
5663
return $this->parseShowIndex();
64+
case 'COLUMNS':
65+
return $this->parseShowColumns($isFull ?? false);
5766
default:
5867
throw new ParserException("Parser error: expected SHOW TABLES");
5968
}
@@ -64,7 +73,7 @@ private function parseShowTables(): ShowTablesQuery
6473
$this->pointer++;
6574

6675
if ($this->tokens[$this->pointer]->value !== 'LIKE') {
67-
throw new ParserException("Parser error: expected SHOW TABLES LIKE");
76+
throw new ParserException("Parser error: expected SHOW [FULL] TABLES LIKE");
6877
}
6978

7079
$this->pointer++;
@@ -102,6 +111,38 @@ private function parseShowIndex(): ShowIndexQuery
102111
list($this->pointer, $expression) = $expression_parser->buildWithPointer();
103112
$query->whereClause = $expression;
104113
}
114+
115+
return $query;
116+
}
117+
118+
private function parseShowColumns(bool $isFull): ShowColumnsQuery
119+
{
120+
$this->pointer++;
121+
122+
if ($this->tokens[$this->pointer]->value !== 'FROM') {
123+
throw new ParserException("Parser error: expected SHOW [FULL] COLUMNS FROM");
124+
}
125+
126+
$this->pointer++;
127+
128+
$token = $this->tokens[$this->pointer];
129+
if ($token->type !== TokenType::IDENTIFIER) {
130+
throw new ParserException("Expected table name after FROM");
131+
}
132+
133+
$query = new ShowColumnsQuery($token->value, $this->sql);
134+
$query->isFull = $isFull;
135+
$this->pointer++;
136+
137+
if ($this->pointer < count($this->tokens)) {
138+
if ($this->tokens[$this->pointer]->value !== 'WHERE') {
139+
throw new ParserException("Parser error: expected SHOW [FULL] COLUMNS FROM [TABLE_NAME] WHERE");
140+
}
141+
$expression_parser = new ExpressionParser($this->tokens, $this->pointer);
142+
list($this->pointer, $expression) = $expression_parser->buildWithPointer();
143+
$query->whereClause = $expression;
144+
}
145+
105146
return $query;
106147
}
107148
}

0 commit comments

Comments
 (0)