From cda30896239f4414dcd32f9fcbbe89de5401c9cb Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sun, 13 Aug 2017 22:15:04 +0200 Subject: [PATCH] Fixed batchInsert casting of double values according to locale (#14448) fixes #6526 --- framework/CHANGELOG.md | 1 + framework/db/QueryBuilder.php | 3 ++ framework/db/oci/QueryBuilder.php | 3 ++ framework/db/pgsql/QueryBuilder.php | 3 ++ framework/db/sqlite/QueryBuilder.php | 3 ++ tests/framework/db/CommandTest.php | 55 ++++++++++++++++++++++++++++ 6 files changed, 68 insertions(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index e93ddb2440..3972e95990 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.13 under development ------------------------ +- Bug #6526: Fixed `yii\db\Command::batchInsert()` casting of double values correctly independent of the locale (cebe, leammas) - Bug #14542: Ensured only ASCII characters are in CSRF cookie value since binary data causes issues with ModSecurity and some browsers (samdark) - Enh #14022: `yii\web\UrlManager::setBaseUrl()` now supports aliases (dmirogin) - Bug #14471: `ContentNegotiator` will always set one of the configured server response formats even if the client does not accept any of them (PowerGamer1) diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index 0aca87cb0c..f20901473c 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -278,6 +278,9 @@ class QueryBuilder extends \yii\base\BaseObject } if (is_string($value)) { $value = $schema->quoteValue($value); + } elseif (is_float($value)) { + // ensure type cast always has . as decimal separator in all locales + $value = str_replace(',', '.', (string) $value); } elseif ($value === false) { $value = 0; } elseif ($value === null) { diff --git a/framework/db/oci/QueryBuilder.php b/framework/db/oci/QueryBuilder.php index 8d38060df7..b3d84c4781 100644 --- a/framework/db/oci/QueryBuilder.php +++ b/framework/db/oci/QueryBuilder.php @@ -268,6 +268,9 @@ EOD; } if (is_string($value)) { $value = $schema->quoteValue($value); + } elseif (is_float($value)) { + // ensure type cast always has . as decimal separator in all locales + $value = str_replace(',', '.', (string) $value); } elseif ($value === false) { $value = 0; } elseif ($value === null) { diff --git a/framework/db/pgsql/QueryBuilder.php b/framework/db/pgsql/QueryBuilder.php index b79533cb0b..904c68c37d 100644 --- a/framework/db/pgsql/QueryBuilder.php +++ b/framework/db/pgsql/QueryBuilder.php @@ -305,6 +305,9 @@ class QueryBuilder extends \yii\db\QueryBuilder } if (is_string($value)) { $value = $schema->quoteValue($value); + } elseif (is_float($value)) { + // ensure type cast always has . as decimal separator in all locales + $value = str_replace(',', '.', (string) $value); } elseif ($value === true) { $value = 'TRUE'; } elseif ($value === false) { diff --git a/framework/db/sqlite/QueryBuilder.php b/framework/db/sqlite/QueryBuilder.php index c9f9fa07c8..ad1b0e86ee 100644 --- a/framework/db/sqlite/QueryBuilder.php +++ b/framework/db/sqlite/QueryBuilder.php @@ -101,6 +101,9 @@ class QueryBuilder extends \yii\db\QueryBuilder } if (is_string($value)) { $value = $schema->quoteValue($value); + } elseif (is_float($value)) { + // ensure type cast always has . as decimal separator in all locales + $value = str_replace(',', '.', (string) $value); } elseif ($value === false) { $value = 0; } elseif ($value === null) { diff --git a/tests/framework/db/CommandTest.php b/tests/framework/db/CommandTest.php index 2fa57d8440..1ecfd500f6 100644 --- a/tests/framework/db/CommandTest.php +++ b/tests/framework/db/CommandTest.php @@ -312,6 +312,61 @@ SQL; } } + /** + * Test batch insert with different data types. + * + * Ensure double is inserted with `.` decimal separator. + * + * https://github.com/yiisoft/yii2/issues/6526 + */ + public function testBatchInsertDataTypesLocale() + { + $locale = setlocale(LC_NUMERIC, 0); + if (false === $locale) { + $this->markTestSkipped('Your platform does not support locales.'); + } + $db = $this->getConnection(); + + try { + // This one sets decimal mark to comma sign + setlocale(LC_NUMERIC, 'ru_RU.utf8'); + + $cols = ['int_col', 'char_col', 'float_col', 'bool_col']; + $data = [ + [1, 'A', 9.735, true], + [2, 'B', -2.123, false], + [3, 'C', 2.123, false], + ]; + + // clear data in "type" table + $db->createCommand()->delete('type')->execute(); + // batch insert on "type" table + $db->createCommand()->batchInsert('type', $cols, $data)->execute(); + + $data = $db->createCommand("SELECT * FROM {{type}} WHERE [[int_col]] IN (1,2,3) ORDER BY [[int_col]];")->queryAll(); + $this->assertEquals(3, count($data)); + $this->assertEquals(1, $data[0]['int_col']); + $this->assertEquals(2, $data[1]['int_col']); + $this->assertEquals(3, $data[2]['int_col']); + $this->assertEquals('A', rtrim($data[0]['char_col'])); // rtrim because Postgres padds the column with whitespace + $this->assertEquals('B', rtrim($data[1]['char_col'])); + $this->assertEquals('C', rtrim($data[2]['char_col'])); + $this->assertEquals('9.735', $data[0]['float_col']); + $this->assertEquals('-2.123', $data[1]['float_col']); + $this->assertEquals('2.123', $data[2]['float_col']); + $this->assertEquals('1', $data[0]['bool_col']); + $this->assertIsOneOf($data[1]['bool_col'], ['0', false]); + $this->assertIsOneOf($data[2]['bool_col'], ['0', false]); + + } catch (\Exception $e) { + setlocale(LC_NUMERIC, $locale); + throw $e; + } catch (\Throwable $e) { + setlocale(LC_NUMERIC, $locale); + throw $e; + } + } + public function testInsert() { $db = $this->getConnection();