diff --git a/core/lib/Drupal/Core/Command/DbDumpCommand.php b/core/lib/Drupal/Core/Command/DbDumpCommand.php
index a9b4308d79dd8e236bbfd1510af2f87443db0c89..8f95455078d3c64f38e6f1a4be8c4be6117bed83 100644
--- a/core/lib/Drupal/Core/Command/DbDumpCommand.php
+++ b/core/lib/Drupal/Core/Command/DbDumpCommand.php
@@ -264,7 +264,10 @@ protected function getTableIndexes(Connection $connection, $table, &$definition)
    *   The schema definition to modify.
    */
   protected function getTableCollation(Connection $connection, $table, &$definition) {
-    $query = $connection->query("SHOW TABLE STATUS LIKE '{" . $table . "}'");
+    // Remove identifier quotes from the table name. See
+    // \Drupal\Core\Database\Driver\mysql\Connection::identifierQuote().
+    $table = trim($connection->prefixTables('{' . $table . '}'), '"');
+    $query = $connection->query("SHOW TABLE STATUS WHERE NAME = :table_name", [':table_name' => $table]);
     $data = $query->fetchAssoc();
 
     // Map the collation to a character set. For example, 'utf8mb4_general_ci'
diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php
index 691a6ae84c4f20950328e47318ab5d978f6ee249..5b68cd9a8d5e9b6a89c8bbd39e0c8d3e968d9bad 100644
--- a/core/lib/Drupal/Core/Database/Connection.php
+++ b/core/lib/Drupal/Core/Database/Connection.php
@@ -144,9 +144,32 @@ abstract class Connection {
    * List of escaped database, table, and field names, keyed by unescaped names.
    *
    * @var array
+   *
+   * @deprecated in drupal:9.0.0 and is removed from drupal:10.0.0. This is no
+   *   longer used. Use \Drupal\Core\Database\Connection::$escapedTables or
+   *   \Drupal\Core\Database\Connection::$escapedFields instead.
+   *
+   * @see https://www.drupal.org/node/2986894
    */
   protected $escapedNames = [];
 
+  /**
+   * List of escaped table names, keyed by unescaped names.
+   *
+   * @var array
+   */
+  protected $escapedTables = [];
+
+  /**
+   * List of escaped field names, keyed by unescaped names.
+   *
+   * There are cases in which escapeField() is called on an empty string. In
+   * this case it should always return an empty string.
+   *
+   * @var array
+   */
+  protected $escapedFields = ["" => ""];
+
   /**
    * List of escaped aliases names, keyed by unescaped aliases.
    *
@@ -255,6 +278,11 @@ public function destroy() {
    *   additional queries (such as inserting new user accounts). In rare cases,
    *   such as creating an SQL function, a ; is needed and can be allowed by
    *   changing this option to TRUE.
+   * - allow_square_brackets: By default, queries which contain square brackets
+   *   will have them replaced with the identifier quote character for the
+   *   database type. In rare cases, such as creating an SQL function, []
+   *   characters might be needed and can be allowed by changing this option to
+   *   TRUE.
    *
    * @return array
    *   An array of default query options.
@@ -265,6 +293,7 @@ protected function defaultOptions() {
       'return' => Database::RETURN_STATEMENT,
       'throw_exception' => TRUE,
       'allow_delimiter_in_query' => FALSE,
+      'allow_square_brackets' => FALSE,
     ];
   }
 
@@ -299,6 +328,7 @@ protected function setPrefix($prefix) {
       $this->prefixes = ['default' => $prefix];
     }
 
+    $identifier_quote = $this->identifierQuote();
     // Set up variables for use in prefixTables(). Replace table-specific
     // prefixes first.
     $this->prefixSearch = [];
@@ -306,14 +336,20 @@ protected function setPrefix($prefix) {
     foreach ($this->prefixes as $key => $val) {
       if ($key != 'default') {
         $this->prefixSearch[] = '{' . $key . '}';
-        $this->prefixReplace[] = $val . $key;
+        // $val can point to another database like 'database.users'. In this
+        // instance we need to quote the identifiers correctly.
+        $val = str_replace('.', $identifier_quote . '.' . $identifier_quote, $val);
+        $this->prefixReplace[] = $identifier_quote . $val . $key . $identifier_quote;
       }
     }
     // Then replace remaining tables with the default prefix.
     $this->prefixSearch[] = '{';
-    $this->prefixReplace[] = $this->prefixes['default'];
+    // $this->prefixes['default'] can point to another database like
+    // 'other_db.'. In this instance we need to quote the identifiers correctly.
+    // For example, "other_db"."PREFIX_table_name".
+    $this->prefixReplace[] = $identifier_quote . str_replace('.', $identifier_quote . '.' . $identifier_quote, $this->prefixes['default']);
     $this->prefixSearch[] = '}';
-    $this->prefixReplace[] = '';
+    $this->prefixReplace[] = $identifier_quote;
 
     // Set up a map of prefixed => un-prefixed tables.
     foreach ($this->prefixes as $table_name => $prefix) {
@@ -323,6 +359,20 @@ protected function setPrefix($prefix) {
     }
   }
 
+  /**
+   * Returns the identifier quote character for the database type.
+   *
+   * The ANSI SQL standard identifier quote character is a double quotation
+   * mark.
+   *
+   * @return string
+   *   The identifier quote character for the database type.
+   */
+  protected function identifierQuote() {
+    @trigger_error('In drupal:10.0.0 this method will be abstract and contrib and custom drivers will have to implement it. See https://www.drupal.org/node/2986894', E_USER_DEPRECATED);
+    return '';
+  }
+
   /**
    * Appends a database prefix to all tables in a query.
    *
@@ -341,6 +391,30 @@ public function prefixTables($sql) {
     return str_replace($this->prefixSearch, $this->prefixReplace, $sql);
   }
 
+  /**
+   * Quotes all identifiers in a query.
+   *
+   * Queries sent to Drupal should wrap all unquoted identifiers in square
+   * brackets. This function searches for this syntax and replaces them with the
+   * database specific identifier. In ANSI SQL this a double quote.
+   *
+   * Note that :variable[] is used to denote array arguments but
+   * Connection::expandArguments() is always called first.
+   *
+   * @param string $sql
+   *   A string containing a partial or entire SQL query.
+   *
+   * @return string
+   *   The string containing a partial or entire SQL query with all identifiers
+   *   quoted.
+   *
+   * @internal
+   *   This method should only be called by database API code.
+   */
+  public function quoteIdentifiers($sql) {
+    return str_replace(['[', ']'], $this->identifierQuote(), $sql);
+  }
+
   /**
    * Find the prefix for a table.
    *
@@ -387,18 +461,25 @@ public function getFullQualifiedTableName($table) {
   /**
    * Prepares a query string and returns the prepared statement.
    *
-   * This method caches prepared statements, reusing them when
-   * possible. It also prefixes tables names enclosed in curly-braces.
+   * This method caches prepared statements, reusing them when possible. It also
+   * prefixes tables names enclosed in curly-braces and, optionally, quotes
+   * identifiers enclosed in square brackets.
    *
    * @param $query
    *   The query string as SQL, with curly-braces surrounding the
    *   table names.
+   * @param bool $quote_identifiers
+   *   (optional) Quote any identifiers enclosed in square brackets. Defaults to
+   *   TRUE.
    *
    * @return \Drupal\Core\Database\StatementInterface
    *   A PDO prepared statement ready for its execute() method.
    */
-  public function prepareQuery($query) {
+  public function prepareQuery($query, $quote_identifiers = TRUE) {
     $query = $this->prefixTables($query);
+    if ($quote_identifiers) {
+      $query = $this->quoteIdentifiers($query);
+    }
 
     return $this->connection->prepare($query);
   }
@@ -494,7 +575,10 @@ public function getLogger() {
    *   A table prefix-parsed string for the sequence name.
    */
   public function makeSequenceName($table, $field) {
-    return $this->prefixTables('{' . $table . '}_' . $field . '_seq');
+    $sequence_name = $this->prefixTables('{' . $table . '}_' . $field . '_seq');
+    // Remove identifier quotes as we are constructing a new name from a
+    // prefixed and quoted table name.
+    return str_replace($this->identifierQuote(), '', $sequence_name);
   }
 
   /**
@@ -628,7 +712,7 @@ public function query($query, array $args = [], $options = []) {
         if (strpos($query, ';') !== FALSE && empty($options['allow_delimiter_in_query'])) {
           throw new \InvalidArgumentException('; is not supported in SQL strings. Use only one statement at a time.');
         }
-        $stmt = $this->prepareQuery($query);
+        $stmt = $this->prepareQuery($query, !$options['allow_square_brackets']);
         $stmt->execute($args, $options);
       }
 
@@ -956,30 +1040,32 @@ public function schema() {
    *   The sanitized database name.
    */
   public function escapeDatabase($database) {
-    if (!isset($this->escapedNames[$database])) {
-      $this->escapedNames[$database] = preg_replace('/[^A-Za-z0-9_.]+/', '', $database);
-    }
-    return $this->escapedNames[$database];
+    $database = preg_replace('/[^A-Za-z0-9_]+/', '', $database);
+    return $this->identifierQuote() . $database . $this->identifierQuote();
   }
 
   /**
    * Escapes a table name string.
    *
    * Force all table names to be strictly alphanumeric-plus-underscore.
-   * For some database drivers, it may also wrap the table name in
-   * database-specific escape characters.
+   * Database drivers should never wrap the table name in database-specific
+   * escape characters. This is done in Connection::prefixTables(). The
+   * database-specific escape characters are added in Connection::setPrefix().
    *
    * @param string $table
    *   An unsanitized table name.
    *
    * @return string
    *   The sanitized table name.
+   *
+   * @see \Drupal\Core\Database\Connection::prefixTables()
+   * @see \Drupal\Core\Database\Connection::setPrefix()
    */
   public function escapeTable($table) {
-    if (!isset($this->escapedNames[$table])) {
-      $this->escapedNames[$table] = preg_replace('/[^A-Za-z0-9_.]+/', '', $table);
+    if (!isset($this->escapedTables[$table])) {
+      $this->escapedTables[$table] = preg_replace('/[^A-Za-z0-9_.]+/', '', $table);
     }
-    return $this->escapedNames[$table];
+    return $this->escapedTables[$table];
   }
 
   /**
@@ -996,10 +1082,14 @@ public function escapeTable($table) {
    *   The sanitized field name.
    */
   public function escapeField($field) {
-    if (!isset($this->escapedNames[$field])) {
-      $this->escapedNames[$field] = preg_replace('/[^A-Za-z0-9_.]+/', '', $field);
+    if (!isset($this->escapedFields[$field])) {
+      $escaped = preg_replace('/[^A-Za-z0-9_.]+/', '', $field);
+      $identifier_quote = $this->identifierQuote();
+      // Sometimes fields have the format table_alias.field. In such cases
+      // both identifiers should be quoted, for example, "table_alias"."field".
+      $this->escapedFields[$field] = $identifier_quote . str_replace('.', $identifier_quote . '.' . $identifier_quote, $escaped) . $identifier_quote;
     }
-    return $this->escapedNames[$field];
+    return $this->escapedFields[$field];
   }
 
   /**
@@ -1018,7 +1108,7 @@ public function escapeField($field) {
    */
   public function escapeAlias($field) {
     if (!isset($this->escapedAliases[$field])) {
-      $this->escapedAliases[$field] = preg_replace('/[^A-Za-z0-9_]+/', '', $field);
+      $this->escapedAliases[$field] = $this->identifierQuote() . preg_replace('/[^A-Za-z0-9_]+/', '', $field) . $this->identifierQuote();
     }
     return $this->escapedAliases[$field];
   }
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
index 86e2f5725553165bacb960513fc863c5946937a4..c6d252ee09239fbb5b004df501e011f703487ca6 100644
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
+++ b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
@@ -63,277 +63,6 @@ class Connection extends DatabaseConnection {
    */
   const MIN_MAX_ALLOWED_PACKET = 1024;
 
-  /**
-   * The list of MySQL reserved key words.
-   *
-   * @link https://dev.mysql.com/doc/refman/8.0/en/keywords.html
-   */
-  private $reservedKeyWords = [
-    'accessible',
-    'add',
-    'admin',
-    'all',
-    'alter',
-    'analyze',
-    'and',
-    'as',
-    'asc',
-    'asensitive',
-    'before',
-    'between',
-    'bigint',
-    'binary',
-    'blob',
-    'both',
-    'by',
-    'call',
-    'cascade',
-    'case',
-    'change',
-    'char',
-    'character',
-    'check',
-    'collate',
-    'column',
-    'condition',
-    'constraint',
-    'continue',
-    'convert',
-    'create',
-    'cross',
-    'cube',
-    'cume_dist',
-    'current_date',
-    'current_time',
-    'current_timestamp',
-    'current_user',
-    'cursor',
-    'database',
-    'databases',
-    'day_hour',
-    'day_microsecond',
-    'day_minute',
-    'day_second',
-    'dec',
-    'decimal',
-    'declare',
-    'default',
-    'delayed',
-    'delete',
-    'dense_rank',
-    'desc',
-    'describe',
-    'deterministic',
-    'distinct',
-    'distinctrow',
-    'div',
-    'double',
-    'drop',
-    'dual',
-    'each',
-    'else',
-    'elseif',
-    'empty',
-    'enclosed',
-    'escaped',
-    'except',
-    'exists',
-    'exit',
-    'explain',
-    'false',
-    'fetch',
-    'first_value',
-    'float',
-    'float4',
-    'float8',
-    'for',
-    'force',
-    'foreign',
-    'from',
-    'fulltext',
-    'function',
-    'generated',
-    'get',
-    'grant',
-    'group',
-    'grouping',
-    'groups',
-    'having',
-    'high_priority',
-    'hour_microsecond',
-    'hour_minute',
-    'hour_second',
-    'if',
-    'ignore',
-    'in',
-    'index',
-    'infile',
-    'inner',
-    'inout',
-    'insensitive',
-    'insert',
-    'int',
-    'int1',
-    'int2',
-    'int3',
-    'int4',
-    'int8',
-    'integer',
-    'interval',
-    'into',
-    'io_after_gtids',
-    'io_before_gtids',
-    'is',
-    'iterate',
-    'join',
-    'json_table',
-    'key',
-    'keys',
-    'kill',
-    'lag',
-    'last_value',
-    'lead',
-    'leading',
-    'leave',
-    'left',
-    'like',
-    'limit',
-    'linear',
-    'lines',
-    'load',
-    'localtime',
-    'localtimestamp',
-    'lock',
-    'long',
-    'longblob',
-    'longtext',
-    'loop',
-    'low_priority',
-    'master_bind',
-    'master_ssl_verify_server_cert',
-    'match',
-    'maxvalue',
-    'mediumblob',
-    'mediumint',
-    'mediumtext',
-    'middleint',
-    'minute_microsecond',
-    'minute_second',
-    'mod',
-    'modifies',
-    'natural',
-    'not',
-    'no_write_to_binlog',
-    'nth_value',
-    'ntile',
-    'null',
-    'numeric',
-    'of',
-    'on',
-    'optimize',
-    'optimizer_costs',
-    'option',
-    'optionally',
-    'or',
-    'order',
-    'out',
-    'outer',
-    'outfile',
-    'over',
-    'partition',
-    'percent_rank',
-    'persist',
-    'persist_only',
-    'precision',
-    'primary',
-    'procedure',
-    'purge',
-    'range',
-    'rank',
-    'read',
-    'reads',
-    'read_write',
-    'real',
-    'recursive',
-    'references',
-    'regexp',
-    'release',
-    'rename',
-    'repeat',
-    'replace',
-    'require',
-    'resignal',
-    'restrict',
-    'return',
-    'revoke',
-    'right',
-    'rlike',
-    'row',
-    'rows',
-    'row_number',
-    'schema',
-    'schemas',
-    'second_microsecond',
-    'select',
-    'sensitive',
-    'separator',
-    'set',
-    'show',
-    'signal',
-    'smallint',
-    'spatial',
-    'specific',
-    'sql',
-    'sqlexception',
-    'sqlstate',
-    'sqlwarning',
-    'sql_big_result',
-    'sql_calc_found_rows',
-    'sql_small_result',
-    'ssl',
-    'starting',
-    'stored',
-    'straight_join',
-    'system',
-    'table',
-    'terminated',
-    'then',
-    'tinyblob',
-    'tinyint',
-    'tinytext',
-    'to',
-    'trailing',
-    'trigger',
-    'true',
-    'undo',
-    'union',
-    'unique',
-    'unlock',
-    'unsigned',
-    'update',
-    'usage',
-    'use',
-    'using',
-    'utc_date',
-    'utc_time',
-    'utc_timestamp',
-    'values',
-    'varbinary',
-    'varchar',
-    'varcharacter',
-    'varying',
-    'virtual',
-    'when',
-    'where',
-    'while',
-    'window',
-    'with',
-    'write',
-    'xor',
-    'year_month',
-    'zerofill',
-  ];
-
   /**
    * Constructs a Connection object.
    */
@@ -467,49 +196,6 @@ public static function open(array &$connection_options = []) {
     return $pdo;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function escapeField($field) {
-    $field = parent::escapeField($field);
-    return $this->quoteIdentifier($field);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function escapeAlias($field) {
-    // Quote fields so that MySQL reserved words like 'function' can be used
-    // as aliases.
-    $field = parent::escapeAlias($field);
-    return $this->quoteIdentifier($field);
-  }
-
-  /**
-   * Quotes an identifier if it matches a MySQL reserved keyword.
-   *
-   * @param string $identifier
-   *   The field to check.
-   *
-   * @return string
-   *   The identifier, quoted if it matches a MySQL reserved keyword.
-   */
-  private function quoteIdentifier($identifier) {
-    // Quote identifiers so that MySQL reserved words like 'function' can be
-    // used as column names. Sometimes the 'table.column_name' format is passed
-    // in. For example,
-    // \Drupal\Core\Entity\Sql\SqlContentEntityStorage::buildQuery() adds a
-    // condition on "base.uid" while loading user entities.
-    if (strpos($identifier, '.') !== FALSE) {
-      list($table, $identifier) = explode('.', $identifier, 2);
-    }
-    if (in_array(strtolower($identifier), $this->reservedKeyWords, TRUE)) {
-      // Quote the string for MySQL reserved keywords.
-      $identifier = '"' . $identifier . '"';
-    }
-    return isset($table) ? $table . '.' . $identifier : $identifier;
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -542,6 +228,15 @@ public function queryTemporary($query, array $args = [], array $options = []) {
     return $tablename;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function identifierQuote() {
+    // The database is using the ANSI option on set up so use ANSI quotes and
+    // not MySQL's custom backtick quote.
+    return '"';
+  }
+
   public function driver() {
     return 'mysql';
   }
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Insert.php b/core/lib/Drupal/Core/Database/Driver/mysql/Insert.php
index 3d397c527545c88b5eed4c4f7dd9537a2f13f764..c65642aed0df0f38afcb04d54357aa12de6bbaaf 100644
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Insert.php
+++ b/core/lib/Drupal/Core/Database/Driver/mysql/Insert.php
@@ -43,7 +43,6 @@ public function __toString() {
 
     // Default fields are always placed first for consistency.
     $insert_fields = array_merge($this->defaultFields, $this->insertFields);
-
     $insert_fields = array_map(function ($field) {
       return $this->connection->escapeField($field);
     }, $insert_fields);
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
index 025b25039d9721da789748490cdca19d64d4fb87..ba781c3aa87fd5c3e9e7a95c27b5163265a5d92a 100644
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
+++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
@@ -49,28 +49,6 @@ class Connection extends DatabaseConnection {
     'NOT REGEXP' => ['operator' => '!~*'],
   ];
 
-  /**
-   * The list of PostgreSQL reserved key words.
-   *
-   * @see http://www.postgresql.org/docs/9.4/static/sql-keywords-appendix.html
-   */
-  protected $postgresqlReservedKeyWords = ['all', 'analyse', 'analyze', 'and',
-    'any', 'array', 'as', 'asc', 'asymmetric', 'authorization', 'binary', 'both',
-    'case', 'cast', 'check', 'collate', 'collation', 'column', 'concurrently',
-    'constraint', 'create', 'cross', 'current_catalog', 'current_date',
-    'current_role', 'current_schema', 'current_time', 'current_timestamp',
-    'current_user', 'default', 'deferrable', 'desc', 'distinct', 'do', 'else',
-    'end', 'except', 'false', 'fetch', 'for', 'foreign', 'freeze', 'from', 'full',
-    'grant', 'group', 'having', 'ilike', 'in', 'initially', 'inner', 'intersect',
-    'into', 'is', 'isnull', 'join', 'lateral', 'leading', 'left', 'like', 'limit',
-    'localtime', 'localtimestamp', 'natural', 'not', 'notnull', 'null', 'offset',
-    'on', 'only', 'or', 'order', 'outer', 'over', 'overlaps', 'placing',
-    'primary', 'references', 'returning', 'right', 'select', 'session_user',
-    'similar', 'some', 'symmetric', 'table', 'tablesample', 'then', 'to',
-    'trailing', 'true', 'union', 'unique', 'user', 'using', 'variadic', 'verbose',
-    'when', 'where', 'window', 'with',
-  ];
-
   /**
    * Constructs a connection object.
    */
@@ -205,12 +183,12 @@ public function query($query, array $args = [], $options = []) {
     return $return;
   }
 
-  public function prepareQuery($query) {
+  public function prepareQuery($query, $quote_identifiers = TRUE) {
     // mapConditionOperator converts some operations (LIKE, REGEXP, etc.) to
     // PostgreSQL equivalents (ILIKE, ~*, etc.). However PostgreSQL doesn't
     // automatically cast the fields to the right type for these operators,
     // so we need to alter the query and add the type-cast.
-    return parent::prepareQuery(preg_replace('/ ([^ ]+) +(I*LIKE|NOT +I*LIKE|~\*|!~\*) /i', ' ${1}::text ${2} ', $query));
+    return parent::prepareQuery(preg_replace('/ ([^ ]+) +(I*LIKE|NOT +I*LIKE|~\*|!~\*) /i', ' ${1}::text ${2} ', $query), $quote_identifiers);
   }
 
   public function queryRange($query, $from, $count, array $args = [], array $options = []) {
@@ -226,74 +204,8 @@ public function queryTemporary($query, array $args = [], array $options = []) {
   /**
    * {@inheritdoc}
    */
-  public function escapeField($field) {
-    $escaped = parent::escapeField($field);
-
-    // Remove any invalid start character.
-    $escaped = preg_replace('/^[^A-Za-z0-9_]/', '', $escaped);
-
-    // The pgsql database driver does not support field names that contain
-    // periods (supported by PostgreSQL server) because this method may be
-    // called by a field with a table alias as part of SQL conditions or
-    // order by statements. This will consider a period as a table alias
-    // identifier, and split the string at the first period.
-    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);
-    }
-    else {
-      $escaped = $this->doEscape($escaped);
-    }
-
-    return $escaped;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function escapeAlias($field) {
-    $escaped = preg_replace('/[^A-Za-z0-9_]+/', '', $field);
-    $escaped = $this->doEscape($escaped);
-    return $escaped;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function escapeTable($table) {
-    $escaped = parent::escapeTable($table);
-
-    // Ensure that each part (database, schema and table) of the table name is
-    // properly and independently escaped.
-    $parts = explode('.', $escaped);
-    $parts = array_map([$this, 'doEscape'], $parts);
-    $escaped = implode('.', $parts);
-
-    return $escaped;
-  }
-
-  /**
-   * Escape a string if needed.
-   *
-   * @param $string
-   *   The string to escape.
-   * @return string
-   *   The escaped string.
-   */
-  protected function doEscape($string) {
-    // Quote identifier to make it case-sensitive.
-    if (preg_match('/[A-Z]/', $string)) {
-      $string = '"' . $string . '"';
-    }
-    elseif (in_array(strtolower($string), $this->postgresqlReservedKeyWords)) {
-      // Quote the string for PostgreSQL reserved key words.
-      $string = '"' . $string . '"';
-    }
-    return $string;
+  protected function identifierQuote() {
+    return '"';
   }
 
   public function driver() {
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php
index 056105e25df593a8fff448beb37aa20687baf54f..6e184aea2fabf53c1648587c6604227939c1c808 100644
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php
+++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php
@@ -268,7 +268,7 @@ public function initializeDatabase() {
           \'SELECT array_to_string((string_to_array($1, $2)) [1:$3], $2);\'
           LANGUAGE \'sql\'',
           [],
-          ['allow_delimiter_in_query' => TRUE]
+          ['allow_delimiter_in_query' => TRUE, 'allow_square_brackets' => TRUE]
         );
       }
       $connection->query('SELECT pg_advisory_unlock(1)');
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php
index fd4579b62fafb9d01d3d68ec768b92d65cdf92ac..50398566ee7644ce612fe8b1818869c03b8209ae 100644
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php
+++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php
@@ -550,7 +550,7 @@ public function renameTable($table, $new_name) {
     }
 
     // Get the schema and tablename for the old table.
-    $old_full_name = $this->connection->prefixTables('{' . $table . '}');
+    $old_full_name = str_replace('"', '', $this->connection->prefixTables('{' . $table . '}'));
     list($old_schema, $old_table_name) = strpos($old_full_name, '.') ? explode('.', $old_full_name) : ['public', $old_full_name];
 
     // Index names and constraint names are global in PostgreSQL, so we need to
@@ -866,8 +866,10 @@ protected function introspectIndexSchema($table) {
       'indexes' => [],
     ];
 
+    // Get the schema and tablename for the table without identifier quotes.
+    $full_name = str_replace('"', '', $this->connection->prefixTables('{' . $table . '}'));
     $result = $this->connection->query("SELECT i.relname AS index_name, a.attname AS column_name FROM pg_class t, pg_class i, pg_index ix, pg_attribute a WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND a.attnum = ANY(ix.indkey) AND t.relkind = 'r' AND t.relname = :table_name ORDER BY index_name ASC, column_name ASC", [
-      ':table_name' => $this->connection->prefixTables('{' . $table . '}'),
+      ':table_name' => $full_name,
     ])->fetchAll();
     foreach ($result as $row) {
       if (preg_match('/_pkey$/', $row->index_name)) {
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php
index fa382d3f572b0afeac087b165b5cea3d89252606..1fc90fe537ee3ad6f27543ed7bac24b31529a144 100644
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php
+++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php
@@ -375,6 +375,13 @@ public function databaseType() {
     return 'sqlite';
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function identifierQuote() {
+    return '"';
+  }
+
   /**
    * Overrides \Drupal\Core\Database\Connection::createDatabase().
    *
@@ -398,8 +405,12 @@ public function mapConditionOperator($operator) {
   /**
    * {@inheritdoc}
    */
-  public function prepareQuery($query) {
-    return $this->prepare($this->prefixTables($query));
+  public function prepareQuery($query, $quote_identifiers = TRUE) {
+    $query = $this->prefixTables($query);
+    if ($quote_identifiers) {
+      $query = $this->quoteIdentifiers($query);
+    }
+    return $this->prepare($query);
   }
 
   public function nextId($existing_id = 0) {
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Insert.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Insert.php
index 0c4fdb668b91488d45bd57ff64391b35747538be..4273dd6536fe3faa972d96f95d6a8a6e8ee391a3 100644
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Insert.php
+++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Insert.php
@@ -35,14 +35,18 @@ public function __toString() {
       $placeholders = array_fill(0, count($this->insertFields), '?');
     }
 
+    $insert_fields = array_map(function ($field) {
+      return $this->connection->escapeField($field);
+    }, $this->insertFields);
+
     // If we're selecting from a SelectQuery, finish building the query and
     // pass it back, as any remaining options are irrelevant.
     if (!empty($this->fromQuery)) {
-      $insert_fields_string = $this->insertFields ? ' (' . implode(', ', $this->insertFields) . ') ' : ' ';
+      $insert_fields_string = $insert_fields ? ' (' . implode(', ', $insert_fields) . ') ' : ' ';
       return $comments . 'INSERT INTO {' . $this->table . '}' . $insert_fields_string . $this->fromQuery;
     }
 
-    return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $this->insertFields) . ') VALUES (' . implode(', ', $placeholders) . ')';
+    return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES (' . implode(', ', $placeholders) . ')';
   }
 
 }
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php
index 088bff2cf2956380d2f982f1e2abd5ada0c85c47..2286aedbba92a6d5dd8d0b11b9465e4f246b6dc6 100644
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php
+++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php
@@ -165,6 +165,7 @@ protected function processField($field) {
    *   The field specification, as per the schema data structure format.
    */
   protected function createFieldSql($name, $spec) {
+    $name = $this->connection->escapeField($name);
     if (!empty($spec['auto_increment'])) {
       $sql = $name . " INTEGER PRIMARY KEY AUTOINCREMENT";
       if (!empty($spec['unsigned'])) {
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Upsert.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Upsert.php
index fc59afde252d44658670f0453aff65178ad4ddb2..ed9a0215335e35733aabe3411b218ee67f6941ce 100644
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Upsert.php
+++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Upsert.php
@@ -18,6 +18,9 @@ public function __toString() {
 
     // Default fields are always placed first for consistency.
     $insert_fields = array_merge($this->defaultFields, $this->insertFields);
+    $insert_fields = array_map(function ($field) {
+      return $this->connection->escapeField($field);
+    }, $insert_fields);
 
     $query = $comments . 'INSERT OR REPLACE INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
 
diff --git a/core/lib/Drupal/Core/Database/Query/Select.php b/core/lib/Drupal/Core/Database/Query/Select.php
index 27892ce5cb29da385c02b5eb3fe085278723c429..fb74d9f1c13eedaed115286508092d0a49476ec4 100644
--- a/core/lib/Drupal/Core/Database/Query/Select.php
+++ b/core/lib/Drupal/Core/Database/Query/Select.php
@@ -813,7 +813,8 @@ public function __toString() {
     foreach ($this->fields as $field) {
       // Always use the AS keyword for field aliases, as some
       // databases require it (e.g., PostgreSQL).
-      $fields[] = (isset($field['table']) ? $this->connection->escapeTable($field['table']) . '.' : '') . $this->connection->escapeField($field['field']) . ' AS ' . $this->connection->escapeAlias($field['alias']);
+      $table = isset($field['table']) ? $field['table'] . '.' : '';
+      $fields[] = $this->connection->escapeField($table . $field['field']) . ' AS ' . $this->connection->escapeAlias($field['alias']);
     }
     foreach ($this->expressions as $expression) {
       $fields[] = $expression['expression'] . ' AS ' . $this->connection->escapeAlias($expression['alias']);
@@ -845,7 +846,7 @@ public function __toString() {
 
       // Don't use the AS keyword for table aliases, as some
       // databases don't support it (e.g., Oracle).
-      $query .= $table_string . ' ' . $this->connection->escapeTable($table['alias']);
+      $query .= $table_string . ' ' . $this->connection->escapeAlias($table['alias']);
 
       if (!empty($table['condition'])) {
         $query .= ' ON ' . (string) $table['condition'];
diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Condition.php b/core/lib/Drupal/Core/Entity/Query/Sql/Condition.php
index 7f9351214d1e1474d146d4fe61007a326a12c105..02bcb34003cbdf4b31219201920c246d9f9c6d92 100644
--- a/core/lib/Drupal/Core/Entity/Query/Sql/Condition.php
+++ b/core/lib/Drupal/Core/Entity/Query/Sql/Condition.php
@@ -50,6 +50,16 @@ public function compile($conditionContainer) {
       else {
         $type = $this->nestedInsideOrCondition || strtoupper($this->conjunction) === 'OR' || $condition['operator'] === 'IS NULL' ? 'LEFT' : 'INNER';
         $field = $tables->addField($condition['field'], $type, $condition['langcode']);
+        // If the field is trying to query on %delta for a single value field
+        // then the only supported delta is 0. No other value than 0 makes
+        // sense. \Drupal\Core\Entity\Query\Sql\Tables::addField() returns 0 as
+        // the field name for single value fields when querying on their %delta.
+        if ($field === 0) {
+          if ($condition['value'] != 0) {
+            $conditionContainer->alwaysFalse();
+          }
+          continue;
+        }
         $condition['real_field'] = $field;
         static::translateCondition($condition, $sql_query, $tables->isFieldCaseSensitive($condition['field']));
 
diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
index daf6bd62c006823c24567c5d73cf2dae8bc0a5e2..de25f69358de6c569047a762b8c9acbec39a5098 100644
--- a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
+++ b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
@@ -132,7 +132,7 @@ protected function prepare() {
     // Add a self-join to the base revision table if we're querying only the
     // latest revisions.
     if ($this->latestRevision && $revision_field) {
-      $this->sqlQuery->leftJoin($base_table, 'base_table_2', "base_table.$id_field = base_table_2.$id_field AND base_table.$revision_field < base_table_2.$revision_field");
+      $this->sqlQuery->leftJoin($base_table, 'base_table_2', "[base_table].[$id_field] = [base_table_2].[$id_field] AND [base_table].[$revision_field] < [base_table_2].[$revision_field]");
       $this->sqlQuery->isNull("base_table_2.$id_field");
     }
 
@@ -346,6 +346,7 @@ public function __toString() {
 
     // Replace table name brackets.
     $sql = $clone->connection->prefixTables((string) $clone->sqlQuery);
+    $sql = $clone->connection->quoteIdentifiers($sql);
 
     return strtr($sql, $quoted);
   }
diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
index 99cab9f1d0c68273556fab4f49ecb0122e967d55..106a7c0b455d2957753e2be313227e8752b31121 100644
--- a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
+++ b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
@@ -351,7 +351,7 @@ protected function ensureEntityTable($index_prefix, $property, $type, $langcode,
         // each join gets a separate alias.
         $key = $index_prefix . ($base_table === 'base_table' ? $table : $base_table);
         if (!isset($this->entityTables[$key])) {
-          $this->entityTables[$key] = $this->addJoin($type, $table, "%alias.$id_field = $base_table.$id_field", $langcode);
+          $this->entityTables[$key] = $this->addJoin($type, $table, "[%alias].[$id_field] = [$base_table].[$id_field]", $langcode);
         }
         return $this->entityTables[$key];
       }
@@ -377,7 +377,7 @@ protected function ensureFieldTable($index_prefix, &$field, $type, $langcode, $b
       if ($field->getCardinality() != 1) {
         $this->sqlQuery->addMetaData('simple_query', FALSE);
       }
-      $this->fieldTables[$index_prefix . $field_name] = $this->addJoin($type, $table, "%alias.$field_id_field = $base_table.$entity_id_field", $langcode, $delta);
+      $this->fieldTables[$index_prefix . $field_name] = $this->addJoin($type, $table, "[%alias].[$field_id_field] = [$base_table].[$entity_id_field]", $langcode, $delta);
     }
     return $this->fieldTables[$index_prefix . $field_name];
   }
@@ -408,12 +408,12 @@ protected function addJoin($type, $table, $join_condition, $langcode, $delta = N
       // tables have an hard-coded 'langcode' column.
       $langcode_key = $entity_type->getDataTable() == $table ? $entity_type->getKey('langcode') : 'langcode';
       $placeholder = ':langcode' . $this->sqlQuery->nextPlaceholder();
-      $join_condition .= ' AND %alias.' . $langcode_key . ' = ' . $placeholder;
+      $join_condition .= ' AND [%alias].[' . $langcode_key . '] = ' . $placeholder;
       $arguments[$placeholder] = $langcode;
     }
     if (isset($delta)) {
       $placeholder = ':delta' . $this->sqlQuery->nextPlaceholder();
-      $join_condition .= ' AND %alias.delta = ' . $placeholder;
+      $join_condition .= ' AND [%alias].[delta] = ' . $placeholder;
       $arguments[$placeholder] = $delta;
     }
     return $this->sqlQuery->addJoin($type, $table, NULL, $join_condition, $arguments);
diff --git a/core/modules/system/tests/modules/database_test/database_test.install b/core/modules/system/tests/modules/database_test/database_test.install
index e8094b2b2194a939683a0779ee60efdff92a20d2..a2b4da4a17db34795637eedee3b5680f9ad7261c 100644
--- a/core/modules/system/tests/modules/database_test/database_test.install
+++ b/core/modules/system/tests/modules/database_test/database_test.install
@@ -313,20 +313,18 @@ function database_test_schema() {
     'primary key' => ['name', 'age'],
   ];
 
-  $schema['test_special_columns'] = [
-    'description' => 'A simple test table with special column names.',
+  // Hopefully no-one will ever name a table 'select' but this example is a
+  // reserved keyword in all supported SQL databases so it is a good test.
+  $schema['select'] = [
+    'description' => 'A test table with an ANSI reserved keyword as its name and one of its column names.',
     'fields' => [
       'id' => [
         'description' => 'Simple unique ID.',
         'type' => 'int',
         'not null' => TRUE,
       ],
-      'offset' => [
-        'description' => 'A column with preserved name.',
-        'type' => 'text',
-      ],
-      'function' => [
-        'description' => 'A column with reserved name in MySQL 8.',
+      'update' => [
+        'description' => 'A column with reserved name.',
         'type' => 'text',
       ],
     ],
diff --git a/core/modules/views_ui/tests/src/Functional/PreviewTest.php b/core/modules/views_ui/tests/src/Functional/PreviewTest.php
index f254097f1f3f7748c9f818184b18495e5f96c993..b913d7b758081cc777b88c4e32d878cddb5f9662 100644
--- a/core/modules/views_ui/tests/src/Functional/PreviewTest.php
+++ b/core/modules/views_ui/tests/src/Functional/PreviewTest.php
@@ -108,9 +108,9 @@ public function testPreviewUI() {
     $this->assertText(t('View render time'));
     $this->assertRaw('<strong>Query</strong>');
     $query_string = <<<SQL
-SELECT views_test_data.name AS views_test_data_name
+SELECT "views_test_data"."name" AS "views_test_data_name"
 FROM
-{views_test_data} views_test_data
+{views_test_data} "views_test_data"
 WHERE (views_test_data.id = '100')
 SQL;
     $this->assertEscaped($query_string);
diff --git a/core/modules/views_ui/tests/src/Functional/SettingsTest.php b/core/modules/views_ui/tests/src/Functional/SettingsTest.php
index d5ce503523f17e273e3f910f2b6e16df83557877..949e0811e9902bc4da862813c6232a6fe5c890e5 100644
--- a/core/modules/views_ui/tests/src/Functional/SettingsTest.php
+++ b/core/modules/views_ui/tests/src/Functional/SettingsTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\views_ui\Functional;
 
+use Drupal\Core\Database\Database;
+
 /**
  * Tests all ui related settings under admin/structure/views/settings.
  *
@@ -122,7 +124,7 @@ public function testEditUI() {
     $xpath = $this->xpath('//div[@class="views-query-info"]//pre');
     $this->assertEqual(count($xpath), 1, 'The views sql is shown.');
     $this->assertFalse(strpos($xpath[0]->getText(), 'db_condition_placeholder') !== FALSE, 'No placeholders are shown in the views sql.');
-    $this->assertTrue(strpos($xpath[0]->getText(), "node_field_data.status = '1'") !== FALSE, 'The placeholders in the views sql is replace by the actual value.');
+    $this->assertTrue(strpos($xpath[0]->getText(), Database::getConnection()->escapeField("node_field_data.status") . " = '1'") !== FALSE, 'The placeholders in the views sql is replace by the actual value.');
 
     // Test the advanced settings form.
 
diff --git a/core/modules/workspaces/src/EntityQuery/Tables.php b/core/modules/workspaces/src/EntityQuery/Tables.php
index b791b92e2e8c929767e300c8fdcf2d9b6d2ae681..5c0c61b07d7ea5d715f0bf505146b622e24ed627 100644
--- a/core/modules/workspaces/src/EntityQuery/Tables.php
+++ b/core/modules/workspaces/src/EntityQuery/Tables.php
@@ -93,7 +93,8 @@ protected function addJoin($type, $table, $join_condition, $langcode, $delta = N
       // If those two conditions are met, we have to update the join condition
       // to also look for a possible workspace-specific revision using COALESCE.
       $condition_parts = explode(' = ', $join_condition);
-      list($base_table, $id_field) = explode('.', $condition_parts[1]);
+      $condition_parts_1 = str_replace(['[', ']'], '', $condition_parts[1]);
+      list($base_table, $id_field) = explode('.', $condition_parts_1);
 
       if (isset($this->baseTablesEntityType[$base_table])) {
         $entity_type_id = $this->baseTablesEntityType[$base_table];
diff --git a/core/tests/Drupal/KernelTests/Core/Database/BasicSyntaxTest.php b/core/tests/Drupal/KernelTests/Core/Database/BasicSyntaxTest.php
index dac837433bf0ca95d94d7a870a516a2ac04c447d..e005283c9017e3a9a0ff10aa78b28104df51ff55 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/BasicSyntaxTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/BasicSyntaxTest.php
@@ -141,4 +141,33 @@ public function testGetFullQualifiedTableName() {
     $this->assertIdentical($num_matches, '4', 'Found 4 records.');
   }
 
+  /**
+   * Tests allowing square brackets in queries.
+   *
+   * @see \Drupal\Core\Database\Connection::prepareQuery()
+   */
+  public function testAllowSquareBrackets() {
+    $this->connection->insert('test')
+      ->fields(['name'])
+      ->values([
+        'name' => '[square]',
+      ])
+      ->execute();
+
+    // Note that this is a very bad example query because arguments should be
+    // passed in via the $args parameter.
+    $result = $this->connection->query("select name from {test} where name = '[square]'", [], ['allow_square_brackets' => TRUE]);
+    $this->assertIdentical('[square]', $result->fetchField());
+
+    // Test that allow_square_brackets has no effect on arguments.
+    $result = $this->connection->query("select name from {test} where name = :value", [':value' => '[square]']);
+    $this->assertIdentical('[square]', $result->fetchField());
+    $result = $this->connection->query("select name from {test} where name = :value", [':value' => '[square]'], ['allow_square_brackets' => TRUE]);
+    $this->assertIdentical('[square]', $result->fetchField());
+
+    // Test square brackets using the query builder.
+    $result = $this->connection->select('test')->fields('test', ['name'])->condition('name', '[square]')->execute();
+    $this->assertIdentical('[square]', $result->fetchField());
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php b/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php
index c9ab16a551addb3fdbd185472a91ef46d94bb435..7e0a5f89ac35674337d5fd18713d219b92d3ee5d 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/ConnectionTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\KernelTests\Core\Database;
 
-use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\DatabaseExceptionWrapper;
 
@@ -156,23 +155,4 @@ public function testMultipleStatements() {
     }
   }
 
-  /**
-   * Test the escapeTable(), escapeField() and escapeAlias() methods with all possible reserved words in PostgreSQL.
-   */
-  public function testPostgresqlReservedWords() {
-    if (Database::getConnection()->databaseType() !== 'pgsql') {
-      $this->markTestSkipped("This test only runs for PostgreSQL");
-    }
-
-    $db = Database::getConnection('default', 'default');
-    $stmt = $db->query("SELECT word FROM pg_get_keywords() WHERE catcode IN ('R', 'T')");
-    $stmt->execute();
-    foreach ($stmt->fetchAllAssoc('word') as $word => $row) {
-      $expected = '"' . $word . '"';
-      $this->assertIdentical($db->escapeTable($word), $expected, new FormattableMarkup('The reserved word %word was correctly escaped when used as a table name.', ['%word' => $word]));
-      $this->assertIdentical($db->escapeField($word), $expected, new FormattableMarkup('The reserved word %word was correctly escaped when used as a column name.', ['%word' => $word]));
-      $this->assertIdentical($db->escapeAlias($word), $expected, new FormattableMarkup('The reserved word %word was correctly escaped when used as an alias.', ['%word' => $word]));
-    }
-  }
-
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Database/DatabaseTestBase.php b/core/tests/Drupal/KernelTests/Core/Database/DatabaseTestBase.php
index ae468c686a9bb3b02c4400d28e6556c059a182ee..dc1151297072c6e6c64a3a1ad35a621e0184334a 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/DatabaseTestBase.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/DatabaseTestBase.php
@@ -35,8 +35,8 @@ protected function setUp() {
       'test_task',
       'test_null',
       'test_serialized',
-      'test_special_columns',
       'TEST_UPPERCASE',
+      'select',
     ]);
     self::addSampleData();
   }
@@ -157,11 +157,10 @@ public static function addSampleData() {
       ])
       ->execute();
 
-    $connection->insert('test_special_columns')
+    $connection->insert('select')
       ->fields([
         'id' => 1,
-        'offset' => 'Offset value 1',
-        'function' => 'Function value 1',
+        'update' => 'Update value 1',
       ])
       ->execute();
   }
diff --git a/core/tests/Drupal/KernelTests/Core/Database/DeleteTruncateTest.php b/core/tests/Drupal/KernelTests/Core/Database/DeleteTruncateTest.php
index b4aa0a9b14dbd3e54d1b251de6fbceba82efdff8..4ca894b980de23258968bbc8497b03b046aa97cd 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/DeleteTruncateTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/DeleteTruncateTest.php
@@ -148,15 +148,15 @@ public function testTruncateTransactionRollback() {
    * Confirms that we can delete a single special column name record successfully.
    */
   public function testSpecialColumnDelete() {
-    $num_records_before = $this->connection->query('SELECT COUNT(*) FROM {test_special_columns}')->fetchField();
+    $num_records_before = $this->connection->query('SELECT COUNT(*) FROM {select}')->fetchField();
 
-    $num_deleted = $this->connection->delete('test_special_columns')
-      ->condition('id', 1)
+    $num_deleted = $this->connection->delete('select')
+      ->condition('update', 'Update value 1')
       ->execute();
-    $this->assertIdentical($num_deleted, 1, 'Deleted 1 special column record.');
+    $this->assertEquals(1, $num_deleted, 'Deleted 1 special column record.');
 
-    $num_records_after = $this->connection->query('SELECT COUNT(*) FROM {test_special_columns}')->fetchField();
-    $this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.');
+    $num_records_after = $this->connection->query('SELECT COUNT(*) FROM {select}')->fetchField();
+    $this->assertEquals($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.');
   }
 
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Database/InsertTest.php b/core/tests/Drupal/KernelTests/Core/Database/InsertTest.php
index 9f52a152664f1853c1ca5c3c65ec5f46472139a4..73fd33494f6a5f8619c80e64a2091e8940f1984c 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/InsertTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/InsertTest.php
@@ -198,20 +198,14 @@ public function testInsertSelectAll() {
    * Tests that we can INSERT INTO a special named column.
    */
   public function testSpecialColumnInsert() {
-    $this->connection->insert('test_special_columns')
+    $this->connection->insert('select')
       ->fields([
         'id' => 2,
-        'offset' => 'Offset value 2',
-        'function' => 'foobar',
+        'update' => 'Update value 2',
       ])
       ->execute();
-    $result = $this->connection->select('test_special_columns')
-      ->fields('test_special_columns', ['offset', 'function'])
-      ->condition('test_special_columns.function', 'foobar')
-      ->execute();
-    $record = $result->fetch();
-    $this->assertSame('Offset value 2', $record->offset);
-    $this->assertSame('foobar', $record->function);
+    $saved_value = $this->connection->query('SELECT [update] FROM {select} WHERE id = :id', [':id' => 2])->fetchField();
+    $this->assertEquals('Update value 2', $saved_value);
   }
 
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Database/MergeTest.php b/core/tests/Drupal/KernelTests/Core/Database/MergeTest.php
index b103b5fc6f6b49b0568f1ad3e9336605ef471003..e8c3b9cfa88b65d6b315a5e50033b142b88f43ac 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/MergeTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/MergeTest.php
@@ -230,4 +230,22 @@ public function testInvalidMerge() {
     $this->fail('No InvalidMergeQueryException thrown');
   }
 
+  /**
+   * Tests that we can merge-insert with reserved keywords.
+   */
+  public function testMergeWithReservedWords() {
+    $num_records_before = $this->connection->query('SELECT COUNT(*) FROM {select}')->fetchField();
+
+    $this->connection->merge('select')
+      ->key('id', 2)
+      ->execute();
+
+    $num_records_after = $this->connection->query('SELECT COUNT(*) FROM {select}')->fetchField();
+    $this->assertEquals($num_records_before + 1, $num_records_after, 'Merge inserted properly.');
+
+    $person = $this->connection->query('SELECT * FROM {select} WHERE id = :id', [':id' => 2])->fetch();
+    $this->assertEquals('', $person->update);
+    $this->assertEquals('2', $person->id);
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Database/QueryTest.php b/core/tests/Drupal/KernelTests/Core/Database/QueryTest.php
index 572c9c7ef951d3e617a0aa79beece46a692bf150..a8560b56c01aff0ffcfc416f6e575175d0b78565 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/QueryTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/QueryTest.php
@@ -150,4 +150,14 @@ public function testNumericExpressionSubstitution() {
     $this->assertEqual($count, $count_expected);
   }
 
+  /**
+   * Tests quoting identifiers in queries.
+   */
+  public function testQuotingIdentifiers() {
+    // Use the table named an ANSI SQL reserved word with a column that is as
+    // well.
+    $result = $this->connection->query('SELECT [update] FROM {select}')->fetchObject();
+    $this->assertEquals('Update value 1', $result->update);
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php b/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php
index 2a86e3470f3124f2f1b1e65cd91c2f47977dd61d..edf3a72a0e1c24a56ae994791c71d12e55ff4343 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php
@@ -1252,4 +1252,27 @@ public function testFindTables() {
     Database::setActiveConnection('default');
   }
 
+  /**
+   * Tests handling of uppercase table names.
+   */
+  public function testUpperCaseTableName() {
+    $table_name = 'A_UPPER_CASE_TABLE_NAME';
+
+    // Create the tables.
+    $table_specification = [
+      'description' => 'Test table.',
+      'fields' => [
+        'id'  => [
+          'type' => 'int',
+          'default' => NULL,
+        ],
+      ],
+    ];
+    $this->schema->createTable($table_name, $table_specification);
+
+    $this->assertTrue($this->schema->tableExists($table_name), 'Table with uppercase table name exists');
+    $this->assertContains($table_name, $this->schema->findTables('%'));
+    $this->assertTrue($this->schema->dropTable($table_name), 'Table with uppercase table name dropped');
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Database/UpdateTest.php b/core/tests/Drupal/KernelTests/Core/Database/UpdateTest.php
index b5b28092e8edcfeeeae2a28c735f5be6f9f64393..5578e760cbfb12a909b779fbe940da1ca7b4b58e 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/UpdateTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/UpdateTest.php
@@ -131,14 +131,16 @@ public function testUpdateAffectedRows() {
    * Confirm that we can update values in a column with special name.
    */
   public function testSpecialColumnUpdate() {
-    $num_updated = $this->connection->update('test_special_columns')
-      ->fields(['offset' => 'New offset value'])
+    $num_updated = $this->connection->update('select')
+      ->fields([
+        'update' => 'New update value',
+      ])
       ->condition('id', 1)
       ->execute();
     $this->assertIdentical($num_updated, 1, 'Updated 1 special column record.');
 
-    $saved_value = $this->connection->query('SELECT "offset" FROM {test_special_columns} WHERE id = :id', [':id' => 1])->fetchField();
-    $this->assertIdentical($saved_value, 'New offset value', 'Updated special column name value successfully.');
+    $saved_value = $this->connection->query('SELECT [update] FROM {select} WHERE id = :id', [':id' => 1])->fetchField();
+    $this->assertEquals('New update value', $saved_value);
   }
 
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Database/UpsertTest.php b/core/tests/Drupal/KernelTests/Core/Database/UpsertTest.php
index 461117797f7e1123101850eb4d6118e488c55277..dd91eb097a7e97d28f0d70b5382e1f8e2d390b42 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/UpsertTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/UpsertTest.php
@@ -54,39 +54,37 @@ public function testUpsert() {
   }
 
   /**
-   * Tests that we can upsert records with a special named column.
+   * Confirms that we can upsert records with keywords successfully.
    */
-  public function testSpecialColumnUpsert() {
-    $num_records_before = $this->connection->query('SELECT COUNT(*) FROM {test_special_columns}')->fetchField();
-    $upsert = $this->connection->upsert('test_special_columns')
+  public function testUpsertWithKeywords() {
+    $num_records_before = $this->connection->query('SELECT COUNT(*) FROM {select}')->fetchField();
+
+    $upsert = $this->connection->upsert('select')
       ->key('id')
-      ->fields(['id', 'offset', 'function']);
+      ->fields(['id', 'update']);
 
     // Add a new row.
     $upsert->values([
       'id' => 2,
-      'offset' => 'Offset 2',
-      'function' => 'Function 2',
+      'update' => 'Update value 2',
     ]);
 
     // Update an existing row.
     $upsert->values([
       'id' => 1,
-      'offset' => 'Offset 1 updated',
-      'function' => 'Function 1 updated',
+      'update' => 'Update value 1 updated',
     ]);
 
     $upsert->execute();
-    $num_records_after = $this->connection->query('SELECT COUNT(*) FROM {test_special_columns}')->fetchField();
+
+    $num_records_after = $this->connection->query('SELECT COUNT(*) FROM {select}')->fetchField();
     $this->assertEquals($num_records_before + 1, $num_records_after, 'Rows were inserted and updated properly.');
 
-    $record = $this->connection->query('SELECT * FROM {test_special_columns} WHERE id = :id', [':id' => 1])->fetch();
-    $this->assertEquals($record->offset, 'Offset 1 updated');
-    $this->assertEquals($record->function, 'Function 1 updated');
+    $record = $this->connection->query('SELECT * FROM {select} WHERE id = :id', [':id' => 1])->fetch();
+    $this->assertEquals('Update value 1 updated', $record->update);
 
-    $record = $this->connection->query('SELECT * FROM {test_special_columns} WHERE id = :id', [':id' => 2])->fetch();
-    $this->assertEquals($record->offset, 'Offset 2');
-    $this->assertEquals($record->function, 'Function 2');
+    $record = $this->connection->query('SELECT * FROM {select} WHERE id = :id', [':id' => 2])->fetch();
+    $this->assertEquals('Update value 2', $record->update);
   }
 
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php
index 43fb9c76a833aec5cb28d1c8029f3216598e15d5..dc64c0bc9248f00f41eed903b848ac12472f1482 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php
@@ -1228,9 +1228,9 @@ public function testToString() {
     $expected = $connection->select("entity_test_mulrev", "base_table");
     $expected->addField("base_table", "revision_id", "revision_id");
     $expected->addField("base_table", "id", "id");
-    $expected->join("entity_test_mulrev__$figures", "entity_test_mulrev__$figures", "entity_test_mulrev__$figures.entity_id = base_table.id");
-    $expected->join("entity_test_mulrev__$figures", "entity_test_mulrev__{$figures}_2", "entity_test_mulrev__{$figures}_2.entity_id = base_table.id");
-    $expected->addJoin("LEFT", "entity_test_mulrev__$figures", "entity_test_mulrev__{$figures}_3", "entity_test_mulrev__{$figures}_3.entity_id = base_table.id");
+    $expected->join("entity_test_mulrev__$figures", "entity_test_mulrev__$figures", '"entity_test_mulrev__' . $figures . '"."entity_id" = "base_table"."id"');
+    $expected->join("entity_test_mulrev__$figures", "entity_test_mulrev__{$figures}_2", '"entity_test_mulrev__' . $figures . '_2"."entity_id" = "base_table"."id"');
+    $expected->addJoin("LEFT", "entity_test_mulrev__$figures", "entity_test_mulrev__{$figures}_3", '"entity_test_mulrev__' . $figures . '_3"."entity_id" = "base_table"."id"');
     $expected->condition("entity_test_mulrev__$figures.{$figures}_color", ["blue"], "IN");
     $expected->condition("entity_test_mulrev__{$figures}_2.{$figures}_color", ["red"], "IN");
     $expected->isNull("entity_test_mulrev__{$figures}_3.{$figures}_color");
diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php
index 8325079986be84762ea6c7842861c273a7b34ca8..753fe87239c847b655ed416bb548ce4b9ae0af29 100644
--- a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php
+++ b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php
@@ -3,11 +3,13 @@
 namespace Drupal\Tests\Core\Database;
 
 use Drupal\Tests\Core\Database\Stub\StubConnection;
+use Drupal\Tests\Core\Database\Stub\StubPDO;
 use Drupal\Tests\UnitTestCase;
 
 /**
  * Tests the Connection class.
  *
+ * @coversDefaultClass \Drupal\Core\Database\Connection
  * @group Database
  */
 class ConnectionTest extends UnitTestCase {
@@ -76,12 +78,13 @@ public function providerTestPrefixTables() {
         'SELECT * FROM test_table',
         'test_',
         'SELECT * FROM {table}',
+        '',
       ],
       [
-        'SELECT * FROM first_table JOIN second_thingie',
+        'SELECT * FROM "first_table" JOIN "second"."thingie"',
         [
           'table' => 'first_',
-          'thingie' => 'second_',
+          'thingie' => 'second.',
         ],
         'SELECT * FROM {table} JOIN {thingie}',
       ],
@@ -93,49 +96,12 @@ public function providerTestPrefixTables() {
    *
    * @dataProvider providerTestPrefixTables
    */
-  public function testPrefixTables($expected, $prefix_info, $query) {
+  public function testPrefixTables($expected, $prefix_info, $query, $quote_identifier = '"') {
     $mock_pdo = $this->createMock('Drupal\Tests\Core\Database\Stub\StubPDO');
-    $connection = new StubConnection($mock_pdo, ['prefix' => $prefix_info]);
+    $connection = new StubConnection($mock_pdo, ['prefix' => $prefix_info], $quote_identifier);
     $this->assertEquals($expected, $connection->prefixTables($query));
   }
 
-  /**
-   * Dataprovider for testEscapeMethods().
-   *
-   * @return array
-   *   Array of arrays with the following elements:
-   *   - Expected escaped string.
-   *   - String to escape.
-   */
-  public function providerEscapeMethods() {
-    return [
-      ['thing', 'thing'],
-      ['_item', '_item'],
-      ['item_', 'item_'],
-      ['_item_', '_item_'],
-      ['', '!@#$%^&*()-=+'],
-      ['123', '!1@2#3'],
-    ];
-  }
-
-  /**
-   * Test the various escaping methods.
-   *
-   * All tested together since they're basically the same method
-   * with different names.
-   *
-   * @dataProvider providerEscapeMethods
-   * @todo Separate test method for each escape method?
-   */
-  public function testEscapeMethods($expected, $name) {
-    $mock_pdo = $this->createMock('Drupal\Tests\Core\Database\Stub\StubPDO');
-    $connection = new StubConnection($mock_pdo, []);
-    $this->assertEquals($expected, $connection->escapeDatabase($name));
-    $this->assertEquals($expected, $connection->escapeTable($name));
-    $this->assertEquals($expected, $connection->escapeField($name));
-    $this->assertEquals($expected, $connection->escapeAlias($name));
-  }
-
   /**
    * Dataprovider for testGetDriverClass().
    *
@@ -297,4 +263,129 @@ public function testFilterComments($expected, $comment) {
     );
   }
 
+  /**
+   * Data provider for testEscapeTable.
+   *
+   * @return array
+   *   An indexed array of where each value is an array of arguments to pass to
+   *   testEscapeField. The first value is the expected value, and the second
+   *   value is the value to test.
+   */
+  public function providerEscapeTables() {
+    return [
+      ['nocase', 'nocase'],
+      ['camelCase', 'camelCase'],
+      ['backtick', '`backtick`', '`'],
+      ['camelCase', '"camelCase"'],
+      ['camelCase', 'camel/Case'],
+      // Sometimes, table names are following the pattern database.schema.table.
+      ['camelCase.nocase.nocase', 'camelCase.nocase.nocase'],
+      ['nocase.camelCase.nocase', 'nocase.camelCase.nocase'],
+      ['nocase.nocase.camelCase', 'nocase.nocase.camelCase'],
+      ['camelCase.camelCase.camelCase', 'camelCase.camelCase.camelCase'],
+    ];
+  }
+
+  /**
+   * @covers ::escapeTable
+   * @dataProvider providerEscapeTables
+   */
+  public function testEscapeTable($expected, $name, $identifier_quote = '"') {
+    $mock_pdo = $this->createMock(StubPDO::class);
+    $connection = new StubConnection($mock_pdo, [], $identifier_quote);
+
+    $this->assertEquals($expected, $connection->escapeTable($name));
+  }
+
+  /**
+   * Data provider for testEscapeAlias.
+   *
+   * @return array
+   *   Array of arrays with the following elements:
+   *   - Expected escaped string.
+   *   - String to escape.
+   */
+  public function providerEscapeAlias() {
+    return [
+      ['!nocase!', 'nocase', '!'],
+      ['`backtick`', 'backtick', '`'],
+      ['nocase', 'nocase', ''],
+      ['"camelCase"', '"camelCase"'],
+      ['"camelCase"', 'camelCase'],
+      ['"camelCase"', 'camel.Case'],
+    ];
+  }
+
+  /**
+   * @covers ::escapeAlias
+   * @dataProvider providerEscapeAlias
+   */
+  public function testEscapeAlias($expected, $name, $identifier_quote = '"') {
+    $mock_pdo = $this->createMock(StubPDO::class);
+    $connection = new StubConnection($mock_pdo, [], $identifier_quote);
+
+    $this->assertEquals($expected, $connection->escapeAlias($name));
+  }
+
+  /**
+   * Data provider for testEscapeField.
+   *
+   * @return array
+   *   Array of arrays with the following elements:
+   *   - Expected escaped string.
+   *   - String to escape.
+   */
+  public function providerEscapeFields() {
+    return [
+      ['/title/', 'title', '/'],
+      ['`backtick`', 'backtick', '`'],
+      ['test.title', 'test.title', ''],
+      ['"isDefaultRevision"', 'isDefaultRevision'],
+      ['"isDefaultRevision"', '"isDefaultRevision"'],
+      ['"entity_test"."isDefaultRevision"', 'entity_test.isDefaultRevision'],
+      ['"entity_test"."isDefaultRevision"', '"entity_test"."isDefaultRevision"'],
+      ['"entityTest"."isDefaultRevision"', '"entityTest"."isDefaultRevision"'],
+      ['"entityTest"."isDefaultRevision"', 'entityTest.isDefaultRevision'],
+    ];
+  }
+
+  /**
+   * @covers ::escapeField
+   * @dataProvider providerEscapeFields
+   */
+  public function testEscapeField($expected, $name, $identifier_quote = '"') {
+    $mock_pdo = $this->createMock(StubPDO::class);
+    $connection = new StubConnection($mock_pdo, [], $identifier_quote);
+
+    $this->assertEquals($expected, $connection->escapeField($name));
+  }
+
+  /**
+   * Data provider for testEscapeDatabase.
+   *
+   * @return array
+   *   An indexed array of where each value is an array of arguments to pass to
+   *   testEscapeField. The first value is the expected value, and the second
+   *   value is the value to test.
+   */
+  public function providerEscapeDatabase() {
+    return [
+      ['/name/', 'name', '/'],
+      ['`backtick`', 'backtick', '`'],
+      ['testname', 'test.name', ''],
+      ['"name"', 'name'],
+    ];
+  }
+
+  /**
+   * @covers ::escapeDatabase
+   * @dataProvider providerEscapeDatabase
+   */
+  public function testEscapeDatabase($expected, $name, $identifier_quote = '"') {
+    $mock_pdo = $this->createMock(StubPDO::class);
+    $connection = new StubConnection($mock_pdo, [], $identifier_quote);
+
+    $this->assertEquals($expected, $connection->escapeDatabase($name));
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Database/Driver/pgsql/PostgresqlConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/Driver/pgsql/PostgresqlConnectionTest.php
deleted file mode 100644
index d6ea4fbf4c605501455f22c60f3f07cc93c161d6..0000000000000000000000000000000000000000
--- a/core/tests/Drupal/Tests/Core/Database/Driver/pgsql/PostgresqlConnectionTest.php
+++ /dev/null
@@ -1,119 +0,0 @@
-<?php
-
-namespace Drupal\Tests\Core\Database\Driver\pgsql;
-
-use Drupal\Core\Database\Driver\pgsql\Connection;
-use Drupal\Tests\UnitTestCase;
-
-/**
- * @coversDefaultClass \Drupal\Core\Database\Driver\pgsql\Connection
- * @group Database
- */
-class PostgresqlConnectionTest extends UnitTestCase {
-
-  /**
-   * Mock PDO object for use in tests.
-   *
-   * @var \PHPUnit\Framework\MockObject\MockObject|\Drupal\Tests\Core\Database\Stub\StubPDO
-   */
-  protected $mockPdo;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-    $this->mockPdo = $this->createMock('Drupal\Tests\Core\Database\Stub\StubPDO');
-  }
-
-  /**
-   * Data provider for testEscapeTable.
-   *
-   * @return array
-   *   An indexed array of where each value is an array of arguments to pass to
-   *   testEscapeField. The first value is the expected value, and the second
-   *   value is the value to test.
-   */
-  public function providerEscapeTables() {
-    return [
-      ['nocase', 'nocase'],
-      ['"camelCase"', 'camelCase'],
-      ['"camelCase"', '"camelCase"'],
-      ['"camelCase"', 'camel/Case'],
-      // Sometimes, table names are following the pattern database.schema.table.
-      ['"camelCase".nocase.nocase', 'camelCase.nocase.nocase'],
-      ['nocase."camelCase".nocase', 'nocase.camelCase.nocase'],
-      ['nocase.nocase."camelCase"', 'nocase.nocase.camelCase'],
-      ['"camelCase"."camelCase"."camelCase"', 'camelCase.camelCase.camelCase'],
-    ];
-  }
-
-  /**
-   * Data provider for testEscapeAlias.
-   *
-   * @return array
-   *   Array of arrays with the following elements:
-   *   - Expected escaped string.
-   *   - String to escape.
-   */
-  public function providerEscapeAlias() {
-    return [
-      ['nocase', 'nocase'],
-      ['"camelCase"', '"camelCase"'],
-      ['"camelCase"', 'camelCase'],
-      ['"camelCase"', 'camel.Case'],
-    ];
-  }
-
-  /**
-   * Data provider for testEscapeField.
-   *
-   * @return array
-   *   Array of arrays with the following elements:
-   *   - Expected escaped string.
-   *   - String to escape.
-   */
-  public function providerEscapeFields() {
-    return [
-      ['title', 'title'],
-      ['"isDefaultRevision"', 'isDefaultRevision'],
-      ['"isDefaultRevision"', '"isDefaultRevision"'],
-      ['entity_test."isDefaultRevision"', 'entity_test.isDefaultRevision'],
-      ['entity_test."isDefaultRevision"', '"entity_test"."isDefaultRevision"'],
-      ['"entityTest"."isDefaultRevision"', '"entityTest"."isDefaultRevision"'],
-      ['"entityTest"."isDefaultRevision"', 'entityTest.isDefaultRevision'],
-      ['entity_test."isDefaultRevision"', 'entity_test.is.Default.Revision'],
-    ];
-  }
-
-  /**
-   * @covers ::escapeTable
-   * @dataProvider providerEscapeTables
-   */
-  public function testEscapeTable($expected, $name) {
-    $pgsql_connection = new Connection($this->mockPdo, []);
-
-    $this->assertEquals($expected, $pgsql_connection->escapeTable($name));
-  }
-
-  /**
-   * @covers ::escapeAlias
-   * @dataProvider providerEscapeAlias
-   */
-  public function testEscapeAlias($expected, $name) {
-    $pgsql_connection = new Connection($this->mockPdo, []);
-
-    $this->assertEquals($expected, $pgsql_connection->escapeAlias($name));
-  }
-
-  /**
-   * @covers ::escapeField
-   * @dataProvider providerEscapeFields
-   */
-  public function testEscapeField($expected, $name) {
-    $pgsql_connection = new Connection($this->mockPdo, []);
-
-    $this->assertEquals($expected, $pgsql_connection->escapeField($name));
-  }
-
-}
diff --git a/core/tests/Drupal/Tests/Core/Database/OrderByTest.php b/core/tests/Drupal/Tests/Core/Database/OrderByTest.php
index ef38627d9ff32cc75acc46af892bd3afa737d1ae..9c28331ca8db353a3cd923e7808c1140abfe6b0c 100644
--- a/core/tests/Drupal/Tests/Core/Database/OrderByTest.php
+++ b/core/tests/Drupal/Tests/Core/Database/OrderByTest.php
@@ -25,6 +25,9 @@ class OrderByTest extends UnitTestCase {
   protected function setUp() {
     $connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
       ->disableOriginalConstructor()
+      // Prevent deprecation message being triggered by
+      // Connection::identifierQuote().
+      ->setMethods(['identifierQuote'])
       ->getMockForAbstractClass();
     $this->query = new Select($connection, 'test', NULL);
   }
diff --git a/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php b/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php
index 4692dffb8714b061ccd099a33e004abca3aaa0e6..7bdd86cc9ff50c88cbc3b0be67a658cd34237ba8 100644
--- a/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php
+++ b/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php
@@ -20,6 +20,35 @@ class StubConnection extends Connection {
    */
   public $driver = 'stub';
 
+  /**
+   * The identifier quote character. Can be set in the constructor for testing.
+   *
+   * @var string
+   */
+  protected $identifierQuote = '';
+
+  /**
+   * Constructs a Connection object.
+   *
+   * @param \PDO $connection
+   *   An object of the PDO class representing a database connection.
+   * @param array $connection_options
+   *   An array of options for the connection.
+   * @param string $identifier_quote
+   *   The identifier quote character. Defaults to an empty string.
+   */
+  public function __construct(\PDO $connection, array $connection_options, $identifier_quote = '') {
+    $this->identifierQuote = $identifier_quote;
+    parent::__construct($connection, $connection_options);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function identifierQuote() {
+    return $this->identifierQuote;
+  }
+
   /**
    * {@inheritdoc}
    */