Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle in dsn optional dbname delimiter #29

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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