Skip to content

Commit

Permalink
Add handling of command SHOW FULL COLUMNS FOR {table_name}
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Andrew Svirin committed Oct 5, 2021
1 parent 5724968 commit 3544708
Show file tree
Hide file tree
Showing 8 changed files with 327 additions and 11 deletions.
39 changes: 36 additions & 3 deletions src/FakePdoStatementTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,19 +137,41 @@ public function universalExecute(?array $params = null)
$create_queries = (new Parser\CreateTableParser())->parse($sql);

foreach ($create_queries as $create_query) {
if (strpos($create_query->name, '.')) {
list($databaseName, $tableName) = explode('.', $create_query->name, 2);
} else {
$databaseName = $this->conn->getDatabaseName();
$tableName = $create_query->name;
}
$this->conn->getServer()->addTableDefinition(
$this->conn->getDatabaseName(),
$create_query->name,
$databaseName,
$tableName,
Processor\CreateProcessor::makeTableDefinition(
$create_query,
$this->conn->getDatabaseName()
$databaseName
)
);
}

return true;
}

// Check that there are multiple INSERT commands in the sql.
$insertPos1 = stripos($sql, 'INSERT INTO');
$insertPos2 = strripos($sql, 'INSERT INTO');
if (false !== $insertPos1 && $insertPos1 !== $insertPos2) {
$insert_queries = (new Parser\InsertMultipleParser())->parse($sql);
foreach ($insert_queries as $insert_query) {
$this->affectedRows += Processor\InsertProcessor::process(
$this->conn,
new Processor\Scope($this->boundValues),
$insert_query
);
}

return true;
}

//echo "\n" . $sql . "\n";

try {
Expand Down Expand Up @@ -284,6 +306,17 @@ function ($row) {
);
break;

case Query\ShowColumnsQuery::class:
$this->result = self::processResult(
$this->conn,
Processor\ShowColumnsProcessor::process(
$this->conn,
new Processor\Scope(array_merge($params ?? [], $this->boundValues)),
$parsed_query
)
);
break;

default:
throw new \UnexpectedValueException('Unsupported operation type ' . $sql);
}
Expand Down
12 changes: 11 additions & 1 deletion src/FakePdoTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function __construct(string $dsn, string $username = '', string $passwd =
$dsn = \Nyholm\Dsn\DsnParser::parse($dsn);
$host = $dsn->getHost();

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

Expand Down Expand Up @@ -87,6 +87,16 @@ public function setAttribute($key, $value)
return true;
}

public function getAttribute($key)
{
switch ($key) {
case \PDO::ATTR_CASE:
$value = $this->lowercaseResultKeys ? \PDO::CASE_LOWER : \PDO::CASE_UPPER;
}

return $value;
}

public function getServer() : Server
{
return $this->server;
Expand Down
8 changes: 7 additions & 1 deletion src/Parser/CreateTableParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,13 @@ private static function parseCreateTable(array $tokens, string $sql) : CreateQue
\array_shift($tokens);
}

$t = \array_shift($tokens);
// Extract [{database}.]{table}
if ($tokens[1] === '.') {
$t = \array_shift($tokens) . \array_shift($tokens) . \array_shift($tokens);
} else {
$t = \array_shift($tokens);
}

$name = static::decodeIdentifier($t);

if (static::nextTokenIs($tokens, 'LIKE')) {
Expand Down
79 changes: 79 additions & 0 deletions src/Parser/InsertMultipleParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

namespace Vimeo\MysqlEngine\Parser;

use Vimeo\MysqlEngine\Query\CreateQuery;
use Vimeo\MysqlEngine\Query\Expression\Expression;
use Vimeo\MysqlEngine\TokenType;
use Vimeo\MysqlEngine\Query\InsertQuery;

final class InsertMultipleParser
{
/**
* @return array<string, CreateQuery>
*/
public function parse(string $sql): array
{
return self::walk($this->splitStatements($sql));
}

/**
* @var list<string>
*/
private $tokens = [];

/**
* @var array<int, array{0:int, 1:int}>
*/
private $sourceMap = [];

/**
* @return non-empty-list<string>
*/
private function splitStatements(string $sql): array
{
$re_split_sql = '%
# Match an SQL record ending with ";"
\s* # Discard leading whitespace.
( # $1: Trimmed non-empty SQL record.
(?: # Group for content alternatives.
\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\' # Either a single quoted string,
| "[^"\\\\]*(?:\\\\.[^"\\\\]*)*" # or a double quoted string,
| /\*[^*]*\*+(?:[^*/][^*]*\*+)*/ # or a multi-line comment,
| \#.* # or a # single line comment,
| --.* # or a -- single line comment,
| [^"\';#] # or one non-["\';#-]
)+ # One or more content alternatives
(?:;|$) # Record end is a ; or string end.
) # End $1: Trimmed SQL record.
%xs';

if (preg_match_all($re_split_sql, $sql, $matches)) {
$statements = $matches[1];
}

return $statements ?? [];
}

/**
* @param array<string> $statements
*
* @return array<string, InsertQuery>
*/
private static function walk(array $statements)
{
$result = [];

foreach ($statements as $statement) {
$statement = trim($statement);
if (false === stripos($statement, 'INSERT INTO')) {
continue;
}
$statement = rtrim($statement, ';');

$result[] = SQLParser::parse($statement);
}

return $result;
}
}
10 changes: 6 additions & 4 deletions src/Parser/SQLParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
InsertQuery,
UpdateQuery,
DropTableQuery,
ShowTablesQuery};
ShowTablesQuery,
ShowColumnsQuery
};

final class SQLParser
{
Expand Down Expand Up @@ -141,11 +143,11 @@ final class SQLParser
'TABLES' => true,
];

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

/**
* @return SelectQuery|InsertQuery|UpdateQuery|TruncateQuery|DeleteQuery|DropTableQuery|ShowTablesQuery|ShowIndexQuery
* @return SelectQuery|InsertQuery|UpdateQuery|TruncateQuery|DeleteQuery|DropTableQuery|ShowTablesQuery|ShowIndexQuery|ShowColumnsQuery
*/
public static function parse(string $sql)
{
Expand All @@ -157,7 +159,7 @@ public static function parse(string $sql)
}

/**
* @return SelectQuery|InsertQuery|UpdateQuery|TruncateQuery|DeleteQuery|DropTableQuery|ShowTablesQuery|ShowIndexQuery
* @return SelectQuery|InsertQuery|UpdateQuery|TruncateQuery|DeleteQuery|DropTableQuery|ShowTablesQuery|ShowIndexQuery|ShowColumnsQuery
*/
private static function parseImpl(string $sql)
{
Expand Down
45 changes: 43 additions & 2 deletions src/Parser/ShowParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

namespace Vimeo\MysqlEngine\Parser;

use Vimeo\MysqlEngine\Query\ShowColumnsQuery;
use Vimeo\MysqlEngine\Query\ShowIndexQuery;
use Vimeo\MysqlEngine\TokenType;
use Vimeo\MysqlEngine\Query\ShowTablesQuery;
use Vimeo\MysqlEngine\TokenType;

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

$this->pointer++;

// For case with TABLES and COLUMNS could be optinaly used argument FULL.
if ($this->tokens[$this->pointer]->value === 'FULL') {
$isFull = true;
$this->pointer++;
}

switch ($this->tokens[$this->pointer]->value) {
case 'TABLES':
return $this->parseShowTables();
case 'INDEX':
case 'INDEXES':
case 'KEYS':
return $this->parseShowIndex();
case 'COLUMNS':
return $this->parseShowColumns($isFull ?? false);
default:
throw new ParserException("Parser error: expected SHOW TABLES");
}
Expand All @@ -64,7 +73,7 @@ private function parseShowTables(): ShowTablesQuery
$this->pointer++;

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

$this->pointer++;
Expand Down Expand Up @@ -102,6 +111,38 @@ private function parseShowIndex(): ShowIndexQuery
list($this->pointer, $expression) = $expression_parser->buildWithPointer();
$query->whereClause = $expression;
}

return $query;
}

private function parseShowColumns(bool $isFull): ShowColumnsQuery
{
$this->pointer++;

if ($this->tokens[$this->pointer]->value !== 'FROM') {
throw new ParserException("Parser error: expected SHOW [FULL] COLUMNS FROM");
}

$this->pointer++;

$token = $this->tokens[$this->pointer];
if ($token->type !== TokenType::IDENTIFIER) {
throw new ParserException("Expected table name after FROM");
}

$query = new ShowColumnsQuery($token->value, $this->sql);
$query->isFull = $isFull;
$this->pointer++;

if ($this->pointer < count($this->tokens)) {
if ($this->tokens[$this->pointer]->value !== 'WHERE') {
throw new ParserException("Parser error: expected SHOW [FULL] COLUMNS FROM [TABLE_NAME] WHERE");
}
$expression_parser = new ExpressionParser($this->tokens, $this->pointer);
list($this->pointer, $expression) = $expression_parser->buildWithPointer();
$query->whereClause = $expression;
}

return $query;
}
}
Loading

0 comments on commit 3544708

Please sign in to comment.