Commit e0032eee authored by Dries's avatar Dries
Browse files

- Patch #844186 by chx: clarify merge queries.

parent 5e1f9113
This diff is collapsed.
......@@ -503,6 +503,22 @@ public function havingCondition($field, $value = NULL, $operator = NULL);
* duplicate the connection itself.
*/
public function __clone();
/**
* Add FOR UPDATE to the query.
*
* FOR UPDATE prevents the rows retrieved by the SELECT statement from being
* modified or deleted by other transactions until the current transaction
* ends. Other transactions that attempt UPDATE, DELETE, or SELECT FOR UPDATE
* of these rows will be blocked until the current transaction ends.
*
* @param $set
* IF TRUE, FOR UPDATE will be added to the query, if FALSE then it won't.
*
* @return QueryConditionInterface
* The called object.
*/
public function forUpdate($set = TRUE);
}
/**
......@@ -740,6 +756,11 @@ public function groupBy($field) {
return $this;
}
public function forUpdate($set = TRUE) {
$this->query->forUpdate($set);
return $this;
}
public function countQuery() {
// Create our new query object that we will mutate into a count query.
$count = clone($this);
......@@ -919,6 +940,11 @@ class SelectQuery extends Query implements SelectQueryInterface {
*/
protected $prepared = FALSE;
/**
* The FOR UPDATE status
*/
protected $forUpdate = FALSE;
public function __construct($table, $alias = NULL, DatabaseConnection $connection, $options = array()) {
$options['return'] = Database::RETURN_STATEMENT;
parent::__construct($connection, $options);
......@@ -1033,6 +1059,12 @@ public function havingIsNotNull($field) {
return $this;
}
public function forUpdate($set = TRUE) {
if (isset($set)) {
$this->forUpdate = $set;
}
return $this;
}
/* Alter accessors to expose the query data to alter hooks. */
......@@ -1476,6 +1508,10 @@ public function __toString() {
}
}
if ($this->forUpdate) {
$query .= ' FOR UPDATE';
}
return $query;
}
......
......@@ -11,6 +11,16 @@
* @{
*/
/**
* SQLite specific query builder for SELECT statements.
*/
class SelectQuery_sqlite extends SelectQuery {
public function forUpdate($set = TRUE) {
// SQLite does not support FOR UPDATE so nothing to do.
return $this;
}
}
/**
* SQLite specific implementation of InsertQuery.
*
......@@ -118,65 +128,6 @@ public function execute() {
}
/**
* SQLite specific implementation of MergeQuery.
*
* SQLite doesn't support row-level locking, but acquire locks on the whole
* database file. We implement MergeQuery using a different strategy:
* - UPDATE xxx WHERE <key condition>
* - if the previous query hasn't matched, INSERT
*
* The first UPDATE query will acquire a RESERVED lock on the database.
*/
class MergeQuery_sqlite extends MergeQuery {
public function execute() {
// If validation fails, simply return NULL.
// Note that validation routines in preExecute() may throw exceptions instead.
if (!$this->preExecute()) {
return NULL;
}
// Wrap multiple queries in a transaction.
$transaction = $this->connection->startTransaction();
if ($this->updateFields) {
$update_fields = $this->updateFields;
}
else {
$update_fields = $this->insertFields;
// If there are no exclude fields, this is a no-op.
foreach ($this->excludeFields as $exclude_field) {
unset($update_fields[$exclude_field]);
}
}
// The update fields are empty, fill them with dummy data.
if (!$update_fields && !$this->expressionFields) {
$update_fields = array_slice($this->keyFields, 0, 1);
}
// Start with an update query, this acquires a RESERVED lock on the database.
// Use the SQLite-specific 'sqlite_return_matched_rows' query option to
// return the number of rows matched by that query, not modified by it.
$update = $this->connection->update($this->table, array('sqlite_return_matched_rows' => TRUE) + $this->queryOptions)->fields($update_fields);
foreach ($this->keyFields as $field => $value) {
$update->condition($field, $value);
}
foreach ($this->expressionFields as $field => $expression) {
$update->expression($field, $expression['expression'], $expression['arguments']);
}
if ($update->execute()) {
return MergeQuery::STATUS_UPDATE;
}
// The UPDATE query failed to match rows, proceed with an INSERT.
$insert_fields = $this->insertFields + $this->keyFields;
$this->connection->insert($this->table, $this->queryOptions)->fields($insert_fields)->execute();
return MergeQuery::STATUS_INSERT;
}
}
/**
* SQLite specific implementation of DeleteQuery.
*
......
......@@ -568,12 +568,11 @@ function poll_update($node) {
db_merge('poll_choice')
->key(array('chid' => $choice['chid']))
->fields(array(
'nid' => $node->nid,
'chtext' => $choice['chtext'],
'chvotes' => (int) $choice['chvotes'],
'weight' => $choice['weight'],
))
->updateExcept('nid')
->insertFields(array('nid' => $node->nid))
->execute();
}
else {
......
......@@ -1093,18 +1093,15 @@ class DatabaseMergeTestCase extends DatabaseTestCase {
}
/**
* Confirm that we can merge-update a record successfully, with exclusion.
* Confirm that we can merge-update a record successfully, with different insert and update.
*/
function testMergeUpdateExcept() {
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
db_merge('test_people')
->key(array('job' => 'Speaker'))
->fields(array(
'age' => 31,
'name' => 'Tiffany',
))
->updateExcept('age')
->insertFields(array('age' => 31))
->updateFields(array('name' => 'Tiffany'))
->execute();
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
......@@ -1124,11 +1121,13 @@ class DatabaseMergeTestCase extends DatabaseTestCase {
db_merge('test_people')
->key(array('job' => 'Speaker'))
->fields(array(
->insertFields(array(
'age' => 31,
'name' => 'Tiffany',
))
->update(array('name' => 'Joe'))
->updateFields(array(
'name' => 'Joe',
))
->execute();
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
......@@ -1155,10 +1154,8 @@ class DatabaseMergeTestCase extends DatabaseTestCase {
// which is what is supposed to happen.
db_merge('test_people')
->key(array('job' => 'Speaker'))
->fields(array(
'age' => 31,
'name' => 'Tiffany',
))
->fields(array('name' => 'Tiffany'))
->insertFields(array('age' => 31))
->expression('age', 'age + :age', array(':age' => 4))
->execute();
......@@ -1210,8 +1207,7 @@ class DatabaseMergeTestCase extends DatabaseTestCase {
db_merge('test_people')
->key(array('job' => 'Speaker'))
->fields(array('age' => 31))
->updateExcept(array('age'))
->insertFields(array('age' => 31))
->execute();
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment