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 all commits
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
57 changes: 54 additions & 3 deletions src/FakePdoStatementTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,19 +137,45 @@ 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;
}

if(false !== stripos($sql, 'SET')){
return true;
}

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

try {
Expand Down Expand Up @@ -282,6 +308,31 @@ function ($row) {
$parsed_query
)
);

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;

case Query\AlterTableAutoincrementQuery::class:
[$databaseName, $tableName] = Processor\Processor::parseTableName($this->conn, $parsed_query->table);
$td = $this->conn->getServer()->getTableDefinition($databaseName, $tableName);

foreach ($td->columns as $columnName => $column) {
if ($column instanceof Schema\Column\IntegerColumn && $column->isAutoIncrement()) {
$td->autoIncrementOffsets[$columnName] = $parsed_query->value - 1;
}
}

break;

default:
Expand Down
14 changes: 13 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,18 @@ 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;
case \PDO::ATTR_SERVER_VERSION:
$value = '5.7.0';
}

return $value;
}

public function getServer() : Server
{
return $this->server;
Expand Down
89 changes: 89 additions & 0 deletions src/Parser/AlterTableParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

namespace Vimeo\MysqlEngine\Parser;

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

/**
* Very limited parser for ALTER TABLE {table} AUTO_INCREMENT=1
*/
final class AlterTableParser
{
/**
* @var int
*/
private $pointer = 0;

/**
* @var array<int, Token>
*/
private $tokens;

/**
* @var string
*/
private $sql;

/**
* @param array<int, Token> $tokens
*/
public function __construct(array $tokens, string $sql)
{
$this->tokens = $tokens;
$this->sql = $sql;
}

/**
* @return AlterTableAutoincrementQuery
* @throws ParserException
*/
public function parse()
{
if ($this->tokens[$this->pointer]->value !== 'ALTER') {
throw new ParserException("Parser error: expected ALTER");
}

$this->pointer++;

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

$this->pointer++;

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

$this->pointer++;

switch ($this->tokens[$this->pointer]->value) {
case 'AUTO_INCREMENT':
return $this->parseAlterTableAutoIncrement($table);
}
}

private function parseAlterTableAutoIncrement(string $table): AlterTableAutoincrementQuery
{
$this->pointer++;

if ($this->tokens[$this->pointer]->value !== '=') {
throw new ParserException("Parser error: expected ALTER TABLE {table} AUTO_INCREMENT=");
}

$this->pointer++;

if ($this->tokens[$this->pointer]->type !== TokenType::NUMERIC_CONSTANT) {
throw new ParserException("Expected numeric after =");
}

$token = $this->tokens[$this->pointer] ?? null;

return new AlterTableAutoincrementQuery($table, $token->value, $this->sql);
}
}
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;
}
}
37 changes: 26 additions & 11 deletions src/Parser/SQLParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
namespace Vimeo\MysqlEngine\Parser;

use Vimeo\MysqlEngine\TokenType;
use Vimeo\MysqlEngine\Query\{SelectQuery,
use Vimeo\MysqlEngine\Query\{AlterTableAutoincrementQuery,
SelectQuery,
DeleteQuery,
ShowIndexQuery,
TruncateQuery,
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|AlterTableAutoincrementQuery
*/
private static function parseImpl(string $sql)
{
Expand All @@ -169,10 +171,20 @@ private static function parseImpl(string $sql)
$tokens = \array_slice($tokens, 1, $close - 1);
$token = $tokens[0];
}
if ($token->type !== TokenType::CLAUSE) {
if ($token->type === TokenType::CLAUSE) {
$command = $token->value;
} elseif ($token->type === TokenType::IDENTIFIER) {
$nextToken = $tokens[1];
if ($nextToken->type === TokenType::RESERVED) {
$command = $token->value . ' ' . $nextToken->value;
} else {
throw new ParserException("Unexpected {$token->value}");
}
} else {
throw new ParserException("Unexpected {$token->value}");
}
switch ($token->value) {

switch ($command) {
case 'SELECT':
$select = new SelectParser(0, $tokens, $sql);
return $select->parse();
Expand All @@ -189,11 +201,14 @@ private static function parseImpl(string $sql)
$truncate = new TruncateParser($tokens, $sql);
return $truncate->parse();
case 'DROP':
$truncate = new DropParser($tokens, $sql);
return $truncate->parse();
$drop = new DropParser($tokens, $sql);
return $drop->parse();
case 'SHOW':
$truncate = new ShowParser($tokens, $sql);
return $truncate->parse();
$show = new ShowParser($tokens, $sql);
return $show->parse();
case 'ALTER TABLE':
$alter = new AlterTableParser($tokens, $sql);
return $alter->parse();
default:
throw new ParserException("Unexpected {$token->value}");
}
Expand Down
Loading