silverstripe-framework/model/connect/DBConnector.php
Damian Mooyman ce3b5a5ace BUG Fix major segfault on PDOConnector after any DDL
BUG Fix issue in PDOQuery::first()
Refactor previewWrite and benchmarkQuery into SS_Database
2015-06-17 13:34:21 +12:00

270 lines
8.0 KiB
PHP

<?php
/**
* Represents an object responsible for wrapping DB connector api
*
* @package framework
* @subpackage model
*/
abstract class DBConnector {
/**
* 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 SS_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 SS_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(get_class($this), 'write_operations'),
Config::inst()->get(get_class($this), '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(get_class($this), '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(get_class($this), '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
*/
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);
/**
* Escapes an identifier (table / database name). Typically the value
* is simply double quoted. Don't pass in already escaped identifiers in,
* as this will double escape the value!
*
* @param string $value The identifier to escape
* @param string $separator optional identifier splitter
*/
public function escapeIdentifier($value, $separator = '.') {
// ANSI standard id escape is to surround with double quotes
if(empty($separator)) return '"'.trim($value).'"';
// Split, escape, and glue back multiple identifiers
$segments = array();
foreach(explode($separator, $value) as $item) {
$segments[] = $this->escapeIdentifier($item, null);
}
return implode($separator, $segments);
}
/**
* 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 SS_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();
}