summaryrefslogtreecommitdiff
path: root/includes/database
diff options
context:
space:
mode:
Diffstat (limited to 'includes/database')
-rw-r--r--includes/database/database.inc12
-rw-r--r--includes/database/mysql/database.inc41
-rw-r--r--includes/database/pgsql/database.inc15
-rw-r--r--includes/database/query.inc6
-rw-r--r--includes/database/select.inc63
-rw-r--r--includes/database/sqlite/database.inc16
6 files changed, 92 insertions, 61 deletions
diff --git a/includes/database/database.inc b/includes/database/database.inc
index 33000aa70..6e40b2765 100644
--- a/includes/database/database.inc
+++ b/includes/database/database.inc
@@ -1016,9 +1016,9 @@ abstract class DatabaseConnection extends PDO {
throw new DatabaseTransactionNoActiveException();
}
// A previous rollback to an earlier savepoint may mean that the savepoint
- // in question has already been rolled back.
- if (!in_array($savepoint_name, $this->transactionLayers)) {
- return;
+ // in question has already been accidentally committed.
+ if (!isset($this->transactionLayers[$savepoint_name])) {
+ throw new DatabaseTransactionNoActiveException();
}
// We need to find the point we're rolling back to, all other savepoints
@@ -1096,8 +1096,12 @@ abstract class DatabaseConnection extends PDO {
if (!$this->supportsTransactions()) {
return;
}
+ // The transaction has already been committed earlier. There is nothing we
+ // need to do. If this transaction was part of an earlier out-of-order
+ // rollback, an exception would already have been thrown by
+ // Database::rollback().
if (!isset($this->transactionLayers[$name])) {
- throw new DatabaseTransactionNoActiveException();
+ return;
}
// Mark this layer as committable.
diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc
index 7d5d85998..e024a7f39 100644
--- a/includes/database/mysql/database.inc
+++ b/includes/database/mysql/database.inc
@@ -37,14 +37,20 @@ class DatabaseConnection_mysql extends DatabaseConnection {
$dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']);
}
$dsn .= ';dbname=' . $connection_options['database'];
- parent::__construct($dsn, $connection_options['username'], $connection_options['password'], array(
+ // Allow PDO options to be overridden.
+ $connection_options += array(
+ 'pdo' => array(),
+ );
+ $connection_options['pdo'] += array(
// So we don't have to mess around with cursors and unbuffered queries by default.
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE,
// Because MySQL's prepared statements skip the query cache, because it's dumb.
PDO::ATTR_EMULATE_PREPARES => TRUE,
// Force column names to lower case.
PDO::ATTR_CASE => PDO::CASE_LOWER,
- ));
+ );
+
+ parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
// Force MySQL to use the UTF-8 character set. Also set the collation, if a
// certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci'
@@ -56,14 +62,22 @@ class DatabaseConnection_mysql extends DatabaseConnection {
$this->exec('SET NAMES utf8');
}
- // Force MySQL's behavior to conform more closely to SQL standards.
- // This allows Drupal to run almost seamlessly on many different
- // kinds of database systems. These settings force MySQL to behave
- // the same as postgresql, or sqlite in regards to syntax interpretation
- // and invalid data handling. See http://drupal.org/node/344575 for
- // further discussion. Also, as MySQL 5.5 changed the meaning of
- // TRADITIONAL we need to spell out the modes one by one.
- $this->exec("SET sql_mode='ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'");
+ // Set MySQL init_commands if not already defined. Default Drupal's MySQL
+ // behavior to conform more closely to SQL standards. This allows Drupal
+ // to run almost seamlessly on many different kinds of database systems.
+ // These settings force MySQL to behave the same as postgresql, or sqlite
+ // in regards to syntax interpretation and invalid data handling. See
+ // http://drupal.org/node/344575 for further discussion. Also, as MySQL 5.5
+ // changed the meaning of TRADITIONAL we need to spell out the modes one by
+ // one.
+ $connection_options += array(
+ 'init_commands' => array(),
+ );
+ $connection_options['init_commands'] += array(
+ 'sql_mode' => "SET sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'",
+ );
+ // Set connection options.
+ $this->exec(implode('; ', $connection_options['init_commands']));
}
public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
@@ -169,8 +183,11 @@ class DatabaseConnection_mysql extends DatabaseConnection {
// succeed for MySQL error code 1305 ("SAVEPOINT does not exist").
if ($e->errorInfo[1] == '1305') {
// If one SAVEPOINT was released automatically, then all were.
- // Therefore, we keep just the topmost transaction.
- $this->transactionLayers = array('drupal_transaction' => 'drupal_transaction');
+ // Therefore, clean the transaction stack.
+ $this->transactionLayers = array();
+ // We also have to explain to PDO that the transaction stack has
+ // been cleaned-up.
+ PDO::commit();
}
else {
throw $e;
diff --git a/includes/database/pgsql/database.inc b/includes/database/pgsql/database.inc
index 39b4e9b69..d42a1cc3c 100644
--- a/includes/database/pgsql/database.inc
+++ b/includes/database/pgsql/database.inc
@@ -47,7 +47,12 @@ class DatabaseConnection_pgsql extends DatabaseConnection {
$this->connectionOptions = $connection_options;
$dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port'];
- parent::__construct($dsn, $connection_options['username'], $connection_options['password'], array(
+
+ // Allow PDO options to be overridden.
+ $connection_options += array(
+ 'pdo' => array(),
+ );
+ $connection_options['pdo'] += array(
// Prepared statements are most effective for performance when queries
// are recycled (used several times). However, if they are not re-used,
// prepared statements become ineffecient. Since most of Drupal's
@@ -59,10 +64,16 @@ class DatabaseConnection_pgsql extends DatabaseConnection {
PDO::ATTR_STRINGIFY_FETCHES => TRUE,
// Force column names to lower case.
PDO::ATTR_CASE => PDO::CASE_LOWER,
- ));
+ );
+ parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
// Force PostgreSQL to use the UTF-8 character set by default.
$this->exec("SET NAMES 'UTF8'");
+
+ // Execute PostgreSQL init_commands.
+ if (isset($connection_options['init_commands'])) {
+ $this->exec(implode('; ', $connection_options['init_commands']));
+ }
}
public function query($query, array $args = array(), $options = array()) {
diff --git a/includes/database/query.inc b/includes/database/query.inc
index c77968767..6020b0ea5 100644
--- a/includes/database/query.inc
+++ b/includes/database/query.inc
@@ -22,6 +22,9 @@ interface QueryConditionInterface {
* parameters, they are taken as $field and $value with $operator having a
* value of IN if $value is an array and = otherwise.
*
+ * Do not use this method to test for NULL values. Instead, use
+ * QueryConditionInterface::isNull() or QueryConditionInterface::isNotNull().
+ *
* @param $field
* The name of the field to check. If you would like to add a more complex
* condition involving operators or functions, use where().
@@ -36,6 +39,9 @@ interface QueryConditionInterface {
*
* @return QueryConditionInterface
* The called object.
+ *
+ * @see QueryConditionInterface::isNull()
+ * @see QueryConditionInterface::isNotNull()
*/
public function condition($field, $value = NULL, $operator = NULL);
diff --git a/includes/database/select.inc b/includes/database/select.inc
index 9b587aebe..7e2af85e7 100644
--- a/includes/database/select.inc
+++ b/includes/database/select.inc
@@ -590,11 +590,13 @@ class SelectQueryExtender implements SelectQueryInterface {
}
public function hasAllTags() {
- return call_user_func_array(array($this->query, 'hasAllTags'), func_get_args());
+ $args = func_get_args();
+ return call_user_func_array(array($this->query, 'hasAllTags'), $args);
}
public function hasAnyTag() {
- return call_user_func_array(array($this->query, 'hasAnyTags'), func_get_args());
+ $args = func_get_args();
+ return call_user_func_array(array($this->query, 'hasAnyTags'), $args);
}
public function addMetaData($key, $object) {
@@ -637,16 +639,16 @@ class SelectQueryExtender implements SelectQueryInterface {
/* Implementations of QueryConditionInterface for the HAVING clause. */
public function havingCondition($field, $value = NULL, $operator = '=') {
- $this->query->condition($field, $value, $operator, $num_args);
+ $this->query->havingCondition($field, $value, $operator);
return $this;
}
public function &havingConditions() {
- return $this->having->conditions();
+ return $this->query->havingConditions();
}
public function havingArguments() {
- return $this->having->arguments();
+ return $this->query->havingArguments();
}
public function having($snippet, $args = array()) {
@@ -790,31 +792,7 @@ class SelectQueryExtender implements SelectQueryInterface {
}
public function countQuery() {
- // Create our new query object that we will mutate into a count query.
- $count = clone($this);
-
- // Zero-out existing fields and expressions.
- $fields =& $count->getFields();
- $fields = array();
- $expressions =& $count->getExpressions();
- $expressions = array();
-
- // Also remove 'all_fields' statements, which are expanded into tablename.*
- // when the query is executed.
- $tables = &$count->getTables();
- foreach ($tables as $alias => &$table) {
- unset($table['all_fields']);
- }
-
- // Ordering a count query is a waste of cycles, and breaks on some
- // databases anyway.
- $orders = &$count->getOrderBy();
- $orders = array();
-
- // COUNT() is an expression, so we add that back in.
- $count->addExpression('COUNT(*)');
-
- return $count;
+ return $this->query->countQuery();
}
function isNull($field) {
@@ -836,7 +814,7 @@ class SelectQueryExtender implements SelectQueryInterface {
$this->query->notExists($select);
return $this;
}
-
+
public function __toString() {
return (string) $this->query;
}
@@ -1005,11 +983,13 @@ class SelectQuery extends Query implements SelectQueryInterface {
}
public function hasAllTags() {
- return !(boolean)array_diff(func_get_args(), array_keys($this->alterTags));
+ $args = func_get_args();
+ return !(boolean)array_diff($args, array_keys($this->alterTags));
}
public function hasAnyTag() {
- return (boolean)array_intersect(func_get_args(), array_keys($this->alterTags));
+ $args = func_get_args();
+ return (boolean)array_intersect($args, array_keys($this->alterTags));
}
public function addMetaData($key, $object) {
@@ -1088,7 +1068,7 @@ class SelectQuery extends Query implements SelectQueryInterface {
$this->where->notExists($select);
return $this;
}
-
+
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) {
$this->where->compile($connection, $queryPlaceholder);
$this->having->compile($connection, $queryPlaceholder);
@@ -1172,17 +1152,17 @@ class SelectQuery extends Query implements SelectQueryInterface {
$this->having->isNotNull($field);
return $this;
}
-
+
public function havingExists(SelectQueryInterface $select) {
$this->having->exists($select);
return $this;
}
-
+
public function havingNotExists(SelectQueryInterface $select) {
$this->having->notExists($select);
return $this;
}
-
+
public function forUpdate($set = TRUE) {
if (isset($set)) {
$this->forUpdate = $set;
@@ -1451,17 +1431,20 @@ class SelectQuery extends Query implements SelectQueryInterface {
$count = clone($this);
$group_by = $count->getGroupBy();
+ $having = $count->havingConditions();
- if (!$count->distinct) {
+ if (!$count->distinct && !isset($having[0])) {
// When not executing a distinct query, we can zero-out existing fields
- // and expressions that are not used by a GROUP BY. Fields listed in
- // the GROUP BY clause need to be present in the query.
+ // and expressions that are not used by a GROUP BY or HAVING. Fields
+ // listed in a GROUP BY or HAVING clause need to be present in the
+ // query.
$fields =& $count->getFields();
foreach (array_keys($fields) as $field) {
if (empty($group_by[$field])) {
unset($fields[$field]);
}
}
+
$expressions =& $count->getExpressions();
foreach (array_keys($expressions) as $field) {
if (empty($group_by[$field])) {
diff --git a/includes/database/sqlite/database.inc b/includes/database/sqlite/database.inc
index 3e2490b00..b4e41b527 100644
--- a/includes/database/sqlite/database.inc
+++ b/includes/database/sqlite/database.inc
@@ -59,16 +59,21 @@ class DatabaseConnection_sqlite extends DatabaseConnection {
$this->statementClass = NULL;
// This driver defaults to transaction support, except if explicitly passed FALSE.
- $this->transactionSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE;
+ $this->transactionSupport = $this->transactionalDDLSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE;
$this->connectionOptions = $connection_options;
- parent::__construct('sqlite:' . $connection_options['database'], '', '', array(
+ // Allow PDO options to be overridden.
+ $connection_options += array(
+ 'pdo' => array(),
+ );
+ $connection_options['pdo'] += array(
// Force column names to lower case.
PDO::ATTR_CASE => PDO::CASE_LOWER,
// Convert numeric values to strings when fetching.
PDO::ATTR_STRINGIFY_FETCHES => TRUE,
- ));
+ );
+ parent::__construct('sqlite:' . $connection_options['database'], '', '', $connection_options['pdo']);
// Attach one database for each registered prefix.
$prefixes = $this->prefixes;
@@ -103,6 +108,11 @@ class DatabaseConnection_sqlite extends DatabaseConnection {
$this->sqliteCreateFunction('substring', array($this, 'sqlFunctionSubstring'), 3);
$this->sqliteCreateFunction('substring_index', array($this, 'sqlFunctionSubstringIndex'), 3);
$this->sqliteCreateFunction('rand', array($this, 'sqlFunctionRand'));
+
+ // Execute sqlite init_commands.
+ if (isset($connection_options['init_commands'])) {
+ $this->exec(implode('; ', $connection_options['init_commands']));
+ }
}
/**