Skip to content
Merged
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
29 changes: 15 additions & 14 deletions src/Shared/Infrastructure/Database/SqlFileParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,35 +36,36 @@ class SqlFileParser
*/
public static function parseFile(string $filename): array
{
$handle = @fopen($filename, 'r');
if ($handle === false) {
$content = @file_get_contents($filename);
if ($content === false) {
return array();
}
// Normalize line endings up front so statement splitting does not depend
// on the file's origin OS. A CRLF/CR file (e.g. committed from Windows or
// baked into an image) must parse the same as an LF file on a Linux host,
// where PHP_EOL is "\n" and would never match a ";\r\n" boundary.
$content = str_replace(["\r\n", "\r"], "\n", $content);

$queries_list = array();
$curr_content = '';
while ($stream = fgets($handle)) {
foreach (explode("\n", $content) as $line) {
// Skip comments
if (str_starts_with($stream, '-- ')) {
if (str_starts_with($line, '-- ')) {
continue;
}
// Add stream to accumulator
$curr_content .= $stream;
// Get queries
$queries = explode(';' . PHP_EOL, $curr_content);
// Replace line by remainders of the last element (incomplete line)
// Add line (with its newline restored) to the accumulator
$curr_content .= $line . "\n";
// Pull out every complete statement, keep the trailing remainder
$queries = explode(";\n", $curr_content);
$curr_content = array_pop($queries);
foreach ($queries as $query) {
$queries_list[] = trim($query);
}
}
// Add final query if there's any remaining content
if (!empty(trim($curr_content))) {
if (trim($curr_content) !== '') {
$queries_list[] = trim($curr_content);
}
if (!feof($handle)) {
// Throw error
}
fclose($handle);
return $queries_list;
}
}
40 changes: 40 additions & 0 deletions tests/backend/Core/Utils/SqlFileParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Lwt\Tests\Core\Utils;

use Lwt\Shared\Infrastructure\Database\SqlFileParser;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

/**
Expand Down Expand Up @@ -52,4 +53,43 @@ public function testParseFileNonexistent(): void
$this->assertIsArray($result);
$this->assertEmpty($result);
}

/**
* Statements must be split regardless of the file's line endings.
*
* Regression test for a CRLF schema file producing a single un-split blob
* that mysqli rejects with error 1064 on a Linux host (issue #241).
*/
#[DataProvider('lineEndingProvider')]
public function testParseFileSplitsStatementsForAnyLineEnding(string $eol): void
{
$sqlContent = "-- Test SQL file" . $eol .
"CREATE TABLE a (id INT);" . $eol .
"CREATE TABLE b (id INT);" . $eol .
"INSERT INTO a VALUES (1);";

$tempFile = sys_get_temp_dir() . '/test_sql_' . uniqid() . '.sql';
file_put_contents($tempFile, $sqlContent);

$queries = SqlFileParser::parseFile($tempFile);
unlink($tempFile);

// Each statement must come back individually, not concatenated.
$this->assertSame(
['CREATE TABLE a (id INT)', 'CREATE TABLE b (id INT)', 'INSERT INTO a VALUES (1)'],
$queries
);
}

/**
* @return array<string, array{string}>
*/
public static function lineEndingProvider(): array
{
return [
'LF (Unix)' => ["\n"],
'CRLF (Windows)' => ["\r\n"],
'CR (classic Mac)' => ["\r"],
];
}
}