From 8515d7f08073dc72be91626e07c4ee91488650f0 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Wed, 10 Apr 2019 07:43:45 -0700 Subject: [PATCH] Restored relevant changes from #2850, PowerKiKi/default-values-with-backslashes --- UPGRADE.md | 12 ++++ .../DBAL/Platforms/AbstractPlatform.php | 2 +- .../DBAL/Schema/PostgreSqlSchemaManager.php | 19 +++++- .../DBAL/Schema/SqliteSchemaManager.php | 5 +- .../SchemaManagerFunctionalTestCase.php | 61 +++++++++++++++++++ 5 files changed, 93 insertions(+), 6 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index a5ca5ddb6d5..60bc45c4915 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,5 +1,17 @@ # Upgrade to 2.10 +## MINOR BC BREAK: escaped default values + +Default values will be automatically escaped. So default values must now be specified non-escaped. + +Before: + + $column->setDefault('Foo\\\\Bar\\\\Baz'); + +After: + + $column->setDefault('Foo\\Bar\\Baz'); + ## Deprecated `Type::*` constants The constants for built-in types have been moved from `Doctrine\DBAL\Types\Type` to a separate class `Doctrine\DBAL\Types\Types`. diff --git a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php index abb7333ac1d..57664e6b8d6 100644 --- a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php @@ -2311,7 +2311,7 @@ public function getDefaultValueDeclarationSQL($field) return " DEFAULT '" . $this->convertBooleans($default) . "'"; } - return " DEFAULT '" . $default . "'"; + return ' DEFAULT ' . $this->quoteStringLiteral($default); } /** diff --git a/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php b/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php index 8b2ac98457e..509b3641f6f 100644 --- a/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php @@ -395,11 +395,12 @@ protected function _getPortableTableColumnDefinition($tableColumn) $length = null; break; case 'text': - $fixed = false; - break; + case '_varchar': case 'varchar': + $tableColumn['default'] = $this->parseDefaultExpression($tableColumn['default']); + $fixed = false; + break; case 'interval': - case '_varchar': $fixed = false; break; case 'char': @@ -479,4 +480,16 @@ private function fixVersion94NegativeNumericDefaultValue($defaultValue) return $defaultValue; } + + /** + * Parses a default value expression as given by PostgreSQL + */ + private function parseDefaultExpression(?string $default) : ?string + { + if ($default === null) { + return $default; + } + + return str_replace("''", "'", $default); + } } diff --git a/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php b/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php index e819d963f03..94c3e936fbd 100644 --- a/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php @@ -325,8 +325,9 @@ protected function _getPortableTableColumnDefinition($tableColumn) $default = null; } if ($default !== null) { - // SQLite returns strings wrapped in single quotes, so we need to strip them - $default = preg_replace("/^'(.*)'$/", '\1', $default); + // SQLite returns strings wrapped in single quotes and escaped, so we need to strip them + $default = preg_replace("/^'(.*)'$/s", '\1', $default); + $default = str_replace("''", "'", $default); } $notnull = (bool) $tableColumn['notnull']; diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php index aeb9f4c90dd..15c99c0395a 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php @@ -1495,6 +1495,67 @@ public function testCreateAndListSequences() : void self::assertEquals($sequence2InitialValue, $actualSequence2->getInitialValue()); } + /** + * Returns potential escaped literals from all platforms combined. + * + * @see https://dev.mysql.com/doc/refman/5.7/en/string-literals.html + * @see http://www.sqlite.org/lang_expr.html + * @see https://www.postgresql.org/docs/9.6/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-ESCAPE + * + * @return mixed[][] + */ + private function getEscapedLiterals() : iterable + { + return [ + ['An ASCII NUL (X\'00\')', "foo\\0bar"], + ['Single quote, C-style', "foo\\'bar"], + ['Single quote, doubled-style', "foo''bar"], + ['Double quote, C-style', 'foo\\"bar'], + ['Double quote, double-style', 'foo""bar'], + ['Backspace', 'foo\\bbar'], + ['New-line', 'foo\\nbar'], + ['Carriage return', 'foo\\rbar'], + ['Tab', 'foo\\tbar'], + ['ASCII 26 (Control+Z)', 'foo\\Zbar'], + ['Backslash (\)', 'foo\\\\bar'], + ['Percent (%)', 'foo\\%bar'], + ['Underscore (_)', 'foo\\_bar'], + ]; + } + + private function createTableForDefaultValues() : void + { + $table = new Table('string_escaped_default_value'); + foreach ($this->getEscapedLiterals() as $i => $literal) { + $table->addColumn('field' . $i, 'string', ['default' => $literal[1]]); + } + + $table->addColumn('def_foo', 'string'); + $this->schemaManager->dropAndCreateTable($table); + } + + public function testEscapedDefaultValueCanBeIntrospected() : void + { + $this->createTableForDefaultValues(); + + $onlineTable = $this->schemaManager->listTableDetails('string_escaped_default_value'); + foreach ($this->getEscapedLiterals() as $i => $literal) { + self::assertSame($literal[1], $onlineTable->getColumn('field' . $i)->getDefault(), 'should be able introspect the value of default for: ' . $literal[0]); + } + } + + public function testEscapedDefaultValueCanBeInserted() : void + { + $this->createTableForDefaultValues(); + + $this->connection->insert('string_escaped_default_value', ['def_foo' => 'foo']); + + foreach ($this->getEscapedLiterals() as $i => $literal) { + $value = $this->connection->fetchColumn('SELECT field' . $i . ' FROM string_escaped_default_value'); + self::assertSame($literal[1], $value, 'inserted default value should be the configured default value for: ' . $literal[0]); + } + } + /** * @group #3086 */