Skip to content

Commit

Permalink
Reworked the tests and fixed other platforms
Browse files Browse the repository at this point in the history
  • Loading branch information
morozov committed Apr 23, 2019
1 parent 8515d7f commit 64c18da
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 125 deletions.
4 changes: 2 additions & 2 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Upgrade to 2.10

## MINOR BC BREAK: escaped default values
## MINOR BC BREAK: Default values are no longer handled as SQL expressions

Default values will be automatically escaped. So default values must now be specified non-escaped.
They are converted to SQL literals (e.g. escaped). Clients must now specify default values in their initial form, not in the form of an SQL literal (e.g. escaped).

Before:

Expand Down
2 changes: 1 addition & 1 deletion lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -1629,7 +1629,7 @@ public function getDefaultValueDeclarationSQL($field)
return " DEFAULT '" . $this->convertBooleans($field['default']) . "'";
}

return " DEFAULT '" . $field['default'] . "'";
return ' DEFAULT ' . $this->quoteStringLiteral($field['default']);
}

/**
Expand Down
9 changes: 7 additions & 2 deletions lib/Doctrine/DBAL/Schema/DB2SchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
use const CASE_LOWER;
use function array_change_key_case;
use function is_resource;
use function preg_match;
use function str_replace;
use function strpos;
use function strtolower;
use function substr;
use function trim;

/**
* IBM Db2 Schema Manager.
Expand Down Expand Up @@ -47,7 +48,11 @@ protected function _getPortableTableColumnDefinition($tableColumn)
$default = null;

if ($tableColumn['default'] !== null && $tableColumn['default'] !== 'NULL') {
$default = trim($tableColumn['default'], "'");
$default = $tableColumn['default'];

if (preg_match('/^\'(.*)\'$/s', $default, $matches)) {
$default = str_replace("''", "'", $matches[1]);
}
}

$type = $this->_platform->getDoctrineTypeMapping($tableColumn['typename']);
Expand Down
25 changes: 22 additions & 3 deletions lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,36 @@
use function explode;
use function is_string;
use function preg_match;
use function str_replace;
use function stripslashes;
use function strpos;
use function strtok;
use function strtolower;
use function strtr;

/**
* Schema manager for the MySql RDBMS.
*/
class MySqlSchemaManager extends AbstractSchemaManager
{
/**
* @see https://mariadb.com/kb/en/library/string-literals/#escape-sequences
*/
private const MARIADB_ESCAPE_SEQUENCES = [
'\\0' => "\0",
"\\'" => "'",
'\\"' => '"',
'\\b' => "\b",
'\\n' => "\n",
'\\r' => "\r",
'\\t' => "\t",
'\\Z' => "\x1a",
'\\\\' => '\\',
'\\%' => '%',
'\\_' => '_',

// Internally, MariaDB escapes single quotes using the standard syntax
"''" => "'",
];

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -219,7 +238,7 @@ private function getMariaDb1027ColumnDefault(MariaDb1027Platform $platform, ?str
}

if (preg_match('/^\'(.*)\'$/', $columnDefault, $matches)) {
return stripslashes(str_replace("''", "'", $matches[1]));
return strtr($matches[1], self::MARIADB_ESCAPE_SEQUENCES);
}

switch ($columnDefault) {
Expand Down
7 changes: 5 additions & 2 deletions lib/Doctrine/DBAL/Schema/OracleSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use function assert;
use function preg_match;
use function sprintf;
use function str_replace;
use function strpos;
use function strtolower;
use function strtoupper;
Expand Down Expand Up @@ -144,8 +145,10 @@ protected function _getPortableTableColumnDefinition($tableColumn)
}

if ($tableColumn['data_default'] !== null) {
// Default values returned from database are enclosed in single quotes.
$tableColumn['data_default'] = trim($tableColumn['data_default'], "'");
// Default values returned from database are represented as literal expressions
if (preg_match('/^\'(.*)\'$/s', $tableColumn['data_default'], $matches)) {
$tableColumn['data_default'] = str_replace("''", "'", $matches[1]);
}
}

if ($tableColumn['data_precision'] !== null) {
Expand Down
7 changes: 2 additions & 5 deletions lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
use function preg_replace;
use function sprintf;
use function str_replace;
use function stripos;
use function strlen;
use function strpos;
use function strtolower;
Expand Down Expand Up @@ -330,11 +329,9 @@ protected function _getPortableTableColumnDefinition($tableColumn)
$autoincrement = true;
}

if (preg_match("/^['(](.*)[')]::.*$/", $tableColumn['default'], $matches)) {
if (preg_match("/^['(](.*)[')]::/", $tableColumn['default'], $matches)) {
$tableColumn['default'] = $matches[1];
}

if (stripos($tableColumn['default'], 'NULL') === 0) {
} elseif (preg_match('/^NULL::/', $tableColumn['default'])) {
$tableColumn['default'] = null;
}

Expand Down
17 changes: 12 additions & 5 deletions lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
use function str_replace;
use function strpos;
use function strtok;
use function trim;

/**
* SQL Server Schema Manager.
Expand Down Expand Up @@ -107,7 +106,7 @@ protected function _getPortableTableColumnDefinition($tableColumn)
'length' => $length === 0 || ! in_array($type, ['text', 'string']) ? null : $length,
'unsigned' => false,
'fixed' => (bool) $fixed,
'default' => $default !== 'NULL' ? $default : null,
'default' => $default,
'notnull' => (bool) $tableColumn['notnull'],
'scale' => $tableColumn['scale'],
'precision' => $tableColumn['precision'],
Expand All @@ -124,10 +123,18 @@ protected function _getPortableTableColumnDefinition($tableColumn)
return $column;
}

private function parseDefaultExpression(string $value) : string
private function parseDefaultExpression(string $value) : ?string
{
while (preg_match('/^\((.*)\)$/', $value, $matches)) {
$value = trim($matches[1], "'");
while (preg_match('/^\((.*)\)$/s', $value, $matches)) {
$value = $matches[1];
}

if ($value === 'NULL') {
return null;
}

if (preg_match('/^\'(.*)\'$/s', $value, $matches)) {
$value = str_replace("''", "'", $matches[1]);
}

if ($value === 'getdate()') {
Expand Down
9 changes: 6 additions & 3 deletions lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -324,11 +324,14 @@ protected function _getPortableTableColumnDefinition($tableColumn)
if ($default === 'NULL') {
$default = null;
}

if ($default !== null) {
// 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);
// SQLite returns the default value as a literal expression, so we need to parse it
if (preg_match('/^\'(.*)\'$/s', $default, $matches)) {
$default = str_replace("''", "'", $matches[1]);
}
}

$notnull = (bool) $tableColumn['notnull'];

if (! isset($tableColumn['name'])) {
Expand Down
151 changes: 151 additions & 0 deletions tests/Doctrine/Tests/DBAL/Functional/Schema/DefaultValueTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\DBAL\Functional\Schema;

use Doctrine\DBAL\Schema\Table;
use Doctrine\Tests\DbalFunctionalTestCase;
use function sprintf;

class DefaultValueTest extends DbalFunctionalTestCase
{
/** @var bool */
private static $initialized = false;

protected function setUp() : void
{
parent::setUp();

if (self::$initialized) {
return;
}

self::$initialized = true;

$table = new Table('default_value');
$table->addColumn('id', 'integer');

foreach (self::columnProvider() as [$name, $default]) {
$table->addColumn($name, 'string', [
'default' => $default,
'notnull' => false,
]);
}

$this->connection->getSchemaManager()
->dropAndCreateTable($table);

$this->connection->insert('default_value', ['id' => 1]);
}

/**
* @dataProvider columnProvider
*/
public function testEscapedDefaultValueCanBeIntrospected(string $name, $expectedDefault) : void
{
self::assertSame(
$expectedDefault,
$this->connection
->getSchemaManager()
->listTableDetails('default_value')
->getColumn($name)
->getDefault()
);
}

/**
* @dataProvider columnProvider
*/
public function testEscapedDefaultValueCanBeInserted(string $name, $expectedDefault) : void
{
$value = $this->connection->fetchColumn(
sprintf('SELECT %s FROM default_value', $name)
);

self::assertSame($expectedDefault, $value);
}

/**
* 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[][]
*/
public static function columnProvider() : iterable
{
return [
'Single quote' => [
'single_quote',
"foo'bar",
],
'Single quote, doubled' => [
'single_quote_doubled',
"foo''bar",
],
'Double quote' => [
'double_quote',
'foo"bar',
],
'Double quote, doubled' => [
'double_quote_doubled',
'foo""bar',
],
'Backspace' => [
'backspace',
"foo\x08bar",
],
'New line' => [
'new_line',
"foo\nbar",
],
'Carriage return' => [
'carriage_return',
"foo\rbar",
],
'Tab' => [
'tab',
"foo\tbar",
],
'Substitute' => [
'substitute',
"foo\x1abar",
],
'Backslash' => [
'backslash',
'foo\\bar',
],
'Backslash, doubled' => [
'backslash_doubled',
'foo\\\\bar',
],
'Percent' => [
'percent_sign',
'foo%bar',
],
'Underscore' => [
'underscore',
'foo_bar',
],
'NULL string' => [
'null_string',
'NULL',
],
'NULL value' => [
'null_value',
null,
],
'SQL expression' => [
'sql_expression',
"'; DROP DATABASE doctrine --",
],
'No double conversion' => [
'no_double_conversion',
"\\'",
],
];
}
}
Loading

0 comments on commit 64c18da

Please sign in to comment.