346 lines
9.0 KiB
PHP
346 lines
9.0 KiB
PHP
|
<?php
|
||
|
declare(strict_types=1);
|
||
|
|
||
|
/**
|
||
|
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||
|
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||
|
*
|
||
|
* Licensed under The MIT License
|
||
|
* For full copyright and license information, please see the LICENSE.txt
|
||
|
* Redistributions of files must retain the above copyright notice.
|
||
|
*
|
||
|
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||
|
* @link https://cakephp.org CakePHP(tm) Project
|
||
|
* @since 3.0.0
|
||
|
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||
|
*/
|
||
|
namespace Cake\Database\Driver;
|
||
|
|
||
|
use Cake\Database\Driver;
|
||
|
use Cake\Database\Query;
|
||
|
use Cake\Database\Schema\MysqlSchemaDialect;
|
||
|
use Cake\Database\Schema\SchemaDialect;
|
||
|
use Cake\Database\Statement\MysqlStatement;
|
||
|
use Cake\Database\StatementInterface;
|
||
|
use PDO;
|
||
|
use function Cake\Core\deprecationWarning;
|
||
|
|
||
|
/**
|
||
|
* MySQL Driver
|
||
|
*/
|
||
|
class Mysql extends Driver
|
||
|
{
|
||
|
use SqlDialectTrait;
|
||
|
|
||
|
/**
|
||
|
* @inheritDoc
|
||
|
*/
|
||
|
protected const MAX_ALIAS_LENGTH = 256;
|
||
|
|
||
|
/**
|
||
|
* Server type MySQL
|
||
|
*
|
||
|
* @var string
|
||
|
*/
|
||
|
protected const SERVER_TYPE_MYSQL = 'mysql';
|
||
|
|
||
|
/**
|
||
|
* Server type MariaDB
|
||
|
*
|
||
|
* @var string
|
||
|
*/
|
||
|
protected const SERVER_TYPE_MARIADB = 'mariadb';
|
||
|
|
||
|
/**
|
||
|
* Base configuration settings for MySQL driver
|
||
|
*
|
||
|
* @var array<string, mixed>
|
||
|
*/
|
||
|
protected $_baseConfig = [
|
||
|
'persistent' => true,
|
||
|
'host' => 'localhost',
|
||
|
'username' => 'root',
|
||
|
'password' => '',
|
||
|
'database' => 'cake',
|
||
|
'port' => '3306',
|
||
|
'flags' => [],
|
||
|
'encoding' => 'utf8mb4',
|
||
|
'timezone' => null,
|
||
|
'init' => [],
|
||
|
];
|
||
|
|
||
|
/**
|
||
|
* The schema dialect for this driver
|
||
|
*
|
||
|
* @var \Cake\Database\Schema\MysqlSchemaDialect|null
|
||
|
*/
|
||
|
protected $_schemaDialect;
|
||
|
|
||
|
/**
|
||
|
* String used to start a database identifier quoting to make it safe
|
||
|
*
|
||
|
* @var string
|
||
|
*/
|
||
|
protected $_startQuote = '`';
|
||
|
|
||
|
/**
|
||
|
* String used to end a database identifier quoting to make it safe
|
||
|
*
|
||
|
* @var string
|
||
|
*/
|
||
|
protected $_endQuote = '`';
|
||
|
|
||
|
/**
|
||
|
* Server type.
|
||
|
*
|
||
|
* If the underlying server is MariaDB, its value will get set to `'mariadb'`
|
||
|
* after `version()` method is called.
|
||
|
*
|
||
|
* @var string
|
||
|
*/
|
||
|
protected $serverType = self::SERVER_TYPE_MYSQL;
|
||
|
|
||
|
/**
|
||
|
* Mapping of feature to db server version for feature availability checks.
|
||
|
*
|
||
|
* @var array<string, array<string, string>>
|
||
|
*/
|
||
|
protected $featureVersions = [
|
||
|
'mysql' => [
|
||
|
'json' => '5.7.0',
|
||
|
'cte' => '8.0.0',
|
||
|
'window' => '8.0.0',
|
||
|
],
|
||
|
'mariadb' => [
|
||
|
'json' => '10.2.7',
|
||
|
'cte' => '10.2.1',
|
||
|
'window' => '10.2.0',
|
||
|
],
|
||
|
];
|
||
|
|
||
|
/**
|
||
|
* Establishes a connection to the database server
|
||
|
*
|
||
|
* @return bool true on success
|
||
|
*/
|
||
|
public function connect(): bool
|
||
|
{
|
||
|
if ($this->_connection) {
|
||
|
return true;
|
||
|
}
|
||
|
$config = $this->_config;
|
||
|
|
||
|
if ($config['timezone'] === 'UTC') {
|
||
|
$config['timezone'] = '+0:00';
|
||
|
}
|
||
|
|
||
|
if (!empty($config['timezone'])) {
|
||
|
$config['init'][] = sprintf("SET time_zone = '%s'", $config['timezone']);
|
||
|
}
|
||
|
|
||
|
$config['flags'] += [
|
||
|
PDO::ATTR_PERSISTENT => $config['persistent'],
|
||
|
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
|
||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||
|
];
|
||
|
|
||
|
if (!empty($config['ssl_key']) && !empty($config['ssl_cert'])) {
|
||
|
$config['flags'][PDO::MYSQL_ATTR_SSL_KEY] = $config['ssl_key'];
|
||
|
$config['flags'][PDO::MYSQL_ATTR_SSL_CERT] = $config['ssl_cert'];
|
||
|
}
|
||
|
if (!empty($config['ssl_ca'])) {
|
||
|
$config['flags'][PDO::MYSQL_ATTR_SSL_CA] = $config['ssl_ca'];
|
||
|
}
|
||
|
|
||
|
if (empty($config['unix_socket'])) {
|
||
|
$dsn = "mysql:host={$config['host']};port={$config['port']};dbname={$config['database']}";
|
||
|
} else {
|
||
|
$dsn = "mysql:unix_socket={$config['unix_socket']};dbname={$config['database']}";
|
||
|
}
|
||
|
|
||
|
if (!empty($config['encoding'])) {
|
||
|
$dsn .= ";charset={$config['encoding']}";
|
||
|
}
|
||
|
|
||
|
$this->_connect($dsn, $config);
|
||
|
|
||
|
if (!empty($config['init'])) {
|
||
|
$connection = $this->getConnection();
|
||
|
foreach ((array)$config['init'] as $command) {
|
||
|
$connection->exec($command);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns whether php is able to use this driver for connecting to database
|
||
|
*
|
||
|
* @return bool true if it is valid to use this driver
|
||
|
*/
|
||
|
public function enabled(): bool
|
||
|
{
|
||
|
return in_array('mysql', PDO::getAvailableDrivers(), true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Prepares a sql statement to be executed
|
||
|
*
|
||
|
* @param \Cake\Database\Query|string $query The query to prepare.
|
||
|
* @return \Cake\Database\StatementInterface
|
||
|
*/
|
||
|
public function prepare($query): StatementInterface
|
||
|
{
|
||
|
$this->connect();
|
||
|
$isObject = $query instanceof Query;
|
||
|
/**
|
||
|
* @psalm-suppress PossiblyInvalidMethodCall
|
||
|
* @psalm-suppress PossiblyInvalidArgument
|
||
|
*/
|
||
|
$statement = $this->_connection->prepare($isObject ? $query->sql() : $query);
|
||
|
$result = new MysqlStatement($statement, $this);
|
||
|
/** @psalm-suppress PossiblyInvalidMethodCall */
|
||
|
if ($isObject && $query->isBufferedResultsEnabled() === false) {
|
||
|
$result->bufferResults(false);
|
||
|
}
|
||
|
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @inheritDoc
|
||
|
*/
|
||
|
public function schemaDialect(): SchemaDialect
|
||
|
{
|
||
|
if ($this->_schemaDialect === null) {
|
||
|
$this->_schemaDialect = new MysqlSchemaDialect($this);
|
||
|
}
|
||
|
|
||
|
return $this->_schemaDialect;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @inheritDoc
|
||
|
*/
|
||
|
public function schema(): string
|
||
|
{
|
||
|
return $this->_config['database'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @inheritDoc
|
||
|
*/
|
||
|
public function disableForeignKeySQL(): string
|
||
|
{
|
||
|
return 'SET foreign_key_checks = 0';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @inheritDoc
|
||
|
*/
|
||
|
public function enableForeignKeySQL(): string
|
||
|
{
|
||
|
return 'SET foreign_key_checks = 1';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @inheritDoc
|
||
|
*/
|
||
|
public function supports(string $feature): bool
|
||
|
{
|
||
|
switch ($feature) {
|
||
|
case static::FEATURE_CTE:
|
||
|
case static::FEATURE_JSON:
|
||
|
case static::FEATURE_WINDOW:
|
||
|
return version_compare(
|
||
|
$this->version(),
|
||
|
$this->featureVersions[$this->serverType][$feature],
|
||
|
'>='
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return parent::supports($feature);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @inheritDoc
|
||
|
*/
|
||
|
public function supportsDynamicConstraints(): bool
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns true if the connected server is MariaDB.
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
public function isMariadb(): bool
|
||
|
{
|
||
|
$this->version();
|
||
|
|
||
|
return $this->serverType === static::SERVER_TYPE_MARIADB;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns connected server version.
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function version(): string
|
||
|
{
|
||
|
if ($this->_version === null) {
|
||
|
$this->connect();
|
||
|
$this->_version = (string)$this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
|
||
|
|
||
|
if (strpos($this->_version, 'MariaDB') !== false) {
|
||
|
$this->serverType = static::SERVER_TYPE_MARIADB;
|
||
|
preg_match('/^(?:5\.5\.5-)?(\d+\.\d+\.\d+.*-MariaDB[^:]*)/', $this->_version, $matches);
|
||
|
$this->_version = $matches[1];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $this->_version;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns true if the server supports common table expressions.
|
||
|
*
|
||
|
* @return bool
|
||
|
* @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_CTE)` instead
|
||
|
*/
|
||
|
public function supportsCTEs(): bool
|
||
|
{
|
||
|
deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
|
||
|
|
||
|
return $this->supports(static::FEATURE_CTE);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns true if the server supports native JSON columns
|
||
|
*
|
||
|
* @return bool
|
||
|
* @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_JSON)` instead
|
||
|
*/
|
||
|
public function supportsNativeJson(): bool
|
||
|
{
|
||
|
deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
|
||
|
|
||
|
return $this->supports(static::FEATURE_JSON);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns true if the connected server supports window functions.
|
||
|
*
|
||
|
* @return bool
|
||
|
* @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_WINDOW)` instead
|
||
|
*/
|
||
|
public function supportsWindowFunctions(): bool
|
||
|
{
|
||
|
deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
|
||
|
|
||
|
return $this->supports(static::FEATURE_WINDOW);
|
||
|
}
|
||
|
}
|