commit 65af5d3bc4d9d1476bfb6eb39a35397cd0b75873 Author: Matthew Radcliffe Date: Sat Feb 7 15:53:15 2015 -0500 Issue #1600670 by mradcliffe: Add unit test cases first, and refactor fix based on that. This no longer quotes every identifier, and only deals with case-sensitivity for now as that is what the base driver supports out of the box. diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php index 0745e29..daa91f9 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php @@ -190,17 +190,31 @@ public function queryTemporary($query, array $args = array(), array $options = a * Escapes a field name string. * * Force all field names to be strictly alphanumeric-plus-underscore. - * The pgsql database drivers supports wrapping the field name in - * double quotes. + * The pgsql database driver supports case-sensitive field names, but do not + * support field names that contain periods despite being valid in + * PostgreSQL. This is because escapeField() may be called by a field with + * a table alias as part of SQL conditions or order by statements. * * @return string * The sanitized field name string. */ public function escapeField($field) { - $escaped = preg_replace('/^[^A-Za-z0-9_][^A-Za-z0-9_.]+/', '', $field); + $escaped = parent::escapeField($field); - // Check for beginning and ending double quote. - if (preg_match('/^["]/', $field) && preg_match('/["]$/', $field)) { + // Escape any invalid start character. + $escaped = preg_replace('/^[^A-Za-z0-9_]/', '', $escaped); + + // Consider a period as a table alias identifier, and split the string. + if (preg_match('/^([A-Za-z0-9_]+)"?[.]"?([A-Za-z0-9_.]+)/', $escaped, $parts)) { + $table = $parts[1]; + $column = $parts[2]; + + // Use escape alias because escapeField may contain multiple periods that + // need to be escaped. + $escaped = $this->escapeTable($table) . '.' . $this->escapeAlias($column); + } + elseif (preg_match('/[A-Z]/', $escaped)) { + // Quote the field name for case-sensitivity. $escaped = '"' . $escaped . '"'; } @@ -213,8 +227,12 @@ public function escapeField($field) { public function escapeAlias($field) { $escaped = preg_replace('/[^A-Za-z0-9_]+/', '', $field); - // Escape the alias in quotes - return '"' . $escaped . '"'; + // Escape the alias in quotes for case-sensitivity. + if (preg_match('/[A-Z]/', $escaped)) { + $escaped = '"' . $escaped . '"'; + } + + return $escaped; } /** @@ -223,8 +241,8 @@ public function escapeAlias($field) { public function escapeTable($table) { $escaped = parent::escapeTable($table); - // Check for beginning and ending double quote. - if (preg_match('/^["]/', $table) && preg_match('/["]$/', $table)) { + // Quote identifier to make it case-sensitive. + if (preg_match('/[A-Z]/', $escaped)) { $escaped = '"' . $escaped . '"'; } diff --git a/core/tests/Drupal/Tests/Core/Database/Driver/pgsql/PostgresqlConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/Driver/pgsql/PostgresqlConnectionTest.php index 7884b15..17de10c 100644 --- a/core/tests/Drupal/Tests/Core/Database/Driver/pgsql/PostgresqlConnectionTest.php +++ b/core/tests/Drupal/Tests/Core/Database/Driver/pgsql/PostgresqlConnectionTest.php @@ -39,7 +39,10 @@ protected function setUp() { */ public function providerEscapeTables() { return array( + array('nocase', 'nocase'), + array('"camelCase"', 'camelCase'), array('"camelCase"', '"camelCase"'), + array('"camelCase"', 'camel/Case'), ); } @@ -54,7 +57,10 @@ public function providerEscapeTables() { */ public function providerEscapeAliases() { return array( - array('"camelCase"', '"camelCase"') + array('nocase', 'nocase'), + array('"camelCase"', '"camelCase"'), + array('"camelCase"', 'camelCase'), + array('"camelCase"', 'camel.Case'), ); } @@ -68,7 +74,14 @@ public function providerEscapeAliases() { */ public function providerEscapeFields() { return array( - array('""isDefaultRevision""', '"isDefaultRevision"') + array('title', 'title'), + array('"isDefaultRevision"', 'isDefaultRevision'), + array('"isDefaultRevision"', '"isDefaultRevision"'), + array('entity_test."isDefaultRevision"', 'entity_test.isDefaultRevision'), + array('entity_test."isDefaultRevision"', '"entity_test"."isDefaultRevision"'), + array('"entityTest"."isDefaultRevision"', '"entityTest"."isDefaultRevision"'), + array('"entityTest"."isDefaultRevision"', 'entityTest.isDefaultRevision'), + array('entity_test."isDefaultRevision"', 'entity_test.is.Default.Revision'), ); }