From 67ebcf6dea887950fa2f8dc38454597321cd60f3 Mon Sep 17 00:00:00 2001
From: "Jonathan H. Wage" <jonwage@gmail.com>
Date: Mon, 29 Apr 2019 21:20:57 -0500
Subject: [PATCH] Improve consistency of exception message formatting.

---
 lib/Doctrine/DBAL/Cache/ArrayStatement.php    |   9 +-
 lib/Doctrine/DBAL/DBALException.php           |  27 ++--
 .../Mysqli/Exception/ConnectionError.php      |  16 ++
 .../Exception/FailedReadingStreamOffset.php   |  16 ++
 .../Mysqli/Exception/StatementError.php       |  16 ++
 .../Mysqli/Exception/UnknownFetchMode.php     |  19 +++
 .../Driver/Mysqli/Exception/UnknownType.php   |  19 +++
 .../DBAL/Driver/Mysqli/MysqliConnection.php   |  11 +-
 .../DBAL/Driver/Mysqli/MysqliException.php    |  11 --
 .../DBAL/Driver/Mysqli/MysqliStatement.php    |  28 ++--
 .../DBAL/Driver/OCI8/OCI8Statement.php        |   6 +-
 lib/Doctrine/DBAL/Driver/PDOStatement.php     |   4 +-
 .../SQLAnywhere/SQLAnywhereStatement.php      |  15 +-
 lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php    |   2 +-
 .../DBAL/Driver/SQLSrv/SQLSrvStatement.php    |   2 +-
 .../DBAL/Exception/DriverRequired.php         |   6 +-
 .../Exception/EmptyCriteriaNotAllowed.php     |   2 +-
 .../DBAL/Exception/GetVariableType.php        |  35 +++++
 .../DBAL/Exception/InvalidDriverClass.php     |   2 +-
 .../DBAL/Exception/InvalidPdoInstance.php     |   2 +-
 .../DBAL/Exception/InvalidPlatformType.php    |   7 +-
 .../DBAL/Exception/InvalidWrapperClass.php    |   2 +-
 ...SQLParam.php => MissingArrayParameter.php} |   6 +-
 .../Exception/MissingArrayParameterType.php   |  16 ++
 .../DBAL/Exception/MissingSQLType.php         |  18 ---
 lib/Doctrine/DBAL/Exception/UnknownDriver.php |   2 +-
 lib/Doctrine/DBAL/Id/TableGenerator.php       |   5 +-
 .../DBAL/Platforms/AbstractPlatform.php       |  32 ++--
 .../Exception/NoColumnsSpecifiedForTable.php  |   2 +-
 .../DBAL/Platforms/Exception/NotSupported.php |   2 +-
 .../DBAL/Platforms/OraclePlatform.php         |   4 +-
 .../DBAL/Platforms/PostgreSqlPlatform.php     |   5 +-
 .../DBAL/Platforms/SQLAnywherePlatform.php    |  18 +--
 .../DBAL/Platforms/SQLServerPlatform.php      |   7 +-
 .../DBAL/Platforms/SqlitePlatform.php         |   8 +-
 .../DBAL/Query/Exception/NonUniqueAlias.php   |   2 +-
 .../DBAL/Query/Exception/UnknownAlias.php     |   2 +-
 lib/Doctrine/DBAL/SQLParserUtils.php          |   8 +-
 .../Schema/Exception/ColumnAlreadyExists.php  |   2 +-
 .../Schema/Exception/ColumnDoesNotExist.php   |   2 +-
 .../Exception/ForeignKeyDoesNotExist.php      |   2 +-
 .../Schema/Exception/IndexAlreadyExists.php   |   2 +-
 .../Schema/Exception/IndexDoesNotExist.php    |   2 +-
 .../Schema/Exception/IndexNameInvalid.php     |   2 +-
 .../Schema/Exception/InvalidTableName.php     |   2 +-
 .../Exception/NamedForeignKeyRequired.php     |   4 +-
 .../Exception/NamespaceAlreadyExists.php      |   2 +-
 .../Exception/SequenceAlreadyExists.php       |   2 +-
 .../Schema/Exception/SequenceDoesNotExist.php |   2 +-
 .../Schema/Exception/TableAlreadyExists.php   |   2 +-
 .../Schema/Exception/TableDoesNotExist.php    |   2 +-
 .../UniqueConstraintDoesNotExist.php          |   2 +-
 lib/Doctrine/DBAL/Schema/Table.php            |   3 +-
 lib/Doctrine/DBAL/Schema/UniqueConstraint.php |  15 +-
 .../Exception/MissingDistributionType.php     |   2 +-
 .../DBAL/Sharding/PoolingShardConnection.php  |  11 +-
 .../SQLAzure/SQLAzureShardManager.php         |   2 +-
 .../SQLAzure/Schema/MultiTenantVisitor.php    |   3 +-
 .../Console/Command/ReservedWordsCommand.php  |  10 +-
 .../Tools/Console/Command/RunSqlCommand.php   |   4 +-
 .../DBAL/Types/Exception/InvalidFormat.php    |   2 +-
 .../DBAL/Types/Exception/InvalidType.php      |   4 +-
 .../Types/Exception/SerializationFailed.php   |   2 +-
 .../DBAL/Types/Exception/TypeNotFound.php     |   2 +-
 .../Types/Exception/TypesAlreadyExists.php    |   2 +-
 .../Types/Exception/ValueNotConvertible.php   |   4 +-
 .../Tests/DBAL/Cache/ArrayStatementTest.php   | 147 ++++++++++++++++++
 tests/Doctrine/Tests/DBAL/ConnectionTest.php  |   2 +-
 .../Doctrine/Tests/DBAL/DBALExceptionTest.php |   8 +-
 .../Mysqli/Exception/UnknownTypeTest.php      |  18 +++
 .../Driver/Mysqli/MysqliConnectionTest.php    |   4 +-
 .../DBAL/Driver/OCI8/OCI8StatementTest.php    |   6 +-
 .../DBAL/Exception/DriverRequiredTest.php     |   4 +-
 .../Exception/EmptyCriteriaNotAllowedTest.php |   2 +-
 .../DBAL/Exception/GetVariableTypeTest.php    |  40 +++++
 .../Exception/InvalidPlatformTypeTest.php     |   4 +-
 .../Tests/DBAL/Functional/ExceptionTest.php   |   2 +-
 .../DBAL/Functional/PDOStatementTest.php      |   2 +-
 .../Platforms/AbstractPlatformTestCase.php    |   2 +-
 .../AbstractPostgreSqlPlatformTestCase.php    |   2 +-
 .../Tests/DBAL/Query/QueryBuilderTest.php     |   4 +-
 .../Tests/DBAL/SQLParserUtilsTest.php         |  10 +-
 .../Sharding/PoolingShardConnectionTest.php   |  25 +--
 .../DBAL/Tools/Console/RunSqlCommandTest.php  |  34 ++--
 tests/Doctrine/Tests/DBAL/Types/ArrayTest.php |   2 +-
 .../DBAL/Types/ConversionExceptionTest.php    |  22 ++-
 .../Exception/SerializationFailedTest.php     |  27 ++++
 .../Doctrine/Tests/DBAL/Types/ObjectTest.php  |   2 +-
 88 files changed, 611 insertions(+), 248 deletions(-)
 create mode 100644 lib/Doctrine/DBAL/Driver/Mysqli/Exception/ConnectionError.php
 create mode 100644 lib/Doctrine/DBAL/Driver/Mysqli/Exception/FailedReadingStreamOffset.php
 create mode 100644 lib/Doctrine/DBAL/Driver/Mysqli/Exception/StatementError.php
 create mode 100644 lib/Doctrine/DBAL/Driver/Mysqli/Exception/UnknownFetchMode.php
 create mode 100644 lib/Doctrine/DBAL/Driver/Mysqli/Exception/UnknownType.php
 create mode 100644 lib/Doctrine/DBAL/Exception/GetVariableType.php
 rename lib/Doctrine/DBAL/Exception/{MissingSQLParam.php => MissingArrayParameter.php} (50%)
 create mode 100644 lib/Doctrine/DBAL/Exception/MissingArrayParameterType.php
 delete mode 100644 lib/Doctrine/DBAL/Exception/MissingSQLType.php
 create mode 100644 tests/Doctrine/Tests/DBAL/Cache/ArrayStatementTest.php
 create mode 100644 tests/Doctrine/Tests/DBAL/Driver/Mysqli/Exception/UnknownTypeTest.php
 create mode 100644 tests/Doctrine/Tests/DBAL/Exception/GetVariableTypeTest.php
 create mode 100644 tests/Doctrine/Tests/DBAL/Types/Exception/SerializationFailedTest.php

diff --git a/lib/Doctrine/DBAL/Cache/ArrayStatement.php b/lib/Doctrine/DBAL/Cache/ArrayStatement.php
index a935f8d3e63..6b9dc836c0c 100644
--- a/lib/Doctrine/DBAL/Cache/ArrayStatement.php
+++ b/lib/Doctrine/DBAL/Cache/ArrayStatement.php
@@ -15,6 +15,7 @@
 use function array_values;
 use function count;
 use function reset;
+use function sprintf;
 
 class ArrayStatement implements IteratorAggregate, ResultStatement
 {
@@ -48,7 +49,7 @@ public function __construct(array $data)
      */
     public function closeCursor() : void
     {
-        unset($this->data);
+        $this->data = null;
     }
 
     /**
@@ -77,7 +78,7 @@ public function rowCount() : int
     public function setFetchMode($fetchMode, ...$args) : void
     {
         if (count($args) > 0) {
-            throw new InvalidArgumentException('Caching layer does not support 2nd/3rd argument to setFetchMode()');
+            throw new InvalidArgumentException('Caching layer does not support 2nd/3rd argument to setFetchMode().');
         }
 
         $this->defaultFetchMode = $fetchMode;
@@ -121,7 +122,9 @@ public function fetch($fetchMode = null, ...$args)
             return reset($row);
         }
 
-        throw new InvalidArgumentException('Invalid fetch-style given for fetching result.');
+        throw new InvalidArgumentException(
+            sprintf('Invalid fetch mode given for fetching result, %d given.', $fetchMode)
+        );
     }
 
     /**
diff --git a/lib/Doctrine/DBAL/DBALException.php b/lib/Doctrine/DBAL/DBALException.php
index 99ca5b35f39..26bb81923b7 100644
--- a/lib/Doctrine/DBAL/DBALException.php
+++ b/lib/Doctrine/DBAL/DBALException.php
@@ -28,13 +28,20 @@ class DBALException extends Exception
      */
     public static function driverExceptionDuringQuery(Driver $driver, Throwable $driverEx, $sql, array $params = [])
     {
-        $msg = "An exception occurred while executing '" . $sql . "'";
-        if ($params) {
-            $msg .= ' with params ' . self::formatParameters($params);
-        }
-        $msg .= ":\n\n" . $driverEx->getMessage();
+        $messageFormat = <<<'MESSAGE'
+An exception occurred while executing "%s"%s:
+
+%s
+MESSAGE;
+
+        $message = sprintf(
+            $messageFormat,
+            $sql,
+            $params !== [] ? sprintf(' with params %s', self::formatParameters($params)) : '',
+            $driverEx->getMessage()
+        );
 
-        return static::wrapException($driver, $driverEx, $msg);
+        return static::wrapException($driver, $driverEx, $message);
     }
 
     /**
@@ -42,7 +49,7 @@ public static function driverExceptionDuringQuery(Driver $driver, Throwable $dri
      */
     public static function driverException(Driver $driver, Throwable $driverEx)
     {
-        return static::wrapException($driver, $driverEx, 'An exception occurred in driver: ' . $driverEx->getMessage());
+        return static::wrapException($driver, $driverEx, sprintf('An exception occurred in driver with message: %s', $driverEx->getMessage()));
     }
 
     /**
@@ -64,11 +71,9 @@ private static function wrapException(Driver $driver, Throwable $driverEx, $msg)
      * Returns a human-readable representation of an array of parameters.
      * This properly handles binary data by returning a hex representation.
      *
-     * @param mixed[] $params
-     *
-     * @return string
+     * @param array<mixed, mixed> $params
      */
-    private static function formatParameters(array $params)
+    private static function formatParameters(array $params) : string
     {
         return '[' . implode(', ', array_map(static function ($param) {
             if (is_resource($param)) {
diff --git a/lib/Doctrine/DBAL/Driver/Mysqli/Exception/ConnectionError.php b/lib/Doctrine/DBAL/Driver/Mysqli/Exception/ConnectionError.php
new file mode 100644
index 00000000000..0382a80b119
--- /dev/null
+++ b/lib/Doctrine/DBAL/Driver/Mysqli/Exception/ConnectionError.php
@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\DBAL\Driver\Mysqli\Exception;
+
+use Doctrine\DBAL\Driver\Mysqli\MysqliException;
+use mysqli;
+
+final class ConnectionError extends MysqliException
+{
+    public static function new(mysqli $connection) : self
+    {
+        return new self($connection->error, $connection->sqlstate ?: null, $connection->errno);
+    }
+}
diff --git a/lib/Doctrine/DBAL/Driver/Mysqli/Exception/FailedReadingStreamOffset.php b/lib/Doctrine/DBAL/Driver/Mysqli/Exception/FailedReadingStreamOffset.php
new file mode 100644
index 00000000000..24a83e34bfa
--- /dev/null
+++ b/lib/Doctrine/DBAL/Driver/Mysqli/Exception/FailedReadingStreamOffset.php
@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\DBAL\Driver\Mysqli\Exception;
+
+use Doctrine\DBAL\Driver\Mysqli\MysqliException;
+use function sprintf;
+
+final class FailedReadingStreamOffset extends MysqliException
+{
+    public static function new(int $offset) : self
+    {
+        return new self(sprintf('Failed reading the stream resource for parameter offset %d.', $offset));
+    }
+}
diff --git a/lib/Doctrine/DBAL/Driver/Mysqli/Exception/StatementError.php b/lib/Doctrine/DBAL/Driver/Mysqli/Exception/StatementError.php
new file mode 100644
index 00000000000..acad64ce57b
--- /dev/null
+++ b/lib/Doctrine/DBAL/Driver/Mysqli/Exception/StatementError.php
@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\DBAL\Driver\Mysqli\Exception;
+
+use Doctrine\DBAL\Driver\Mysqli\MysqliException;
+use mysqli_stmt;
+
+final class StatementError extends MysqliException
+{
+    public static function new(mysqli_stmt $statement) : self
+    {
+        return new self($statement->error, $statement->sqlstate ?: null, $statement->errno);
+    }
+}
diff --git a/lib/Doctrine/DBAL/Driver/Mysqli/Exception/UnknownFetchMode.php b/lib/Doctrine/DBAL/Driver/Mysqli/Exception/UnknownFetchMode.php
new file mode 100644
index 00000000000..42ed619d279
--- /dev/null
+++ b/lib/Doctrine/DBAL/Driver/Mysqli/Exception/UnknownFetchMode.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\DBAL\Driver\Mysqli\Exception;
+
+use Doctrine\DBAL\Driver\Mysqli\MysqliException;
+use function sprintf;
+
+final class UnknownFetchMode extends MysqliException
+{
+    /**
+     * @param mixed $fetchMode
+     */
+    public static function new($fetchMode) : self
+    {
+        return new self(sprintf('Unknown fetch mode %d.', $fetchMode));
+    }
+}
diff --git a/lib/Doctrine/DBAL/Driver/Mysqli/Exception/UnknownType.php b/lib/Doctrine/DBAL/Driver/Mysqli/Exception/UnknownType.php
new file mode 100644
index 00000000000..d0e7c274762
--- /dev/null
+++ b/lib/Doctrine/DBAL/Driver/Mysqli/Exception/UnknownType.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\DBAL\Driver\Mysqli\Exception;
+
+use Doctrine\DBAL\Driver\Mysqli\MysqliException;
+use function sprintf;
+
+final class UnknownType extends MysqliException
+{
+    /**
+     * @param mixed $type
+     */
+    public static function new($type) : self
+    {
+        return new self(sprintf('Unknown type, %d given.', $type));
+    }
+}
diff --git a/lib/Doctrine/DBAL/Driver/Mysqli/MysqliConnection.php b/lib/Doctrine/DBAL/Driver/Mysqli/MysqliConnection.php
index 37b29be6f5b..b02a3ccb852 100644
--- a/lib/Doctrine/DBAL/Driver/Mysqli/MysqliConnection.php
+++ b/lib/Doctrine/DBAL/Driver/Mysqli/MysqliConnection.php
@@ -5,6 +5,7 @@
 namespace Doctrine\DBAL\Driver\Mysqli;
 
 use Doctrine\DBAL\Driver\Connection;
+use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionError;
 use Doctrine\DBAL\Driver\PingableConnection;
 use Doctrine\DBAL\Driver\ResultStatement;
 use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
@@ -73,7 +74,7 @@ public function __construct(array $params, $username, $password, array $driverOp
         });
         try {
             if (! $this->conn->real_connect($host, $username, $password, $dbname, $port, $socket, $flags)) {
-                throw MysqliException::fromConnectionError($this->conn);
+                throw ConnectionError::new($this->conn);
             }
         } finally {
             restore_error_handler();
@@ -161,7 +162,7 @@ public function quote(string $input) : string
     public function exec(string $statement) : int
     {
         if ($this->conn->query($statement) === false) {
-            throw MysqliException::fromConnectionError($this->conn);
+            throw ConnectionError::new($this->conn);
         }
 
         return $this->conn->affected_rows;
@@ -189,7 +190,7 @@ public function beginTransaction() : void
     public function commit() : void
     {
         if (! $this->conn->commit()) {
-            throw MysqliException::fromConnectionError($this->conn);
+            throw ConnectionError::new($this->conn);
         }
     }
 
@@ -199,7 +200,7 @@ public function commit() : void
     public function rollBack() : void
     {
         if (! $this->conn->rollback()) {
-            throw MysqliException::fromConnectionError($this->conn);
+            throw ConnectionError::new($this->conn);
         }
     }
 
@@ -242,7 +243,7 @@ private function setDriverOptions(array $driverOptions = [])
                 continue;
             }
 
-            throw MysqliException::fromConnectionError($this->conn);
+            throw ConnectionError::new($this->conn);
         }
     }
 
diff --git a/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php b/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php
index 3a47d317bcb..b779bc40457 100644
--- a/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php
+++ b/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php
@@ -5,21 +5,10 @@
 namespace Doctrine\DBAL\Driver\Mysqli;
 
 use Doctrine\DBAL\Driver\AbstractDriverException;
-use mysqli;
-use mysqli_stmt;
 
 /**
  * Exception thrown in case the mysqli driver errors.
  */
 class MysqliException extends AbstractDriverException
 {
-    public static function fromConnectionError(mysqli $connection) : self
-    {
-        return new self($connection->error, $connection->sqlstate ?: null, $connection->errno);
-    }
-
-    public static function fromStatementError(mysqli_stmt $statement) : self
-    {
-        return new self($statement->error, $statement->sqlstate ?: null, $statement->errno);
-    }
 }
diff --git a/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php b/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php
index 9dd8e456dcd..af16a78e62c 100644
--- a/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php
+++ b/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php
@@ -5,6 +5,11 @@
 namespace Doctrine\DBAL\Driver\Mysqli;
 
 use Doctrine\DBAL\Driver\DriverException;
+use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionError;
+use Doctrine\DBAL\Driver\Mysqli\Exception\FailedReadingStreamOffset;
+use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError;
+use Doctrine\DBAL\Driver\Mysqli\Exception\UnknownFetchMode;
+use Doctrine\DBAL\Driver\Mysqli\Exception\UnknownType;
 use Doctrine\DBAL\Driver\Statement;
 use Doctrine\DBAL\Driver\StatementIterator;
 use Doctrine\DBAL\Exception\InvalidArgumentException;
@@ -25,7 +30,6 @@
 use function is_array;
 use function is_int;
 use function is_resource;
-use function sprintf;
 use function str_repeat;
 
 class MysqliStatement implements IteratorAggregate, Statement
@@ -87,7 +91,7 @@ public function __construct(mysqli $conn, $prepareString)
         $stmt = $conn->prepare($prepareString);
 
         if ($stmt === false) {
-            throw MysqliException::fromConnectionError($this->_conn);
+            throw ConnectionError::new($this->_conn);
         }
 
         $this->_stmt = $stmt;
@@ -109,7 +113,7 @@ public function bindParam($column, &$variable, $type = ParameterType::STRING, $l
         assert(is_int($column));
 
         if (! isset(self::$_paramTypeMap[$type])) {
-            throw new MysqliException(sprintf("Unknown type: '%s'", $type));
+            throw UnknownType::new($type);
         }
 
         $this->_bindedValues[$column] =& $variable;
@@ -124,7 +128,7 @@ public function bindValue($param, $value, $type = ParameterType::STRING) : void
         assert(is_int($param));
 
         if (! isset(self::$_paramTypeMap[$type])) {
-            throw new MysqliException(sprintf("Unknown type: '%s'", $type));
+            throw UnknownType::new($type);
         }
 
         $this->_values[$param]       = $value;
@@ -139,14 +143,14 @@ public function execute($params = null) : void
     {
         if ($params !== null && count($params) > 0) {
             if (! $this->bindUntypedValues($params)) {
-                throw MysqliException::fromStatementError($this->_stmt);
+                throw StatementError::new($this->_stmt);
             }
         } else {
             $this->bindTypedParameters();
         }
 
         if (! $this->_stmt->execute()) {
-            throw MysqliException::fromStatementError($this->_stmt);
+            throw StatementError::new($this->_stmt);
         }
 
         if ($this->_columnNames === null) {
@@ -193,7 +197,7 @@ public function execute($params = null) : void
             }
 
             if (! $this->_stmt->bind_result(...$refs)) {
-                throw MysqliException::fromStatementError($this->_stmt);
+                throw StatementError::new($this->_stmt);
             }
         }
 
@@ -232,7 +236,7 @@ private function bindTypedParameters()
         }
 
         if (count($values) > 0 && ! $this->_stmt->bind_param($types, ...$values)) {
-            throw MysqliException::fromStatementError($this->_stmt);
+            throw StatementError::new($this->_stmt);
         }
 
         $this->sendLongData($streams);
@@ -250,11 +254,11 @@ private function sendLongData($streams)
                 $chunk = fread($stream, 8192);
 
                 if ($chunk === false) {
-                    throw new MysqliException("Failed reading the stream resource for parameter offset ${paramNr}.");
+                    throw FailedReadingStreamOffset::new($paramNr);
                 }
 
                 if (! $this->_stmt->send_long_data($paramNr - 1, $chunk)) {
-                    throw MysqliException::fromStatementError($this->_stmt);
+                    throw StatementError::new($this->_stmt);
                 }
             }
         }
@@ -322,7 +326,7 @@ public function fetch($fetchMode = null, ...$args)
         }
 
         if ($values === false) {
-            throw MysqliException::fromStatementError($this->_stmt);
+            throw StatementError::new($this->_stmt);
         }
 
         if ($fetchMode === FetchMode::NUMERIC) {
@@ -344,7 +348,7 @@ public function fetch($fetchMode = null, ...$args)
                 return (object) $assoc;
 
             default:
-                throw new MysqliException(sprintf("Unknown fetch type '%s'", $fetchMode));
+                throw UnknownFetchMode::new($fetchMode);
         }
     }
 
diff --git a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php
index b06ab3ae51e..12ac618354f 100644
--- a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php
+++ b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php
@@ -156,7 +156,7 @@ public static function convertPositionalToNamedPlaceholders($statement)
 
         if ($currentLiteralDelimiter) {
             throw new OCI8Exception(sprintf(
-                'The statement contains non-terminated string literal starting at offset %d',
+                'The statement contains non-terminated string literal starting at offset %d.',
                 $tokenOffset - 1
             ));
         }
@@ -407,7 +407,7 @@ public function fetch($fetchMode = null, ...$args)
         }
 
         if (! isset(self::$fetchModeMap[$fetchMode])) {
-            throw new InvalidArgumentException('Invalid fetch style: ' . $fetchMode);
+            throw new InvalidArgumentException(sprintf('Invalid fetch mode %d.', $fetchMode));
         }
 
         return oci_fetch_array(
@@ -434,7 +434,7 @@ public function fetchAll($fetchMode = null, ...$args)
         }
 
         if (! isset(self::$fetchModeMap[$fetchMode])) {
-            throw new InvalidArgumentException('Invalid fetch style: ' . $fetchMode);
+            throw new InvalidArgumentException(sprintf('Invalid fetch mode %d.', $fetchMode));
         }
 
         if (self::$fetchModeMap[$fetchMode] === OCI_BOTH) {
diff --git a/lib/Doctrine/DBAL/Driver/PDOStatement.php b/lib/Doctrine/DBAL/Driver/PDOStatement.php
index cfcbb92cf31..abe2d054929 100644
--- a/lib/Doctrine/DBAL/Driver/PDOStatement.php
+++ b/lib/Doctrine/DBAL/Driver/PDOStatement.php
@@ -206,7 +206,7 @@ private function convertParamType(int $type) : int
         if (! isset(self::PARAM_TYPE_MAP[$type])) {
             // TODO: next major: throw an exception
             @trigger_error(sprintf(
-                'Using a PDO parameter type (%d given) is deprecated and will cause an error in Doctrine 3.0',
+                'Using a PDO parameter type (%d given) is deprecated and will cause an error in Doctrine 3.0.',
                 $type
             ), E_USER_DEPRECATED);
 
@@ -227,7 +227,7 @@ private function convertFetchMode(int $fetchMode) : int
             // TODO: next major: throw an exception
             @trigger_error(sprintf(
                 'Using a PDO fetch mode or their combination (%d given)' .
-                ' is deprecated and will cause an error in Doctrine 3.0',
+                ' is deprecated and will cause an error in Doctrine 3.0.',
                 $fetchMode
             ), E_USER_DEPRECATED);
 
diff --git a/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php b/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php
index 8e5c59fb1f5..42f6f12e6ba 100644
--- a/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php
+++ b/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php
@@ -6,6 +6,7 @@
 
 use Doctrine\DBAL\Driver\Statement;
 use Doctrine\DBAL\Driver\StatementIterator;
+use Doctrine\DBAL\Exception\GetVariableType;
 use Doctrine\DBAL\Exception\InvalidColumnIndex;
 use Doctrine\DBAL\FetchMode;
 use Doctrine\DBAL\ParameterType;
@@ -17,7 +18,6 @@
 use function array_key_exists;
 use function count;
 use function func_get_args;
-use function gettype;
 use function is_array;
 use function is_int;
 use function is_object;
@@ -73,7 +73,10 @@ class SQLAnywhereStatement implements IteratorAggregate, Statement
     public function __construct($conn, $sql)
     {
         if (! is_resource($conn)) {
-            throw new SQLAnywhereException('Invalid SQL Anywhere connection resource: ' . $conn);
+            throw new SQLAnywhereException(sprintf(
+                'Invalid SQL Anywhere connection resource, %s given.',
+                (new GetVariableType())->__invoke($conn)
+            ));
         }
 
         $this->conn = $conn;
@@ -108,7 +111,7 @@ public function bindParam($column, &$variable, $type = ParameterType::STRING, $l
                 break;
 
             default:
-                throw new SQLAnywhereException('Unknown type: ' . $type);
+                throw new SQLAnywhereException(sprintf('Unknown type %d.', $type));
         }
 
         $this->boundValues[$column] =& $variable;
@@ -215,7 +218,7 @@ public function fetch($fetchMode = null, ...$args)
                 return sasql_fetch_object($this->result);
 
             default:
-                throw new SQLAnywhereException('Fetch mode is not supported: ' . $fetchMode);
+                throw new SQLAnywhereException(sprintf('Fetch mode is not supported %d.', $fetchMode));
         }
     }
 
@@ -314,8 +317,8 @@ private function castObject(stdClass $sourceObject, $destinationClass, array $ct
         if (! is_string($destinationClass)) {
             if (! is_object($destinationClass)) {
                 throw new SQLAnywhereException(sprintf(
-                    'Destination class has to be of type string or object, %s given.',
-                    gettype($destinationClass)
+                    'Destination class has to be of type string or object, "%s" given.',
+                    (new GetVariableType())->__invoke($destinationClass)
                 ));
             }
         } else {
diff --git a/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php b/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php
index 287972c74a8..20e2d99f545 100644
--- a/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php
+++ b/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php
@@ -17,7 +17,7 @@ class Driver extends AbstractSQLServerDriver
     public function connect(array $params, $username = null, $password = null, array $driverOptions = [])
     {
         if (! isset($params['host'])) {
-            throw new SQLSrvException("Missing 'host' in configuration for sqlsrv driver.");
+            throw new SQLSrvException('Missing "host" in configuration for sqlsrv driver.');
         }
 
         $serverName = $params['host'];
diff --git a/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php b/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php
index 4cb8c65d9e7..f708ea136d8 100644
--- a/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php
+++ b/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php
@@ -345,7 +345,7 @@ public function fetch($fetchMode = null, ...$args)
             return sqlsrv_fetch_object($this->stmt, $className, $ctorArgs) ?: false;
         }
 
-        throw new SQLSrvException('Fetch mode is not supported!');
+        throw new SQLSrvException('Fetch mode is not supported.');
     }
 
     /**
diff --git a/lib/Doctrine/DBAL/Exception/DriverRequired.php b/lib/Doctrine/DBAL/Exception/DriverRequired.php
index 6f0086628c2..9623b2a8a51 100644
--- a/lib/Doctrine/DBAL/Exception/DriverRequired.php
+++ b/lib/Doctrine/DBAL/Exception/DriverRequired.php
@@ -17,15 +17,15 @@ public static function new(?string $url = null) : self
         if ($url !== null) {
             return new self(
                 sprintf(
-                    "The options 'driver' or 'driverClass' are mandatory if a connection URL without scheme "
-                        . 'is given to DriverManager::getConnection(). Given URL: %s',
+                    'The options "driver" or "driverClass" are mandatory if a connection URL without scheme '
+                        . 'is given to DriverManager::getConnection(). Given URL "%s".',
                     $url
                 )
             );
         }
 
         return new self(
-            "The options 'driver' or 'driverClass' are mandatory if no PDO "
+            'The options "driver" or "driverClass" are mandatory if no PDO '
                 . 'instance is given to DriverManager::getConnection().'
         );
     }
diff --git a/lib/Doctrine/DBAL/Exception/EmptyCriteriaNotAllowed.php b/lib/Doctrine/DBAL/Exception/EmptyCriteriaNotAllowed.php
index 517e693ff52..fa378b3b769 100644
--- a/lib/Doctrine/DBAL/Exception/EmptyCriteriaNotAllowed.php
+++ b/lib/Doctrine/DBAL/Exception/EmptyCriteriaNotAllowed.php
@@ -8,6 +8,6 @@ final class EmptyCriteriaNotAllowed extends InvalidArgumentException
 {
     public static function new() : self
     {
-        return new self('Empty criteria was used, expected non-empty criteria');
+        return new self('Empty criteria was used, expected non-empty criteria.');
     }
 }
diff --git a/lib/Doctrine/DBAL/Exception/GetVariableType.php b/lib/Doctrine/DBAL/Exception/GetVariableType.php
new file mode 100644
index 00000000000..8ccf63d1bd1
--- /dev/null
+++ b/lib/Doctrine/DBAL/Exception/GetVariableType.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\DBAL\Exception;
+
+use function get_class;
+use function get_resource_type;
+use function gettype;
+use function is_bool;
+use function is_object;
+use function is_resource;
+
+final class GetVariableType
+{
+    /**
+     * @param mixed $value
+     */
+    public function __invoke($value) : string
+    {
+        if (is_object($value)) {
+            return get_class($value);
+        }
+
+        if (is_resource($value)) {
+            return get_resource_type($value);
+        }
+
+        if (is_bool($value)) {
+            return $value === true ? 'true' : 'false';
+        }
+
+        return gettype($value);
+    }
+}
diff --git a/lib/Doctrine/DBAL/Exception/InvalidDriverClass.php b/lib/Doctrine/DBAL/Exception/InvalidDriverClass.php
index e297bd5953c..4c090607e21 100644
--- a/lib/Doctrine/DBAL/Exception/InvalidDriverClass.php
+++ b/lib/Doctrine/DBAL/Exception/InvalidDriverClass.php
@@ -14,7 +14,7 @@ public static function new(string $driverClass) : self
     {
         return new self(
             sprintf(
-                "The given 'driverClass' %s has to implement the %s interface.",
+                'The given "driverClass" %s has to implement the %s interface.',
                 $driverClass,
                 Driver::class
             )
diff --git a/lib/Doctrine/DBAL/Exception/InvalidPdoInstance.php b/lib/Doctrine/DBAL/Exception/InvalidPdoInstance.php
index 1d895d73ada..dd5609e65d9 100644
--- a/lib/Doctrine/DBAL/Exception/InvalidPdoInstance.php
+++ b/lib/Doctrine/DBAL/Exception/InvalidPdoInstance.php
@@ -10,6 +10,6 @@ final class InvalidPdoInstance extends DBALException
 {
     public static function new() : self
     {
-        return new self("The 'pdo' option was used in DriverManager::getConnection() but no instance of PDO was given.");
+        return new self('The "pdo" option was used in DriverManager::getConnection() but no instance of PDO was given.');
     }
 }
diff --git a/lib/Doctrine/DBAL/Exception/InvalidPlatformType.php b/lib/Doctrine/DBAL/Exception/InvalidPlatformType.php
index b8658533503..c10a7d774f1 100644
--- a/lib/Doctrine/DBAL/Exception/InvalidPlatformType.php
+++ b/lib/Doctrine/DBAL/Exception/InvalidPlatformType.php
@@ -7,7 +7,6 @@
 use Doctrine\DBAL\DBALException;
 use Doctrine\DBAL\Platforms\AbstractPlatform;
 use function get_class;
-use function gettype;
 use function is_object;
 use function sprintf;
 
@@ -21,7 +20,7 @@ public static function new($invalidPlatform) : self
         if (is_object($invalidPlatform)) {
             return new self(
                 sprintf(
-                    "Option 'platform' must be a subtype of '%s', instance of '%s' given",
+                    'Option "platform" must be a subtype of %s, instance of %s given.',
                     AbstractPlatform::class,
                     get_class($invalidPlatform)
                 )
@@ -30,9 +29,9 @@ public static function new($invalidPlatform) : self
 
         return new self(
             sprintf(
-                "Option 'platform' must be an object and subtype of '%s'. Got '%s'",
+                'Option "platform" must be an object and subtype of %s. Got %s.',
                 AbstractPlatform::class,
-                gettype($invalidPlatform)
+                (new GetVariableType())->__invoke($invalidPlatform)
             )
         );
     }
diff --git a/lib/Doctrine/DBAL/Exception/InvalidWrapperClass.php b/lib/Doctrine/DBAL/Exception/InvalidWrapperClass.php
index 474f639d719..5c81978b120 100644
--- a/lib/Doctrine/DBAL/Exception/InvalidWrapperClass.php
+++ b/lib/Doctrine/DBAL/Exception/InvalidWrapperClass.php
@@ -14,7 +14,7 @@ public static function new(string $wrapperClass) : self
     {
         return new self(
             sprintf(
-                "The given 'wrapperClass' %s has to be a subtype of %s.",
+                'The given "wrapperClass" %s has to be a subtype of %s.',
                 $wrapperClass,
                 Connection::class
             )
diff --git a/lib/Doctrine/DBAL/Exception/MissingSQLParam.php b/lib/Doctrine/DBAL/Exception/MissingArrayParameter.php
similarity index 50%
rename from lib/Doctrine/DBAL/Exception/MissingSQLParam.php
rename to lib/Doctrine/DBAL/Exception/MissingArrayParameter.php
index c15302555bc..e3e21f25f27 100644
--- a/lib/Doctrine/DBAL/Exception/MissingSQLParam.php
+++ b/lib/Doctrine/DBAL/Exception/MissingArrayParameter.php
@@ -7,12 +7,10 @@
 use Doctrine\DBAL\SQLParserUtilsException;
 use function sprintf;
 
-final class MissingSQLParam extends SQLParserUtilsException
+final class MissingArrayParameter extends SQLParserUtilsException
 {
     public static function new(string $paramName) : self
     {
-        return new self(
-            sprintf('Value for :%1$s not found in params array. Params array key should be "%1$s"', $paramName)
-        );
+        return new self(sprintf('Parameter "%s" is missing.', $paramName));
     }
 }
diff --git a/lib/Doctrine/DBAL/Exception/MissingArrayParameterType.php b/lib/Doctrine/DBAL/Exception/MissingArrayParameterType.php
new file mode 100644
index 00000000000..61523381b0e
--- /dev/null
+++ b/lib/Doctrine/DBAL/Exception/MissingArrayParameterType.php
@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\DBAL\Exception;
+
+use Doctrine\DBAL\SQLParserUtilsException;
+use function sprintf;
+
+final class MissingArrayParameterType extends SQLParserUtilsException
+{
+    public static function new(string $paramName) : self
+    {
+        return new self(sprintf('Type of array parameter "%s" is missing.', $paramName));
+    }
+}
diff --git a/lib/Doctrine/DBAL/Exception/MissingSQLType.php b/lib/Doctrine/DBAL/Exception/MissingSQLType.php
deleted file mode 100644
index c059324bf8c..00000000000
--- a/lib/Doctrine/DBAL/Exception/MissingSQLType.php
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Doctrine\DBAL\Exception;
-
-use Doctrine\DBAL\SQLParserUtilsException;
-use function sprintf;
-
-final class MissingSQLType extends SQLParserUtilsException
-{
-    public static function new(string $typeName) : self
-    {
-        return new self(
-            sprintf('Value for :%1$s not found in types array. Types array key should be "%1$s"', $typeName)
-        );
-    }
-}
diff --git a/lib/Doctrine/DBAL/Exception/UnknownDriver.php b/lib/Doctrine/DBAL/Exception/UnknownDriver.php
index 98d5d4770f5..73ff10a2d24 100644
--- a/lib/Doctrine/DBAL/Exception/UnknownDriver.php
+++ b/lib/Doctrine/DBAL/Exception/UnknownDriver.php
@@ -17,7 +17,7 @@ public static function new(string $unknownDriverName, array $knownDrivers) : sel
     {
         return new self(
             sprintf(
-                "The given 'driver' %s is unknown, Doctrine currently supports only the following drivers: %s",
+                'The given "driver" "%s" is unknown, Doctrine currently supports only the following drivers: %s',
                 $unknownDriverName,
                 implode(', ', $knownDrivers)
             )
diff --git a/lib/Doctrine/DBAL/Id/TableGenerator.php b/lib/Doctrine/DBAL/Id/TableGenerator.php
index ad4770598e4..466b30fca0f 100644
--- a/lib/Doctrine/DBAL/Id/TableGenerator.php
+++ b/lib/Doctrine/DBAL/Id/TableGenerator.php
@@ -12,6 +12,7 @@
 use Throwable;
 use const CASE_LOWER;
 use function array_change_key_case;
+use function sprintf;
 
 /**
  * Table ID Generator for those poor languages that are missing sequences.
@@ -126,7 +127,7 @@ public function nextValue($sequenceName)
                 $rows = $this->conn->executeUpdate($sql, [$sequenceName, $row['sequence_value']]);
 
                 if ($rows !== 1) {
-                    throw new DBALException('Race-condition detected while updating sequence. Aborting generation');
+                    throw new DBALException('Race condition detected while updating sequence. Aborting generation.');
                 }
             } else {
                 $this->conn->insert(
@@ -139,7 +140,7 @@ public function nextValue($sequenceName)
             $this->conn->commit();
         } catch (Throwable $e) {
             $this->conn->rollBack();
-            throw new DBALException('Error occurred while generating ID with TableGenerator, aborted generation: ' . $e->getMessage(), 0, $e);
+            throw new DBALException(sprintf('Error occurred while generating ID with TableGenerator, aborted generation with error: %s', $e->getMessage()), 0, $e);
         }
 
         return $value;
diff --git a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php
index 26d067ecd4d..a93a6445819 100644
--- a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php
+++ b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php
@@ -420,7 +420,11 @@ public function getDoctrineTypeMapping($dbType)
         $dbType = strtolower($dbType);
 
         if (! isset($this->doctrineTypeMapping[$dbType])) {
-            throw new DBALException('Unknown database type ' . $dbType . ' requested, ' . static::class . ' may not support it.');
+            throw new DBALException(sprintf(
+                'Unknown database type "%s" requested, %s may not support it.',
+                $dbType,
+                static::class
+            ));
         }
 
         return $this->doctrineTypeMapping[$dbType];
@@ -762,7 +766,7 @@ public function getTrimExpression(string $str, int $mode = TrimMode::UNSPECIFIED
             default:
                 throw new InvalidArgumentException(
                     sprintf(
-                        'The value of $mode is expected to be one of the TrimMode constants, %d given',
+                        'The value of $mode is expected to be one of the TrimMode constants, %d given.',
                         $mode
                     )
                 );
@@ -1323,7 +1327,7 @@ public function getDropTableSQL($table)
                 $sql = $eventArgs->getSql();
 
                 if ($sql === null) {
-                    throw new UnexpectedValueException('Default implementation of DROP TABLE was overridden with NULL');
+                    throw new UnexpectedValueException('Default implementation of DROP TABLE was overridden with NULL.');
                 }
 
                 return $sql;
@@ -1428,7 +1432,7 @@ public function getDropForeignKeySQL($foreignKey, $table)
     public function getCreateTableSQL(Table $table, $createFlags = self::CREATE_INDEXES)
     {
         if (! is_int($createFlags)) {
-            throw new InvalidArgumentException('Second argument of AbstractPlatform::getCreateTableSQL() has to be integer.');
+            throw new InvalidArgumentException('Second argument of AbstractPlatform::getCreateTableSQL() has to be an integer.');
         }
 
         if (count($table->getColumns()) === 0) {
@@ -1701,7 +1705,7 @@ public function getCreateIndexSQL(Index $index, $table)
         $columns = $index->getColumns();
 
         if (count($columns) === 0) {
-            throw new InvalidArgumentException("Incomplete definition. 'columns' required.");
+            throw new InvalidArgumentException('Incomplete definition. "columns" required.');
         }
 
         if ($index->isPrimary()) {
@@ -2262,7 +2266,7 @@ public function getUniqueConstraintDeclarationSQL($name, UniqueConstraint $const
         $name    = new Identifier($name);
 
         if (count($columns) === 0) {
-            throw new InvalidArgumentException("Incomplete definition. 'columns' required.");
+            throw new InvalidArgumentException('Incomplete definition. "columns" required.');
         }
 
         $flags = ['UNIQUE'];
@@ -2295,7 +2299,7 @@ public function getIndexDeclarationSQL($name, Index $index)
         $name    = new Identifier($name);
 
         if (count($columns) === 0) {
-            throw new InvalidArgumentException("Incomplete definition. 'columns' required.");
+            throw new InvalidArgumentException('Incomplete definition. "columns" required.');
         }
 
         return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name->getQuotedName($this) . ' ('
@@ -2433,7 +2437,7 @@ public function getForeignKeyReferentialActionSQL($action)
             case 'SET DEFAULT':
                 return $upper;
             default:
-                throw new InvalidArgumentException('Invalid foreign key action: ' . $upper);
+                throw new InvalidArgumentException(sprintf('Invalid foreign key action "%s".', $upper));
         }
     }
 
@@ -2454,13 +2458,13 @@ public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey
         $sql .= 'FOREIGN KEY (';
 
         if (count($foreignKey->getLocalColumns()) === 0) {
-            throw new InvalidArgumentException("Incomplete definition. 'local' required.");
+            throw new InvalidArgumentException('Incomplete definition. "local" required.');
         }
         if (count($foreignKey->getForeignColumns()) === 0) {
-            throw new InvalidArgumentException("Incomplete definition. 'foreign' required.");
+            throw new InvalidArgumentException('Incomplete definition. "foreign" required.');
         }
         if (strlen($foreignKey->getForeignTableName()) === 0) {
-            throw new InvalidArgumentException("Incomplete definition. 'foreignTable' required.");
+            throw new InvalidArgumentException('Incomplete definition. "foreignTable" required.');
         }
 
         return $sql . implode(', ', $foreignKey->getQuotedLocalColumns($this))
@@ -2642,7 +2646,7 @@ protected function _getTransactionIsolationLevelSQL($level)
             case TransactionIsolationLevel::SERIALIZABLE:
                 return 'SERIALIZABLE';
             default:
-                throw new InvalidArgumentException('Invalid isolation level:' . $level);
+                throw new InvalidArgumentException(sprintf('Invalid isolation level "%s".', $level));
         }
     }
 
@@ -3286,14 +3290,14 @@ final public function modifyLimitQuery(string $query, ?int $limit, int $offset =
     {
         if ($offset < 0) {
             throw new DBALException(sprintf(
-                'Offset must be a positive integer or zero, %d given',
+                'Offset must be a positive integer or zero, %d given.',
                 $offset
             ));
         }
 
         if ($offset > 0 && ! $this->supportsLimitOffset()) {
             throw new DBALException(sprintf(
-                'Platform %s does not support offset values in limit queries.',
+                'Platform "%s" does not support offset values in limit queries.',
                 $this->getName()
             ));
         }
diff --git a/lib/Doctrine/DBAL/Platforms/Exception/NoColumnsSpecifiedForTable.php b/lib/Doctrine/DBAL/Platforms/Exception/NoColumnsSpecifiedForTable.php
index 2e96b5ffe90..c16c745e024 100644
--- a/lib/Doctrine/DBAL/Platforms/Exception/NoColumnsSpecifiedForTable.php
+++ b/lib/Doctrine/DBAL/Platforms/Exception/NoColumnsSpecifiedForTable.php
@@ -11,6 +11,6 @@ final class NoColumnsSpecifiedForTable extends DBALException implements Platform
 {
     public static function new(string $tableName) : self
     {
-        return new self(sprintf('No columns specified for table %s', $tableName));
+        return new self(sprintf('No columns specified for table "%s".', $tableName));
     }
 }
diff --git a/lib/Doctrine/DBAL/Platforms/Exception/NotSupported.php b/lib/Doctrine/DBAL/Platforms/Exception/NotSupported.php
index 5cd890308f4..0086f1ef7dd 100644
--- a/lib/Doctrine/DBAL/Platforms/Exception/NotSupported.php
+++ b/lib/Doctrine/DBAL/Platforms/Exception/NotSupported.php
@@ -11,6 +11,6 @@ final class NotSupported extends DBALException implements PlatformException
 {
     public static function new(string $method) : self
     {
-        return new self(sprintf('Operation \'%s\' is not supported by platform.', $method));
+        return new self(sprintf('Operation "%s" is not supported by platform.', $method));
     }
 }
diff --git a/lib/Doctrine/DBAL/Platforms/OraclePlatform.php b/lib/Doctrine/DBAL/Platforms/OraclePlatform.php
index 63f7442786c..ddc320fbe5f 100644
--- a/lib/Doctrine/DBAL/Platforms/OraclePlatform.php
+++ b/lib/Doctrine/DBAL/Platforms/OraclePlatform.php
@@ -42,7 +42,7 @@ class OraclePlatform extends AbstractPlatform
     public static function assertValidIdentifier($identifier)
     {
         if (! preg_match('(^(([a-zA-Z]{1}[a-zA-Z0-9_$#]{0,})|("[^"]+"))$)', $identifier)) {
-            throw new DBALException('Invalid Oracle identifier');
+            throw new DBALException('Invalid Oracle identifier.');
         }
     }
 
@@ -740,7 +740,7 @@ public function getForeignKeyReferentialActionSQL($action)
 
             default:
                 // SET DEFAULT is not supported, throw exception instead.
-                throw new InvalidArgumentException('Invalid foreign key action: ' . $action);
+                throw new InvalidArgumentException(sprintf('Invalid foreign key action "%s".', $action));
         }
     }
 
diff --git a/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php b/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php
index b2601286ff7..29c484146b3 100644
--- a/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php
+++ b/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php
@@ -828,7 +828,10 @@ private function convertSingleBooleanValue($value, $callback)
             return $callback(true);
         }
 
-        throw new UnexpectedValueException("Unrecognized boolean literal '${value}'");
+        throw new UnexpectedValueException(sprintf(
+            'Unrecognized boolean literal, %s given.',
+            $value
+        ));
     }
 
     /**
diff --git a/lib/Doctrine/DBAL/Platforms/SQLAnywherePlatform.php b/lib/Doctrine/DBAL/Platforms/SQLAnywherePlatform.php
index f868cfbd8e6..2c7114d3310 100644
--- a/lib/Doctrine/DBAL/Platforms/SQLAnywherePlatform.php
+++ b/lib/Doctrine/DBAL/Platforms/SQLAnywherePlatform.php
@@ -551,7 +551,7 @@ public function getDropIndexSQL($index, $table = null)
 
         if (! is_string($index)) {
             throw new InvalidArgumentException(
-                'SQLAnywherePlatform::getDropIndexSQL() expects $index parameter to be string or ' . Index::class . '.'
+                sprintf('SQLAnywherePlatform::getDropIndexSQL() expects $index parameter to be a string or an instance of %s.', Index::class)
             );
         }
 
@@ -565,7 +565,7 @@ public function getDropIndexSQL($index, $table = null)
 
         if (! is_string($table)) {
             throw new InvalidArgumentException(
-                'SQLAnywherePlatform::getDropIndexSQL() expects $table parameter to be string or ' . Index::class . '.'
+                sprintf('SQLAnywherePlatform::getDropIndexSQL() expects $table parameter to be a string or an instance of %s.', Index::class)
             );
         }
 
@@ -596,15 +596,15 @@ public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey
         }
 
         if (empty($localColumns)) {
-            throw new InvalidArgumentException("Incomplete definition. 'local' required.");
+            throw new InvalidArgumentException('Incomplete definition. "local" required.');
         }
 
         if (empty($foreignColumns)) {
-            throw new InvalidArgumentException("Incomplete definition. 'foreign' required.");
+            throw new InvalidArgumentException('Incomplete definition. "foreign" required.');
         }
 
         if (empty($foreignTableName)) {
-            throw new InvalidArgumentException("Incomplete definition. 'foreignTable' required.");
+            throw new InvalidArgumentException('Incomplete definition. "foreignTable" required.');
         }
 
         if ($foreignKey->hasOption('notnull') && (bool) $foreignKey->getOption('notnull')) {
@@ -644,7 +644,7 @@ public function getForeignKeyMatchClauseSQL($type)
             case self::FOREIGN_KEY_MATCH_FULL_UNIQUE:
                 return 'UNIQUE FULL';
             default:
-                throw new InvalidArgumentException('Invalid foreign key match type: ' . $type);
+                throw new InvalidArgumentException(sprintf('Invalid foreign key match type "%s".', $type));
         }
     }
 
@@ -1355,7 +1355,7 @@ protected function _getTransactionIsolationLevelSQL($level)
             case TransactionIsolationLevel::SERIALIZABLE:
                 return 3;
             default:
-                throw new InvalidArgumentException('Invalid isolation level:' . $level);
+                throw new InvalidArgumentException(sprintf('Invalid isolation level %d.', $level));
         }
     }
 
@@ -1446,7 +1446,7 @@ protected function getTableConstraintDeclarationSQL(Constraint $constraint, $nam
         }
 
         if (! $constraint instanceof Index) {
-            throw new InvalidArgumentException('Unsupported constraint type: ' . get_class($constraint));
+            throw new InvalidArgumentException(sprintf('Unsupported constraint type %s.', get_class($constraint)));
         }
 
         if (! $constraint->isPrimary() && ! $constraint->isUnique()) {
@@ -1459,7 +1459,7 @@ protected function getTableConstraintDeclarationSQL(Constraint $constraint, $nam
         $constraintColumns = $constraint->getQuotedColumns($this);
 
         if (empty($constraintColumns)) {
-            throw new InvalidArgumentException("Incomplete definition. 'columns' required.");
+            throw new InvalidArgumentException('Incomplete definition. "columns" required.');
         }
 
         $sql   = '';
diff --git a/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php b/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php
index 4fdde820d25..43bf722d2c8 100644
--- a/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php
+++ b/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php
@@ -215,7 +215,10 @@ public function getDropIndexSQL($index, $table = null)
         if ($index instanceof Index) {
             $index = $index->getQuotedName($this);
         } elseif (! is_string($index)) {
-            throw new InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.');
+            throw new InvalidArgumentException(sprintf(
+                'AbstractPlatform::getDropIndexSQL() expects $index parameter to be a string or an instanceof %s.',
+                Index::class
+            ));
         }
 
         if (! isset($table)) {
@@ -384,7 +387,7 @@ protected function getCreateColumnCommentSQL($tableName, $columnName, $comment)
     public function getDefaultConstraintDeclarationSQL($table, array $column)
     {
         if (! isset($column['default'])) {
-            throw new InvalidArgumentException("Incomplete column definition. 'default' required.");
+            throw new InvalidArgumentException('Incomplete column definition. "default" required.');
         }
 
         $columnName = new Identifier($column['name']);
diff --git a/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php b/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php
index a8630569e6d..f820c88cdb9 100644
--- a/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php
+++ b/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php
@@ -80,7 +80,7 @@ public function getTrimExpression(string $str, int $mode = TrimMode::UNSPECIFIED
             default:
                 throw new InvalidArgumentException(
                     sprintf(
-                        'The value of $mode is expected to be one of the TrimMode constants, %d given',
+                        'The value of $mode is expected to be one of the TrimMode constants, %d given.',
                         $mode
                     )
                 );
@@ -655,7 +655,7 @@ protected function getReservedKeywordsClass()
     protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
     {
         if (! $diff->fromTable instanceof Table) {
-            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
+            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema.');
         }
 
         $sql = [];
@@ -676,7 +676,7 @@ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
     protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff)
     {
         if (! $diff->fromTable instanceof Table) {
-            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
+            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema.');
         }
 
         $sql       = [];
@@ -813,7 +813,7 @@ public function getAlterTableSQL(TableDiff $diff)
 
         $fromTable = $diff->fromTable;
         if (! $fromTable instanceof Table) {
-            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
+            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema.');
         }
 
         $table = clone $fromTable;
diff --git a/lib/Doctrine/DBAL/Query/Exception/NonUniqueAlias.php b/lib/Doctrine/DBAL/Query/Exception/NonUniqueAlias.php
index 7fb71b4463a..fdd20a9a8ce 100644
--- a/lib/Doctrine/DBAL/Query/Exception/NonUniqueAlias.php
+++ b/lib/Doctrine/DBAL/Query/Exception/NonUniqueAlias.php
@@ -17,7 +17,7 @@ public static function new(string $alias, array $registeredAliases) : self
     {
         return new self(
             sprintf(
-                "The given alias '%s' is not unique in FROM and JOIN clause table. "
+                'The given alias "%s" is not unique in FROM and JOIN clause table. '
                     . 'The currently registered aliases are: %s.',
                 $alias,
                 implode(', ', $registeredAliases)
diff --git a/lib/Doctrine/DBAL/Query/Exception/UnknownAlias.php b/lib/Doctrine/DBAL/Query/Exception/UnknownAlias.php
index bb355179174..5d7600a6aa4 100644
--- a/lib/Doctrine/DBAL/Query/Exception/UnknownAlias.php
+++ b/lib/Doctrine/DBAL/Query/Exception/UnknownAlias.php
@@ -17,7 +17,7 @@ public static function new(string $alias, array $registeredAliases) : self
     {
         return new self(
             sprintf(
-                "The given alias '%s' is not part of any FROM or JOIN clause table. "
+                'The given alias "%s" is not part of any FROM or JOIN clause table. '
                     . 'The currently registered aliases are: %s.',
                 $alias,
                 implode(', ', $registeredAliases)
diff --git a/lib/Doctrine/DBAL/SQLParserUtils.php b/lib/Doctrine/DBAL/SQLParserUtils.php
index 3c5d92b8a2f..3ed2ad90038 100644
--- a/lib/Doctrine/DBAL/SQLParserUtils.php
+++ b/lib/Doctrine/DBAL/SQLParserUtils.php
@@ -4,8 +4,8 @@
 
 namespace Doctrine\DBAL;
 
-use Doctrine\DBAL\Exception\MissingSQLParam;
-use Doctrine\DBAL\Exception\MissingSQLType;
+use Doctrine\DBAL\Exception\MissingArrayParameter;
+use Doctrine\DBAL\Exception\MissingArrayParameterType;
 use const PREG_OFFSET_CAPTURE;
 use function array_fill;
 use function array_key_exists;
@@ -287,9 +287,9 @@ private static function extractParam($paramName, $paramsOrTypes, $isParam, $defa
         }
 
         if ($isParam) {
-            throw MissingSQLParam::new($paramName);
+            throw MissingArrayParameter::new($paramName);
         }
 
-        throw MissingSQLType::new($paramName);
+        throw MissingArrayParameterType::new($paramName);
     }
 }
diff --git a/lib/Doctrine/DBAL/Schema/Exception/ColumnAlreadyExists.php b/lib/Doctrine/DBAL/Schema/Exception/ColumnAlreadyExists.php
index 6205bd3120d..80abd19757d 100644
--- a/lib/Doctrine/DBAL/Schema/Exception/ColumnAlreadyExists.php
+++ b/lib/Doctrine/DBAL/Schema/Exception/ColumnAlreadyExists.php
@@ -12,7 +12,7 @@ final class ColumnAlreadyExists extends SchemaException
     public static function new(string $tableName, string $columnName) : self
     {
         return new self(
-            sprintf("The column '%s' on table '%s' already exists.", $columnName, $tableName),
+            sprintf('The column "%s" on table "%s" already exists.', $columnName, $tableName),
             self::COLUMN_ALREADY_EXISTS
         );
     }
diff --git a/lib/Doctrine/DBAL/Schema/Exception/ColumnDoesNotExist.php b/lib/Doctrine/DBAL/Schema/Exception/ColumnDoesNotExist.php
index e83612d39d2..94428a5fe9b 100644
--- a/lib/Doctrine/DBAL/Schema/Exception/ColumnDoesNotExist.php
+++ b/lib/Doctrine/DBAL/Schema/Exception/ColumnDoesNotExist.php
@@ -12,7 +12,7 @@ final class ColumnDoesNotExist extends SchemaException
     public static function new(string $columnName, string $table) : self
     {
         return new self(
-            sprintf("There is no column with name '%s' on table '%s'.", $columnName, $table),
+            sprintf('There is no column with name "%s" on table "%s".', $columnName, $table),
             self::COLUMN_DOESNT_EXIST
         );
     }
diff --git a/lib/Doctrine/DBAL/Schema/Exception/ForeignKeyDoesNotExist.php b/lib/Doctrine/DBAL/Schema/Exception/ForeignKeyDoesNotExist.php
index 89804953179..df4da53314e 100644
--- a/lib/Doctrine/DBAL/Schema/Exception/ForeignKeyDoesNotExist.php
+++ b/lib/Doctrine/DBAL/Schema/Exception/ForeignKeyDoesNotExist.php
@@ -12,7 +12,7 @@ final class ForeignKeyDoesNotExist extends SchemaException
     public static function new(string $foreignKeyName, string $table) : self
     {
         return new self(
-            sprintf("There exists no foreign key with the name '%s' on table '%s'.", $foreignKeyName, $table),
+            sprintf('There exists no foreign key with the name "%s" on table "%s".', $foreignKeyName, $table),
             self::FOREIGNKEY_DOESNT_EXIST
         );
     }
diff --git a/lib/Doctrine/DBAL/Schema/Exception/IndexAlreadyExists.php b/lib/Doctrine/DBAL/Schema/Exception/IndexAlreadyExists.php
index 7525cab27d3..8bb16b685fd 100644
--- a/lib/Doctrine/DBAL/Schema/Exception/IndexAlreadyExists.php
+++ b/lib/Doctrine/DBAL/Schema/Exception/IndexAlreadyExists.php
@@ -12,7 +12,7 @@ final class IndexAlreadyExists extends SchemaException
     public static function new(string $indexName, string $table) : self
     {
         return new self(
-            sprintf("An index with name '%s' was already defined on table '%s'.", $indexName, $table),
+            sprintf('An index with name "%s" was already defined on table "%s".', $indexName, $table),
             self::INDEX_ALREADY_EXISTS
         );
     }
diff --git a/lib/Doctrine/DBAL/Schema/Exception/IndexDoesNotExist.php b/lib/Doctrine/DBAL/Schema/Exception/IndexDoesNotExist.php
index fcca725d48f..93c2aee0dd6 100644
--- a/lib/Doctrine/DBAL/Schema/Exception/IndexDoesNotExist.php
+++ b/lib/Doctrine/DBAL/Schema/Exception/IndexDoesNotExist.php
@@ -12,7 +12,7 @@ final class IndexDoesNotExist extends SchemaException
     public static function new(string $indexName, string $table) : self
     {
         return new self(
-            sprintf("Index '%s' does not exist on table '%s'.", $indexName, $table),
+            sprintf('Index "%s" does not exist on table "%s".', $indexName, $table),
             self::INDEX_DOESNT_EXIST
         );
     }
diff --git a/lib/Doctrine/DBAL/Schema/Exception/IndexNameInvalid.php b/lib/Doctrine/DBAL/Schema/Exception/IndexNameInvalid.php
index 174ef1284b6..eed5db8435b 100644
--- a/lib/Doctrine/DBAL/Schema/Exception/IndexNameInvalid.php
+++ b/lib/Doctrine/DBAL/Schema/Exception/IndexNameInvalid.php
@@ -12,7 +12,7 @@ final class IndexNameInvalid extends SchemaException
     public static function new(string $indexName) : self
     {
         return new self(
-            sprintf('Invalid index-name %s given, has to be [a-zA-Z0-9_]', $indexName),
+            sprintf('Invalid index name "%s" given, has to be [a-zA-Z0-9_].', $indexName),
             self::INDEX_INVALID_NAME
         );
     }
diff --git a/lib/Doctrine/DBAL/Schema/Exception/InvalidTableName.php b/lib/Doctrine/DBAL/Schema/Exception/InvalidTableName.php
index 50d405adce5..974b251653d 100644
--- a/lib/Doctrine/DBAL/Schema/Exception/InvalidTableName.php
+++ b/lib/Doctrine/DBAL/Schema/Exception/InvalidTableName.php
@@ -11,6 +11,6 @@ final class InvalidTableName extends SchemaException
 {
     public static function new(string $tableName) : self
     {
-        return new self(sprintf('Invalid table name specified: %s', $tableName));
+        return new self(sprintf('Invalid table name specified "%s".', $tableName));
     }
 }
diff --git a/lib/Doctrine/DBAL/Schema/Exception/NamedForeignKeyRequired.php b/lib/Doctrine/DBAL/Schema/Exception/NamedForeignKeyRequired.php
index 43bd0852ee4..88695d865e5 100644
--- a/lib/Doctrine/DBAL/Schema/Exception/NamedForeignKeyRequired.php
+++ b/lib/Doctrine/DBAL/Schema/Exception/NamedForeignKeyRequired.php
@@ -16,8 +16,8 @@ public static function new(Table $localTable, ForeignKeyConstraint $foreignKey)
     {
         return new self(
             sprintf(
-                'The performed schema operation on %s requires a named foreign key, ' .
-                "but the given foreign key from (%s) onto foreign table '%s' (%s) is currently unnamed.",
+                'The performed schema operation on "%s" requires a named foreign key, ' .
+                'but the given foreign key from (%s) onto foreign table "%s" (%s) is currently unnamed.',
                 $localTable->getName(),
                 implode(', ', $foreignKey->getColumns()),
                 $foreignKey->getForeignTableName(),
diff --git a/lib/Doctrine/DBAL/Schema/Exception/NamespaceAlreadyExists.php b/lib/Doctrine/DBAL/Schema/Exception/NamespaceAlreadyExists.php
index 641d82e8222..15c08620b4c 100644
--- a/lib/Doctrine/DBAL/Schema/Exception/NamespaceAlreadyExists.php
+++ b/lib/Doctrine/DBAL/Schema/Exception/NamespaceAlreadyExists.php
@@ -12,7 +12,7 @@ final class NamespaceAlreadyExists extends SchemaException
     public static function new(string $namespaceName) : self
     {
         return new self(
-            sprintf("The namespace with name '%s' already exists.", $namespaceName),
+            sprintf('The namespace with name "%s" already exists.', $namespaceName),
             self::NAMESPACE_ALREADY_EXISTS
         );
     }
diff --git a/lib/Doctrine/DBAL/Schema/Exception/SequenceAlreadyExists.php b/lib/Doctrine/DBAL/Schema/Exception/SequenceAlreadyExists.php
index b88c5ae44fa..3ec47e60d63 100644
--- a/lib/Doctrine/DBAL/Schema/Exception/SequenceAlreadyExists.php
+++ b/lib/Doctrine/DBAL/Schema/Exception/SequenceAlreadyExists.php
@@ -12,7 +12,7 @@ final class SequenceAlreadyExists extends SchemaException
     public static function new(string $sequenceName) : self
     {
         return new self(
-            sprintf("The sequence '%s' already exists.", $sequenceName),
+            sprintf('The sequence "%s" already exists.', $sequenceName),
             self::SEQUENCE_ALREADY_EXISTS
         );
     }
diff --git a/lib/Doctrine/DBAL/Schema/Exception/SequenceDoesNotExist.php b/lib/Doctrine/DBAL/Schema/Exception/SequenceDoesNotExist.php
index 0d39f71feed..0995f9a343c 100644
--- a/lib/Doctrine/DBAL/Schema/Exception/SequenceDoesNotExist.php
+++ b/lib/Doctrine/DBAL/Schema/Exception/SequenceDoesNotExist.php
@@ -12,7 +12,7 @@ final class SequenceDoesNotExist extends SchemaException
     public static function new(string $sequenceName) : self
     {
         return new self(
-            sprintf("There exists no sequence with the name '%s'.", $sequenceName),
+            sprintf('There exists no sequence with the name "%s".', $sequenceName),
             self::SEQUENCE_DOENST_EXIST
         );
     }
diff --git a/lib/Doctrine/DBAL/Schema/Exception/TableAlreadyExists.php b/lib/Doctrine/DBAL/Schema/Exception/TableAlreadyExists.php
index 18c65dabfe7..4daeea27267 100644
--- a/lib/Doctrine/DBAL/Schema/Exception/TableAlreadyExists.php
+++ b/lib/Doctrine/DBAL/Schema/Exception/TableAlreadyExists.php
@@ -12,7 +12,7 @@ final class TableAlreadyExists extends SchemaException
     public static function new(string $tableName) : self
     {
         return new self(
-            sprintf("The table with name '%s' already exists.", $tableName),
+            sprintf('The table with name "%s" already exists.', $tableName),
             self::TABLE_ALREADY_EXISTS
         );
     }
diff --git a/lib/Doctrine/DBAL/Schema/Exception/TableDoesNotExist.php b/lib/Doctrine/DBAL/Schema/Exception/TableDoesNotExist.php
index eba042f42df..197f4f8588f 100644
--- a/lib/Doctrine/DBAL/Schema/Exception/TableDoesNotExist.php
+++ b/lib/Doctrine/DBAL/Schema/Exception/TableDoesNotExist.php
@@ -12,7 +12,7 @@ final class TableDoesNotExist extends SchemaException
     public static function new(string $tableName) : self
     {
         return new self(
-            sprintf("There is no table with name '%s' in the schema.", $tableName),
+            sprintf('There is no table with name "%s" in the schema.', $tableName),
             self::TABLE_DOESNT_EXIST
         );
     }
diff --git a/lib/Doctrine/DBAL/Schema/Exception/UniqueConstraintDoesNotExist.php b/lib/Doctrine/DBAL/Schema/Exception/UniqueConstraintDoesNotExist.php
index a252b3cc4a3..ff04953b54f 100644
--- a/lib/Doctrine/DBAL/Schema/Exception/UniqueConstraintDoesNotExist.php
+++ b/lib/Doctrine/DBAL/Schema/Exception/UniqueConstraintDoesNotExist.php
@@ -12,7 +12,7 @@ final class UniqueConstraintDoesNotExist extends SchemaException
     public static function new(string $constraintName, string $table) : self
     {
         return new self(
-            sprintf("There exists no unique constraint with the name '%s' on table '%s'.", $constraintName, $table),
+            sprintf('There exists no unique constraint with the name "%s" on table "%s".', $constraintName, $table),
             self::CONSTRAINT_DOESNT_EXIST
         );
     }
diff --git a/lib/Doctrine/DBAL/Schema/Table.php b/lib/Doctrine/DBAL/Schema/Table.php
index 4983d3effb2..6eb93a51ad2 100644
--- a/lib/Doctrine/DBAL/Schema/Table.php
+++ b/lib/Doctrine/DBAL/Schema/Table.php
@@ -22,6 +22,7 @@
 use function in_array;
 use function is_string;
 use function preg_match;
+use function sprintf;
 use function strlen;
 use function strtolower;
 use function uksort;
@@ -649,7 +650,7 @@ public function getPrimaryKeyColumns()
         $primaryKey = $this->getPrimaryKey();
 
         if ($primaryKey === null) {
-            throw new DBALException('Table ' . $this->getName() . ' has no primary key.');
+            throw new DBALException(sprintf('Table "%s" has no primary key.', $this->getName()));
         }
 
         return $primaryKey->getColumns();
diff --git a/lib/Doctrine/DBAL/Schema/UniqueConstraint.php b/lib/Doctrine/DBAL/Schema/UniqueConstraint.php
index 44f0f5cd150..2ffe6022ab5 100644
--- a/lib/Doctrine/DBAL/Schema/UniqueConstraint.php
+++ b/lib/Doctrine/DBAL/Schema/UniqueConstraint.php
@@ -5,10 +5,8 @@
 namespace Doctrine\DBAL\Schema;
 
 use Doctrine\DBAL\Platforms\AbstractPlatform;
-use InvalidArgumentException;
 use function array_keys;
 use function array_map;
-use function is_string;
 use function strtolower;
 
 /**
@@ -168,19 +166,8 @@ public function getOptions()
         return $this->options;
     }
 
-    /**
-     * @param string $column
-     *
-     * @return void
-     *
-     * @throws InvalidArgumentException
-     */
-    protected function _addColumn($column)
+    protected function _addColumn(string $column) : void
     {
-        if (! is_string($column)) {
-            throw new InvalidArgumentException('Expecting a string as Index Column');
-        }
-
         $this->columns[$column] = new Identifier($column);
     }
 }
diff --git a/lib/Doctrine/DBAL/Sharding/Exception/MissingDistributionType.php b/lib/Doctrine/DBAL/Sharding/Exception/MissingDistributionType.php
index 258e6afbc3c..e7dc99b408b 100644
--- a/lib/Doctrine/DBAL/Sharding/Exception/MissingDistributionType.php
+++ b/lib/Doctrine/DBAL/Sharding/Exception/MissingDistributionType.php
@@ -10,6 +10,6 @@ final class MissingDistributionType extends ShardingException
 {
     public static function new() : self
     {
-        return new self("You have to specify a sharding distribution type such as 'integer', 'string', 'guid'.");
+        return new self('You have to specify a sharding distribution type such as "integer", "string", "guid".');
     }
 }
diff --git a/lib/Doctrine/DBAL/Sharding/PoolingShardConnection.php b/lib/Doctrine/DBAL/Sharding/PoolingShardConnection.php
index 99e015b176e..ad7379a5d78 100644
--- a/lib/Doctrine/DBAL/Sharding/PoolingShardConnection.php
+++ b/lib/Doctrine/DBAL/Sharding/PoolingShardConnection.php
@@ -16,6 +16,7 @@
 use function array_merge;
 use function is_numeric;
 use function is_string;
+use function sprintf;
 
 /**
  * Sharding implementation that pools many different connections
@@ -69,11 +70,11 @@ class PoolingShardConnection extends Connection
     public function __construct(array $params, Driver $driver, ?Configuration $config = null, ?EventManager $eventManager = null)
     {
         if (! isset($params['global'], $params['shards'])) {
-            throw new InvalidArgumentException("Connection Parameters require 'global' and 'shards' configurations.");
+            throw new InvalidArgumentException('Connection Parameters require "global" and "shards" configurations.');
         }
 
         if (! isset($params['shardChoser'])) {
-            throw new InvalidArgumentException("Missing Shard Choser configuration 'shardChoser'");
+            throw new InvalidArgumentException('Missing Shard Choser configuration "shardChoser".');
         }
 
         if (is_string($params['shardChoser'])) {
@@ -81,14 +82,14 @@ public function __construct(array $params, Driver $driver, ?Configuration $confi
         }
 
         if (! ($params['shardChoser'] instanceof ShardChoser)) {
-            throw new InvalidArgumentException("The 'shardChoser' configuration is not a valid instance of Doctrine\DBAL\Sharding\ShardChoser\ShardChoser");
+            throw new InvalidArgumentException('The "shardChoser" configuration is not a valid instance of Doctrine\DBAL\Sharding\ShardChoser\ShardChoser');
         }
 
         $this->connectionParameters[0] = array_merge($params, $params['global']);
 
         foreach ($params['shards'] as $shard) {
             if (! isset($shard['id'])) {
-                throw new InvalidArgumentException("Missing 'id' for one configured shard. Please specify a unique shard-id.");
+                throw new InvalidArgumentException('Missing "id" for one configured shard. Please specify a unique shard-id.');
             }
 
             if (! is_numeric($shard['id']) || $shard['id'] < 1) {
@@ -96,7 +97,7 @@ public function __construct(array $params, Driver $driver, ?Configuration $confi
             }
 
             if (isset($this->connectionParameters[$shard['id']])) {
-                throw new InvalidArgumentException('Shard ' . $shard['id'] . ' is duplicated in the configuration.');
+                throw new InvalidArgumentException(sprintf('Shard "%s" is duplicated in the configuration.', $shard['id']));
             }
 
             $this->connectionParameters[$shard['id']] = array_merge($params, $shard);
diff --git a/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureShardManager.php b/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureShardManager.php
index a5a8ac680b5..ad5964a66e9 100644
--- a/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureShardManager.php
+++ b/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureShardManager.php
@@ -173,7 +173,7 @@ public function queryAll($sql, array $params = [], array $types = [])
     {
         $shards = $this->getShards();
         if (! $shards) {
-            throw new RuntimeException('No shards found for ' . $this->federationName);
+            throw new RuntimeException(sprintf('No shards found for "%s".', $this->federationName));
         }
 
         $result          = [];
diff --git a/lib/Doctrine/DBAL/Sharding/SQLAzure/Schema/MultiTenantVisitor.php b/lib/Doctrine/DBAL/Sharding/SQLAzure/Schema/MultiTenantVisitor.php
index dd35e119a57..21f3762ba0c 100644
--- a/lib/Doctrine/DBAL/Sharding/SQLAzure/Schema/MultiTenantVisitor.php
+++ b/lib/Doctrine/DBAL/Sharding/SQLAzure/Schema/MultiTenantVisitor.php
@@ -13,6 +13,7 @@
 use Doctrine\DBAL\Schema\Visitor\Visitor;
 use RuntimeException;
 use function in_array;
+use function sprintf;
 
 /**
  * Converts a single tenant schema into a multi-tenant schema for SQL Azure
@@ -110,7 +111,7 @@ private function getClusteredIndex($table)
                 return $index;
             }
         }
-        throw new RuntimeException('No clustered index found on table ' . $table->getName());
+        throw new RuntimeException(sprintf('No clustered index found on table "%s".', $table->getName()));
     }
 
     /**
diff --git a/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php b/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php
index 020e9b2164f..6e42f31931d 100644
--- a/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php
+++ b/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php
@@ -26,6 +26,7 @@
 use function array_keys;
 use function count;
 use function implode;
+use function sprintf;
 
 class ReservedWordsCommand extends Command
 {
@@ -121,10 +122,11 @@ protected function execute(InputInterface $input, OutputInterface $output)
         $keywords = [];
         foreach ($keywordLists as $keywordList) {
             if (! isset($this->keywordListClasses[$keywordList])) {
-                throw new InvalidArgumentException(
-                    "There exists no keyword list with name '" . $keywordList . "'. " .
-                    'Known lists: ' . implode(', ', array_keys($this->keywordListClasses))
-                );
+                throw new InvalidArgumentException(sprintf(
+                    'There exists no keyword list with name "%s". Known lists: %s',
+                    $keywordList,
+                    implode(', ', array_keys($this->keywordListClasses))
+                ));
             }
             $class      = $this->keywordListClasses[$keywordList];
             $keywords[] = new $class();
diff --git a/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php b/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php
index 2a55fb6df36..4e6ab9cd275 100644
--- a/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php
+++ b/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php
@@ -52,7 +52,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
         $sql = $input->getArgument('sql');
 
         if ($sql === null) {
-            throw new RuntimeException("Argument 'SQL' is required in order to execute this command correctly.");
+            throw new RuntimeException('Argument "sql" is required in order to execute this command correctly.');
         }
 
         assert(is_string($sql));
@@ -60,7 +60,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
         $depth = $input->getOption('depth');
 
         if (! is_numeric($depth)) {
-            throw new LogicException("Option 'depth' must contains an integer value");
+            throw new LogicException('Option "depth" must contains an integer value.');
         }
 
         if (stripos($sql, 'select') === 0 || $input->getOption('force-fetch')) {
diff --git a/lib/Doctrine/DBAL/Types/Exception/InvalidFormat.php b/lib/Doctrine/DBAL/Types/Exception/InvalidFormat.php
index 7f2a92b2657..8eb4b86417c 100644
--- a/lib/Doctrine/DBAL/Types/Exception/InvalidFormat.php
+++ b/lib/Doctrine/DBAL/Types/Exception/InvalidFormat.php
@@ -24,7 +24,7 @@ public static function new(
     ) : self {
         return new self(
             sprintf(
-                'Could not convert database value "%s" to Doctrine Type %s. Expected format: %s',
+                'Could not convert database value "%s" to Doctrine Type %s. Expected format "%s".',
                 strlen($value) > 32 ? substr($value, 0, 20) . '...' : $value,
                 $toType,
                 $expectedFormat ?? ''
diff --git a/lib/Doctrine/DBAL/Types/Exception/InvalidType.php b/lib/Doctrine/DBAL/Types/Exception/InvalidType.php
index 4200d6c5040..2ee2439619a 100644
--- a/lib/Doctrine/DBAL/Types/Exception/InvalidType.php
+++ b/lib/Doctrine/DBAL/Types/Exception/InvalidType.php
@@ -31,7 +31,7 @@ public static function new($value, string $toType, array $possibleTypes) : self
         if (is_scalar($value)) {
             return new self(
                 sprintf(
-                    "Could not convert PHP value '%s' of type '%s' to type '%s'. Expected one of the following types: %s",
+                    'Could not convert PHP value "%s" of type "%s" to type "%s". Expected one of the following types: %s.',
                     $value,
                     $actualType,
                     $toType,
@@ -42,7 +42,7 @@ public static function new($value, string $toType, array $possibleTypes) : self
 
         return new self(
             sprintf(
-                "Could not convert PHP value of type '%s' to type '%s'. Expected one of the following types: %s",
+                'Could not convert PHP value of type "%s" to type "%s". Expected one of the following types: %s.',
                 $actualType,
                 $toType,
                 implode(', ', $possibleTypes)
diff --git a/lib/Doctrine/DBAL/Types/Exception/SerializationFailed.php b/lib/Doctrine/DBAL/Types/Exception/SerializationFailed.php
index beab8da4329..6ffcc923f88 100644
--- a/lib/Doctrine/DBAL/Types/Exception/SerializationFailed.php
+++ b/lib/Doctrine/DBAL/Types/Exception/SerializationFailed.php
@@ -21,7 +21,7 @@ public static function new($value, string $format, string $error) : self
 
         return new self(
             sprintf(
-                "Could not convert PHP type '%s' to '%s', as an '%s' error was triggered by the serialization",
+                'Could not convert PHP type "%s" to "%s". An error was triggered by the serialization: %s',
                 $actualType,
                 $format,
                 $error
diff --git a/lib/Doctrine/DBAL/Types/Exception/TypeNotFound.php b/lib/Doctrine/DBAL/Types/Exception/TypeNotFound.php
index c36f512ac7e..08a92d07275 100644
--- a/lib/Doctrine/DBAL/Types/Exception/TypeNotFound.php
+++ b/lib/Doctrine/DBAL/Types/Exception/TypeNotFound.php
@@ -11,6 +11,6 @@ final class TypeNotFound extends DBALException implements TypesException
 {
     public static function new(string $name) : self
     {
-        return new self(sprintf('Type to be overwritten %s does not exist.', $name));
+        return new self(sprintf('Type to be overwritten "%s" does not exist.', $name));
     }
 }
diff --git a/lib/Doctrine/DBAL/Types/Exception/TypesAlreadyExists.php b/lib/Doctrine/DBAL/Types/Exception/TypesAlreadyExists.php
index b0564059e0e..fe4bd105ab0 100644
--- a/lib/Doctrine/DBAL/Types/Exception/TypesAlreadyExists.php
+++ b/lib/Doctrine/DBAL/Types/Exception/TypesAlreadyExists.php
@@ -11,6 +11,6 @@ final class TypesAlreadyExists extends DBALException implements TypesException
 {
     public static function new(string $name) : self
     {
-        return new self(sprintf('Type %s already exists.', $name));
+        return new self(sprintf('Type "%s" already exists.', $name));
     }
 }
diff --git a/lib/Doctrine/DBAL/Types/Exception/ValueNotConvertible.php b/lib/Doctrine/DBAL/Types/Exception/ValueNotConvertible.php
index c7802c86d1d..92a5a5eab86 100644
--- a/lib/Doctrine/DBAL/Types/Exception/ValueNotConvertible.php
+++ b/lib/Doctrine/DBAL/Types/Exception/ValueNotConvertible.php
@@ -20,7 +20,7 @@ public static function new($value, $toType, ?string $message = null) : self
         if ($message !== null) {
             return new self(
                 sprintf(
-                    "Could not convert database value to '%s' as an error was triggered by the unserialization: '%s'",
+                    'Could not convert database value to "%s" as an error was triggered by the unserialization: %s',
                     $toType,
                     $message
                 )
@@ -29,7 +29,7 @@ public static function new($value, $toType, ?string $message = null) : self
 
         return new self(
             sprintf(
-                'Could not convert database value "%s" to Doctrine Type %s',
+                'Could not convert database value "%s" to Doctrine Type "%s".',
                 is_string($value) && strlen($value) > 32 ? substr($value, 0, 20) . '...' : $value,
                 $toType
             )
diff --git a/tests/Doctrine/Tests/DBAL/Cache/ArrayStatementTest.php b/tests/Doctrine/Tests/DBAL/Cache/ArrayStatementTest.php
new file mode 100644
index 00000000000..ad8b3d46102
--- /dev/null
+++ b/tests/Doctrine/Tests/DBAL/Cache/ArrayStatementTest.php
@@ -0,0 +1,147 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\Tests\DBAL\Cache;
+
+use Doctrine\DBAL\Cache\ArrayStatement;
+use Doctrine\DBAL\FetchMode;
+use InvalidArgumentException;
+use PHPUnit\Framework\TestCase;
+use function array_values;
+use function iterator_to_array;
+
+class ArrayStatementTest extends TestCase
+{
+    /** @var array<int, array<string, mixed>> */
+    private $users = [
+        [
+            'username' => 'jwage',
+            'active' => true,
+        ],
+        [
+            'username' => 'romanb',
+            'active' => false,
+        ],
+    ];
+
+    public function testCloseCursor() : void
+    {
+        $statement = $this->createTestArrayStatement();
+
+        self::assertSame(2, $statement->rowCount());
+
+        $statement->closeCursor();
+
+        self::assertSame(0, $statement->rowCount());
+    }
+
+    public function testColumnCount() : void
+    {
+        $statement = $this->createTestArrayStatement();
+
+        self::assertSame(2, $statement->columnCount());
+    }
+
+    public function testRowCount() : void
+    {
+        $statement = $this->createTestArrayStatement();
+
+        self::assertSame(2, $statement->rowCount());
+    }
+
+    public function testSetFetchMode() : void
+    {
+        $statement = $this->createTestArrayStatement();
+
+        $statement->setFetchMode(FetchMode::ASSOCIATIVE);
+
+        self::assertSame($this->users[0], $statement->fetch());
+    }
+
+    public function testSetFetchModeThrowsInvalidArgumentException() : void
+    {
+        $statement = $this->createTestArrayStatement();
+
+        self::expectException(InvalidArgumentException::class);
+        self::expectExceptionMessage('Caching layer does not support 2nd/3rd argument to setFetchMode().');
+
+        $statement->setFetchMode(FetchMode::ASSOCIATIVE, 'arg1', 'arg2');
+    }
+
+    public function testGetIterator() : void
+    {
+        $statement = $this->createTestArrayStatement();
+        $statement->setFetchMode(FetchMode::ASSOCIATIVE);
+
+        self::assertSame($this->users, iterator_to_array($statement->getIterator()));
+    }
+
+    public function testFetchModeAssociative() : void
+    {
+        $statement = $this->createTestArrayStatement();
+
+        self::assertSame($this->users[0], $statement->fetch(FetchMode::ASSOCIATIVE));
+    }
+
+    public function testFetchModeNumeric() : void
+    {
+        $statement = $this->createTestArrayStatement();
+
+        self::assertSame(array_values($this->users[0]), $statement->fetch(FetchMode::NUMERIC));
+    }
+
+    public function testFetchModeMixed() : void
+    {
+        $statement = $this->createTestArrayStatement();
+
+        self::assertSame([
+            'username' => 'jwage',
+            'active' => true,
+            0 => 'jwage',
+            1 => true,
+        ], $statement->fetch(FetchMode::MIXED));
+    }
+
+    public function testFetchModeColumn() : void
+    {
+        $statement = $this->createTestArrayStatement();
+
+        self::assertSame('jwage', $statement->fetch(FetchMode::COLUMN));
+    }
+
+    public function testFetchThrowsInvalidArgumentException() : void
+    {
+        $statement = $this->createTestArrayStatement();
+
+        self::expectException(InvalidArgumentException::class);
+        self::expectExceptionMessage('Invalid fetch mode given for fetching result, 9999 given.');
+
+        $statement->fetch(9999);
+    }
+
+    public function testFetchAll() : void
+    {
+        $statement = $this->createTestArrayStatement();
+
+        self::assertSame($this->users, $statement->fetchAll(FetchMode::ASSOCIATIVE));
+    }
+
+    public function testFetchColumn() : void
+    {
+        $statement = $this->createTestArrayStatement();
+
+        self::assertSame('jwage', $statement->fetchColumn(0));
+        self::assertSame('romanb', $statement->fetchColumn(0));
+
+        $statement = $this->createTestArrayStatement();
+
+        self::assertSame(true, $statement->fetchColumn(1));
+        self::assertSame(false, $statement->fetchColumn(1));
+    }
+
+    private function createTestArrayStatement() : ArrayStatement
+    {
+        return new ArrayStatement($this->users);
+    }
+}
diff --git a/tests/Doctrine/Tests/DBAL/ConnectionTest.php b/tests/Doctrine/Tests/DBAL/ConnectionTest.php
index 560a3339630..b1eb4fc7ebe 100644
--- a/tests/Doctrine/Tests/DBAL/ConnectionTest.php
+++ b/tests/Doctrine/Tests/DBAL/ConnectionTest.php
@@ -191,7 +191,7 @@ public function testEventManagerPassedToPlatform()
     public function testDriverExceptionIsWrapped($method)
     {
         $this->expectException(DBALException::class);
-        $this->expectExceptionMessage("An exception occurred while executing 'MUUHAAAAHAAAA':\n\nSQLSTATE[HY000]: General error: 1 near \"MUUHAAAAHAAAA\"");
+        $this->expectExceptionMessage("An exception occurred while executing \"MUUHAAAAHAAAA\":\n\nSQLSTATE[HY000]: General error: 1 near \"MUUHAAAAHAAAA\"");
 
         $connection = DriverManager::getConnection([
             'driver' => 'pdo_sqlite',
diff --git a/tests/Doctrine/Tests/DBAL/DBALExceptionTest.php b/tests/Doctrine/Tests/DBAL/DBALExceptionTest.php
index 22f6c30de38..0663529c47f 100644
--- a/tests/Doctrine/Tests/DBAL/DBALExceptionTest.php
+++ b/tests/Doctrine/Tests/DBAL/DBALExceptionTest.php
@@ -61,8 +61,8 @@ public function testDriverRequiredWithUrl()
         self::assertInstanceOf(DBALException::class, $exception);
         self::assertSame(
             sprintf(
-                "The options 'driver' or 'driverClass' are mandatory if a connection URL without scheme " .
-                'is given to DriverManager::getConnection(). Given URL: %s',
+                'The options "driver" or "driverClass" are mandatory if a connection URL without scheme ' .
+                'is given to DriverManager::getConnection(). Given URL "%s".',
                 $url
             ),
             $exception->getMessage()
@@ -77,7 +77,7 @@ public function testInvalidPlatformTypeObject() : void
         $exception = InvalidPlatformType::new(new stdClass());
 
         self::assertSame(
-            "Option 'platform' must be a subtype of 'Doctrine\DBAL\Platforms\AbstractPlatform', instance of 'stdClass' given",
+            'Option "platform" must be a subtype of Doctrine\DBAL\Platforms\AbstractPlatform, instance of stdClass given.',
             $exception->getMessage()
         );
     }
@@ -90,7 +90,7 @@ public function testInvalidPlatformTypeScalar() : void
         $exception = InvalidPlatformType::new('some string');
 
         self::assertSame(
-            "Option 'platform' must be an object and subtype of 'Doctrine\DBAL\Platforms\AbstractPlatform'. Got 'string'",
+            'Option "platform" must be an object and subtype of Doctrine\DBAL\Platforms\AbstractPlatform. Got string.',
             $exception->getMessage()
         );
     }
diff --git a/tests/Doctrine/Tests/DBAL/Driver/Mysqli/Exception/UnknownTypeTest.php b/tests/Doctrine/Tests/DBAL/Driver/Mysqli/Exception/UnknownTypeTest.php
new file mode 100644
index 00000000000..cc3aedd358c
--- /dev/null
+++ b/tests/Doctrine/Tests/DBAL/Driver/Mysqli/Exception/UnknownTypeTest.php
@@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\Tests\DBAL\Driver\Mysqli\Exception;
+
+use Doctrine\DBAL\Driver\Mysqli\Exception\UnknownType;
+use PHPUnit\Framework\TestCase;
+
+final class UnknownTypeTest extends TestCase
+{
+    public function testNew() : void
+    {
+        $exception = UnknownType::new('9999');
+
+        self::assertSame('Unknown type, 9999 given.', $exception->getMessage());
+    }
+}
diff --git a/tests/Doctrine/Tests/DBAL/Driver/Mysqli/MysqliConnectionTest.php b/tests/Doctrine/Tests/DBAL/Driver/Mysqli/MysqliConnectionTest.php
index bab55bbbc69..4ee63d10927 100644
--- a/tests/Doctrine/Tests/DBAL/Driver/Mysqli/MysqliConnectionTest.php
+++ b/tests/Doctrine/Tests/DBAL/Driver/Mysqli/MysqliConnectionTest.php
@@ -4,8 +4,8 @@
 
 namespace Doctrine\Tests\DBAL\Driver\Mysqli;
 
+use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionError;
 use Doctrine\DBAL\Driver\Mysqli\MysqliConnection;
-use Doctrine\DBAL\Driver\Mysqli\MysqliException;
 use Doctrine\DBAL\Platforms\MySqlPlatform;
 use Doctrine\Tests\DbalFunctionalTestCase;
 use PHPUnit\Framework\MockObject\MockObject;
@@ -54,7 +54,7 @@ public function testRestoresErrorHandlerOnException()
         try {
             new MysqliConnection(['host' => '255.255.255.255'], 'user', 'pass');
             self::fail('An exception was supposed to be raised');
-        } catch (MysqliException $e) {
+        } catch (ConnectionError $e) {
             // Do nothing
         }
 
diff --git a/tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8StatementTest.php b/tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8StatementTest.php
index dcca544efbb..d6e5ceebdd5 100644
--- a/tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8StatementTest.php
+++ b/tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8StatementTest.php
@@ -104,15 +104,15 @@ public static function nonTerminatedLiteralProvider()
         return [
             'no-matching-quote' => [
                 "SELECT 'literal FROM DUAL",
-                '/offset 7/',
+                '/offset 7./',
             ],
             'no-matching-double-quote' => [
                 'SELECT 1 "COL1 FROM DUAL',
-                '/offset 9/',
+                '/offset 9./',
             ],
             'incorrect-escaping-syntax' => [
                 "SELECT 'quoted \\'string' FROM DUAL",
-                '/offset 23/',
+                '/offset 23./',
             ],
         ];
     }
diff --git a/tests/Doctrine/Tests/DBAL/Exception/DriverRequiredTest.php b/tests/Doctrine/Tests/DBAL/Exception/DriverRequiredTest.php
index ccde18475ea..27b73d1d13d 100644
--- a/tests/Doctrine/Tests/DBAL/Exception/DriverRequiredTest.php
+++ b/tests/Doctrine/Tests/DBAL/Exception/DriverRequiredTest.php
@@ -19,8 +19,8 @@ public function testDriverRequiredWithUrl() : void
         self::assertInstanceOf(DBALException::class, $exception);
         self::assertSame(
             sprintf(
-                "The options 'driver' or 'driverClass' are mandatory if a connection URL without scheme " .
-                'is given to DriverManager::getConnection(). Given URL: %s',
+                'The options "driver" or "driverClass" are mandatory if a connection URL without scheme ' .
+                'is given to DriverManager::getConnection(). Given URL "%s".',
                 $url
             ),
             $exception->getMessage()
diff --git a/tests/Doctrine/Tests/DBAL/Exception/EmptyCriteriaNotAllowedTest.php b/tests/Doctrine/Tests/DBAL/Exception/EmptyCriteriaNotAllowedTest.php
index acd1fe876a3..e67de9b916e 100644
--- a/tests/Doctrine/Tests/DBAL/Exception/EmptyCriteriaNotAllowedTest.php
+++ b/tests/Doctrine/Tests/DBAL/Exception/EmptyCriteriaNotAllowedTest.php
@@ -15,6 +15,6 @@ public function testNew() : void
         $exception = EmptyCriteriaNotAllowed::new();
 
         self::assertInstanceOf(InvalidArgumentException::class, $exception);
-        self::assertSame('Empty criteria was used, expected non-empty criteria', $exception->getMessage());
+        self::assertSame('Empty criteria was used, expected non-empty criteria.', $exception->getMessage());
     }
 }
diff --git a/tests/Doctrine/Tests/DBAL/Exception/GetVariableTypeTest.php b/tests/Doctrine/Tests/DBAL/Exception/GetVariableTypeTest.php
new file mode 100644
index 00000000000..4096088b128
--- /dev/null
+++ b/tests/Doctrine/Tests/DBAL/Exception/GetVariableTypeTest.php
@@ -0,0 +1,40 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\Tests\DBAL\Exception;
+
+use Doctrine\DBAL\Exception\GetVariableType;
+use PHPUnit\Framework\TestCase;
+use stdClass;
+use function tmpfile;
+
+class GetVariableTypeTest extends TestCase
+{
+    /**
+     * @dataProvider provideDataForFormatVariable
+     */
+    public function testFormatVariable($expected, $value) : void
+    {
+        self::assertSame($expected, (new GetVariableType())->__invoke($value));
+    }
+
+    /**
+     * @return array<int, array<int, mixed>>
+     */
+    public function provideDataForFormatVariable() : array
+    {
+        return [
+            ['string', ''],
+            ['string', 'test'],
+            ['double', 1.0],
+            ['integer', 1],
+            ['NULL', null],
+            ['stdClass', new stdClass()],
+            ['stream', tmpfile()],
+            ['true', true],
+            ['false', false],
+            ['array', [true, 1, 2, 3, 'test']],
+        ];
+    }
+}
diff --git a/tests/Doctrine/Tests/DBAL/Exception/InvalidPlatformTypeTest.php b/tests/Doctrine/Tests/DBAL/Exception/InvalidPlatformTypeTest.php
index 35f69a70f63..3b8ea6f81a2 100644
--- a/tests/Doctrine/Tests/DBAL/Exception/InvalidPlatformTypeTest.php
+++ b/tests/Doctrine/Tests/DBAL/Exception/InvalidPlatformTypeTest.php
@@ -18,7 +18,7 @@ public function testInvalidPlatformTypeObject() : void
         $exception = InvalidPlatformType::new(new stdClass());
 
         self::assertSame(
-            "Option 'platform' must be a subtype of 'Doctrine\DBAL\Platforms\AbstractPlatform', instance of 'stdClass' given",
+            'Option "platform" must be a subtype of Doctrine\DBAL\Platforms\AbstractPlatform, instance of stdClass given.',
             $exception->getMessage()
         );
     }
@@ -31,7 +31,7 @@ public function testInvalidPlatformTypeScalar() : void
         $exception = InvalidPlatformType::new('some string');
 
         self::assertSame(
-            "Option 'platform' must be an object and subtype of 'Doctrine\DBAL\Platforms\AbstractPlatform'. Got 'string'",
+            'Option "platform" must be an object and subtype of Doctrine\DBAL\Platforms\AbstractPlatform. Got string.',
             $exception->getMessage()
         );
     }
diff --git a/tests/Doctrine/Tests/DBAL/Functional/ExceptionTest.php b/tests/Doctrine/Tests/DBAL/Functional/ExceptionTest.php
index 58ce132d017..1391af8d83b 100644
--- a/tests/Doctrine/Tests/DBAL/Functional/ExceptionTest.php
+++ b/tests/Doctrine/Tests/DBAL/Functional/ExceptionTest.php
@@ -323,7 +323,7 @@ public function testConnectionExceptionSqLite()
 
         $this->expectException(Exception\ReadOnlyException::class);
         $this->expectExceptionMessage(<<<EOT
-An exception occurred while executing 'CREATE TABLE no_connection (id INTEGER NOT NULL)':
+An exception occurred while executing "CREATE TABLE no_connection (id INTEGER NOT NULL)":
 
 SQLSTATE[HY000]: General error: 8 attempt to write a readonly database
 EOT
diff --git a/tests/Doctrine/Tests/DBAL/Functional/PDOStatementTest.php b/tests/Doctrine/Tests/DBAL/Functional/PDOStatementTest.php
index 533a4f83038..c1982cbc0c8 100644
--- a/tests/Doctrine/Tests/DBAL/Functional/PDOStatementTest.php
+++ b/tests/Doctrine/Tests/DBAL/Functional/PDOStatementTest.php
@@ -32,7 +32,7 @@ protected function setUp() : void
 
     /**
      * @group legacy
-     * @expectedDeprecation Using a PDO fetch mode or their combination (%d given) is deprecated and will cause an error in Doctrine 3.0
+     * @expectedDeprecation Using a PDO fetch mode or their combination (%d given) is deprecated and will cause an error in Doctrine 3.0.
      */
     public function testPDOSpecificModeIsAccepted()
     {
diff --git a/tests/Doctrine/Tests/DBAL/Platforms/AbstractPlatformTestCase.php b/tests/Doctrine/Tests/DBAL/Platforms/AbstractPlatformTestCase.php
index eb313fc500a..630fc341cad 100644
--- a/tests/Doctrine/Tests/DBAL/Platforms/AbstractPlatformTestCase.php
+++ b/tests/Doctrine/Tests/DBAL/Platforms/AbstractPlatformTestCase.php
@@ -1271,7 +1271,7 @@ public function testThrowsExceptionOnGeneratingInlineColumnCommentSQLIfUnsupport
         }
 
         $this->expectException(DBALException::class);
-        $this->expectExceptionMessage("Operation 'Doctrine\\DBAL\\Platforms\\AbstractPlatform::getInlineColumnCommentSQL' is not supported by platform.");
+        $this->expectExceptionMessage('Operation "Doctrine\\DBAL\\Platforms\\AbstractPlatform::getInlineColumnCommentSQL" is not supported by platform.');
         $this->expectExceptionCode(0);
 
         $this->platform->getInlineColumnCommentSQL('unsupported');
diff --git a/tests/Doctrine/Tests/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php b/tests/Doctrine/Tests/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php
index b4bfa055f23..e698ef0392f 100644
--- a/tests/Doctrine/Tests/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php
+++ b/tests/Doctrine/Tests/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php
@@ -485,7 +485,7 @@ public function testThrowsExceptionWithInvalidBooleanLiteral()
         $platform = $this->createPlatform();
 
         $this->expectException(UnexpectedValueException::class);
-        $this->expectExceptionMessage("Unrecognized boolean literal 'my-bool'");
+        $this->expectExceptionMessage('Unrecognized boolean literal, my-bool given.');
 
         $platform->convertBooleansToDatabaseValue('my-bool');
     }
diff --git a/tests/Doctrine/Tests/DBAL/Query/QueryBuilderTest.php b/tests/Doctrine/Tests/DBAL/Query/QueryBuilderTest.php
index 11b46008cea..79302936033 100644
--- a/tests/Doctrine/Tests/DBAL/Query/QueryBuilderTest.php
+++ b/tests/Doctrine/Tests/DBAL/Query/QueryBuilderTest.php
@@ -673,7 +673,7 @@ public function testReferenceJoinFromJoin()
             ->where('nt.lang = :lang AND n.deleted != 1');
 
         $this->expectException(QueryException::class);
-        $this->expectExceptionMessage("The given alias 'invalid' is not part of any FROM or JOIN clause table. The currently registered aliases are: news, nv.");
+        $this->expectExceptionMessage('The given alias "invalid" is not part of any FROM or JOIN clause table. The currently registered aliases are: news, nv.');
         self::assertEquals('', $qb->getSQL());
     }
 
@@ -926,7 +926,7 @@ public function testJoinWithNonUniqueAliasThrowsException()
             ->join('a', 'table_b', 'a', 'a.fk_b = a.id');
 
         $this->expectException(QueryException::class);
-        $this->expectExceptionMessage("The given alias 'a' is not unique in FROM and JOIN clause table. The currently registered aliases are: a.");
+        $this->expectExceptionMessage('The given alias "a" is not unique in FROM and JOIN clause table. The currently registered aliases are: a.');
 
         $qb->getSQL();
     }
diff --git a/tests/Doctrine/Tests/DBAL/SQLParserUtilsTest.php b/tests/Doctrine/Tests/DBAL/SQLParserUtilsTest.php
index 29e67935df5..dd9144986ae 100644
--- a/tests/Doctrine/Tests/DBAL/SQLParserUtilsTest.php
+++ b/tests/Doctrine/Tests/DBAL/SQLParserUtilsTest.php
@@ -67,7 +67,7 @@ public function dataGetPlaceholderPositions()
             ['SELECT table.field1, ARRAY[:foo]::integer[] FROM schema.table table WHERE table.f1 = :bar AND ARRAY[\'3\']::integer[]', false, [27 => 'foo', 85 => 'bar']],
             [
                 <<<'SQLDATA'
-SELECT * FROM foo WHERE 
+SELECT * FROM foo WHERE
 bar = ':not_a_param1 ''":not_a_param2"'''
 OR bar=:a_param1
 OR bar=:a_param2||':not_a_param3'
@@ -78,9 +78,9 @@ public function dataGetPlaceholderPositions()
                 ,
                 false,
                 [
-                    74 => 'a_param1',
-                    91 => 'a_param2',
-                    190 => 'a_param3',
+                    73 => 'a_param1',
+                    90 => 'a_param2',
+                    189 => 'a_param3',
                 ],
             ],
             ["SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE '\\\\') AND (data.description LIKE :condition_1 ESCAPE '\\\\') ORDER BY id ASC", false, [121 => 'condition_0', 174 => 'condition_1']],
@@ -468,7 +468,7 @@ public function dataQueryWithMissingParameters()
     public function testExceptionIsThrownForMissingParam($query, $params, $types = [])
     {
         $this->expectException(SQLParserUtilsException::class);
-        $this->expectExceptionMessage('Value for :param not found in params array. Params array key should be "param"');
+        $this->expectExceptionMessage('Parameter "param" is missing.');
 
         SQLParserUtils::expandListParameters($query, $params, $types);
     }
diff --git a/tests/Doctrine/Tests/DBAL/Sharding/PoolingShardConnectionTest.php b/tests/Doctrine/Tests/DBAL/Sharding/PoolingShardConnectionTest.php
index d1dcf3938b2..2550ed7eab2 100644
--- a/tests/Doctrine/Tests/DBAL/Sharding/PoolingShardConnectionTest.php
+++ b/tests/Doctrine/Tests/DBAL/Sharding/PoolingShardConnectionTest.php
@@ -8,6 +8,7 @@
 use Doctrine\DBAL\Sharding\PoolingShardConnection;
 use Doctrine\DBAL\Sharding\ShardChoser\MultiTenantShardChoser;
 use Doctrine\DBAL\Sharding\ShardingException;
+use InvalidArgumentException;
 use PHPUnit\Framework\TestCase;
 use stdClass;
 
@@ -52,8 +53,8 @@ public function testConnect()
 
     public function testNoGlobalServerException()
     {
-        $this->expectException('InvalidArgumentException');
-        $this->expectExceptionMessage("Connection Parameters require 'global' and 'shards' configurations.");
+        $this->expectException(InvalidArgumentException::class);
+        $this->expectExceptionMessage('Connection Parameters require "global" and "shards" configurations.');
 
         DriverManager::getConnection([
             'wrapperClass' => PoolingShardConnection::class,
@@ -68,8 +69,8 @@ public function testNoGlobalServerException()
 
     public function testNoShardsServersException()
     {
-        $this->expectException('InvalidArgumentException');
-        $this->expectExceptionMessage("Connection Parameters require 'global' and 'shards' configurations.");
+        $this->expectException(InvalidArgumentException::class);
+        $this->expectExceptionMessage('Connection Parameters require "global" and "shards" configurations.');
 
         DriverManager::getConnection([
             'wrapperClass' => PoolingShardConnection::class,
@@ -81,8 +82,8 @@ public function testNoShardsServersException()
 
     public function testNoShardsChoserException()
     {
-        $this->expectException('InvalidArgumentException');
-        $this->expectExceptionMessage("Missing Shard Choser configuration 'shardChoser'");
+        $this->expectException(InvalidArgumentException::class);
+        $this->expectExceptionMessage('Missing Shard Choser configuration "shardChoser".');
 
         DriverManager::getConnection([
             'wrapperClass' => PoolingShardConnection::class,
@@ -97,8 +98,8 @@ public function testNoShardsChoserException()
 
     public function testShardChoserWrongInstance()
     {
-        $this->expectException('InvalidArgumentException');
-        $this->expectExceptionMessage("The 'shardChoser' configuration is not a valid instance of Doctrine\DBAL\Sharding\ShardChoser\ShardChoser");
+        $this->expectException(InvalidArgumentException::class);
+        $this->expectExceptionMessage('The "shardChoser" configuration is not a valid instance of Doctrine\DBAL\Sharding\ShardChoser\ShardChoser');
 
         DriverManager::getConnection([
             'wrapperClass' => PoolingShardConnection::class,
@@ -130,8 +131,8 @@ public function testShardNonNumericId()
 
     public function testShardMissingId()
     {
-        $this->expectException('InvalidArgumentException');
-        $this->expectExceptionMessage("Missing 'id' for one configured shard. Please specify a unique shard-id.");
+        $this->expectException(InvalidArgumentException::class);
+        $this->expectExceptionMessage('Missing "id" for one configured shard. Please specify a unique shard-id.');
 
         DriverManager::getConnection([
             'wrapperClass' => PoolingShardConnection::class,
@@ -146,8 +147,8 @@ public function testShardMissingId()
 
     public function testDuplicateShardId()
     {
-        $this->expectException('InvalidArgumentException');
-        $this->expectExceptionMessage('Shard 1 is duplicated in the configuration.');
+        $this->expectException(InvalidArgumentException::class);
+        $this->expectExceptionMessage('Shard "1" is duplicated in the configuration.');
 
         DriverManager::getConnection([
             'wrapperClass' => PoolingShardConnection::class,
diff --git a/tests/Doctrine/Tests/DBAL/Tools/Console/RunSqlCommandTest.php b/tests/Doctrine/Tests/DBAL/Tools/Console/RunSqlCommandTest.php
index 076ebd9084c..65fd14bfb8d 100644
--- a/tests/Doctrine/Tests/DBAL/Tools/Console/RunSqlCommandTest.php
+++ b/tests/Doctrine/Tests/DBAL/Tools/Console/RunSqlCommandTest.php
@@ -43,29 +43,25 @@ protected function setUp() : void
 
     public function testMissingSqlArgument()
     {
-        try {
-            $this->commandTester->execute([
-                'command' => $this->command->getName(),
-                'sql' => null,
-            ]);
-            $this->fail('Expected a runtime exception when omitting sql argument');
-        } catch (RuntimeException $e) {
-            self::assertStringContainsString("Argument 'SQL", $e->getMessage());
-        }
+        self::expectException(RuntimeException::class);
+        self::expectExceptionMessage('Argument "sql" is required in order to execute this command correctly.');
+
+        $this->commandTester->execute([
+            'command' => $this->command->getName(),
+            'sql' => null,
+        ]);
     }
 
     public function testIncorrectDepthOption()
     {
-        try {
-            $this->commandTester->execute([
-                'command' => $this->command->getName(),
-                'sql' => 'SELECT 1',
-                '--depth' => 'string',
-            ]);
-            $this->fail('Expected a logic exception when executing with a stringy depth');
-        } catch (LogicException $e) {
-            self::assertStringContainsString("Option 'depth'", $e->getMessage());
-        }
+        self::expectException(LogicException::class);
+        self::expectExceptionMessage('Option "depth" must contains an integer value.');
+
+        $this->commandTester->execute([
+            'command' => $this->command->getName(),
+            'sql' => 'SELECT 1',
+            '--depth' => 'string',
+        ]);
     }
 
     public function testSelectStatementsPrintsResult()
diff --git a/tests/Doctrine/Tests/DBAL/Types/ArrayTest.php b/tests/Doctrine/Tests/DBAL/Types/ArrayTest.php
index 3e5e0ec6bb6..aeed8e7e255 100644
--- a/tests/Doctrine/Tests/DBAL/Types/ArrayTest.php
+++ b/tests/Doctrine/Tests/DBAL/Types/ArrayTest.php
@@ -39,7 +39,7 @@ public function testArrayConvertsToPHPValue()
     public function testConversionFailure()
     {
         $this->expectException(ConversionException::class);
-        $this->expectExceptionMessage("Could not convert database value to 'array' as an error was triggered by the unserialization: 'unserialize(): Error at offset 0 of 7 bytes'");
+        $this->expectExceptionMessage('Could not convert database value to "array" as an error was triggered by the unserialization: unserialize(): Error at offset 0 of 7 bytes');
         $this->type->convertToPHPValue('abcdefg', $this->platform);
     }
 
diff --git a/tests/Doctrine/Tests/DBAL/Types/ConversionExceptionTest.php b/tests/Doctrine/Tests/DBAL/Types/ConversionExceptionTest.php
index 37622f4d884..8174fe94ea3 100644
--- a/tests/Doctrine/Tests/DBAL/Types/ConversionExceptionTest.php
+++ b/tests/Doctrine/Tests/DBAL/Types/ConversionExceptionTest.php
@@ -10,6 +10,10 @@
 use Exception;
 use PHPUnit\Framework\TestCase;
 use stdClass;
+use function get_class;
+use function gettype;
+use function is_object;
+use function sprintf;
 use function tmpfile;
 
 class ConversionExceptionTest extends TestCase
@@ -23,10 +27,15 @@ public function testConversionFailedInvalidTypeWithScalar($scalarValue)
     {
         $exception = InvalidType::new($scalarValue, 'foo', ['bar', 'baz']);
 
+        $type = is_object($scalarValue) ? get_class($scalarValue) : gettype($scalarValue);
+
         self::assertInstanceOf(ConversionException::class, $exception);
-        self::assertRegExp(
-            '/^Could not convert PHP value \'.*\' of type \'(string|boolean|float|double|integer)\' to type \'foo\'. '
-            . 'Expected one of the following types: bar, baz$/',
+        self::assertSame(
+            sprintf(
+                'Could not convert PHP value "%s" of type "%s" to type "foo". Expected one of the following types: bar, baz.',
+                $scalarValue,
+                $type
+            ),
             $exception->getMessage()
         );
     }
@@ -40,10 +49,11 @@ public function testConversionFailedInvalidTypeWithNonScalar($nonScalar)
     {
         $exception = InvalidType::new($nonScalar, 'foo', ['bar', 'baz']);
 
+        $type = is_object($nonScalar) ? get_class($nonScalar) : gettype($nonScalar);
+
         self::assertInstanceOf(ConversionException::class, $exception);
-        self::assertRegExp(
-            '/^Could not convert PHP value of type \'(.*)\' to type \'foo\'. '
-            . 'Expected one of the following types: bar, baz$/',
+        self::assertSame(
+            sprintf('Could not convert PHP value of type "%s" to type "foo". Expected one of the following types: bar, baz.', $type),
             $exception->getMessage()
         );
     }
diff --git a/tests/Doctrine/Tests/DBAL/Types/Exception/SerializationFailedTest.php b/tests/Doctrine/Tests/DBAL/Types/Exception/SerializationFailedTest.php
new file mode 100644
index 00000000000..8e246b5b79d
--- /dev/null
+++ b/tests/Doctrine/Tests/DBAL/Types/Exception/SerializationFailedTest.php
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\Tests\DBAL\Types\Exception;
+
+use Doctrine\DBAL\Types\Exception\SerializationFailed;
+use PHPUnit\Framework\TestCase;
+use const NAN;
+use function json_encode;
+use function json_last_error_msg;
+
+class SerializationFailedTest extends TestCase
+{
+    public function testNew() : void
+    {
+        $value   = NAN;
+        $encoded = json_encode($value);
+
+        $exception = SerializationFailed::new($value, 'json', json_last_error_msg());
+
+        self::assertSame(
+            'Could not convert PHP type "double" to "json". An error was triggered by the serialization: Inf and NaN cannot be JSON encoded',
+            $exception->getMessage()
+        );
+    }
+}
diff --git a/tests/Doctrine/Tests/DBAL/Types/ObjectTest.php b/tests/Doctrine/Tests/DBAL/Types/ObjectTest.php
index 19fe13d6cd4..1122a1f2e98 100644
--- a/tests/Doctrine/Tests/DBAL/Types/ObjectTest.php
+++ b/tests/Doctrine/Tests/DBAL/Types/ObjectTest.php
@@ -40,7 +40,7 @@ public function testObjectConvertsToPHPValue()
     public function testConversionFailure()
     {
         $this->expectException(ConversionException::class);
-        $this->expectExceptionMessage("Could not convert database value to 'object' as an error was triggered by the unserialization: 'unserialize(): Error at offset 0 of 7 bytes'");
+        $this->expectExceptionMessage('Could not convert database value to "object" as an error was triggered by the unserialization: unserialize(): Error at offset 0 of 7 bytes');
 
         $this->type->convertToPHPValue('abcdefg', $this->platform);
     }