asdasd
This commit is contained in:
@@ -3,6 +3,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Modules\MiningChecker\Infrastructure;
|
namespace Modules\MiningChecker\Infrastructure;
|
||||||
|
|
||||||
|
use App\SqlDataImporter;
|
||||||
use Modules\MiningChecker\Support\ApiException;
|
use Modules\MiningChecker\Support\ApiException;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ final class SchemaManager
|
|||||||
private string $prefix;
|
private string $prefix;
|
||||||
private string $moduleBasePath;
|
private string $moduleBasePath;
|
||||||
private string $driver;
|
private string $driver;
|
||||||
|
private SqlDataImporter $sqlImporter;
|
||||||
|
|
||||||
public function __construct(PDO $pdo, string $prefix, string $moduleBasePath)
|
public function __construct(PDO $pdo, string $prefix, string $moduleBasePath)
|
||||||
{
|
{
|
||||||
@@ -19,6 +21,7 @@ final class SchemaManager
|
|||||||
$this->prefix = $prefix;
|
$this->prefix = $prefix;
|
||||||
$this->moduleBasePath = rtrim($moduleBasePath, '/');
|
$this->moduleBasePath = rtrim($moduleBasePath, '/');
|
||||||
$this->driver = strtolower((string) $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME));
|
$this->driver = strtolower((string) $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME));
|
||||||
|
$this->sqlImporter = new SqlDataImporter($this->pdo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ensureSchema(): void
|
public function ensureSchema(): void
|
||||||
@@ -391,7 +394,7 @@ final class SchemaManager
|
|||||||
throw new ApiException('Die hochgeladene SQL-Datei ist leer oder konnte nicht gelesen werden.', 422, ['file' => $originalName]);
|
throw new ApiException('Die hochgeladene SQL-Datei ist leer oder konnte nicht gelesen werden.', 422, ['file' => $originalName]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$statementCount = $this->executeSqlStatements($this->splitSqlStatements($sql), $originalName);
|
$statementCount = $this->executeSqlContent($sql, $originalName);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'file' => $originalName,
|
'file' => $originalName,
|
||||||
@@ -413,283 +416,38 @@ final class SchemaManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
$sql = (string) file_get_contents($schemaFile);
|
$sql = (string) file_get_contents($schemaFile);
|
||||||
$this->executeSqlStatements($this->splitSqlStatements($sql), $schemaFile, 'Schema-Import fuer Mining-Checker fehlgeschlagen.');
|
$this->executeSqlContent($sql, $schemaFile, 'Schema-Import fuer Mining-Checker fehlgeschlagen.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function executeSqlContent(string $sql, string $sourceLabel, string $errorMessage = 'SQL-Import fuer Mining-Checker fehlgeschlagen.'): int
|
||||||
* @param list<string> $statements
|
|
||||||
*/
|
|
||||||
private function executeSqlStatements(array $statements, string $sourceLabel, string $errorMessage = 'SQL-Import fuer Mining-Checker fehlgeschlagen.'): int
|
|
||||||
{
|
{
|
||||||
$currentStatement = null;
|
|
||||||
$useTransaction = $this->driver === 'pgsql' && !$this->pdo->inTransaction();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($useTransaction) {
|
return $this->sqlImporter->importString($sql);
|
||||||
$this->pdo->beginTransaction();
|
} catch (\RuntimeException $exception) {
|
||||||
}
|
$previous = $exception->getPrevious() ?? $exception;
|
||||||
|
|
||||||
$normalizedStatements = [];
|
throw new ApiException(
|
||||||
foreach ($statements as $statement) {
|
$errorMessage,
|
||||||
$trimmed = trim($statement);
|
500,
|
||||||
if ($trimmed === '') {
|
[
|
||||||
continue;
|
'message' => $previous->getMessage(),
|
||||||
}
|
'source' => $sourceLabel,
|
||||||
|
'statement' => substr($exception->getMessage(), 0, 1000),
|
||||||
$normalizedStatements[] = $this->normalizeImportStatement($trimmed);
|
]
|
||||||
}
|
);
|
||||||
|
|
||||||
$executed = $this->executeImportPass($normalizedStatements, $currentStatement);
|
|
||||||
|
|
||||||
if ($useTransaction && $this->pdo->inTransaction()) {
|
|
||||||
$this->pdo->commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $executed;
|
|
||||||
} catch (\Throwable $exception) {
|
} catch (\Throwable $exception) {
|
||||||
if ($useTransaction && $this->pdo->inTransaction()) {
|
|
||||||
$this->pdo->rollBack();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ApiException(
|
throw new ApiException(
|
||||||
$errorMessage,
|
$errorMessage,
|
||||||
500,
|
500,
|
||||||
[
|
[
|
||||||
'message' => $exception->getMessage(),
|
'message' => $exception->getMessage(),
|
||||||
'source' => $sourceLabel,
|
'source' => $sourceLabel,
|
||||||
'statement' => $currentStatement !== null ? substr($currentStatement, 0, 1000) : null,
|
'statement' => null,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param list<string> $statements
|
|
||||||
*/
|
|
||||||
private function executeImportPass(array $statements, ?string &$currentStatement): int
|
|
||||||
{
|
|
||||||
$pendingStatements = $statements;
|
|
||||||
$executed = 0;
|
|
||||||
$maxPasses = max(1, count($pendingStatements));
|
|
||||||
|
|
||||||
for ($pass = 0; $pass < $maxPasses && $pendingStatements !== []; $pass++) {
|
|
||||||
$deferredStatements = [];
|
|
||||||
$progressMade = false;
|
|
||||||
|
|
||||||
foreach ($pendingStatements as $statement) {
|
|
||||||
$currentStatement = $statement;
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->pdo->exec($statement);
|
|
||||||
$executed++;
|
|
||||||
$progressMade = true;
|
|
||||||
} catch (\Throwable $exception) {
|
|
||||||
if ($this->shouldRetryDeferredImportStatement($exception, $statement)) {
|
|
||||||
$deferredStatements[] = $statement;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw $exception;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($deferredStatements === []) {
|
|
||||||
return $executed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$progressMade) {
|
|
||||||
$currentStatement = $deferredStatements[0];
|
|
||||||
$this->pdo->exec($deferredStatements[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$pendingStatements = $deferredStatements;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $executed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function shouldRetryDeferredImportStatement(\Throwable $exception, string $statement): bool
|
|
||||||
{
|
|
||||||
if ($this->driver !== 'pgsql') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!preg_match('/^(INSERT|COPY)\\b/i', ltrim($statement))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$errorCode = (string) $exception->getCode();
|
|
||||||
return $errorCode === '23503';
|
|
||||||
}
|
|
||||||
|
|
||||||
private function normalizeImportStatement(string $statement): string
|
|
||||||
{
|
|
||||||
if ($this->driver !== 'pgsql') {
|
|
||||||
return $statement;
|
|
||||||
}
|
|
||||||
|
|
||||||
$resolvedSetval = $this->normalizePgsqlSetvalStatement($statement);
|
|
||||||
return $resolvedSetval ?? $statement;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function normalizePgsqlSetvalStatement(string $statement): ?string
|
|
||||||
{
|
|
||||||
$pattern = "/^SELECT\\s+setval\\(\\s*'([^']+)'\\s*,\\s*([0-9]+)\\s*,\\s*(true|false)\\s*\\)$/i";
|
|
||||||
if (!preg_match($pattern, trim($statement), $matches)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sequenceReference = $matches[1];
|
|
||||||
$nextValue = $matches[2];
|
|
||||||
$isCalled = strtolower($matches[3]);
|
|
||||||
|
|
||||||
if ($this->pgsqlRelationExists($sequenceReference)) {
|
|
||||||
return $statement;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sequenceName = $sequenceReference;
|
|
||||||
$schemaName = 'public';
|
|
||||||
if (str_contains($sequenceReference, '.')) {
|
|
||||||
[$schemaName, $sequenceName] = explode('.', $sequenceReference, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
$schemaName = trim($schemaName, "\"'");
|
|
||||||
$sequenceName = trim($sequenceName, "\"'");
|
|
||||||
|
|
||||||
if (!preg_match('/^(.*)_([A-Za-z0-9]+)_seq\d*$/', $sequenceName, $parts)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$tableName = $parts[1];
|
|
||||||
$columnName = $parts[2];
|
|
||||||
$actualSequence = $this->resolvePgsqlSerialSequence($schemaName, $tableName, $columnName);
|
|
||||||
if ($actualSequence === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sprintf("SELECT setval('%s', %s, %s)", $actualSequence, $nextValue, $isCalled);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function pgsqlRelationExists(string $qualifiedName): bool
|
|
||||||
{
|
|
||||||
$statement = $this->pdo->prepare('SELECT to_regclass(:name) IS NOT NULL');
|
|
||||||
$statement->execute(['name' => $qualifiedName]);
|
|
||||||
return (bool) $statement->fetchColumn();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function resolvePgsqlSerialSequence(string $schemaName, string $tableName, string $columnName): ?string
|
|
||||||
{
|
|
||||||
$qualifiedTable = sprintf('"%s"."%s"', str_replace('"', '""', $schemaName), str_replace('"', '""', $tableName));
|
|
||||||
$statement = $this->pdo->prepare('SELECT pg_get_serial_sequence(:table_name, :column_name)');
|
|
||||||
$statement->execute([
|
|
||||||
'table_name' => $qualifiedTable,
|
|
||||||
'column_name' => $columnName,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$result = $statement->fetchColumn();
|
|
||||||
return is_string($result) && $result !== '' ? $result : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return list<string>
|
|
||||||
*/
|
|
||||||
private function splitSqlStatements(string $sql): array
|
|
||||||
{
|
|
||||||
$statements = [];
|
|
||||||
$buffer = '';
|
|
||||||
$length = strlen($sql);
|
|
||||||
$inSingleQuote = false;
|
|
||||||
$inDoubleQuote = false;
|
|
||||||
$inBacktickQuote = false;
|
|
||||||
$inLineComment = false;
|
|
||||||
$inBlockComment = false;
|
|
||||||
|
|
||||||
for ($index = 0; $index < $length; $index++) {
|
|
||||||
$char = $sql[$index];
|
|
||||||
$next = $index + 1 < $length ? $sql[$index + 1] : '';
|
|
||||||
|
|
||||||
if ($inLineComment) {
|
|
||||||
if ($char === "\n") {
|
|
||||||
$inLineComment = false;
|
|
||||||
$buffer .= $char;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($inBlockComment) {
|
|
||||||
if ($char === '*' && $next === '/') {
|
|
||||||
$inBlockComment = false;
|
|
||||||
$index++;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$inSingleQuote && !$inDoubleQuote && !$inBacktickQuote) {
|
|
||||||
if ($char === '-' && $next === '-') {
|
|
||||||
$afterNext = $index + 2 < $length ? $sql[$index + 2] : '';
|
|
||||||
if ($afterNext === '' || ctype_space($afterNext)) {
|
|
||||||
$inLineComment = true;
|
|
||||||
$index++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($char === '#') {
|
|
||||||
$inLineComment = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($char === '/' && $next === '*') {
|
|
||||||
$inBlockComment = true;
|
|
||||||
$index++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($char === "'" && !$inDoubleQuote && !$inBacktickQuote) {
|
|
||||||
$escaped = $index > 0 && $sql[$index - 1] === '\\';
|
|
||||||
if (!$escaped) {
|
|
||||||
$inSingleQuote = !$inSingleQuote;
|
|
||||||
}
|
|
||||||
$buffer .= $char;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($char === '"' && !$inSingleQuote && !$inBacktickQuote) {
|
|
||||||
$escaped = $index > 0 && $sql[$index - 1] === '\\';
|
|
||||||
if (!$escaped) {
|
|
||||||
$inDoubleQuote = !$inDoubleQuote;
|
|
||||||
}
|
|
||||||
$buffer .= $char;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($char === '`' && !$inSingleQuote && !$inDoubleQuote) {
|
|
||||||
$inBacktickQuote = !$inBacktickQuote;
|
|
||||||
$buffer .= $char;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($char === ';' && !$inSingleQuote && !$inDoubleQuote && !$inBacktickQuote) {
|
|
||||||
$trimmed = trim($buffer);
|
|
||||||
if ($trimmed !== '') {
|
|
||||||
$statements[] = $trimmed;
|
|
||||||
}
|
|
||||||
$buffer = '';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$buffer .= $char;
|
|
||||||
}
|
|
||||||
|
|
||||||
$trimmed = trim($buffer);
|
|
||||||
if ($trimmed !== '') {
|
|
||||||
$statements[] = $trimmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $statements;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function dropExistingTables(): array
|
private function dropExistingTables(): array
|
||||||
{
|
{
|
||||||
$tables = array_reverse($this->schemaStatus()['present_tables']);
|
$tables = array_reverse($this->schemaStatus()['present_tables']);
|
||||||
|
|||||||
288
src/App/SqlDataImporter.php
Normal file
288
src/App/SqlDataImporter.php
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
final class SqlDataImporter
|
||||||
|
{
|
||||||
|
private PDO $pdo;
|
||||||
|
private string $driver;
|
||||||
|
|
||||||
|
public function __construct(PDO $pdo)
|
||||||
|
{
|
||||||
|
$this->pdo = $pdo;
|
||||||
|
$this->driver = strtolower((string) $pdo->getAttribute(PDO::ATTR_DRIVER_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function importString(string $sql): int
|
||||||
|
{
|
||||||
|
$statements = [];
|
||||||
|
foreach ($this->splitStatements($sql) as $statement) {
|
||||||
|
$trimmed = trim($statement);
|
||||||
|
if ($trimmed === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$statements[] = $this->normalizeImportStatement($trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->executeStatements($statements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string> $statements
|
||||||
|
*/
|
||||||
|
private function executeStatements(array $statements): int
|
||||||
|
{
|
||||||
|
$useTransaction = $this->driver === 'pgsql' && !$this->pdo->inTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($useTransaction) {
|
||||||
|
$this->pdo->beginTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
$executed = $this->executeImportPass($statements);
|
||||||
|
|
||||||
|
if ($useTransaction && $this->pdo->inTransaction()) {
|
||||||
|
$this->pdo->commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $executed;
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
if ($useTransaction && $this->pdo->inTransaction()) {
|
||||||
|
$this->pdo->rollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw $exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string> $statements
|
||||||
|
*/
|
||||||
|
private function executeImportPass(array $statements): int
|
||||||
|
{
|
||||||
|
$pendingStatements = $statements;
|
||||||
|
$executed = 0;
|
||||||
|
$maxPasses = max(1, count($pendingStatements));
|
||||||
|
|
||||||
|
for ($pass = 0; $pass < $maxPasses && $pendingStatements !== []; $pass++) {
|
||||||
|
$deferredStatements = [];
|
||||||
|
$progressMade = false;
|
||||||
|
|
||||||
|
foreach ($pendingStatements as $statement) {
|
||||||
|
try {
|
||||||
|
$this->pdo->exec($statement);
|
||||||
|
$executed++;
|
||||||
|
$progressMade = true;
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
if ($this->shouldRetryDeferredImportStatement($exception, $statement)) {
|
||||||
|
$deferredStatements[] = $statement;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \RuntimeException($statement, 0, $exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($deferredStatements === []) {
|
||||||
|
return $executed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$progressMade) {
|
||||||
|
try {
|
||||||
|
$this->pdo->exec($deferredStatements[0]);
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
throw new \RuntimeException($deferredStatements[0], 0, $exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pendingStatements = $deferredStatements;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $executed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function shouldRetryDeferredImportStatement(\Throwable $exception, string $statement): bool
|
||||||
|
{
|
||||||
|
if ($this->driver !== 'pgsql') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!preg_match('/^(INSERT|COPY)\b/i', ltrim($statement))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $exception->getCode() === '23503';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeImportStatement(string $statement): string
|
||||||
|
{
|
||||||
|
if ($this->driver !== 'pgsql') {
|
||||||
|
return $statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
$resolvedSetval = $this->normalizePgsqlSetvalStatement($statement);
|
||||||
|
return $resolvedSetval ?? $statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizePgsqlSetvalStatement(string $statement): ?string
|
||||||
|
{
|
||||||
|
$pattern = "/^SELECT\\s+setval\\(\\s*'([^']+)'\\s*,\\s*([0-9]+)\\s*,\\s*(true|false)\\s*\\)$/i";
|
||||||
|
if (!preg_match($pattern, trim($statement), $matches)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sequenceReference = $matches[1];
|
||||||
|
$nextValue = $matches[2];
|
||||||
|
$isCalled = strtolower($matches[3]);
|
||||||
|
|
||||||
|
if ($this->pgsqlRelationExists($sequenceReference)) {
|
||||||
|
return $statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sequenceName = $sequenceReference;
|
||||||
|
$schemaName = 'public';
|
||||||
|
if (str_contains($sequenceReference, '.')) {
|
||||||
|
[$schemaName, $sequenceName] = explode('.', $sequenceReference, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
$schemaName = trim($schemaName, "\"'");
|
||||||
|
$sequenceName = trim($sequenceName, "\"'");
|
||||||
|
|
||||||
|
if (!preg_match('/^(.*)_([A-Za-z0-9]+)_seq\d*$/', $sequenceName, $parts)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tableName = $parts[1];
|
||||||
|
$columnName = $parts[2];
|
||||||
|
$actualSequence = $this->resolvePgsqlSerialSequence($schemaName, $tableName, $columnName);
|
||||||
|
if ($actualSequence === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf("SELECT setval('%s', %s, %s)", $actualSequence, $nextValue, $isCalled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function pgsqlRelationExists(string $qualifiedName): bool
|
||||||
|
{
|
||||||
|
$statement = $this->pdo->prepare('SELECT to_regclass(:name) IS NOT NULL');
|
||||||
|
$statement->execute(['name' => $qualifiedName]);
|
||||||
|
return (bool) $statement->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolvePgsqlSerialSequence(string $schemaName, string $tableName, string $columnName): ?string
|
||||||
|
{
|
||||||
|
$qualifiedTable = sprintf('"%s"."%s"', str_replace('"', '""', $schemaName), str_replace('"', '""', $tableName));
|
||||||
|
$statement = $this->pdo->prepare('SELECT pg_get_serial_sequence(:table_name, :column_name)');
|
||||||
|
$statement->execute([
|
||||||
|
'table_name' => $qualifiedTable,
|
||||||
|
'column_name' => $columnName,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = $statement->fetchColumn();
|
||||||
|
return is_string($result) && $result !== '' ? $result : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
private function splitStatements(string $sql): array
|
||||||
|
{
|
||||||
|
$statements = [];
|
||||||
|
$buffer = '';
|
||||||
|
$length = strlen($sql);
|
||||||
|
$inSingleQuote = false;
|
||||||
|
$inDoubleQuote = false;
|
||||||
|
$inBacktickQuote = false;
|
||||||
|
$inLineComment = false;
|
||||||
|
$inBlockComment = false;
|
||||||
|
|
||||||
|
for ($index = 0; $index < $length; $index++) {
|
||||||
|
$char = $sql[$index];
|
||||||
|
$next = $index + 1 < $length ? $sql[$index + 1] : '';
|
||||||
|
|
||||||
|
if ($inLineComment) {
|
||||||
|
if ($char === "\n") {
|
||||||
|
$inLineComment = false;
|
||||||
|
$buffer .= $char;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($inBlockComment) {
|
||||||
|
if ($char === '*' && $next === '/') {
|
||||||
|
$inBlockComment = false;
|
||||||
|
$index++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$inSingleQuote && !$inDoubleQuote && !$inBacktickQuote) {
|
||||||
|
if ($char === '-' && $next === '-') {
|
||||||
|
$afterNext = $index + 2 < $length ? $sql[$index + 2] : '';
|
||||||
|
if ($afterNext === '' || ctype_space($afterNext)) {
|
||||||
|
$inLineComment = true;
|
||||||
|
$index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($char === '#') {
|
||||||
|
$inLineComment = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($char === '/' && $next === '*') {
|
||||||
|
$inBlockComment = true;
|
||||||
|
$index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($char === "'" && !$inDoubleQuote && !$inBacktickQuote) {
|
||||||
|
$escaped = $index > 0 && $sql[$index - 1] === '\\';
|
||||||
|
if (!$escaped) {
|
||||||
|
$inSingleQuote = !$inSingleQuote;
|
||||||
|
}
|
||||||
|
$buffer .= $char;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($char === '"' && !$inSingleQuote && !$inBacktickQuote) {
|
||||||
|
$escaped = $index > 0 && $sql[$index - 1] === '\\';
|
||||||
|
if (!$escaped) {
|
||||||
|
$inDoubleQuote = !$inDoubleQuote;
|
||||||
|
}
|
||||||
|
$buffer .= $char;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($char === '`' && !$inSingleQuote && !$inDoubleQuote) {
|
||||||
|
$inBacktickQuote = !$inBacktickQuote;
|
||||||
|
$buffer .= $char;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($char === ';' && !$inSingleQuote && !$inDoubleQuote && !$inBacktickQuote) {
|
||||||
|
$trimmed = trim($buffer);
|
||||||
|
if ($trimmed !== '') {
|
||||||
|
$statements[] = $trimmed;
|
||||||
|
}
|
||||||
|
$buffer = '';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffer .= $char;
|
||||||
|
}
|
||||||
|
|
||||||
|
$trimmed = trim($buffer);
|
||||||
|
if ($trimmed !== '') {
|
||||||
|
$statements[] = $trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $statements;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user