<?php declare(strict_types=1); namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver\DriverException as DriverExceptionInterface; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\Exception\InvalidPlatformVersion; use Doctrine\DBAL\Platforms\MariaDb1027Platform; use Doctrine\DBAL\Platforms\MySQL57Platform; use Doctrine\DBAL\Platforms\MySQL80Platform; use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\MySqlSchemaManager; use Doctrine\DBAL\VersionAwarePlatformDriver; use function preg_match; use function stripos; use function version_compare; /** * Abstract base implementation of the {@link Doctrine\DBAL\Driver} interface for MySQL based drivers. */ abstract class AbstractMySQLDriver implements ExceptionConverterDriver, VersionAwarePlatformDriver { /**#@+ * MySQL server error codes. * * @link https://dev.mysql.com/doc/refman/8.0/en/server-error-reference.html */ private const ER_DBACCESS_DENIED_ERROR = 1044; private const ER_ACCESS_DENIED_ERROR = 1045; private const ER_NO_DB_ERROR = 1046; private const ER_BAD_NULL_ERROR = 1048; private const ER_BAD_DB_ERROR = 1049; private const ER_TABLE_EXISTS_ERROR = 1050; private const ER_BAD_TABLE_ERROR = 1051; private const ER_NON_UNIQ_ERROR = 1052; private const ER_BAD_FIELD_ERROR = 1054; private const ER_DUP_FIELDNAME = 1060; private const ER_DUP_ENTRY = 1062; private const ER_PARSE_ERROR = 1064; private const ER_KILL_DENIED_ERROR = 1095; private const ER_FIELD_SPECIFIED_TWICE = 1110; private const ER_NULL_COLUMN_IN_INDEX = 1121; private const ER_INVALID_USE_OF_NULL = 1138; private const ER_TABLEACCESS_DENIED_ERROR = 1142; private const ER_COLUMNACCESS_DENIED_ERROR = 1143; private const ER_NO_SUCH_TABLE = 1146; private const ER_SYNTAX_ERROR = 1149; private const ER_WRONG_COLUMN_NAME = 1166; private const ER_PRIMARY_CANT_HAVE_NULL = 1171; private const ER_LOCK_WAIT_TIMEOUT = 1205; private const ER_LOCK_DEADLOCK = 1213; private const ER_NO_REFERENCED_ROW = 1216; private const ER_ROW_IS_REFERENCED = 1217; private const ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227; private const ER_SPATIAL_CANT_HAVE_NULL = 1252; private const ER_WARN_NULL_TO_NOTNULL = 1263; private const ER_WARN_DEPRECATED_SYNTAX = 1287; private const ER_FPARSER_BAD_HEADER = 1341; private const ER_FPARSER_EOF_IN_COMMENT = 1342; private const ER_FPARSER_ERROR_IN_PARAMETER = 1343; private const ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344; private const ER_NO_DEFAULT_FOR_FIELD = 1364; private const ER_PROCACCESS_DENIED_ERROR = 1370; private const ER_RESERVED_SYNTAX = 1382; private const ER_CONNECT_TO_FOREIGN_DATA_SOURCE = 1429; private const ER_ROW_IS_REFERENCED_2 = 1451; private const ER_NO_REFERENCED_ROW_2 = 1452; private const ER_PARTITION_REQUIRES_VALUES_ERROR = 1479; private const ER_EVENT_DROP_FAILED = 1541; private const ER_WARN_DEPRECATED_SYNTAX_WITH_VER = 1554; private const ER_FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557; private const ER_NULL_IN_VALUES_LESS_THAN = 1566; private const ER_DUP_ENTRY_AUTOINCREMENT_CASE = 1569; private const ER_DUP_ENTRY_WITH_KEY_NAME = 1586; private const ER_LOAD_DATA_INVALID_COLUMN = 1611; private const ER_CONFLICT_FN_PARSE_ERROR = 1626; private const ER_TRUNCATE_ILLEGAL_FK = 1701; /**#@-*/ /**#@+ * MySQL client error codes. * * @link https://dev.mysql.com/doc/refman/8.0/en/client-error-reference.html */ private const CR_CONNECTION_ERROR = 2002; private const CR_UNKNOWN_HOST = 2005; /**#@-*/ /** * {@inheritdoc} */ public function convertException(string $message, DriverExceptionInterface $exception) : DriverException { switch ($exception->getCode()) { case self::ER_LOCK_DEADLOCK: return new Exception\DeadlockException($message, $exception); case self::ER_LOCK_WAIT_TIMEOUT: return new Exception\LockWaitTimeoutException($message, $exception); case self::ER_TABLE_EXISTS_ERROR: return new Exception\TableExistsException($message, $exception); case self::ER_BAD_TABLE_ERROR: case self::ER_NO_SUCH_TABLE: return new Exception\TableNotFoundException($message, $exception); case self::ER_NO_REFERENCED_ROW: case self::ER_ROW_IS_REFERENCED: case self::ER_ROW_IS_REFERENCED_2: case self::ER_NO_REFERENCED_ROW_2: case self::ER_TRUNCATE_ILLEGAL_FK: return new Exception\ForeignKeyConstraintViolationException($message, $exception); case self::ER_DUP_ENTRY: case self::ER_FOREIGN_DUPLICATE_KEY_OLD_UNUSED: case self::ER_DUP_ENTRY_AUTOINCREMENT_CASE: case self::ER_DUP_ENTRY_WITH_KEY_NAME: return new Exception\UniqueConstraintViolationException($message, $exception); case self::ER_BAD_FIELD_ERROR: case self::ER_WRONG_COLUMN_NAME: case self::ER_LOAD_DATA_INVALID_COLUMN: return new Exception\InvalidFieldNameException($message, $exception); case self::ER_NON_UNIQ_ERROR: case self::ER_DUP_FIELDNAME: case self::ER_FIELD_SPECIFIED_TWICE: return new Exception\NonUniqueFieldNameException($message, $exception); case self::ER_PARSE_ERROR: case self::ER_SYNTAX_ERROR: case self::ER_WARN_DEPRECATED_SYNTAX: case self::ER_FPARSER_BAD_HEADER: case self::ER_FPARSER_EOF_IN_COMMENT: case self::ER_FPARSER_ERROR_IN_PARAMETER: case self::ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER: case self::ER_RESERVED_SYNTAX: case self::ER_PARTITION_REQUIRES_VALUES_ERROR: case self::ER_EVENT_DROP_FAILED: case self::ER_WARN_DEPRECATED_SYNTAX_WITH_VER: case self::ER_CONFLICT_FN_PARSE_ERROR: return new Exception\SyntaxErrorException($message, $exception); case self::ER_DBACCESS_DENIED_ERROR: case self::ER_ACCESS_DENIED_ERROR: case self::ER_NO_DB_ERROR: case self::ER_BAD_DB_ERROR: case self::ER_KILL_DENIED_ERROR: case self::ER_TABLEACCESS_DENIED_ERROR: case self::ER_COLUMNACCESS_DENIED_ERROR: case self::ER_SPECIFIC_ACCESS_DENIED_ERROR: case self::ER_PROCACCESS_DENIED_ERROR: case self::ER_CONNECT_TO_FOREIGN_DATA_SOURCE: case self::CR_CONNECTION_ERROR: case self::CR_UNKNOWN_HOST: return new Exception\ConnectionException($message, $exception); case self::ER_BAD_NULL_ERROR: case self::ER_NULL_COLUMN_IN_INDEX: case self::ER_INVALID_USE_OF_NULL: case self::ER_PRIMARY_CANT_HAVE_NULL: case self::ER_SPATIAL_CANT_HAVE_NULL: case self::ER_WARN_NULL_TO_NOTNULL: case self::ER_NO_DEFAULT_FOR_FIELD: case self::ER_NULL_IN_VALUES_LESS_THAN: return new Exception\NotNullConstraintViolationException($message, $exception); } return new DriverException($message, $exception); } /** * {@inheritdoc} * * @throws DBALException */ public function createDatabasePlatformForVersion(string $version) : AbstractPlatform { $mariadb = stripos($version, 'mariadb') !== false; if ($mariadb && version_compare($this->getMariaDbMysqlVersionNumber($version), '10.2.7', '>=')) { return new MariaDb1027Platform(); } if (! $mariadb) { $oracleMysqlVersion = $this->getOracleMysqlVersionNumber($version); if (version_compare($oracleMysqlVersion, '8', '>=')) { return new MySQL80Platform(); } if (version_compare($oracleMysqlVersion, '5.7.9', '>=')) { return new MySQL57Platform(); } } return $this->getDatabasePlatform(); } /** * Get a normalized 'version number' from the server string * returned by Oracle MySQL servers. * * @param string $versionString Version string returned by the driver, i.e. '5.7.10' * * @throws DBALException */ private function getOracleMysqlVersionNumber(string $versionString) : string { if (! preg_match( '/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+))?)?/', $versionString, $versionParts )) { throw InvalidPlatformVersion::new( $versionString, '<major_version>.<minor_version>.<patch_version>' ); } $majorVersion = $versionParts['major']; $minorVersion = $versionParts['minor'] ?? 0; $patchVersion = $versionParts['patch'] ?? null; if ($majorVersion === '5' && $minorVersion === '7' && $patchVersion === null) { $patchVersion = '9'; } return $majorVersion . '.' . $minorVersion . '.' . $patchVersion; } /** * Detect MariaDB server version, including hack for some mariadb distributions * that starts with the prefix '5.5.5-' * * @param string $versionString Version string as returned by mariadb server, i.e. '5.5.5-Mariadb-10.0.8-xenial' * * @throws DBALException */ private function getMariaDbMysqlVersionNumber(string $versionString) : string { if (! preg_match( '/^(?:5\.5\.5-)?(mariadb-)?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)/i', $versionString, $versionParts )) { throw InvalidPlatformVersion::new( $versionString, '^(?:5\.5\.5-)?(mariadb-)?<major_version>.<minor_version>.<patch_version>' ); } return $versionParts['major'] . '.' . $versionParts['minor'] . '.' . $versionParts['patch']; } /** * {@inheritdoc} * * @return MySqlPlatform */ public function getDatabasePlatform() : AbstractPlatform { return new MySqlPlatform(); } /** * {@inheritdoc} * * @return MySqlSchemaManager */ public function getSchemaManager(Connection $conn) : AbstractSchemaManager { return new MySqlSchemaManager($conn); } }