silverstripe-framework/src/ORM/Connect/DBConnector.php

265 lines
8.2 KiB
PHP

<?php
namespace SilverStripe\ORM\Connect;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\View\Parsers\SQLFormatter;
/**
* Represents an object responsible for wrapping DB connector api
*/
abstract class DBConnector
{
use Configurable;
/**
* List of operations to treat as write
* Implicitly includes all ddl_operations
*
* @config
* @var array
*/
private static $write_operations = array('insert', 'update', 'delete', 'replace');
/**
* List of operations to treat as DDL
*
* @config
* @var array
*/
private static $ddl_operations = array('alter', 'drop', 'create', 'truncate');
/**
* Error handler for database errors.
* All database errors will call this function to report the error. It isn't a static function;
* it will be called on the object itself and as such can be overridden in a subclass.
* Subclasses should run all errors through this function.
*
* @todo hook this into a more well-structured error handling system.
* @param string $msg The error message.
* @param integer $errorLevel The level of the error to throw.
* @param string $sql The SQL related to this query
* @param array $parameters Parameters passed to the query
* @throws DatabaseException
*/
protected function databaseError($msg, $errorLevel = E_USER_ERROR, $sql = null, $parameters = array())
{
// Prevent errors when error checking is set at zero level
if (empty($errorLevel)) {
return;
}
// Format query if given
if (!empty($sql)) {
$formatter = new SQLFormatter();
$formattedSQL = $formatter->formatPlain($sql);
$msg = "Couldn't run query:\n\n{$formattedSQL}\n\n{$msg}";
}
if ($errorLevel === E_USER_ERROR) {
// Treating errors as exceptions better allows for responding to errors
// in code, such as credential checking during installation
throw new DatabaseException($msg, 0, null, $sql, $parameters);
} else {
user_error($msg, $errorLevel);
}
}
/**
* Determine if this SQL statement is a destructive operation (write or ddl)
*
* @param string $sql
* @return bool
*/
public function isQueryMutable($sql)
{
$operations = array_merge(
Config::inst()->get(static::class, 'write_operations'),
Config::inst()->get(static::class, 'ddl_operations')
);
return $this->isQueryType($sql, $operations);
}
/**
* Determine if this SQL statement is a DDL operation
*
* @param string $sql
* @return bool
*/
public function isQueryDDL($sql)
{
$operations = Config::inst()->get(static::class, 'ddl_operations');
return $this->isQueryType($sql, $operations);
}
/**
* Determine if this SQL statement is a write operation
* (alters content but not structure)
*
* @param string $sql
* @return bool
*/
public function isQueryWrite($sql)
{
$operations = Config::inst()->get(static::class, 'write_operations');
return $this->isQueryType($sql, $operations);
}
/**
* Determine if a query is of the given type
*
* @param string $sql Raw SQL
* @param string|array $type Type or list of types (first word in the query). Must be lowercase
* @return bool
*/
protected function isQueryType($sql, $type)
{
if (!preg_match('/^(?<operation>\w+)\b/', $sql, $matches)) {
return false;
}
$operation = $matches['operation'];
if (is_array($type)) {
return in_array(strtolower($operation), $type);
} else {
return strcasecmp($sql, $type) === 0;
}
}
/**
* Extracts only the parameter values for error reporting
*
* @param array $parameters
* @return array List of parameter values
*/
protected function parameterValues($parameters)
{
$values = array();
foreach ($parameters as $value) {
$values[] = is_array($value) ? $value['value'] : $value;
}
return $values;
}
/**
* Link this connector to the database given the specified parameters
* Will throw an exception rather than return a success state.
* The connector should not select the database once connected until
* explicitly called by selectDatabase()
*
* @param array $parameters List of parameters such as
* <ul>
* <li>type</li>
* <li>server</li>
* <li>username</li>
* <li>password</li>
* <li>database</li>
* <li>path</li>
* </ul>
* @param boolean $selectDB By default database selection should be
* handled by the database controller (to enable database creation on the
* fly if necessary), but some interfaces require that the database is
* specified during connection (SQLite, Azure, etc).
*/
abstract public function connect($parameters, $selectDB = false);
/**
* Query for the version of the currently connected database
*
* @return string Version of this database
*/
abstract public function getVersion();
/**
* Given a value escape this for use in a query for the current database
* connector. Note that this does not quote the value.
*
* @param string $value The value to be escaped
* @return string The appropritaely escaped string for value
*/
abstract public function escapeString($value);
/**
* Given a value escape and quote this appropriately for the current
* database connector.
*
* @param string $value The value to be injected into a query
* @return string The appropriately escaped and quoted string for $value
*/
abstract public function quoteString($value);
/**
* Executes the following query with the specified error level.
* Implementations of this function should respect previewWrite and benchmarkQuery
*
* @see http://php.net/manual/en/errorfunc.constants.php
* @param string $sql The SQL query to execute
* @param integer $errorLevel For errors to this query, raise PHP errors
* using this error level.
*/
abstract public function query($sql, $errorLevel = E_USER_ERROR);
/**
* Execute the given SQL parameterised query with the specified arguments
*
* @param string $sql The SQL query to execute. The ? character will denote parameters.
* @param array $parameters An ordered list of arguments.
* @param int $errorLevel The level of error reporting to enable for the query
* @return Query
*/
abstract public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR);
/**
* Select a database by name
*
* @param string $name Name of database
* @return boolean Flag indicating success
*/
abstract public function selectDatabase($name);
/**
* Retrieves the name of the currently selected database
*
* @return string Name of the database, or null if none selected
*/
abstract public function getSelectedDatabase();
/**
* De-selects the currently selected database
*/
abstract public function unloadDatabase();
/**
* Retrieves the last error generated from the database connection
*
* @return string The error message
*/
abstract public function getLastError();
/**
* Determines the last ID generated from the specified table.
* Note that some connectors may not be able to return $table specific responses,
* and this parameter may be ignored.
*
* @param string $table The target table to return the last generated ID for
* @return integer ID value
*/
abstract public function getGeneratedID($table);
/**
* Determines the number of affected rows from the last SQL query
*
* @return integer Number of affected rows
*/
abstract public function affectedRows();
/**
* Determines if we are connected to a server AND have a valid database
* selected.
*
* @return boolean Flag indicating that a valid database is connected
*/
abstract public function isActive();
}