mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Remove support for PDO (#10499)
This commit is contained in:
parent
5da5555ce3
commit
03b929dd33
@ -56,8 +56,6 @@ mappings:
|
||||
MySQLQueryBuilder: SilverStripe\ORM\Connect\MySQLQueryBuilder
|
||||
MySQLSchemaManager: SilverStripe\ORM\Connect\MySQLSchemaManager
|
||||
MySQLStatement: SilverStripe\ORM\Connect\MySQLStatement
|
||||
PDOConnector: SilverStripe\ORM\Connect\PDOConnector
|
||||
PDOQuery: SilverStripe\ORM\Connect\PDOQuery
|
||||
SS_Query: SilverStripe\ORM\Connect\Query
|
||||
SilverStripe\ORM\Connect\SS_Query: SilverStripe\ORM\Connect\Query
|
||||
DBBoolean: SilverStripe\ORM\FieldType\DBBoolean
|
||||
@ -703,7 +701,6 @@ mappings:
|
||||
MySQLDatabaseTest: SilverStripe\ORM\Tests\MySQLDatabaseTest
|
||||
MySQLDatabaseTest_Data: SilverStripe\ORM\Tests\MySQLDatabaseTest\Data
|
||||
PaginatedListTest: SilverStripe\ORM\Tests\PaginatedListTest
|
||||
PDODatabaseTest: SilverStripe\ORM\Tests\PDODatabaseTest
|
||||
PolymorphicHasManyListTest: SilverStripe\ORM\Tests\PolymorphicHasManyListTest
|
||||
SQLInsertTest: SilverStripe\ORM\Tests\SQLInsertTest
|
||||
SQLInsertTestBase: SilverStripe\ORM\Tests\SQLInsertTest\SQLInsertTestBase
|
||||
@ -887,10 +884,8 @@ mappings:
|
||||
ForgotPasswordEmail_ss: SilverStripe\Control\Email\ChangePasswordEmail_ss
|
||||
GridFieldEditButton_ss: SilverStripe\Forms\GridField\GridFieldEditButton_ss
|
||||
skipYML:
|
||||
- MySQLPDODatabase
|
||||
- MySQLDatabase
|
||||
- MySQLiConnector
|
||||
- PDOConnector
|
||||
- MySQLSchemaManager
|
||||
- MySQLQueryBuilder
|
||||
- EndsWithFilter
|
||||
|
@ -9,7 +9,6 @@ SilverStripe\Dev\Backtrace:
|
||||
- ['mysqli', 'mysqli']
|
||||
- ['mysqli', 'real_connect']
|
||||
- ['mysqli', 'select_db']
|
||||
- ['PDO', '__construct']
|
||||
- ['SilverStripe\Control\Middleware\ConfirmationMiddleware\GetParameter', buildConfirmationItem]
|
||||
- ['SilverStripe\Control\Middleware\ConfirmationMiddleware\Url', buildConfirmationItem]
|
||||
- ['SilverStripe\Control\Middleware\ConfirmationMiddleware\UrlPathStartswith', buildConfirmationItem]
|
||||
|
@ -2,12 +2,6 @@
|
||||
name: databaseconnectors
|
||||
---
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
MySQLPDODatabase:
|
||||
class: 'SilverStripe\ORM\Connect\MySQLDatabase'
|
||||
properties:
|
||||
connector: '%$PDOConnector'
|
||||
schemaManager: '%$MySQLSchemaManager'
|
||||
queryBuilder: '%$MySQLQueryBuilder'
|
||||
MySQLDatabase:
|
||||
class: 'SilverStripe\ORM\Connect\MySQLDatabase'
|
||||
properties:
|
||||
@ -17,9 +11,6 @@ SilverStripe\Core\Injector\Injector:
|
||||
MySQLiConnector:
|
||||
class: 'SilverStripe\ORM\Connect\MySQLiConnector'
|
||||
type: prototype
|
||||
PDOConnector:
|
||||
class: 'SilverStripe\ORM\Connect\PDOConnector'
|
||||
type: prototype
|
||||
MySQLSchemaManager:
|
||||
class: 'SilverStripe\ORM\Connect\MySQLSchemaManager'
|
||||
MySQLQueryBuilder:
|
||||
|
@ -19,20 +19,3 @@ DatabaseAdapterRegistry::register(
|
||||
PHP extension is not available. Please install or enable it and refresh this page.'
|
||||
]
|
||||
);
|
||||
|
||||
// Register MySQL PDO as a database adapter (listed as first option in Dev/Install/config-form.html)
|
||||
DatabaseAdapterRegistry::register(
|
||||
[
|
||||
/** @skipUpgrade */
|
||||
'class' => 'MySQLPDODatabase',
|
||||
'module' => 'framework',
|
||||
'title' => 'MySQL 5.0+ (using PDO) - <b>not recommended</b>',
|
||||
'helperPath' => __DIR__ . '/src/Dev/Install/MySQLDatabaseConfigurationHelper.php',
|
||||
'helperClass' => MySQLDatabaseConfigurationHelper::class,
|
||||
'supported' => (class_exists('PDO') && in_array('mysql', PDO::getAvailableDrivers())),
|
||||
'missingExtensionText' =>
|
||||
'Either the <a href="http://www.php.net/manual/en/book.pdo.php">PDO Extension</a> or
|
||||
the <a href="http://www.php.net/manual/en/ref.pdo-mysql.php">MySQL PDO Driver</a>
|
||||
are unavailable. Please install or enable these and refresh this page.'
|
||||
]
|
||||
);
|
||||
|
@ -3,12 +3,10 @@
|
||||
namespace SilverStripe\Dev\Install;
|
||||
|
||||
use mysqli;
|
||||
use PDO;
|
||||
use Exception;
|
||||
use mysqli_result;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\ORM\Connect\MySQLiConnector;
|
||||
use SilverStripe\ORM\Connect\PDOConnector;
|
||||
|
||||
/**
|
||||
* This is a helper class for the SS installer.
|
||||
@ -65,41 +63,6 @@ class MySQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
|
||||
: 'Unknown connection error';
|
||||
return null;
|
||||
}
|
||||
case 'MySQLPDODatabase':
|
||||
// May throw a PDOException if fails
|
||||
|
||||
// Set SSL parameters
|
||||
$ssl = null;
|
||||
|
||||
if (array_key_exists('ssl_key', $databaseConfig) &&
|
||||
array_key_exists('ssl_cert', $databaseConfig)
|
||||
) {
|
||||
$ssl = [
|
||||
PDO::MYSQL_ATTR_SSL_KEY => $databaseConfig['ssl_key'],
|
||||
PDO::MYSQL_ATTR_SSL_CERT => $databaseConfig['ssl_cert'],
|
||||
];
|
||||
if (array_key_exists('ssl_ca', $databaseConfig)) {
|
||||
$ssl[PDO::MYSQL_ATTR_SSL_CA] = $databaseConfig['ssl_ca'];
|
||||
}
|
||||
// use default cipher if not provided
|
||||
$ssl[PDO::MYSQL_ATTR_SSL_CA] = array_key_exists('ssl_ca', $databaseConfig)
|
||||
? $databaseConfig['ssl_ca']
|
||||
: Config::inst()->get(PDOConnector::class, 'ssl_cipher_default');
|
||||
}
|
||||
|
||||
$conn = @new PDO(
|
||||
'mysql:host=' . $databaseConfig['server'],
|
||||
$databaseConfig['username'],
|
||||
$databaseConfig['password'],
|
||||
$ssl
|
||||
);
|
||||
if ($conn) {
|
||||
$conn->query("SET sql_mode = 'ANSI'");
|
||||
return $conn;
|
||||
} else {
|
||||
$error = 'Unknown connection error';
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
$error = 'Invalid connection type: ' . $databaseConfig['type'];
|
||||
return null;
|
||||
@ -155,8 +118,6 @@ class MySQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
|
||||
return false;
|
||||
} elseif ($conn instanceof mysqli) {
|
||||
return $conn->server_info;
|
||||
} elseif ($conn instanceof PDO) {
|
||||
return $conn->getAttribute(PDO::ATTR_SERVER_VERSION);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -845,11 +845,6 @@ abstract class Database
|
||||
*/
|
||||
public function connect($parameters)
|
||||
{
|
||||
// Ensure that driver is available (required by PDO)
|
||||
if (empty($parameters['driver'])) {
|
||||
$parameters['driver'] = $this->getDatabaseServer();
|
||||
}
|
||||
|
||||
// Notify connector of parameters
|
||||
$this->connector->connect($parameters);
|
||||
|
||||
|
@ -76,11 +76,6 @@ class MySQLDatabase extends Database implements TransactionManager
|
||||
|
||||
public function connect($parameters)
|
||||
{
|
||||
// Ensure that driver is available (required by PDO)
|
||||
if (empty($parameters['driver'])) {
|
||||
$parameters['driver'] = $this->getDatabaseServer();
|
||||
}
|
||||
|
||||
// Set charset
|
||||
if (empty($parameters['charset']) && ($charset = static::config()->get('connection_charset'))) {
|
||||
$parameters['charset'] = $charset;
|
||||
@ -317,7 +312,6 @@ class MySQLDatabase extends Database implements TransactionManager
|
||||
return $list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the TransactionManager to handle transactions for this database.
|
||||
*
|
||||
@ -326,13 +320,7 @@ class MySQLDatabase extends Database implements TransactionManager
|
||||
protected function getTransactionManager()
|
||||
{
|
||||
if (!$this->transactionManager) {
|
||||
// PDOConnector providers this
|
||||
if ($this->connector instanceof TransactionManager) {
|
||||
$this->transactionManager = new NestedTransactionManager($this->connector);
|
||||
// Direct database access does not
|
||||
} else {
|
||||
$this->transactionManager = new NestedTransactionManager(new MySQLTransactionManager($this));
|
||||
}
|
||||
$this->transactionManager = new NestedTransactionManager(new MySQLTransactionManager($this));
|
||||
}
|
||||
return $this->transactionManager;
|
||||
}
|
||||
|
@ -136,19 +136,6 @@ class MySQLSchemaManager extends DBSchemaManager
|
||||
|
||||
public function checkAndRepairTable($tableName)
|
||||
{
|
||||
// Flag to ensure we only send the warning about PDO + native mode once
|
||||
static $pdo_warning_sent = false;
|
||||
|
||||
// If running PDO and not in emulated mode, check table will fail
|
||||
if ($this->database->getConnector() instanceof PDOConnector && !PDOConnector::is_emulate_prepare()) {
|
||||
if (!$pdo_warning_sent) {
|
||||
$this->alterationMessage('CHECK TABLE command disabled for PDO in native mode', 'notice');
|
||||
$pdo_warning_sent = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Perform check
|
||||
if ($this->runTableCheckCommand("CHECK TABLE \"$tableName\"")) {
|
||||
return true;
|
||||
|
@ -1,593 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Connect;
|
||||
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use InvalidArgumentException;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* PDO driver database connector
|
||||
*/
|
||||
class PDOConnector extends DBConnector implements TransactionManager
|
||||
{
|
||||
|
||||
/**
|
||||
* Should ATTR_EMULATE_PREPARES flag be used to emulate prepared statements?
|
||||
*
|
||||
* @config
|
||||
* @var boolean
|
||||
*/
|
||||
private static $emulate_prepare = false;
|
||||
|
||||
/**
|
||||
* Should we return everything as a string in order to allow transaction savepoints?
|
||||
* This preserves the behaviour of <= 4.3, including some bugs.
|
||||
*
|
||||
* @config
|
||||
* @var boolean
|
||||
*/
|
||||
private static $legacy_types = false;
|
||||
|
||||
/**
|
||||
* Default strong SSL cipher to be used
|
||||
*
|
||||
* @config
|
||||
* @var string
|
||||
*/
|
||||
private static $ssl_cipher_default = 'DHE-RSA-AES256-SHA';
|
||||
|
||||
/**
|
||||
* The PDO connection instance
|
||||
*
|
||||
* @var PDO
|
||||
*/
|
||||
protected $pdoConnection = null;
|
||||
|
||||
/**
|
||||
* Name of the currently selected database
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $databaseName = null;
|
||||
|
||||
/**
|
||||
* If available, the row count of the last executed statement
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $rowCount = null;
|
||||
|
||||
/**
|
||||
* Error generated by the errorInfo() method of the last PDOStatement
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
protected $lastStatementError = null;
|
||||
|
||||
/**
|
||||
* List of prepared statements, cached by SQL string
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $cachedStatements = [];
|
||||
|
||||
/**
|
||||
* Driver
|
||||
* @var string
|
||||
*/
|
||||
protected $driver = null;
|
||||
|
||||
/*
|
||||
* Is a transaction currently active?
|
||||
* @var bool
|
||||
*/
|
||||
protected $inTransaction = false;
|
||||
|
||||
/**
|
||||
* Flush all prepared statements
|
||||
*/
|
||||
public function flushStatements()
|
||||
{
|
||||
$this->cachedStatements = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a prepared statement for a given SQL string, or return an already prepared version if
|
||||
* one exists for the given query
|
||||
*
|
||||
* @param string $sql
|
||||
* @return PDOStatementHandle|false
|
||||
*/
|
||||
public function getOrPrepareStatement($sql)
|
||||
{
|
||||
// Return cached statements
|
||||
if (isset($this->cachedStatements[$sql])) {
|
||||
return $this->cachedStatements[$sql];
|
||||
}
|
||||
|
||||
// Generate new statement
|
||||
try {
|
||||
$statement = $this->pdoConnection->prepare(
|
||||
$sql,
|
||||
[PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
$statement = false;
|
||||
$this->databaseError($e->getMessage(), E_USER_ERROR, $sql);
|
||||
}
|
||||
|
||||
// Wrap in a PDOStatementHandle, to cache column metadata
|
||||
$statementHandle = ($statement === false) ? false : new PDOStatementHandle($statement);
|
||||
|
||||
// Only cache select statements
|
||||
if (preg_match('/^(\s*)select\b/i', $sql ?? '')) {
|
||||
$this->cachedStatements[$sql] = $statementHandle;
|
||||
}
|
||||
return $statementHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is PDO running in emulated mode
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_emulate_prepare()
|
||||
{
|
||||
return self::config()->get('emulate_prepare');
|
||||
}
|
||||
|
||||
public function connect($parameters, $selectDB = false)
|
||||
{
|
||||
Deprecation::notice('4.5', 'Use native database drivers in favour of PDO. '
|
||||
. 'https://github.com/silverstripe/silverstripe-framework/issues/8598');
|
||||
|
||||
$this->flushStatements();
|
||||
|
||||
// Note that we don't select the database here until explicitly
|
||||
// requested via selectDatabase
|
||||
$this->driver = $parameters['driver'];
|
||||
|
||||
// Build DSN string
|
||||
$dsn = [];
|
||||
|
||||
// Typically this is false, but some drivers will request this
|
||||
if ($selectDB) {
|
||||
// Specify complete file path immediately following driver (SQLLite3)
|
||||
if (!empty($parameters['filepath'])) {
|
||||
$dsn[] = $parameters['filepath'];
|
||||
} elseif (!empty($parameters['database'])) {
|
||||
// Some databases require a selected database at connection (SQLite3, Azure)
|
||||
if ($parameters['driver'] === 'sqlsrv') {
|
||||
$dsn[] = "Database={$parameters['database']}";
|
||||
} else {
|
||||
$dsn[] = "dbname={$parameters['database']}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Syntax for sql server is slightly different
|
||||
if ($parameters['driver'] === 'sqlsrv') {
|
||||
$server = $parameters['server'];
|
||||
if (!empty($parameters['port'])) {
|
||||
$server .= ",{$parameters['port']}";
|
||||
}
|
||||
$dsn[] = "Server=$server";
|
||||
} elseif ($parameters['driver'] === 'dblib') {
|
||||
$server = $parameters['server'];
|
||||
if (!empty($parameters['port'])) {
|
||||
$server .= ":{$parameters['port']}";
|
||||
}
|
||||
$dsn[] = "host={$server}";
|
||||
} else {
|
||||
if (!empty($parameters['server'])) {
|
||||
// Use Server instead of host for sqlsrv
|
||||
$dsn[] = "host={$parameters['server']}";
|
||||
}
|
||||
|
||||
if (!empty($parameters['port'])) {
|
||||
$dsn[] = "port={$parameters['port']}";
|
||||
}
|
||||
}
|
||||
|
||||
// Connection charset and collation
|
||||
$connCharset = Config::inst()->get(MySQLDatabase::class, 'connection_charset');
|
||||
$connCollation = Config::inst()->get(MySQLDatabase::class, 'connection_collation');
|
||||
|
||||
// Set charset if given and not null. Can explicitly set to empty string to omit
|
||||
if (!in_array($parameters['driver'], ['sqlsrv', 'pgsql'])) {
|
||||
$charset = isset($parameters['charset'])
|
||||
? $parameters['charset']
|
||||
: $connCharset;
|
||||
if (!empty($charset)) {
|
||||
$dsn[] = "charset=$charset";
|
||||
}
|
||||
}
|
||||
|
||||
// Connection commands to be run on every re-connection
|
||||
if (!isset($charset)) {
|
||||
$charset = $connCharset;
|
||||
}
|
||||
|
||||
$options = [];
|
||||
if ($parameters['driver'] === 'mysql') {
|
||||
$options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES ' . $charset . ' COLLATE ' . $connCollation;
|
||||
}
|
||||
|
||||
// Set SSL options if they are defined
|
||||
if (array_key_exists('ssl_key', $parameters ?? []) &&
|
||||
array_key_exists('ssl_cert', $parameters ?? [])
|
||||
) {
|
||||
$options[PDO::MYSQL_ATTR_SSL_KEY] = $parameters['ssl_key'];
|
||||
$options[PDO::MYSQL_ATTR_SSL_CERT] = $parameters['ssl_cert'];
|
||||
if (array_key_exists('ssl_ca', $parameters ?? [])) {
|
||||
$options[PDO::MYSQL_ATTR_SSL_CA] = $parameters['ssl_ca'];
|
||||
}
|
||||
// use default cipher if not provided
|
||||
$options[PDO::MYSQL_ATTR_SSL_CIPHER] =
|
||||
array_key_exists('ssl_cipher', $parameters ?? []) ?
|
||||
$parameters['ssl_cipher'] :
|
||||
self::config()->get('ssl_cipher_default');
|
||||
}
|
||||
|
||||
if (static::config()->get('legacy_types')) {
|
||||
$options[PDO::ATTR_STRINGIFY_FETCHES] = true;
|
||||
$options[PDO::ATTR_EMULATE_PREPARES] = true;
|
||||
} else {
|
||||
// Set emulate prepares (unless null / default)
|
||||
$isEmulatePrepares = self::is_emulate_prepare();
|
||||
if (isset($isEmulatePrepares)) {
|
||||
$options[PDO::ATTR_EMULATE_PREPARES] = (bool)$isEmulatePrepares;
|
||||
}
|
||||
|
||||
// Disable stringified fetches
|
||||
$options[PDO::ATTR_STRINGIFY_FETCHES] = false;
|
||||
}
|
||||
|
||||
// May throw a PDOException if fails
|
||||
$this->pdoConnection = new PDO(
|
||||
$this->driver . ':' . implode(';', $dsn),
|
||||
empty($parameters['username']) ? '' : $parameters['username'],
|
||||
empty($parameters['password']) ? '' : $parameters['password'],
|
||||
$options
|
||||
);
|
||||
|
||||
// Show selected DB if requested
|
||||
if ($this->pdoConnection && $selectDB && !empty($parameters['database'])) {
|
||||
$this->databaseName = $parameters['database'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the driver for this connector
|
||||
* E.g. 'mysql', 'sqlsrv', 'pgsql'
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDriver()
|
||||
{
|
||||
return $this->driver;
|
||||
}
|
||||
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->pdoConnection->getAttribute(PDO::ATTR_SERVER_VERSION);
|
||||
}
|
||||
|
||||
public function escapeString($value)
|
||||
{
|
||||
$value = $this->quoteString($value);
|
||||
|
||||
// Since the PDO library quotes the value, we should remove this to maintain
|
||||
// consistency with MySQLDatabase::escapeString
|
||||
if (preg_match('/^\'(?<value>.*)\'$/', $value ?? '', $matches)) {
|
||||
$value = $matches['value'];
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function quoteString($value)
|
||||
{
|
||||
return $this->pdoConnection->quote($value ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked before any query is executed
|
||||
*
|
||||
* @param string $sql
|
||||
*/
|
||||
protected function beforeQuery($sql)
|
||||
{
|
||||
// Reset state
|
||||
$this->rowCount = 0;
|
||||
$this->lastStatementError = null;
|
||||
|
||||
// Flush if necessary
|
||||
if ($this->isQueryDDL($sql)) {
|
||||
$this->flushStatements();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a query that doesn't return a resultset
|
||||
*
|
||||
* @param string $sql The SQL query to execute
|
||||
* @param integer $errorLevel For errors to this query, raise PHP errors
|
||||
* using this error level.
|
||||
* @return int
|
||||
*/
|
||||
public function exec($sql, $errorLevel = E_USER_ERROR)
|
||||
{
|
||||
$this->beforeQuery($sql);
|
||||
|
||||
// Directly exec this query
|
||||
$result = $this->pdoConnection->exec($sql);
|
||||
|
||||
// Check for errors
|
||||
if ($result !== false) {
|
||||
return $this->rowCount = $result;
|
||||
}
|
||||
|
||||
$this->databaseError($this->getLastError(), $errorLevel, $sql);
|
||||
return null;
|
||||
}
|
||||
|
||||
public function query($sql, $errorLevel = E_USER_ERROR)
|
||||
{
|
||||
$this->beforeQuery($sql);
|
||||
|
||||
// Directly query against connection
|
||||
$statement = $this->pdoConnection->query($sql);
|
||||
|
||||
// Generate results
|
||||
if ($statement === false) {
|
||||
$this->databaseError($this->getLastError(), $errorLevel, $sql);
|
||||
} else {
|
||||
return $this->prepareResults(new PDOStatementHandle($statement), $errorLevel, $sql);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the PDO::PARAM_* type for a given PHP type string
|
||||
* @param string $phpType Type of object in PHP
|
||||
* @return integer PDO Parameter constant value
|
||||
*/
|
||||
public function getPDOParamType($phpType)
|
||||
{
|
||||
switch ($phpType) {
|
||||
case 'boolean':
|
||||
return PDO::PARAM_BOOL;
|
||||
case 'NULL':
|
||||
return PDO::PARAM_NULL;
|
||||
case 'integer':
|
||||
return PDO::PARAM_INT;
|
||||
case 'object': // Allowed if the object or resource has a __toString method
|
||||
case 'resource':
|
||||
case 'float': // Not actually returnable from get_type
|
||||
case 'double':
|
||||
case 'string':
|
||||
return PDO::PARAM_STR;
|
||||
case 'blob':
|
||||
return PDO::PARAM_LOB;
|
||||
case 'array':
|
||||
case 'unknown type':
|
||||
default:
|
||||
throw new InvalidArgumentException("Cannot bind parameter as it is an unsupported type ($phpType)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind all parameters to a PDOStatement
|
||||
*
|
||||
* @param PDOStatement $statement
|
||||
* @param array $parameters
|
||||
*/
|
||||
public function bindParameters(PDOStatement $statement, $parameters)
|
||||
{
|
||||
// Bind all parameters
|
||||
$parameterCount = count($parameters ?? []);
|
||||
for ($index = 0; $index < $parameterCount; $index++) {
|
||||
$value = $parameters[$index];
|
||||
$phpType = gettype($value);
|
||||
|
||||
// Allow overriding of parameter type using an associative array
|
||||
if ($phpType === 'array') {
|
||||
$phpType = $value['type'];
|
||||
$value = $value['value'];
|
||||
}
|
||||
|
||||
// Check type of parameter
|
||||
$type = $this->getPDOParamType($phpType);
|
||||
if ($type === PDO::PARAM_STR) {
|
||||
$value = (string) $value;
|
||||
}
|
||||
|
||||
// Bind this value
|
||||
$statement->bindValue($index+1, $value, $type);
|
||||
}
|
||||
}
|
||||
|
||||
public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR)
|
||||
{
|
||||
$this->beforeQuery($sql);
|
||||
|
||||
// Fetch cached statement, or create it
|
||||
$statementHandle = $this->getOrPrepareStatement($sql);
|
||||
|
||||
// Error handling
|
||||
if ($statementHandle === false) {
|
||||
$this->databaseError($this->getLastError(), $errorLevel, $sql, $this->parameterValues($parameters));
|
||||
return null;
|
||||
}
|
||||
|
||||
// Bind parameters
|
||||
$this->bindParameters($statementHandle->getPDOStatement(), $parameters);
|
||||
$statementHandle->execute($parameters);
|
||||
|
||||
// Generate results
|
||||
return $this->prepareResults($statementHandle, $errorLevel, $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a PDOStatement that has just been executed, generate results
|
||||
* and report any errors
|
||||
*
|
||||
* @param PDOStatementHandle $statement
|
||||
* @param int $errorLevel
|
||||
* @param string $sql
|
||||
* @param array $parameters
|
||||
* @return PDOQuery
|
||||
*/
|
||||
protected function prepareResults(PDOStatementHandle $statement, $errorLevel, $sql, $parameters = [])
|
||||
{
|
||||
|
||||
// Catch error
|
||||
if ($this->hasError($statement)) {
|
||||
$this->lastStatementError = $statement->errorInfo();
|
||||
$statement->closeCursor();
|
||||
|
||||
$this->databaseError($this->getLastError(), $errorLevel, $sql, $this->parameterValues($parameters));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Count and return results
|
||||
$this->rowCount = $statement->rowCount();
|
||||
return new PDOQuery($statement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a resource has an attached error
|
||||
*
|
||||
* @param PDOStatement|PDO $resource the resource to check
|
||||
* @return boolean Flag indicating true if the resource has an error
|
||||
*/
|
||||
protected function hasError($resource)
|
||||
{
|
||||
// No error if no resource
|
||||
if (empty($resource)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the error code is empty the statement / connection has not been run yet
|
||||
$code = $resource->errorCode();
|
||||
if (empty($code)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip 'ok' and undefined 'warning' types.
|
||||
// @see http://docstore.mik.ua/orelly/java-ent/jenut/ch08_06.htm
|
||||
return $code !== '00000' && $code !== '01000';
|
||||
}
|
||||
|
||||
public function getLastError()
|
||||
{
|
||||
$error = null;
|
||||
if ($this->lastStatementError) {
|
||||
$error = $this->lastStatementError;
|
||||
} elseif ($this->hasError($this->pdoConnection)) {
|
||||
$error = $this->pdoConnection->errorInfo();
|
||||
}
|
||||
if ($error) {
|
||||
return sprintf("%s-%s: %s", $error[0], $error[1], $error[2]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getGeneratedID($table)
|
||||
{
|
||||
return (int) $this->pdoConnection->lastInsertId();
|
||||
}
|
||||
|
||||
public function affectedRows()
|
||||
{
|
||||
return $this->rowCount;
|
||||
}
|
||||
|
||||
public function selectDatabase($name)
|
||||
{
|
||||
$this->exec("USE \"{$name}\"");
|
||||
$this->databaseName = $name;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getSelectedDatabase()
|
||||
{
|
||||
return $this->databaseName;
|
||||
}
|
||||
|
||||
public function unloadDatabase()
|
||||
{
|
||||
$this->databaseName = null;
|
||||
}
|
||||
|
||||
public function isActive()
|
||||
{
|
||||
return $this->databaseName && $this->pdoConnection;
|
||||
}
|
||||
|
||||
public function transactionStart($transactionMode = false, $sessionCharacteristics = false)
|
||||
{
|
||||
$this->inTransaction = true;
|
||||
|
||||
if ($transactionMode) {
|
||||
$this->query("SET TRANSACTION $transactionMode");
|
||||
}
|
||||
|
||||
if ($this->pdoConnection->beginTransaction()) {
|
||||
if ($sessionCharacteristics) {
|
||||
$this->query("SET SESSION CHARACTERISTICS AS TRANSACTION $sessionCharacteristics");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function transactionEnd()
|
||||
{
|
||||
$this->inTransaction = false;
|
||||
return $this->pdoConnection->commit();
|
||||
}
|
||||
|
||||
public function transactionRollback($savepoint = null)
|
||||
{
|
||||
if ($savepoint) {
|
||||
if ($this->supportsSavepoints()) {
|
||||
$this->exec("ROLLBACK TO SAVEPOINT $savepoint");
|
||||
} else {
|
||||
throw new DatabaseException("Savepoints not supported on this PDO connection");
|
||||
}
|
||||
}
|
||||
|
||||
// Note: $this->inTransaction may not match the 'in-transaction' state in PDO
|
||||
$this->inTransaction = false;
|
||||
if ($this->pdoConnection->inTransaction()) {
|
||||
return $this->pdoConnection->rollBack();
|
||||
}
|
||||
// return false because it did not rollback.
|
||||
return false;
|
||||
}
|
||||
|
||||
public function transactionDepth()
|
||||
{
|
||||
return (int)$this->inTransaction;
|
||||
}
|
||||
|
||||
public function transactionSavepoint($savepoint = null)
|
||||
{
|
||||
if ($this->supportsSavepoints()) {
|
||||
$this->exec("SAVEPOINT $savepoint");
|
||||
} else {
|
||||
throw new DatabaseException("Savepoints not supported on this PDO connection");
|
||||
}
|
||||
}
|
||||
|
||||
public function supportsSavepoints()
|
||||
{
|
||||
return static::config()->get('legacy_types');
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Connect;
|
||||
|
||||
/**
|
||||
* A result-set from a PDO database.
|
||||
*/
|
||||
class PDOQuery extends Query
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $results = null;
|
||||
|
||||
/**
|
||||
* Hook the result-set given into a Query class, suitable for use by SilverStripe.
|
||||
* @param PDOStatement $statement The internal PDOStatement containing the results
|
||||
*/
|
||||
public function __construct(PDOStatementHandle $statement)
|
||||
{
|
||||
// Since no more than one PDOStatement for any one connection can be safely
|
||||
// traversed, each statement simply requests all rows at once for safety.
|
||||
// This could be re-engineered to call fetchAll on an as-needed basis
|
||||
|
||||
$this->results = $statement->typeCorrectedFetchAll();
|
||||
$statement->closeCursor();
|
||||
}
|
||||
|
||||
public function seek($row)
|
||||
{
|
||||
$this->rowNum = $row - 1;
|
||||
return $this->nextRecord();
|
||||
}
|
||||
|
||||
public function numRecords()
|
||||
{
|
||||
return count($this->results ?? []);
|
||||
}
|
||||
|
||||
public function nextRecord()
|
||||
{
|
||||
$index = $this->rowNum + 1;
|
||||
|
||||
if (isset($this->results[$index])) {
|
||||
return $this->results[$index];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,155 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Connect;
|
||||
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
|
||||
/**
|
||||
* A handle to a PDOStatement, with cached column metadata, and type conversion
|
||||
*
|
||||
* Column metadata can't be fetched from a native PDOStatement after multiple calls in some DB backends,
|
||||
* so we wrap in this handle object, which also takes care of tidying up content types to keep in line
|
||||
* with the SilverStripe 4.4+ type expectations.
|
||||
*/
|
||||
class PDOStatementHandle
|
||||
{
|
||||
|
||||
/**
|
||||
* The statement to provide a handle to
|
||||
*
|
||||
* @var PDOStatement
|
||||
*/
|
||||
private $statement;
|
||||
|
||||
/**
|
||||
* Cached column metadata
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $columnMeta = null;
|
||||
|
||||
/**
|
||||
* Create a new handle.
|
||||
*
|
||||
* @param $statement The statement to provide a handle to
|
||||
*/
|
||||
public function __construct(PDOStatement $statement)
|
||||
{
|
||||
$this->statement = $statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping of PDO-reported "native types" to PHP types
|
||||
*/
|
||||
protected static $type_mapping = [
|
||||
// PGSQL
|
||||
'float8' => 'float',
|
||||
'float16' => 'float',
|
||||
'numeric' => 'float',
|
||||
'bool' => 'int', // Bools should be ints
|
||||
|
||||
// MySQL
|
||||
'NEWDECIMAL' => 'float',
|
||||
|
||||
// SQlite
|
||||
'integer' => 'int',
|
||||
'double' => 'float',
|
||||
];
|
||||
|
||||
/**
|
||||
* Fetch a record form the statement with its type data corrected
|
||||
* Returns data as an array of maps
|
||||
* @return array
|
||||
*/
|
||||
public function typeCorrectedFetchAll()
|
||||
{
|
||||
if ($this->columnMeta === null) {
|
||||
$columnCount = $this->statement->columnCount();
|
||||
$this->columnMeta = [];
|
||||
for ($i = 0; $i<$columnCount; $i++) {
|
||||
$this->columnMeta[$i] = $this->statement->getColumnMeta($i);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-map fetched data using columnMeta
|
||||
return array_map(
|
||||
function ($rowArray) {
|
||||
$row = [];
|
||||
foreach ($this->columnMeta as $i => $meta) {
|
||||
// Coerce any column types that aren't correctly retrieved from the database
|
||||
if (isset($meta['native_type']) && isset(self::$type_mapping[$meta['native_type']])) {
|
||||
settype($rowArray[$i], self::$type_mapping[$meta['native_type']] ?? '');
|
||||
}
|
||||
$row[$meta['name']] = $rowArray[$i];
|
||||
}
|
||||
return $row;
|
||||
},
|
||||
$this->statement->fetchAll(PDO::FETCH_NUM) ?? []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the cursor, enabling the statement to be executed again (PDOStatement::closeCursor)
|
||||
*
|
||||
* @return bool Returns true on success
|
||||
*/
|
||||
public function closeCursor()
|
||||
{
|
||||
return $this->statement->closeCursor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the SQLSTATE associated with the last operation on the statement handle
|
||||
* (PDOStatement::errorCode)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function errorCode()
|
||||
{
|
||||
return $this->statement->errorCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch extended error information associated with the last operation on the statement handle
|
||||
* (PDOStatement::errorInfo)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function errorInfo()
|
||||
{
|
||||
return $this->statement->errorInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of rows affected by the last SQL statement (PDOStatement::rowCount)
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function rowCount()
|
||||
{
|
||||
return $this->statement->rowCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a prepared statement (PDOStatement::execute)
|
||||
*
|
||||
* @param $parameters An array of values with as many elements as there are bound parameters in the SQL statement
|
||||
* being executed
|
||||
* @return bool Returns true on success
|
||||
*/
|
||||
public function execute(array $parameters)
|
||||
{
|
||||
return $this->statement->execute($parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the PDOStatement that this object provides a handle to
|
||||
*
|
||||
* @return PDOStatement
|
||||
*/
|
||||
public function getPDOStatement()
|
||||
{
|
||||
return $this->statement;
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ namespace SilverStripe\ORM\Connect;
|
||||
* Represents an object that is capable of controlling transactions.
|
||||
*
|
||||
* The TransactionManager might be the database connection itself, calling queries to orchestrate
|
||||
* transactions, or a connector such as the PDOConnector.
|
||||
* transactions, or a connector.
|
||||
*
|
||||
* Generally speaking you should rely on your Database object to manage the creation of a TansactionManager
|
||||
* for you; unless you are building new database connectors this should be treated as an internal API.
|
||||
@ -58,8 +58,8 @@ interface TransactionManager
|
||||
|
||||
/**
|
||||
* Return true if savepoints are supported by this transaction manager.
|
||||
* Savepoints aren't supported by all database connectors (notably PDO doesn't support them)
|
||||
* and should be used with caution.
|
||||
* Savepoints aren't supported by all database connectors and should be
|
||||
* used with caution.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
|
@ -10,14 +10,14 @@ if (!Environment::getEnv('SS_DATABASE_CLASS') && !Environment::getEnv('SS_DATABA
|
||||
// Database connection, including PDO and legacy ORM support
|
||||
switch (Environment::getEnv('DB')) {
|
||||
case "PGSQL";
|
||||
$pgDatabaseClass = Environment::getEnv('PDO') ? 'PostgrePDODatabase' : 'PostgreSQLDatabase';
|
||||
$pgDatabaseClass = 'PostgreSQLDatabase';
|
||||
Environment::setEnv('SS_DATABASE_CLASS', $pgDatabaseClass);
|
||||
Environment::setEnv('SS_DATABASE_USERNAME', 'postgres');
|
||||
Environment::setEnv('SS_DATABASE_PASSWORD', '');
|
||||
break;
|
||||
|
||||
case "SQLITE":
|
||||
$sqliteDatabaseClass = Environment::getEnv('PDO') ? 'SQLite3PDODatabase' : 'SQLite3Database';
|
||||
$sqliteDatabaseClass = 'SQLite3Database';
|
||||
Environment::setEnv('SS_DATABASE_CLASS', $sqliteDatabaseClass);
|
||||
Environment::setEnv('SS_DATABASE_USERNAME', 'root');
|
||||
Environment::setEnv('SS_DATABASE_PASSWORD', '');
|
||||
@ -25,7 +25,7 @@ if (!Environment::getEnv('SS_DATABASE_CLASS') && !Environment::getEnv('SS_DATABA
|
||||
break;
|
||||
|
||||
default:
|
||||
$mysqlDatabaseClass = Environment::getEnv('PDO') ? 'MySQLPDODatabase' : 'MySQLDatabase';
|
||||
$mysqlDatabaseClass = 'MySQLDatabase';
|
||||
Environment::setEnv('SS_DATABASE_CLASS', $mysqlDatabaseClass);
|
||||
Environment::setEnv('SS_DATABASE_USERNAME', 'root');
|
||||
Environment::setEnv('SS_DATABASE_PASSWORD', '');
|
||||
|
@ -1,141 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Tests;
|
||||
|
||||
use PDO;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\ORM\Connect\MySQLDatabase;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\Tests\MySQLPDOConnectorTest\PDOConnector;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\Tests\ORM\Utf8\Utf8TestHelper;
|
||||
|
||||
/**
|
||||
* @requires extension PDO
|
||||
* @requires extension pdo_mysql
|
||||
*/
|
||||
class MySQLPDOConnectorTest extends SapphireTest implements TestOnly
|
||||
{
|
||||
/**
|
||||
* @dataProvider charsetProvider
|
||||
*/
|
||||
public function testConnectionCharsetControl($charset, $defaultCollation)
|
||||
{
|
||||
$config = DB::getConfig();
|
||||
$config['driver'] = 'mysql';
|
||||
$config['charset'] = $charset;
|
||||
$config['database'] = 'information_schema';
|
||||
Config::inst()->set(MySQLDatabase::class, 'connection_collation', $defaultCollation);
|
||||
|
||||
if (strtolower(substr($config['type'] ?? '', 0, 5)) !== 'mysql') {
|
||||
return $this->markTestSkipped('The test only relevant for MySQL');
|
||||
}
|
||||
|
||||
$connector = new PDOConnector();
|
||||
$connector->connect($config);
|
||||
$connection = $connector->getPDOConnection();
|
||||
|
||||
$cset = $connection->query('show variables like "character_set_connection"')->fetch(PDO::FETCH_NUM)[1];
|
||||
$collation = $connection->query('show variables like "collation_connection"')->fetch(PDO::FETCH_NUM)[1];
|
||||
|
||||
$helper = new Utf8TestHelper();
|
||||
$this->assertEquals($helper->getUpdatedUtfCharsetForCurrentDB($charset), $cset);
|
||||
$this->assertEquals($helper->getUpdatedUtfCollationForCurrentDB($defaultCollation), $collation);
|
||||
|
||||
unset($cset, $connection, $connector, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider charsetProvider
|
||||
*/
|
||||
public function testConnectionCollationControl($charset, $defaultCollation, $customCollation)
|
||||
{
|
||||
$config = DB::getConfig();
|
||||
$config['charset'] = $charset;
|
||||
$config['driver'] = 'mysql';
|
||||
$config['database'] = 'information_schema';
|
||||
Config::inst()->set(MySQLDatabase::class, 'connection_collation', $customCollation);
|
||||
|
||||
if (strtolower(substr($config['type'] ?? '', 0, 5)) !== 'mysql') {
|
||||
return $this->markTestSkipped('The test only relevant for MySQL');
|
||||
}
|
||||
|
||||
$connector = new PDOConnector();
|
||||
$connector->connect($config);
|
||||
$connection = $connector->getPDOConnection();
|
||||
|
||||
$cset = $connection->query('show variables like "character_set_connection"')->fetch(PDO::FETCH_NUM)[1];
|
||||
$collation = $connection->query('show variables like "collation_connection"')->fetch(PDO::FETCH_NUM)[1];
|
||||
|
||||
$helper = new Utf8TestHelper();
|
||||
$this->assertEquals($helper->getUpdatedUtfCharsetForCurrentDB($charset), $cset);
|
||||
$this->assertEquals($helper->getUpdatedUtfCollationForCurrentDB($customCollation), $collation);
|
||||
|
||||
unset($cset, $connection, $connector, $config);
|
||||
}
|
||||
|
||||
public function charsetProvider()
|
||||
{
|
||||
return [
|
||||
['ascii', 'ascii_general_ci', 'ascii_bin'],
|
||||
['utf8', 'utf8_general_ci', 'utf8_unicode_520_ci'],
|
||||
['utf8mb4', 'utf8mb4_general_ci', 'utf8mb4_unicode_520_ci']
|
||||
];
|
||||
}
|
||||
|
||||
public function testUtf8mb4GeneralCollation()
|
||||
{
|
||||
$charset = 'utf8mb4';
|
||||
$collation = 'utf8mb4_general_ci';
|
||||
|
||||
$config = DB::getConfig();
|
||||
$config['charset'] = $charset;
|
||||
$config['driver'] = 'mysql';
|
||||
$config['database'] = 'information_schema';
|
||||
Config::inst()->set(MySQLDatabase::class, 'connection_collation', $collation);
|
||||
|
||||
if (strtolower(substr($config['type'] ?? '', 0, 5)) !== 'mysql') {
|
||||
return $this->markTestSkipped('The test only relevant for MySQL');
|
||||
}
|
||||
|
||||
$connector = new PDOConnector();
|
||||
$connector->connect($config);
|
||||
$connection = $connector->getPDOConnection();
|
||||
|
||||
$result = $connection->query(
|
||||
"select `a`.`value` from (select 'rst' `value` union select 'rßt' `value`) `a` order by `value`"
|
||||
)->fetchAll();
|
||||
|
||||
$this->assertCount(1, $result, '`utf8mb4_general_ci` handles both values as equal to "rst"');
|
||||
$this->assertEquals('rst', $result[0][0]);
|
||||
}
|
||||
|
||||
public function testUtf8mb4UnicodeCollation()
|
||||
{
|
||||
$charset = 'utf8mb4';
|
||||
$collation = 'utf8mb4_unicode_ci';
|
||||
|
||||
$config = DB::getConfig();
|
||||
$config['charset'] = $charset;
|
||||
$config['driver'] = 'mysql';
|
||||
$config['database'] = 'information_schema';
|
||||
Config::inst()->set(MySQLDatabase::class, 'connection_collation', $collation);
|
||||
|
||||
if (strtolower(substr($config['type'] ?? '', 0, 5)) !== 'mysql') {
|
||||
return $this->markTestSkipped('The test only relevant for MySQL');
|
||||
}
|
||||
|
||||
$connector = new PDOConnector();
|
||||
$connector->connect($config);
|
||||
$connection = $connector->getPDOConnection();
|
||||
|
||||
$result = $connection->query(
|
||||
"select `a`.`value` from (select 'rst' `value` union select 'rßt' `value`) `a` order by `value`"
|
||||
)->fetchAll();
|
||||
|
||||
$this->assertCount(2, $result, '`utf8mb4_unicode_ci` must recognise "rst" and "rßt" as different values');
|
||||
$this->assertEquals('rßt', $result[0][0]);
|
||||
$this->assertEquals('rst', $result[1][0]);
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Tests\MySQLPDOConnectorTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\Connect\PDOConnector as OriginalPDOConnector;
|
||||
use PDO;
|
||||
|
||||
class PDOConnector extends OriginalPDOConnector implements TestOnly
|
||||
{
|
||||
public function getPDOConnection(): PDO
|
||||
{
|
||||
return $this->pdoConnection;
|
||||
}
|
||||
}
|
@ -35,7 +35,6 @@ class MySQLiConnectorTest extends SapphireTest implements TestOnly
|
||||
// Note: we do not need to update the utf charset here because mysqli with newer
|
||||
// version of mysql/mariadb still self-reports as 'utf8' rather than 'utf8mb3'
|
||||
// This is unlike self::testConnectionCollationControl()
|
||||
// And also unlike MySQLPDOConnectorTest::testConnectionCharsetControl()
|
||||
$this->assertEquals($charset, $cset->charset);
|
||||
$this->assertEquals($defaultCollation, $cset->collation);
|
||||
|
||||
|
@ -1,122 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Tests;
|
||||
|
||||
use SilverStripe\ORM\Connect\PDOQuery;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\ORM\Connect\PDOConnector;
|
||||
use SilverStripe\ORM\Queries\SQLUpdate;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
/**
|
||||
* @skipUpgrade
|
||||
*/
|
||||
class PDODatabaseTest extends SapphireTest
|
||||
{
|
||||
protected static $fixture_file = 'MySQLDatabaseTest.yml';
|
||||
|
||||
protected static $extra_dataobjects = [
|
||||
MySQLDatabaseTest\Data::class
|
||||
];
|
||||
|
||||
public function testPreparedStatements()
|
||||
{
|
||||
if (!(DB::get_connector() instanceof PDOConnector)) {
|
||||
$this->markTestSkipped('This test requires the current DB connector is PDO');
|
||||
}
|
||||
|
||||
// Test preparation of equivalent statemetns
|
||||
$result1 = DB::get_connector()->preparedQuery(
|
||||
'SELECT "Sort", "Title" FROM "MySQLDatabaseTest_Data" WHERE "Sort" > ? ORDER BY "Sort"',
|
||||
[0]
|
||||
);
|
||||
|
||||
$result2 = DB::get_connector()->preparedQuery(
|
||||
'SELECT "Sort", "Title" FROM "MySQLDatabaseTest_Data" WHERE "Sort" > ? ORDER BY "Sort"',
|
||||
[2]
|
||||
);
|
||||
$this->assertInstanceOf(PDOQuery::class, $result1);
|
||||
$this->assertInstanceOf(PDOQuery::class, $result2);
|
||||
|
||||
// Also select non-prepared statement
|
||||
$result3 = DB::get_connector()->query('SELECT "Sort", "Title" FROM "MySQLDatabaseTest_Data" ORDER BY "Sort"');
|
||||
$this->assertInstanceOf(PDOQuery::class, $result3);
|
||||
|
||||
// Iterating one level should not buffer, but return the right result
|
||||
$this->assertEquals(
|
||||
[
|
||||
'Sort' => 1,
|
||||
'Title' => 'First Item'
|
||||
],
|
||||
$result1->next()
|
||||
);
|
||||
$this->assertEquals(
|
||||
[
|
||||
'Sort' => 2,
|
||||
'Title' => 'Second Item'
|
||||
],
|
||||
$result1->next()
|
||||
);
|
||||
|
||||
// Test first
|
||||
$this->assertEquals(
|
||||
[
|
||||
'Sort' => 1,
|
||||
'Title' => 'First Item'
|
||||
],
|
||||
$result1->first()
|
||||
);
|
||||
|
||||
// Test seek
|
||||
$this->assertEquals(
|
||||
[
|
||||
'Sort' => 2,
|
||||
'Title' => 'Second Item'
|
||||
],
|
||||
$result1->seek(1)
|
||||
);
|
||||
|
||||
// Test count
|
||||
$this->assertEquals(4, $result1->numRecords());
|
||||
|
||||
// Test second statement
|
||||
$this->assertEquals(
|
||||
[
|
||||
'Sort' => 3,
|
||||
'Title' => 'Third Item'
|
||||
],
|
||||
$result2->next()
|
||||
);
|
||||
|
||||
// Test non-prepared query
|
||||
$this->assertEquals(
|
||||
[
|
||||
'Sort' => 1,
|
||||
'Title' => 'First Item'
|
||||
],
|
||||
$result3->next()
|
||||
);
|
||||
}
|
||||
|
||||
public function testAffectedRows()
|
||||
{
|
||||
if (!(DB::get_connector() instanceof PDOConnector)) {
|
||||
$this->markTestSkipped('This test requires the current DB connector is PDO');
|
||||
}
|
||||
|
||||
$query = new SQLUpdate('"MySQLDatabaseTest_Data"');
|
||||
$query->setAssignments(['"Title"' => 'New Title']);
|
||||
|
||||
// Test update which affects no rows
|
||||
$query->setWhere(['"Title"' => 'Bob']);
|
||||
$result = $query->execute();
|
||||
$this->assertInstanceOf(PDOQuery::class, $result);
|
||||
$this->assertEquals(0, DB::affected_rows());
|
||||
|
||||
// Test update which affects some rows
|
||||
$query->setWhere(['"Title"' => 'First Item']);
|
||||
$result = $query->execute();
|
||||
$this->assertInstanceOf(PDOQuery::class, $result);
|
||||
$this->assertEquals(1, DB::affected_rows());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user