diff options
Diffstat (limited to 'includes/database')
-rw-r--r-- | includes/database/database.inc | 12 | ||||
-rw-r--r-- | includes/database/mysql/database.inc | 41 | ||||
-rw-r--r-- | includes/database/pgsql/database.inc | 15 | ||||
-rw-r--r-- | includes/database/query.inc | 6 | ||||
-rw-r--r-- | includes/database/select.inc | 63 | ||||
-rw-r--r-- | includes/database/sqlite/database.inc | 16 |
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'])); + } } /** |