diff --git a/.upgrade.yml b/.upgrade.yml index a71ae6987..4a1a15a81 100644 --- a/.upgrade.yml +++ b/.upgrade.yml @@ -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 diff --git a/_config/backtrace.yml b/_config/backtrace.yml index dd90eabbe..f7b657c28 100644 --- a/_config/backtrace.yml +++ b/_config/backtrace.yml @@ -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] diff --git a/_config/database.yml b/_config/database.yml index e08086d7e..7584cc14e 100644 --- a/_config/database.yml +++ b/_config/database.yml @@ -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: diff --git a/_register_database.php b/_register_database.php index 977e4a84b..00952bc04 100644 --- a/_register_database.php +++ b/_register_database.php @@ -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) - not recommended', - 'helperPath' => __DIR__ . '/src/Dev/Install/MySQLDatabaseConfigurationHelper.php', - 'helperClass' => MySQLDatabaseConfigurationHelper::class, - 'supported' => (class_exists('PDO') && in_array('mysql', PDO::getAvailableDrivers())), - 'missingExtensionText' => - 'Either the PDO Extension or - the MySQL PDO Driver - are unavailable. Please install or enable these and refresh this page.' - ] -); diff --git a/src/Dev/Install/MySQLDatabaseConfigurationHelper.php b/src/Dev/Install/MySQLDatabaseConfigurationHelper.php index 22552a9b5..e6f572651 100644 --- a/src/Dev/Install/MySQLDatabaseConfigurationHelper.php +++ b/src/Dev/Install/MySQLDatabaseConfigurationHelper.php @@ -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; } diff --git a/src/ORM/Connect/Database.php b/src/ORM/Connect/Database.php index 35c30228c..72054c662 100644 --- a/src/ORM/Connect/Database.php +++ b/src/ORM/Connect/Database.php @@ -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); diff --git a/src/ORM/Connect/MySQLDatabase.php b/src/ORM/Connect/MySQLDatabase.php index 9200f3a8c..b46e6e341 100644 --- a/src/ORM/Connect/MySQLDatabase.php +++ b/src/ORM/Connect/MySQLDatabase.php @@ -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; } diff --git a/src/ORM/Connect/MySQLSchemaManager.php b/src/ORM/Connect/MySQLSchemaManager.php index 1933dc37d..f44b1c0d8 100644 --- a/src/ORM/Connect/MySQLSchemaManager.php +++ b/src/ORM/Connect/MySQLSchemaManager.php @@ -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; diff --git a/src/ORM/Connect/PDOConnector.php b/src/ORM/Connect/PDOConnector.php deleted file mode 100644 index bb1637d65..000000000 --- a/src/ORM/Connect/PDOConnector.php +++ /dev/null @@ -1,593 +0,0 @@ -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 ?? '', $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'); - } -} diff --git a/src/ORM/Connect/PDOQuery.php b/src/ORM/Connect/PDOQuery.php deleted file mode 100644 index 7180de28d..000000000 --- a/src/ORM/Connect/PDOQuery.php +++ /dev/null @@ -1,50 +0,0 @@ -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; - } - } -} diff --git a/src/ORM/Connect/PDOStatementHandle.php b/src/ORM/Connect/PDOStatementHandle.php deleted file mode 100644 index b2ad1971f..000000000 --- a/src/ORM/Connect/PDOStatementHandle.php +++ /dev/null @@ -1,155 +0,0 @@ -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; - } -} diff --git a/src/ORM/Connect/TransactionManager.php b/src/ORM/Connect/TransactionManager.php index 7dbb6be1f..feb29da1b 100644 --- a/src/ORM/Connect/TransactionManager.php +++ b/src/ORM/Connect/TransactionManager.php @@ -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 */ diff --git a/tests/bootstrap/environment.php b/tests/bootstrap/environment.php index 24d15f9d2..fae3b1169 100644 --- a/tests/bootstrap/environment.php +++ b/tests/bootstrap/environment.php @@ -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', ''); diff --git a/tests/php/ORM/MySQLPDOConnectorTest.php b/tests/php/ORM/MySQLPDOConnectorTest.php deleted file mode 100644 index 473d71eb9..000000000 --- a/tests/php/ORM/MySQLPDOConnectorTest.php +++ /dev/null @@ -1,141 +0,0 @@ -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]); - } -} diff --git a/tests/php/ORM/MySQLPDOConnectorTest/PDOConnector.php b/tests/php/ORM/MySQLPDOConnectorTest/PDOConnector.php deleted file mode 100644 index 323aeea69..000000000 --- a/tests/php/ORM/MySQLPDOConnectorTest/PDOConnector.php +++ /dev/null @@ -1,15 +0,0 @@ -pdoConnection; - } -} diff --git a/tests/php/ORM/MySQLiConnectorTest.php b/tests/php/ORM/MySQLiConnectorTest.php index a037d58ed..8f0f264f6 100644 --- a/tests/php/ORM/MySQLiConnectorTest.php +++ b/tests/php/ORM/MySQLiConnectorTest.php @@ -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); diff --git a/tests/php/ORM/PDODatabaseTest.php b/tests/php/ORM/PDODatabaseTest.php deleted file mode 100644 index fa91676c5..000000000 --- a/tests/php/ORM/PDODatabaseTest.php +++ /dev/null @@ -1,122 +0,0 @@ -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()); - } -}