You are here

transaction.module in Transaction 5

Same filename and directory in other branches
  1. 8 transaction.module
  2. 6 transaction.module

Provides a database transaction system for use with InnoDB tables in MySQL.

File

transaction.module
View source
<?php

/**
 * @file Provides a database transaction system for use with InnoDB tables
 * in MySQL.
 */

/**
 * Provides a nestable transaction system for handling commits and rollbacks.
 *
 * Although transactions can be nested, a rollback or commit from a nested
 * transaction will cause it to propagate upwards.
 */
class Transaction {

  /** The number of transactions that have been nested within this one. */
  private static $Layers = 0;

  /** Will the commit be allowed? */
  private static $AllowCommit = TRUE;

  /**
   * Create a new transaction.
   */
  public function __construct() {
    if (self::$Layers == 0) {
      db_query('BEGIN');
    }
    self::$Layers++;
  }

  /**
   * Complete the transaction by either performing a commit or a rollback
   * depending on the state of the object's allow_commit property.
   */
  public function __destruct() {
    self::$Layers--;
    if (self::$Layers == 0) {
      if (self::$AllowCommit) {
        db_query('COMMIT');
      }
      else {
        db_query('ROLLBACK');
      }

      // Reset the ROLLBACK propagator
      self::$AllowCommit = TRUE;
    }
  }

  /**
   * If the given value is FALSE (value AND type), roll back the current
   * transaction.
   *
   * @param boolean $var Roll back the current transaction iff TRUE.
   */
  public function rollbackIfFalse($var) {
    if ($var === FALSE) {
      $this
        ->rollback();
    }
  }

  /**
   * Force this transaction to roll back when finished.
   */
  public function rollback() {
    self::$AllowCommit = FALSE;
  }

  /**
   * Will this transaction roll back instead of committing?
   *
   * @return boolean TRUE if the transaction will roll back, FALSE if it
   *         will commit.
   */
  public function willRollback() {
    return !self::$AllowCommit;
  }

}

/**
 * Transaction-friendly version of db_next_id(). The default Drupal
 * implementation performs a table lock which will terminate any open
 * transaction.
 *
 * @param string The un-prefixed table name to fetch and ID for.
 *
 * @return int The next ID in the sequence.
 */
function transaction_db_next_id($name) {
  $name = db_prefix_tables($name);

  // Reset LAST_INSERT_ID so it doesn't have a stale value if INSERT succeeds.
  db_query('SELECT LAST_INSERT_ID(1)');
  db_query('INSERT INTO {sequences} VALUES ("%s", 1) ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id + 1)', $name);
  $id = db_result(db_query('SELECT LAST_INSERT_ID()'));
  return $id;
}

/**
 * Utility function that abstracts transactionalized updates or inserts
 * on a single row.
 *
 * @param string $table (required) The un-prefixed table name without curly 
 *        braces.
 * @param mixed $key_field (required) The name(s) of the column(s) to use when
 *        identifying the row to update. If only a single column is being used
 *        to select the row, this may be a string. If more than one column
 *        is being used, this must be an array of strings.
 *        If this operation is an update, these columns must be present as keys
 *        in $fields with their desired WHERE = values.
 * @param array $fields An associative array mapping column names (keys) to
 *        the values to be inserted/updated. For updates, only the columns
 *        with updated values need to be in this array.
 *
 * @return mixed  
 */
function transaction_update($table, $key_field, $fields) {
  $txn = new Transaction();

  // Accept a string if the kei field is one column
  $return_simple_key_field = FALSE;
  if (!is_array($key_field)) {
    $return_simple_key_field = TRUE;
    $key_field = array(
      $key_field,
    );
  }

  // Convert a passed object to an array
  $fields = (array) $fields;

  // Check if primary key columns are specified
  $key_field_set = TRUE;
  foreach ($key_field as $column) {
    if (!array_key_exists($column, $fields)) {
      $key_field_set = FALSE;
    }
  }

  // Update
  if ($key_field_set) {
    $sql = 'UPDATE {' . db_escape_table($table) . '} t SET ';
    $where = '';
    $updates = array();
    foreach ($fields as $key => $value) {
      if (!in_array($key, $key_field)) {
        if ($value !== NULL) {
          $updates[] = 't.' . $key . ' = "' . db_escape_string($value) . '"';
        }
        else {
          $updates[] = 't.' . $key . ' = NULL';
        }
      }
    }
    $sql .= implode(',', $updates) . ' ';
    $where .= ' WHERE 1 ';
    foreach ($key_field as $column) {
      if ($fields[$column] !== NULL) {
        $where .= 'AND ' . $column . ' = "' . $fields[$column] . '" ';
      }
      else {
        $where .= 'AND ' . $column . ' IS NULL ';
      }
    }
    $sql .= $where;
    db_query($sql);

    // Return if we've actually updated something
    if (db_affected_rows()) {

      // If we were passed a simple primary key, return the same
      if ($return_simple_key_field) {
        return $fields[$key_field[0]];
      }
      $return_fields = array();
      foreach ($key_field as $column) {
        $return_fields[$column] = $fields[$column];
      }
      return $return_fields;
    }

    // Return if there was nothing to change, but the row actually exists
    $sql = 'SELECT COUNT(*) FROM {' . db_escape_table($table) . '} ' . $where;
    $exists = db_result(db_query($sql, 0, 1));
    if ($exists) {
      $return_fields = array();
      foreach ($key_field as $column) {
        $return_fields[$column] = $fields[$column];
      }
      return $return_fields;
    }
  }
  else {

    // We can't autogenerate if the primary key is more than one column
    if (count($key_field) > 1) {
      return FALSE;
    }
    $fields[$key_field[0]] = transaction_db_next_id('{' . db_escape_table($table) . '}_' . $key_field[0]);
  }

  // Insert
  $sql = 'INSERT INTO {' . db_escape_table($table) . '} ';
  $keys = array_keys($fields);
  $values = array();
  foreach ($fields as $key => $value) {
    if ($value !== NULL) {
      $values[] = '"' . db_escape_string($value) . '"';
    }
    else {
      $values[] = 'NULL';
    }
  }
  $sql .= '({' . db_escape_table($table) . '}.' . implode(',{' . db_escape_table($table) . '}.', $keys) . ') ';
  $sql .= 'VALUES (' . implode(',', $values) . ')';
  db_query($sql);

  // If we were passed a scalar primary key, return the primary key of the new row as a scalar
  if ($return_simple_key_field) {
    return $fields[$key_field[0]];
  }
  $return_fields = array();
  foreach ($key_field as $column) {
    $return_fields[$column] = $fields[$column];
  }
  return $return_fields;
}

/**
 * Legacy compatibility wrapper for transaction class.
 *
 * @see transaction
 * @deprecated 2008-08-12
 */
class pressflow_transaction extends Transaction {

  /**
   * @see Transaction::rollbackIfFalse
   */
  public function rollback_if_false($var) {
    return parent::rollbackIfFalse($var);
  }

  /**
   * @see Transaction::willRollback
   */
  public function will_rollback() {
    return parent::willRollback();
  }

}

/**
 * Legacy compatibility wrapper for transaction_db_next_id().
 *
 * @see transaction_db_next_id
 * @deprecated 2008-08-12
 */
function pressflow_transaction_db_next_id($name) {
  return transaction_db_next_id($name);
}

/**
 * Legacy compatibility wrapper for transaction_update().
 *
 * @see transaction_update
 * @deprecated 2008-08-12
 */
function pressflow_transaction_update($table, $key_field, $fields) {
  return transaction_update($table, $key_field, $fields);
}

Functions

Namesort descending Description
pressflow_transaction_db_next_id Deprecated Legacy compatibility wrapper for transaction_db_next_id().
pressflow_transaction_update Deprecated Legacy compatibility wrapper for transaction_update().
transaction_db_next_id Transaction-friendly version of db_next_id(). The default Drupal implementation performs a table lock which will terminate any open transaction.
transaction_update Utility function that abstracts transactionalized updates or inserts on a single row.

Classes

Namesort descending Description
pressflow_transaction Deprecated Legacy compatibility wrapper for transaction class.
Transaction Provides a nestable transaction system for handling commits and rollbacks.