=== modified file 'includes/database/query.inc'
--- includes/database/query.inc	2010-08-05 08:26:35 +0000
+++ includes/database/query.inc	2010-09-10 14:52:35 +0000
@@ -571,332 +571,6 @@ class InsertQuery extends Query {
 }
 
 /**
- * General class for an abstracted MERGE operation.
- */
-class MergeQuery extends Query {
-  /**
-   * Returned by execute() if an INSERT query has been executed.
-   */
-  const STATUS_INSERT = 1;
-
-  /**
-   * Returned by execute() if an UPDATE query has been executed.
-   */
-  const STATUS_UPDATE = 2;
-
-  /**
-   * The table on which to insert.
-   *
-   * @var string
-   */
-  protected $table;
-
-  /**
-   * An array of fields on which to insert.
-   *
-   * @var array
-   */
-  protected $insertFields = array();
-
-  /**
-   * An array of fields to update instead of the values specified in
-   * $insertFields;
-   *
-   * @var array
-   */
-  protected $updateFields = array();
-
-  /**
-   * An array of key fields for this query.
-   *
-   * @var array
-   */
-  protected $keyFields = array();
-
-  /**
-   * An array of fields to not update in case of a duplicate record.
-   *
-   * @var array
-   */
-  protected $excludeFields = array();
-
-  /**
-   * An array of fields to update to an expression in case of a duplicate record.
-   *
-   * This variable is a nested array in the following format:
-   * <some field> => array(
-   *  'condition' => <condition to execute, as a string>
-   *  'arguments' => <array of arguments for condition, or NULL for none>
-   * );
-   *
-   * @var array
-   */
-  protected $expressionFields = array();
-
-  public function __construct($connection, $table, array $options = array()) {
-    $options['return'] = Database::RETURN_AFFECTED;
-    parent::__construct($connection, $options);
-    $this->table = $table;
-  }
-
-  /**
-   * Set the field->value pairs to be merged into the table.
-   *
-   * This method should only be called once. It may be called either
-   * with a single associative array or two indexed arrays. If called
-   * with an associative array, the keys are taken to be the fields
-   * and the values are taken to be the corresponding values to set.
-   * If called with two arrays, the first array is taken as the fields
-   * and the second array is taken as the corresponding values.
-   *
-   * @param $fields
-   *   An array of fields to set.
-   * @param $values
-   *   An array of fields to set into the database. The values must be
-   *   specified in the same order as the $fields array.
-   * @return MergeQuery
-   *   The called object.
-   */
-  public function fields(array $fields, array $values = array()) {
-    if (count($values) > 0) {
-      $fields = array_combine($fields, $values);
-    }
-    $this->insertFields = $fields;
-
-    return $this;
-  }
-
-  /**
-   * Set the key field(s) to be used to insert or update into the table.
-   *
-   * This method should only be called once. It may be called either
-   * with a single associative array or two indexed arrays. If called
-   * with an associative array, the keys are taken to be the fields
-   * and the values are taken to be the corresponding values to set.
-   * If called with two arrays, the first array is taken as the fields
-   * and the second array is taken as the corresponding values.
-   *
-   * These fields are the "pivot" fields of the query. Typically they
-   * will be the fields of the primary key. If the record does not
-   * yet exist, they will be inserted into the table along with the
-   * values set in the fields() method. If the record does exist,
-   * these fields will be used in the WHERE clause to select the
-   * record to update.
-   *
-   * @param $fields
-   *   An array of fields to set.
-   * @param $values
-   *   An array of fields to set into the database. The values must be
-   *   specified in the same order as the $fields array.
-   * @return MergeQuery
-   *   The called object.
-   */
-  public function key(array $fields, array $values = array()) {
-    if ($values) {
-      $fields = array_combine($fields, $values);
-    }
-    $this->keyFields = $fields;
-
-    return $this;
-  }
-
-  /**
-   * Specify fields to update in case of a duplicate record.
-   *
-   * If a record with the values in keys() already exists, the fields and values
-   * specified here will be updated in that record. If this method is not called,
-   * it defaults to the same values as were passed to the fields() method.
-   *
-   * @param $fields
-   *   An array of fields to set.
-   * @param $values
-   *   An array of fields to set into the database. The values must be
-   *   specified in the same order as the $fields array.
-   * @return MergeQuery
-   *   The called object.
-   */
-  public function update(array $fields, array $values = array()) {
-    if ($values) {
-      $fields = array_combine($fields, $values);
-    }
-    $this->updateFields = $fields;
-
-    return $this;
-  }
-
-  /**
-   * Specify fields that should not be updated in case of a duplicate record.
-   *
-   * If this method is called and a record with the values in keys() already
-   * exists, Drupal will instead update the record with the values passed
-   * in the fields() method except for the fields specified in this method. That
-   * is, calling this method is equivalent to calling update() with identical
-   * parameters as fields() minus the keys specified here.
-   *
-   * The update() method takes precedent over this method. If update() is called,
-   * this method has no effect.
-   *
-   * @param $exclude_fields
-   *   An array of fields in the query that should not be updated to match those
-   *   specified by the fields() method.
-   *   Alternatively, the fields may be specified as a variable number of string
-   *   parameters.
-   * @return MergeQuery
-   *   The called object.
-   */
-  public function updateExcept($exclude_fields) {
-    if (!is_array($exclude_fields)) {
-      $exclude_fields = func_get_args();
-    }
-    $this->excludeFields = $exclude_fields;
-
-    return $this;
-  }
-
-  /**
-   * Specify fields to be updated as an expression.
-   *
-   * Expression fields are cases such as counter=counter+1. This method only
-   * applies if a duplicate key is detected. This method takes precedent over
-   * both update() and updateExcept().
-   *
-   * @param $field
-   *   The field to set.
-   * @param $expression
-   *   The field will be set to the value of this expression. This parameter
-   *   may include named placeholders.
-   * @param $arguments
-   *   If specified, this is an array of key/value pairs for named placeholders
-   *   corresponding to the expression.
-   * @return MergeQuery
-   *   The called object.
-   */
-  public function expression($field, $expression, array $arguments = NULL) {
-    $this->expressionFields[$field] = array(
-      'expression' => $expression,
-      'arguments' => $arguments,
-    );
-
-    return $this;
-  }
-
-  /**
-   * Generic preparation and validation for a MERGE query.
-   *
-   * @return
-   *   TRUE if the validation was successful, FALSE if not.
-   *
-   * @throws InvalidMergeQueryException
-   */
-  public function preExecute() {
-
-    // A merge query without any key field is invalid.
-    if (count($this->keyFields) == 0) {
-      throw new InvalidMergeQueryException("You need to specify key fields before executing a merge query");
-    }
-
-    return TRUE;
-  }
-
-  /**
-   * Run the MERGE query against the database.
-   *
-   * @return
-   *   A status indicating the executed operation:
-   *   - MergeQuery::STATUS_INSERT for an INSERT operation.
-   *   - MergeQuery::STATUS_UPDATE for an UPDATE operation.
-   *
-   * @throws InvalidMergeQueryException
-   */
-  public function execute() {
-    // If validation fails, simply return NULL.
-    // Note that validation routines in preExecute() may throw exceptions instead.
-    if (!$this->preExecute()) {
-      return NULL;
-    }
-
-    // In the degenerate case of this query type, we have to run multiple
-    // queries as there is no universal single-query mechanism that will work.
-
-    // Wrap multiple queries in a transaction, if the database supports it.
-    $transaction = $this->connection->startTransaction();
-
-    try {
-      // Manually check if the record already exists.
-      // We build a 'SELECT 1 FROM table WHERE conditions FOR UPDATE' query,
-      // that will lock the rows that matches our set of conditions as well as
-      // return the information that there are such rows.
-      $select = $this->connection->select($this->table);
-      $select->addExpression('1');
-      foreach ($this->keyFields as $field => $value) {
-        $select->condition($field, $value);
-      }
-
-      // Using SELECT FOR UPDATE syntax will lock the rows we want to attempt to update.
-      $sql = ((string) $select) . ' FOR UPDATE';
-      $arguments = $select->getArguments();
-
-      // If there are no existing records, run an insert query.
-      if (!$this->connection->query($sql, $arguments)->fetchField()) {
-        // If there is no existing record, run an insert query.
-        $insert_fields = $this->insertFields + $this->keyFields;
-        try {
-          $this->connection->insert($this->table, $this->queryOptions)->fields($insert_fields)->execute();
-          return MergeQuery::STATUS_INSERT;
-        }
-        catch (Exception $e) {
-          // The insert query failed, maybe it's because a racing insert query
-          // beat us in inserting the same row. Retry the select query, if it
-          // returns a row, ignore the error and continue with the update
-          // query below.
-          if (!$this->connection->query($sql, $arguments)->fetchField()) {
-            throw $e;
-          }
-        }
-      }
-
-      // Proceed with an update query if a row was found.
-      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]);
-        }
-      }
-      if ($update_fields || $this->expressionFields) {
-        // Only run the update if there are fields or expressions to update.
-        $update = $this->connection->update($this->table, $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']);
-        }
-        $update->execute();
-        return MergeQuery::STATUS_UPDATE;
-      }
-    }
-    catch (Exception $e) {
-      // Something really wrong happened here, bubble up the exception to the
-      // caller.
-      $transaction->rollback();
-      throw $e;
-    }
-    // Transaction commits here where $transaction looses scope.
-  }
-
-  public function __toString() {
-    // In the degenerate case, there is no string-able query as this operation
-    // is potentially two queries.
-    return '';
-  }
-}
-
-
-/**
  * General class for an abstracted DELETE operation.
  */
 class DeleteQuery extends Query implements QueryConditionInterface {
@@ -1206,6 +880,377 @@ class UpdateQuery extends Query implemen
 }
 
 /**
+ * General class for an abstracted MERGE operation.
+ *
+ * An ANSI SQL:2003 compatible database would run the following query:
+ *
+ * @code
+ * MERGE INTO table_name_1 USING table_name_2 ON (condition)
+ *   WHEN MATCHED THEN
+ *   UPDATE SET column1 = value1 [, column2 = value2 ...]
+ *   WHEN NOT MATCHED THEN
+ *   INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ...
+ * @endcode
+ *
+ * Other databases (most notably MySQL, PostgreSQL and SQLite) will emulate
+ * this statement by running a SELECT and then INSERT or UPDATE.
+ *
+ * By default, the two table names are identical and they are passed into the
+ * the constructor. table_name_2 can be specified by the
+ * MergeQuery::conditionTable() method. It can be either a string or a
+ * subquery.
+ *
+ * The condition is built exactly like SelectQuery or UpdateQuery conditions,
+ * the UPDATE query part is built similarly like an UpdateQuery and finally the
+ * INSERT query part is built similarly like an InsertQuery. However, both
+ * UpdateQuery and InsertQuery has a fields method so
+ * MergeQuery::updateFields() and MergeQuery::insertFields() needs to be called
+ * instead. MergeQuery::fields() can also be called which calls both of these
+ * methods as the common case is to use the same column-value pairs for both
+ * INSERT and UPDATE. However, this is not mandatory. Another convinient
+ * wrapper is MergeQuery::key() which adds the same column-value pairs to all
+ * three parts: the condition, the INSERT query part and the UPDATE query part.
+ *
+ * Several methods (key(), fields(), insertFields()) can be called to set a
+ * key-value pair for the INSERT query part. Subsequent calls for the same
+ * fields override the earlier ones. The same is true for UPDATE and key(),
+ * fields() and updateFields().
+ */
+class MergeQuery extends Query implements QueryConditionInterface {
+  /**
+   * Returned by execute() if an INSERT query has been executed.
+   */
+  const STATUS_INSERT = 1;
+
+  /**
+   * Returned by execute() if an UPDATE query has been executed.
+   */
+  const STATUS_UPDATE = 2;
+
+  /**
+   * The table to be used for INSERT and UPDATE.
+   *
+   * @var string
+   */
+  protected $table;
+
+  /**
+   * The table or subquery to be used for the condition.
+   */
+  protected $conditionTable;
+
+  /**
+   * An array of fields on which to insert.
+   *
+   * @var array
+   */
+  protected $insertFields = array();
+
+  /**
+   * An array of fields which should be set to their database-defined defaults.
+   *
+   * Used on INSERT.
+   *
+   * @var array
+   */
+  protected $defaultFields = array();
+
+  /**
+   * An array of values to be inserted.
+   *
+   * @var string
+   */
+  protected $insertValues = array();
+
+  /**
+   * An array of fields that will be updated.
+   *
+   * @var array
+   */
+  protected $updateFields = array();
+
+  /**
+   * An array of fields to update to an expression in case of a duplicate record.
+   *
+   * This variable is a nested array in the following format:
+   * <some field> => array(
+   *  'condition' => <condition to execute, as a string>
+   *  'arguments' => <array of arguments for condition, or NULL for none>
+   * );
+   *
+   * @var array
+   */
+  protected $expressionFields = array();
+
+  /**
+   * Flag indicated whether an UPDATE is necessary.
+   *
+   * @var boolean
+   */
+  protected $needsUpdate = FALSE;
+
+  public function __construct(DatabaseConnection $connection, $table, array $options = array()) {
+    $options['return'] = Database::RETURN_AFFECTED;
+    parent::__construct($connection, $options);
+    $this->table = $table;
+    $this->conditionTable = $table;
+    $this->condition = new DatabaseCondition('AND');
+  }
+
+  /**
+   * Set the table or subquery to be used for the condition.
+   *
+   * @param $table
+   *   The table name or the subquery to be used. Use a SelectQuery object to
+   *   pass in a subquery.
+   *
+   * @return MergeQuery
+   *   The called object.
+   */
+  protected function conditionTable($table) {
+    $this->conditionTable = $table;
+    return $this;
+  }
+
+  /**
+   * Adds a set of field->value pairs to be updated.
+   *
+   * @param $fields
+   *   An associative array of fields to write into the database. The array keys
+   *   are the field names while the values are the values to which to set them.
+   *
+   * @return MergeQuery
+   *   The called object.
+   */
+  public function updateFields(array $fields) {
+    $this->updateFields = $fields + $this->updateFields;
+    $this->needsUpdate = TRUE;
+    return $this;
+  }
+
+  /**
+   * Specify fields to be updated as an expression.
+   *
+   * Expression fields are cases such as counter = counter + 1. This method
+   * takes precedence over MergeQuery::updateFields() and it's wrappers,
+   * MergeQuery::key() and MergeQuery::fields().
+   *
+   * @param $field
+   *   The field to set.
+   * @param $expression
+   *   The field will be set to the value of this expression. This parameter
+   *   may include named placeholders.
+   * @param $arguments
+   *   If specified, this is an array of key/value pairs for named placeholders
+   *   corresponding to the expression.
+   * @return MergeQuery
+   *   The called object.
+   */
+  public function expression($field, $expression, array $arguments = NULL) {
+    $this->expressionFields[$field] = array(
+      'expression' => $expression,
+      'arguments' => $arguments,
+    );
+    $this->needsUpdate = TRUE;
+    return $this;
+  }
+
+  /**
+   * Adds a set of field->value pairs to be inserted.
+   *
+   * Warning: unlike InsertQuery::fields() this method can be called more than
+   * once, much like UpdateQuery::fields() and MergeQuery::updateFields().
+   *
+   * @param $fields
+   *   An array of fields on which to insert. This array may be indexed or
+   *   associative. If indexed, the array is taken to be the list of fields.
+   *   If associative, the keys of the array are taken to be the fields and
+   *   the values are taken to be corresponding values to insert. If a
+   *   $values argument is provided, $fields must be indexed.
+   * @param $values
+   *   An array of fields to insert into the database. The values must be
+   *   specified in the same order as the $fields array.
+   *
+   * @return MergeQuery
+   *   The called object.
+   */
+  public function insertFields(array $fields, array $values = array()) {
+    if ($values) {
+      $fields = array_combine($fields, $values);
+    }
+    $this->insertFields = $fields + $this->insertFields;
+    return $this;
+  }
+
+  /**
+   * Specifies fields for which the database-defaults should be used.
+   *
+   * If you want to force a given field to use the database-defined default,
+   * not NULL or undefined, use this method to instruct the database to use
+   * default values explicitly. In most cases this will not be necessary
+   * unless you are inserting a row that is all default values, as you cannot
+   * specify no values in an INSERT query.
+   *
+   * Specifying a field both in fields() and in useDefaults() is an error
+   * and will not execute.
+   *
+   * @param $fields
+   *   An array of values for which to use the default values
+   *   specified in the table definition.
+   *
+   * @return MergeQuery
+   *   The called object.
+   */
+  public function useDefaults(array $fields) {
+    $this->defaultFields = $fields;
+    return $this;
+  }
+
+  /**
+   * Set common field-value pairs in the INSERT and UPDATE query parts.
+   *
+   * @param $fields
+   *   An associative array of fields on which to insert. The keys of the
+   *   array are taken to be the fields and the values are taken to be
+   *   corresponding values to insert.
+   *
+   * @return MergeQuery
+   *   The called object.
+   */
+  public function fields(array $fields) {
+    $this->insertFields($fields);
+    $this->updateFields($fields);
+    return $this;
+  }
+
+  /**
+   * Set the key field(s) to be used everywhere.
+   *
+   * This method should only be called once. It may be called either
+   * with a single associative array or two indexed arrays. If called
+   * with an associative array, the keys are taken to be the fields
+   * and the values are taken to be the corresponding values to set.
+   * If called with two arrays, the first array is taken as the fields
+   * and the second array is taken as the corresponding values.
+   *
+   * The fields are copied to all three parts of the query: the condition,
+   * the UPDATE part and the INSERT part. If no other method is called, the
+   * UPDATE will become a no-op.
+   *
+   * @param $fields
+   *   An array of fields to set.
+   * @param $values
+   *   An array of fields to set into the database. The values must be
+   *   specified in the same order as the $fields array.
+   * @return MergeQuery
+   *   The called object.
+   */
+  public function key(array $fields, array $values = array()) {
+    if ($values) {
+      $fields = array_combine($fields, $values);
+    }
+    // The MergeQuery::fields method sets needsUpdate to TRUE unconditionally,
+    // however if only key() is called, the UPDATE is not necessary, so save
+    // the value of the needsUpdate flag and restore after.
+    $needsUpdate = $this->needsUpdate;
+    $this->fields($fields);
+    $this->needsUpdate = $needsUpdate;
+    foreach ($fields as $key => $value) {
+      $this->condition($key, $value);
+    }
+    return $this;
+  }
+
+  public function condition($field, $value = NULL, $operator = NULL) {
+    $this->condition->condition($field, $value, $operator);
+    return $this;
+  }
+
+  public function isNull($field) {
+    $this->condition->isNull($field);
+    return $this;
+  }
+
+  public function isNotNull($field) {
+    $this->condition->isNotNull($field);
+    return $this;
+  }
+
+  public function &conditions() {
+    return $this->condition->conditions();
+  }
+
+  public function arguments() {
+    return $this->condition->arguments();
+  }
+
+  public function where($snippet, $args = array()) {
+    $this->condition->where($snippet, $args);
+    return $this;
+  }
+
+  public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder = NULL) {
+    return $this->condition->compile($connection, isset($queryPlaceholder) ? $queryPlaceholder : $this);
+  }
+
+  public function __toString() {
+  }
+
+  public function execute() {
+    // Wrap multiple queries in a transaction, if the database supports it.
+    $transaction = $this->connection->startTransaction();
+    try {
+      if (!count($this->condition)) {
+        throw new InvalidMergeQueryException(t('Invalid merge query: no conditions'));
+      }
+      $select = $this->connection->select($this->conditionTable)
+        ->condition($this->condition)
+        ->forUpdate()
+        ->addExpression('1');
+      if (!$select->execute()->fetchField()) {
+        try {
+          $insert = db_insert($this->table)->fields($this->insertFields);
+          if ($this->defaultFields) {
+            $insert->useDefaults($this->defaultFields);
+          }
+          $insert->execute();
+          return MergeQuery::STATUS_INSERT;
+        }
+        catch (Exception $e) {
+          echo $e->getMessage();
+          // The insert query failed, maybe it's because a racing insert query
+          // beat us in inserting the same row. Retry the select query, if it
+          // returns a row, ignore the error and continue with the update
+          // query below.
+          if (!$select->execute()->fetchField()) {
+            throw $e;
+          }
+        }
+      }
+      if ($this->needsUpdate) {
+        $update = db_update($this->table)
+          ->fields($this->updateFields)
+          ->condition($this->condition);
+        if ($this->expressionFields) {
+          foreach ($this->expressionFields as $field => $data) {
+            $update->expression($field, $data['expression'], $data['arguments']);
+          }
+        }
+        $update->execute();
+        return MergeQuery::STATUS_UPDATE;
+      }
+    }
+    catch (Exception $e) {
+      // Something really wrong happened here, bubble up the exception to the
+      // caller.
+      $transaction->rollback();
+      throw $e;
+    }
+    // Transaction commits here where $transaction looses scope.
+  }
+}
+
+/**
  * Generic class for a series of conditions in a query.
  */
 class DatabaseCondition implements QueryConditionInterface, Countable {

=== modified file 'includes/database/select.inc'
--- includes/database/select.inc	2010-09-03 19:06:55 +0000
+++ includes/database/select.inc	2010-09-10 14:42:32 +0000
@@ -503,6 +503,22 @@ interface SelectQueryInterface extends Q
    * 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 @@ class SelectQueryExtender implements Sel
     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 implemen
    */
   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 @@ class SelectQuery extends Query implemen
     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 @@ class SelectQuery extends Query implemen
       }
     }
 
+    if ($this->forUpdate) {
+      $query .= ' FOR UPDATE';
+    }
+
     return $query;
   }
 

=== modified file 'includes/database/sqlite/query.inc'
--- includes/database/sqlite/query.inc	2010-07-03 20:45:45 +0000
+++ includes/database/sqlite/query.inc	2010-09-10 14:42:32 +0000
@@ -12,6 +12,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.
  *
  * We ignore all the default fields and use the clever SQLite syntax:
@@ -119,65 +129,6 @@ class UpdateQuery_sqlite extends UpdateQ
 }
 
 /**
- * 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.
  *
  * When the WHERE is omitted from a DELETE statement and the table being deleted

=== modified file 'modules/poll/poll.module'
--- modules/poll/poll.module	2010-09-09 23:01:48 +0000
+++ modules/poll/poll.module	2010-09-10 14:42:32 +0000
@@ -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 {

=== modified file 'modules/simpletest/tests/database_test.test'
--- modules/simpletest/tests/database_test.test	2010-09-09 23:18:30 +0000
+++ modules/simpletest/tests/database_test.test	2010-09-10 14:42:32 +0000
@@ -1093,18 +1093,15 @@ class DatabaseMergeTestCase extends Data
   }
 
   /**
-   * 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 Data
 
     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 Data
     // 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 Data
 
     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();

