From ce12a87858394d62e8acecd2f8b6cbfa7e7a3b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ey=C3=BCp=20Can=20Akman?= Date: Mon, 27 Apr 2026 16:47:18 +0300 Subject: [PATCH] Fix #20284 - Preserve numeric strings in XML imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the XML file embeds the table structure, $analyses is null and buildSql() falls back to is_numeric() to decide whether to quote. That strips quotes from values like "01234", which silently drops the leading zero on a varchar column and causes a duplicate-key error if the table also contains "1234". Add a roundtrip check: a value is still treated as numeric only when casting it to int or float gives back the original string. "01234", "1e10" and similar fail the roundtrip and stay quoted; plain integers like 1 and 1234 keep their existing unquoted form. Signed-off-by: Eyüp Can Akman --- libraries/classes/Import.php | 12 +++-- phpstan-baseline.neon | 10 ++--- test/classes/Plugins/Import/ImportXmlTest.php | 45 +++++++++++++++++++ ...n_importXML_Numeric_String_For_Testing.xml | 43 ++++++++++++++++++ 4 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 test/test_data/phpmyadmin_importXML_Numeric_String_For_Testing.xml diff --git a/libraries/classes/Import.php b/libraries/classes/Import.php index b647a544db..363c9f2485 100644 --- a/libraries/classes/Import.php +++ b/libraries/classes/Import.php @@ -1176,20 +1176,24 @@ class Import ) { $tempSQLStr .= (string) $tables[$i][self::ROWS][$j][$k]; } else { + $value = (string) $tables[$i][self::ROWS][$j][$k]; if ($analyses != null) { $isVarchar = ($analyses[$i][self::TYPES][$colCount] === self::VARCHAR); } else { - $isVarchar = ! is_numeric($tables[$i][self::ROWS][$j][$k]) - && ! preg_match('/^0x[0-9a-f]+$/', (string) $tables[$i][self::ROWS][$j][$k]); + $isVarchar = ! preg_match('/^0x[0-9a-f]+$/', $value) + && ( + ! is_numeric($value) + || ($value !== (string) (int) $value && $value !== (string) (float) $value) + ); } /* Don't put quotes around NULL fields */ - if (! strcmp((string) $tables[$i][self::ROWS][$j][$k], 'NULL')) { + if (! strcmp($value, 'NULL')) { $isVarchar = false; } $tempSQLStr .= $isVarchar ? "'" : ''; - $tempSQLStr .= $dbi->escapeString((string) $tables[$i][self::ROWS][$j][$k]); + $tempSQLStr .= $dbi->escapeString($value); $tempSQLStr .= $isVarchar ? "'" : ''; } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 59e93e1623..ac006880f9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -20192,12 +20192,12 @@ parameters: - message: "#^Cannot access offset 2 on mixed\\.$#" - count: 9 + count: 6 path: libraries/classes/Import.php - message: "#^Cannot access offset int\\<0, max\\> on mixed\\.$#" - count: 22 + count: 17 path: libraries/classes/Import.php - @@ -20247,7 +20247,7 @@ parameters: - message: "#^Cannot cast mixed to string\\.$#" - count: 8 + count: 6 path: libraries/classes/Import.php - @@ -46857,12 +46857,12 @@ parameters: - message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\TestCase\\:\\:any\\(\\)\\.$#" - count: 3 + count: 4 path: test/classes/Plugins/Import/ImportXmlTest.php - message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\TestCase\\:\\:returnArgument\\(\\)\\.$#" - count: 3 + count: 4 path: test/classes/Plugins/Import/ImportXmlTest.php - diff --git a/test/classes/Plugins/Import/ImportXmlTest.php b/test/classes/Plugins/Import/ImportXmlTest.php index 66426a4260..5f6a96a620 100644 --- a/test/classes/Plugins/Import/ImportXmlTest.php +++ b/test/classes/Plugins/Import/ImportXmlTest.php @@ -181,6 +181,51 @@ SQL; self::assertTrue($GLOBALS['finished']); } + /** + * Test for doImport with numeric-looking string values + * + * @group medium + * @requires extension simplexml + */ + public function testDoImportNumericStringValues(): void + { + global $sql_query; + + $dbi = $this->getMockBuilder(DatabaseInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $dbi->expects($this->any())->method('escapeString') + ->will($this->returnArgument(0)); + $GLOBALS['dbi'] = $dbi; + + $GLOBALS['import_file'] = 'test/test_data/phpmyadmin_importXML_Numeric_String_For_Testing.xml'; + + $importHandle = new File($GLOBALS['import_file']); + $importHandle->open(); + + $this->object->doImport($importHandle); + + $expectedQuery = <<<'SQL' +CREATE DATABASE IF NOT EXISTS `testdata` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;;USE `testdata`; +CREATE TABLE IF NOT EXISTS `test_data` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `data` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `data` (`data`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + ;INSERT INTO `testdata`.`test_data` (`id`, `data`) VALUES (3, '01234'), + (2, 1234), + (1, 'abcd');; +SQL; + + self::assertSame( + $expectedQuery, + $sql_query + ); + + self::assertTrue($GLOBALS['finished']); + } + /** * Test for doImport using no database dataset * diff --git a/test/test_data/phpmyadmin_importXML_Numeric_String_For_Testing.xml b/test/test_data/phpmyadmin_importXML_Numeric_String_For_Testing.xml new file mode 100644 index 0000000000..b44df4aa58 --- /dev/null +++ b/test/test_data/phpmyadmin_importXML_Numeric_String_For_Testing.xml @@ -0,0 +1,43 @@ + + + + + + + + + CREATE TABLE `test_data` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `data` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `data` (`data`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8; + + + + + + + + + 3 + 01234 +
+ + 2 + 1234 +
+ + 1 + abcd +
+
+