2013-06-21 00:32:08 +02:00
|
|
|
<?php
|
|
|
|
|
2016-06-15 06:03:16 +02:00
|
|
|
namespace SilverStripe\ORM\Connect;
|
|
|
|
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\Control\Director;
|
|
|
|
use SilverStripe\Core\Config\Config;
|
|
|
|
use SilverStripe\Dev\Debug;
|
|
|
|
use SilverStripe\ORM\PaginatedList;
|
2016-06-15 06:03:16 +02:00
|
|
|
use SilverStripe\ORM\Queries\SQLUpdate;
|
|
|
|
use SilverStripe\ORM\Queries\SQLInsert;
|
2016-08-19 00:51:35 +02:00
|
|
|
use BadMethodCallException;
|
|
|
|
use Exception;
|
2016-06-15 06:03:16 +02:00
|
|
|
|
2013-06-21 00:32:08 +02:00
|
|
|
/**
|
|
|
|
* Abstract database connectivity class.
|
|
|
|
* Sub-classes of this implement the actual database connection libraries
|
|
|
|
*/
|
2016-09-09 08:43:05 +02:00
|
|
|
abstract class Database {
|
2013-06-21 00:32:08 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Database connector object
|
|
|
|
*
|
|
|
|
* @var DBConnector
|
|
|
|
*/
|
|
|
|
protected $connector = null;
|
2016-07-01 04:26:18 +02:00
|
|
|
|
2016-06-20 07:40:58 +02:00
|
|
|
/**
|
|
|
|
* In cases where your environment does not have 'SHOW DATABASES' permission,
|
|
|
|
* you can set this to true. Then selectDatabase() will always connect without
|
|
|
|
* doing databaseExists() check.
|
|
|
|
*
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
private static $optimistic_connect = false;
|
|
|
|
|
2016-05-29 04:15:45 +02:00
|
|
|
/**
|
|
|
|
* Amount of queries executed, for debugging purposes.
|
2016-07-01 04:26:18 +02:00
|
|
|
*
|
2016-05-29 04:15:45 +02:00
|
|
|
* @var int
|
|
|
|
*/
|
|
|
|
protected $queryCount = 0;
|
2013-06-21 00:32:08 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the current connector
|
|
|
|
*
|
|
|
|
* @return DBConnector
|
|
|
|
*/
|
|
|
|
public function getConnector() {
|
|
|
|
return $this->connector;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Injector injection point for connector dependency
|
|
|
|
*
|
|
|
|
* @param DBConnector $connector
|
|
|
|
*/
|
|
|
|
public function setConnector(DBConnector $connector) {
|
|
|
|
$this->connector = $connector;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Database schema manager object
|
|
|
|
*
|
|
|
|
* @var DBSchemaManager
|
|
|
|
*/
|
|
|
|
protected $schemaManager = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the current schema manager
|
|
|
|
*
|
|
|
|
* @return DBSchemaManager
|
|
|
|
*/
|
|
|
|
public function getSchemaManager() {
|
|
|
|
return $this->schemaManager;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Injector injection point for schema manager
|
|
|
|
*
|
|
|
|
* @param DBSchemaManager $schemaManager
|
|
|
|
*/
|
|
|
|
public function setSchemaManager(DBSchemaManager $schemaManager) {
|
|
|
|
$this->schemaManager = $schemaManager;
|
|
|
|
|
|
|
|
if ($this->schemaManager) {
|
|
|
|
$this->schemaManager->setDatabase($this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Query builder object
|
|
|
|
*
|
|
|
|
* @var DBQueryBuilder
|
|
|
|
*/
|
|
|
|
protected $queryBuilder = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the current query builder
|
|
|
|
*
|
|
|
|
* @return DBQueryBuilder
|
|
|
|
*/
|
|
|
|
public function getQueryBuilder() {
|
|
|
|
return $this->queryBuilder;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Injector injection point for schema manager
|
|
|
|
*
|
|
|
|
* @param DBQueryBuilder $queryBuilder
|
|
|
|
*/
|
|
|
|
public function setQueryBuilder(DBQueryBuilder $queryBuilder) {
|
|
|
|
$this->queryBuilder = $queryBuilder;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute the given SQL query.
|
|
|
|
*
|
|
|
|
* @param string $sql The SQL query to execute
|
|
|
|
* @param int $errorLevel The level of error reporting to enable for the query
|
2016-09-09 08:43:05 +02:00
|
|
|
* @return Query
|
2013-06-21 00:32:08 +02:00
|
|
|
*/
|
|
|
|
public function query($sql, $errorLevel = E_USER_ERROR) {
|
2015-06-16 05:04:20 +02:00
|
|
|
// Check if we should only preview this query
|
|
|
|
if ($this->previewWrite($sql)) {
|
2016-08-19 00:51:35 +02:00
|
|
|
return null;
|
2015-06-16 05:04:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Benchmark query
|
|
|
|
$connector = $this->connector;
|
|
|
|
return $this->benchmarkQuery(
|
|
|
|
$sql,
|
|
|
|
function($sql) use($connector, $errorLevel) {
|
|
|
|
return $connector->query($sql, $errorLevel);
|
|
|
|
}
|
|
|
|
);
|
2013-06-21 00:32:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
2016-09-09 08:43:05 +02:00
|
|
|
* @return Query
|
2013-06-21 00:32:08 +02:00
|
|
|
*/
|
|
|
|
public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR) {
|
2015-06-16 05:04:20 +02:00
|
|
|
// Check if we should only preview this query
|
|
|
|
if ($this->previewWrite($sql)) {
|
2016-08-19 00:51:35 +02:00
|
|
|
return null;
|
2015-06-16 05:04:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Benchmark query
|
|
|
|
$connector = $this->connector;
|
|
|
|
return $this->benchmarkQuery(
|
|
|
|
$sql,
|
|
|
|
function($sql) use($connector, $parameters, $errorLevel) {
|
|
|
|
return $connector->preparedQuery($sql, $parameters, $errorLevel);
|
2015-09-04 05:50:09 +02:00
|
|
|
},
|
|
|
|
$parameters
|
2015-06-16 05:04:20 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines if the query should be previewed, and thus interrupted silently.
|
|
|
|
* If so, this function also displays the query via the debuging system.
|
|
|
|
* Subclasess should respect the results of this call for each query, and not
|
|
|
|
* execute any queries that generate a true response.
|
|
|
|
*
|
|
|
|
* @param string $sql The query to be executed
|
|
|
|
* @return boolean Flag indicating that the query was previewed
|
|
|
|
*/
|
|
|
|
protected function previewWrite($sql) {
|
|
|
|
// Only preview if previewWrite is set, we are in dev mode, and
|
|
|
|
// the query is mutable
|
|
|
|
if (isset($_REQUEST['previewwrite'])
|
|
|
|
&& Director::isDev()
|
|
|
|
&& $this->connector->isQueryMutable($sql)
|
|
|
|
) {
|
|
|
|
// output preview message
|
|
|
|
Debug::message("Will execute: $sql");
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Allows the display and benchmarking of queries as they are being run
|
|
|
|
*
|
|
|
|
* @param string $sql Query to run, and single parameter to callback
|
|
|
|
* @param callable $callback Callback to execute code
|
2016-09-14 23:15:19 +02:00
|
|
|
* @param array $parameters Parameters for any parameterised query
|
2015-06-16 05:04:20 +02:00
|
|
|
* @return mixed Result of query
|
|
|
|
*/
|
2016-09-14 23:15:19 +02:00
|
|
|
protected function benchmarkQuery($sql, $callback, $parameters = array()) {
|
2015-06-16 05:04:20 +02:00
|
|
|
if (isset($_REQUEST['showqueries']) && Director::isDev()) {
|
2016-05-29 04:15:45 +02:00
|
|
|
$this->queryCount++;
|
2015-06-16 05:04:20 +02:00
|
|
|
$starttime = microtime(true);
|
|
|
|
$result = $callback($sql);
|
|
|
|
$endtime = round(microtime(true) - $starttime, 4);
|
2016-09-14 23:15:19 +02:00
|
|
|
// replace parameters as closely as possible to what we'd expect the DB to put in
|
|
|
|
if (strtolower($_REQUEST['showqueries']) == 'inline') {
|
|
|
|
$sql = DB::inline_parameters($sql, $parameters);
|
2015-09-04 05:50:09 +02:00
|
|
|
}
|
2015-06-16 05:04:20 +02:00
|
|
|
Debug::message("\n$sql\n{$endtime}s\n", false);
|
|
|
|
return $result;
|
|
|
|
} else {
|
|
|
|
return $callback($sql);
|
|
|
|
}
|
2013-06-21 00:32:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the autogenerated ID from the previous INSERT query.
|
|
|
|
*
|
|
|
|
* @param string $table The name of the table to get the generated ID for
|
|
|
|
* @return integer the most recently generated ID for the specified table
|
|
|
|
*/
|
|
|
|
public function getGeneratedID($table) {
|
|
|
|
return $this->connector->getGeneratedID($table);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines if we are connected to a server AND have a valid database
|
|
|
|
* selected.
|
|
|
|
*
|
|
|
|
* @return boolean Flag indicating that a valid database is connected
|
|
|
|
*/
|
|
|
|
public function isActive() {
|
|
|
|
return $this->connector->isActive();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an escaped string. This string won't be quoted, so would be suitable
|
|
|
|
* for appending to other quoted strings.
|
|
|
|
*
|
|
|
|
* @param mixed $value Value to be prepared for database query
|
|
|
|
* @return string Prepared string
|
|
|
|
*/
|
|
|
|
public function escapeString($value) {
|
|
|
|
return $this->connector->escapeString($value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wrap a string into DB-specific quotes.
|
|
|
|
*
|
|
|
|
* @param mixed $value Value to be prepared for database query
|
|
|
|
* @return string Prepared string
|
|
|
|
*/
|
|
|
|
public function quoteString($value) {
|
|
|
|
return $this->connector->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!
|
|
|
|
*
|
2016-05-25 07:09:29 +02:00
|
|
|
* @param string|array $value The identifier to escape or list of split components
|
|
|
|
* @param string $separator Splitter for each component
|
|
|
|
* @return string
|
2013-06-21 00:32:08 +02:00
|
|
|
*/
|
|
|
|
public function escapeIdentifier($value, $separator = '.') {
|
2016-05-25 07:09:29 +02:00
|
|
|
// Split string into components
|
|
|
|
if(!is_array($value)) {
|
|
|
|
$value = explode($separator, $value);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implode quoted column
|
|
|
|
return '"' . implode('"'.$separator.'"', $value) . '"';
|
2013-06-21 00:32:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Escapes unquoted columns keys in an associative array
|
|
|
|
*
|
|
|
|
* @param array $fieldValues
|
|
|
|
* @return array List of field values with the keys as escaped column names
|
|
|
|
*/
|
|
|
|
protected function escapeColumnKeys($fieldValues) {
|
|
|
|
$out = array();
|
|
|
|
foreach($fieldValues as $field => $value) {
|
|
|
|
$out[$this->escapeIdentifier($field)] = $value;
|
|
|
|
}
|
|
|
|
return $out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute a complex manipulation on the database.
|
|
|
|
* A manipulation is an array of insert / or update sequences. The keys of the array are table names,
|
|
|
|
* and the values are map containing 'command' and 'fields'. Command should be 'insert' or 'update',
|
|
|
|
* and fields should be a map of field names to field values, NOT including quotes.
|
|
|
|
*
|
|
|
|
* The field values could also be in paramaterised format, such as
|
|
|
|
* array('MAX(?,?)' => array(42, 69)), allowing the use of raw SQL values such as
|
|
|
|
* array('NOW()' => array()).
|
|
|
|
*
|
|
|
|
* @see SQLWriteExpression::addAssignments for syntax examples
|
|
|
|
*
|
|
|
|
* @param array $manipulation
|
|
|
|
*/
|
|
|
|
public function manipulate($manipulation) {
|
|
|
|
if (empty($manipulation)) return;
|
|
|
|
|
|
|
|
foreach ($manipulation as $table => $writeInfo) {
|
|
|
|
if(empty($writeInfo['fields'])) continue;
|
|
|
|
// Note: keys of $fieldValues are not escaped
|
|
|
|
$fieldValues = $writeInfo['fields'];
|
|
|
|
|
|
|
|
// Switch command type
|
|
|
|
switch ($writeInfo['command']) {
|
|
|
|
case "update":
|
|
|
|
|
|
|
|
// Build update
|
|
|
|
$query = new SQLUpdate("\"$table\"", $this->escapeColumnKeys($fieldValues));
|
|
|
|
|
|
|
|
// Set best condition to use
|
|
|
|
if(!empty($writeInfo['where'])) {
|
|
|
|
$query->addWhere($writeInfo['where']);
|
|
|
|
} elseif(!empty($writeInfo['id'])) {
|
|
|
|
$query->addWhere(array('"ID"' => $writeInfo['id']));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test to see if this update query shouldn't, in fact, be an insert
|
|
|
|
if($query->toSelect()->count()) {
|
|
|
|
$query->execute();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// ...if not, we'll skip on to the insert code
|
|
|
|
|
|
|
|
case "insert":
|
|
|
|
// Ensure that the ID clause is given if possible
|
|
|
|
if (!isset($fieldValues['ID']) && isset($writeInfo['id'])) {
|
|
|
|
$fieldValues['ID'] = $writeInfo['id'];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build insert
|
|
|
|
$query = new SQLInsert("\"$table\"", $this->escapeColumnKeys($fieldValues));
|
|
|
|
|
|
|
|
$query->execute();
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
user_error("SS_Database::manipulate() Can't recognise command '{$writeInfo['command']}'",
|
|
|
|
E_USER_ERROR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enable supression of database messages.
|
|
|
|
*/
|
|
|
|
public function quiet() {
|
|
|
|
$this->schemaManager->quiet();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clear all data out of the database
|
|
|
|
*/
|
|
|
|
public function clearAllData() {
|
|
|
|
$tables = $this->getSchemaManager()->tableList();
|
|
|
|
foreach ($tables as $table) {
|
|
|
|
$this->clearTable($table);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clear all data in a given table
|
|
|
|
*
|
|
|
|
* @param string $table Name of table
|
|
|
|
*/
|
|
|
|
public function clearTable($table) {
|
|
|
|
$this->query("TRUNCATE \"$table\"");
|
|
|
|
}
|
|
|
|
|
2015-05-19 03:39:39 +02:00
|
|
|
/**
|
|
|
|
* Generates a WHERE clause for null comparison check
|
|
|
|
*
|
|
|
|
* @param string $field Quoted field name
|
|
|
|
* @param bool $isNull Whether to check for NULL or NOT NULL
|
|
|
|
* @return string Non-parameterised null comparison clause
|
|
|
|
*/
|
|
|
|
public function nullCheckClause($field, $isNull) {
|
|
|
|
$clause = $isNull
|
|
|
|
? "%s IS NULL"
|
|
|
|
: "%s IS NOT NULL";
|
|
|
|
return sprintf($clause, $field);
|
|
|
|
}
|
|
|
|
|
2013-06-21 00:32:08 +02:00
|
|
|
/**
|
|
|
|
* Generate a WHERE clause for text matching.
|
|
|
|
*
|
|
|
|
* @param String $field Quoted field name
|
|
|
|
* @param String $value Escaped search. Can include percentage wildcards.
|
|
|
|
* Ignored if $parameterised is true.
|
|
|
|
* @param boolean $exact Exact matches or wildcard support.
|
|
|
|
* @param boolean $negate Negate the clause.
|
|
|
|
* @param boolean $caseSensitive Enforce case sensitivity if TRUE or FALSE.
|
|
|
|
* Fallback to default collation if set to NULL.
|
|
|
|
* @param boolean $parameterised Insert the ? placeholder rather than the
|
|
|
|
* given value. If this is true then $value is ignored.
|
|
|
|
* @return String SQL
|
|
|
|
*/
|
|
|
|
abstract public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null,
|
|
|
|
$parameterised = false);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* function to return an SQL datetime expression that can be used with the adapter in use
|
|
|
|
* used for querying a datetime in a certain format
|
|
|
|
*
|
|
|
|
* @param string $date to be formated, can be either 'now', literal datetime like '1973-10-14 10:30:00' or
|
|
|
|
* field name, e.g. '"SiteTree"."Created"'
|
|
|
|
* @param string $format to be used, supported specifiers:
|
|
|
|
* %Y = Year (four digits)
|
|
|
|
* %m = Month (01..12)
|
|
|
|
* %d = Day (01..31)
|
|
|
|
* %H = Hour (00..23)
|
|
|
|
* %i = Minutes (00..59)
|
|
|
|
* %s = Seconds (00..59)
|
|
|
|
* %U = unix timestamp, can only be used on it's own
|
|
|
|
* @return string SQL datetime expression to query for a formatted datetime
|
|
|
|
*/
|
|
|
|
abstract public function formattedDatetimeClause($date, $format);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* function to return an SQL datetime expression that can be used with the adapter in use
|
|
|
|
* used for querying a datetime addition
|
|
|
|
*
|
|
|
|
* @param string $date, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name,
|
|
|
|
* e.g. '"SiteTree"."Created"'
|
|
|
|
* @param string $interval to be added, use the format [sign][integer] [qualifier], e.g. -1 Day, +15 minutes,
|
|
|
|
* +1 YEAR
|
|
|
|
* supported qualifiers:
|
|
|
|
* - years
|
|
|
|
* - months
|
|
|
|
* - days
|
|
|
|
* - hours
|
|
|
|
* - minutes
|
|
|
|
* - seconds
|
|
|
|
* This includes the singular forms as well
|
|
|
|
* @return string SQL datetime expression to query for a datetime (YYYY-MM-DD hh:mm:ss) which is the result of
|
|
|
|
* the addition
|
|
|
|
*/
|
|
|
|
abstract public function datetimeIntervalClause($date, $interval);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* function to return an SQL datetime expression that can be used with the adapter in use
|
|
|
|
* used for querying a datetime substraction
|
|
|
|
*
|
|
|
|
* @param string $date1, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name
|
|
|
|
* e.g. '"SiteTree"."Created"'
|
|
|
|
* @param string $date2 to be substracted of $date1, can be either 'now', literal datetime
|
|
|
|
* like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"'
|
|
|
|
* @return string SQL datetime expression to query for the interval between $date1 and $date2 in seconds which
|
|
|
|
* is the result of the substraction
|
|
|
|
*/
|
|
|
|
abstract public function datetimeDifferenceClause($date1, $date2);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if this database supports collations
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
abstract public function supportsCollations();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Can the database override timezone as a connection setting,
|
|
|
|
* or does it use the system timezone exclusively?
|
|
|
|
*
|
|
|
|
* @return Boolean
|
|
|
|
*/
|
|
|
|
abstract public function supportsTimezoneOverride();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Query for the version of the currently connected database
|
|
|
|
* @return string Version of this database
|
|
|
|
*/
|
|
|
|
public function getVersion() {
|
|
|
|
return $this->connector->getVersion();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the database server type (e.g. mysql, postgresql).
|
|
|
|
* This value is passed to the connector as the 'driver' argument when
|
|
|
|
* initiating a database connection
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
abstract public function getDatabaseServer();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the number of rows affected by the previous operation.
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function affectedRows() {
|
|
|
|
return $this->connector->affectedRows();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The core search engine, used by this class and its subclasses to do fun stuff.
|
|
|
|
* Searches both SiteTree and File.
|
|
|
|
*
|
|
|
|
* @param array $classesToSearch List of classes to search
|
|
|
|
* @param string $keywords Keywords as a string.
|
|
|
|
* @param integer $start Item to start returning results from
|
|
|
|
* @param integer $pageLength Number of items per page
|
|
|
|
* @param string $sortBy Sort order expression
|
|
|
|
* @param string $extraFilter Additional filter
|
|
|
|
* @param boolean $booleanSearch Flag for boolean search mode
|
|
|
|
* @param string $alternativeFileFilter
|
|
|
|
* @param boolean $invertedMatch
|
|
|
|
* @return PaginatedList Search results
|
|
|
|
*/
|
|
|
|
abstract public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "Relevance DESC",
|
|
|
|
$extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines if this database supports transactions
|
|
|
|
*
|
|
|
|
* @return boolean Flag indicating support for transactions
|
|
|
|
*/
|
|
|
|
abstract public function supportsTransactions();
|
|
|
|
|
2016-03-23 01:42:51 +01:00
|
|
|
/**
|
|
|
|
* Invoke $callback within a transaction
|
|
|
|
*
|
|
|
|
* @param callable $callback Callback to run
|
|
|
|
* @param callable $errorCallback Optional callback to run after rolling back transaction.
|
|
|
|
* @param bool|string $transactionMode Optional transaction mode to use
|
|
|
|
* @param bool $errorIfTransactionsUnsupported If true, this method will fail if transactions are unsupported.
|
|
|
|
* Otherwise, the $callback will potentially be invoked outside of a transaction.
|
|
|
|
* @throws Exception
|
|
|
|
*/
|
|
|
|
public function withTransaction(
|
|
|
|
$callback, $errorCallback = null, $transactionMode = false, $errorIfTransactionsUnsupported = false
|
|
|
|
) {
|
|
|
|
$supported = $this->supportsTransactions();
|
|
|
|
if(!$supported && $errorIfTransactionsUnsupported) {
|
|
|
|
throw new BadMethodCallException("Transactions not supported by this database.");
|
|
|
|
}
|
|
|
|
if($supported) {
|
|
|
|
$this->transactionStart($transactionMode);
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
call_user_func($callback);
|
|
|
|
} catch (Exception $ex) {
|
|
|
|
if($supported) {
|
|
|
|
$this->transactionRollback();
|
|
|
|
}
|
|
|
|
if($errorCallback) {
|
|
|
|
call_user_func($errorCallback);
|
|
|
|
}
|
|
|
|
throw $ex;
|
|
|
|
}
|
|
|
|
if($supported) {
|
|
|
|
$this->transactionEnd();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-21 00:32:08 +02:00
|
|
|
/*
|
|
|
|
* Determines if the current database connection supports a given list of extensions
|
|
|
|
*
|
|
|
|
* @param array $extensions List of extensions to check for support of. The key of this array
|
|
|
|
* will be an extension name, and the value the configuration for that extension. This
|
|
|
|
* could be one of partitions, tablespaces, or clustering
|
|
|
|
* @return boolean Flag indicating support for all of the above
|
|
|
|
* @todo Write test cases
|
|
|
|
*/
|
2016-07-01 04:26:18 +02:00
|
|
|
public function supportsExtensions($extensions) {
|
2013-06-21 00:32:08 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start a prepared transaction
|
|
|
|
* See http://developer.postgresql.org/pgdocs/postgres/sql-set-transaction.html for details on
|
|
|
|
* transaction isolation options
|
|
|
|
*
|
|
|
|
* @param string|boolean $transactionMode Transaction mode, or false to ignore
|
|
|
|
* @param string|boolean $sessionCharacteristics Session characteristics, or false to ignore
|
|
|
|
*/
|
|
|
|
abstract public function transactionStart($transactionMode = false, $sessionCharacteristics = false);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a savepoint that you can jump back to if you encounter problems
|
|
|
|
*
|
|
|
|
* @param string $savepoint Name of savepoint
|
|
|
|
*/
|
|
|
|
abstract public function transactionSavepoint($savepoint);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Rollback or revert to a savepoint if your queries encounter problems
|
|
|
|
* If you encounter a problem at any point during a transaction, you may
|
|
|
|
* need to rollback that particular query, or return to a savepoint
|
|
|
|
*
|
|
|
|
* @param string|boolean $savepoint Name of savepoint, or leave empty to rollback
|
|
|
|
* to last savepoint
|
|
|
|
*/
|
|
|
|
abstract public function transactionRollback($savepoint = false);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Commit everything inside this transaction so far
|
|
|
|
*
|
|
|
|
* @param boolean $chain
|
|
|
|
*/
|
|
|
|
abstract public function transactionEnd($chain = false);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines if the used database supports application-level locks,
|
|
|
|
* which is different from table- or row-level locking.
|
|
|
|
* See {@link getLock()} for details.
|
|
|
|
*
|
|
|
|
* @return boolean Flag indicating that locking is available
|
|
|
|
*/
|
|
|
|
public function supportsLocks() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns if the lock is available.
|
|
|
|
* See {@link supportsLocks()} to check if locking is generally supported.
|
|
|
|
*
|
|
|
|
* @param string $name Name of the lock
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function canLock($name) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets an application-level lock so that no two processes can run at the same time,
|
|
|
|
* also called a "cooperative advisory lock".
|
|
|
|
*
|
|
|
|
* Return FALSE if acquiring the lock fails; otherwise return TRUE, if lock was acquired successfully.
|
|
|
|
* Lock is automatically released if connection to the database is broken (either normally or abnormally),
|
|
|
|
* making it less prone to deadlocks than session- or file-based locks.
|
|
|
|
* Should be accompanied by a {@link releaseLock()} call after the logic requiring the lock has completed.
|
|
|
|
* Can be called multiple times, in which case locks "stack" (PostgreSQL, SQL Server),
|
|
|
|
* or auto-releases the previous lock (MySQL).
|
|
|
|
*
|
|
|
|
* Note that this might trigger the database to wait for the lock to be released, delaying further execution.
|
|
|
|
*
|
|
|
|
* @param string $name Name of lock
|
|
|
|
* @param integer $timeout Timeout in seconds
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function getLock($name, $timeout = 5) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove an application-level lock file to allow another process to run
|
|
|
|
* (if the execution aborts (e.g. due to an error) all locks are automatically released).
|
|
|
|
*
|
|
|
|
* @param string $name Name of the lock
|
|
|
|
* @return boolean Flag indicating whether the lock was successfully released
|
|
|
|
*/
|
|
|
|
public function releaseLock($name) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Instruct the database to generate a live connection
|
|
|
|
*
|
|
|
|
* @param array $parameters An map of parameters, which should include:
|
|
|
|
* - server: The server, eg, localhost
|
|
|
|
* - username: The username to log on with
|
|
|
|
* - password: The password to log on with
|
|
|
|
* - database: The database to connect to
|
|
|
|
* - charset: The character set to use. Defaults to utf8
|
|
|
|
* - timezone: (optional) The timezone offset. For example: +12:00, "Pacific/Auckland", or "SYSTEM"
|
|
|
|
* - driver: (optional) Driver name
|
|
|
|
*/
|
|
|
|
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);
|
|
|
|
|
|
|
|
// SS_Database subclass maintains responsibility for selecting database
|
|
|
|
// once connected in order to correctly handle schema queries about
|
|
|
|
// existence of database, error handling at the correct level, etc
|
|
|
|
if (!empty($parameters['database'])) {
|
|
|
|
$this->selectDatabase($parameters['database'], false, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determine if the database with the specified name exists
|
|
|
|
*
|
|
|
|
* @param string $name Name of the database to check for
|
|
|
|
* @return boolean Flag indicating whether this database exists
|
|
|
|
*/
|
|
|
|
public function databaseExists($name) {
|
|
|
|
return $this->schemaManager->databaseExists($name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves the list of all databases the user has access to
|
|
|
|
*
|
|
|
|
* @return array List of database names
|
|
|
|
*/
|
|
|
|
public function databaseList() {
|
|
|
|
return $this->schemaManager->databaseList();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Change the connection to the specified database, optionally creating the
|
|
|
|
* database if it doesn't exist in the current schema.
|
|
|
|
*
|
|
|
|
* @param string $name Name of the database
|
|
|
|
* @param boolean $create Flag indicating whether the database should be created
|
|
|
|
* if it doesn't exist. If $create is false and the database doesn't exist
|
|
|
|
* then an error will be raised
|
|
|
|
* @param int|boolean $errorLevel The level of error reporting to enable for the query, or false if no error
|
|
|
|
* should be raised
|
|
|
|
* @return boolean Flag indicating success
|
|
|
|
*/
|
|
|
|
public function selectDatabase($name, $create = false, $errorLevel = E_USER_ERROR) {
|
2016-06-20 07:40:58 +02:00
|
|
|
// In case our live environment is locked down, we can bypass a SHOW DATABASE check
|
|
|
|
$canConnect = Config::inst()->get(get_class($this), 'optimistic_connect')
|
|
|
|
|| $this->schemaManager->databaseExists($name);
|
|
|
|
if($canConnect) {
|
|
|
|
return $this->connector->selectDatabase($name);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check DB creation permisson
|
|
|
|
if (!$create) {
|
|
|
|
if ($errorLevel !== false) {
|
|
|
|
user_error("Attempted to connect to non-existing database \"$name\"", $errorLevel);
|
2013-06-21 00:32:08 +02:00
|
|
|
}
|
2016-06-20 07:40:58 +02:00
|
|
|
// Unselect database
|
|
|
|
$this->connector->unloadDatabase();
|
|
|
|
return false;
|
2013-06-21 00:32:08 +02:00
|
|
|
}
|
2016-06-20 07:40:58 +02:00
|
|
|
$this->schemaManager->createDatabase($name);
|
2013-06-21 00:32:08 +02:00
|
|
|
return $this->connector->selectDatabase($name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Drop the database that this object is currently connected to.
|
|
|
|
* Use with caution.
|
|
|
|
*/
|
|
|
|
public function dropSelectedDatabase() {
|
|
|
|
$databaseName = $this->connector->getSelectedDatabase();
|
|
|
|
if ($databaseName) {
|
|
|
|
$this->connector->unloadDatabase();
|
|
|
|
$this->schemaManager->dropDatabase($databaseName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the name of the currently selected database
|
|
|
|
*
|
|
|
|
* @return string|null Name of the selected database, or null if none selected
|
|
|
|
*/
|
|
|
|
public function getSelectedDatabase() {
|
|
|
|
return $this->connector->getSelectedDatabase();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return SQL expression used to represent the current date/time
|
|
|
|
*
|
|
|
|
* @return string Expression for the current date/time
|
|
|
|
*/
|
|
|
|
abstract public function now();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the database-specific version of the random() function
|
|
|
|
*
|
|
|
|
* @return string Expression for a random value
|
|
|
|
*/
|
|
|
|
abstract public function random();
|
|
|
|
}
|