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
13 changes: 11 additions & 2 deletions src/Application/Export/Services/BackupFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,17 @@ private function backupTables(string $dbName, BackupHandlers $handlers): void
function (mixed $value) {
if ($value === null) {
return 'NULL';
} elseif (is_numeric($value)) {
return $value;
}

// Decided by what the column is, not by what the value looks like. PDO
// hands integer columns back as ints and every text column as a string, so
// only a real number is written bare. This used to ask is_numeric(), which
// is true of text that merely looks numeric — a login of `0123`, an account
// named `0800`, a note reading `1e5` — and a bare literal is a number to
// MySQL, so restoring the backup stored `123`, `800` and `100000` in their
// place. A quoted number is still read correctly into a numeric column.
if (is_int($value) || is_float($value)) {
return (string)$value;
}

return $this->databaseUtil->escape((string)$value);
Expand Down
53 changes: 53 additions & 0 deletions tests/Unit/Application/Export/Services/FileBackupServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,59 @@ public function testDoBackup(): void
$this->assertSame(TMP_PATH, $this->builtPath);
}

/**
* Text that looks like a number is dumped as text.
*
* The dump used to write any value is_numeric() accepted as a bare literal, and a bare literal
* is a number to MySQL — so restoring a login of `0123`, an account named `0800` or a note of
* `1e5` stored `123`, `800` and `100000` in their place, and ` 7` lost its space. Only what PDO
* hands back as a number — an integer or float column — is written bare now.
*
* @throws ServiceException
* @throws Exception
*/
public function testTextThatLooksLikeANumberIsDumpedAsText(): void
{
$this->config->getConfigData()->setDbName('a_db');

$this->database->method('runQuery')->willReturnCallback(
fn() => $this->buildCreateResult('table')
);
$this->database->method('doFetchWithOptions')->willReturnCallback(static function () {
yield ['0123', 5, '1e5', ' 7', 2.5, null];
});

$databaseUtil = $this->createStub(DatabaseUtilService::class);
$databaseUtil->method('escape')->willReturnCallback(static fn(string $value) => "'" . $value . "'");

$written = [];

$this->dbFileHandler->method('write')->willReturnCallback(
function (string $data) use (&$written) {
$written[] = $data;

return $this->dbFileHandler;
}
);

$backup = new BackupFile(
$this->application,
$this->database,
$databaseUtil,
new \SP\Infrastructure\Database\QueryDataFactory(),
$this->backupHandlersFactory
);

$backup->doBackup(TMP_PATH, APP_PATH);

$table = DatabaseUtilService::TABLES[0];

self::assertContains(
sprintf("INSERT INTO `%s` VALUES('0123',5,'1e5',' 7',2.5,NULL);", $table) . PHP_EOL,
$written
);
}

private function buildCreateResult(string $type): QueryResult
{
$data = new stdClass();
Expand Down
Loading