| 1 |
<?php
|
| 2 |
// $Id$
|
| 3 |
/**
|
| 4 |
* @file Provides a database transaction system for use with InnoDB tables
|
| 5 |
* in MySQL.
|
| 6 |
*/
|
| 7 |
|
| 8 |
/**
|
| 9 |
* Provides a nestable transaction system for handling commits and rollbacks.
|
| 10 |
*
|
| 11 |
* Although transactions can be nested, a rollback or commit from a nested
|
| 12 |
* transaction will cause it to propagate upwards.
|
| 13 |
*/
|
| 14 |
class Transaction {
|
| 15 |
/** The number of transactions that have been nested within this one. */
|
| 16 |
private static $Layers = 0;
|
| 17 |
|
| 18 |
/** Will the commit be allowed? */
|
| 19 |
private static $AllowCommit = TRUE;
|
| 20 |
|
| 21 |
/**
|
| 22 |
* Create a new transaction.
|
| 23 |
*/
|
| 24 |
public function __construct()
|
| 25 |
{
|
| 26 |
if (self::$Layers == 0)
|
| 27 |
db_query('BEGIN');
|
| 28 |
self::$Layers++;
|
| 29 |
}
|
| 30 |
|
| 31 |
/**
|
| 32 |
* Complete the transaction by either performing a commit or a rollback
|
| 33 |
* depending on the state of the object's allow_commit property.
|
| 34 |
*/
|
| 35 |
public function __destruct()
|
| 36 |
{
|
| 37 |
self::$Layers--;
|
| 38 |
if (self::$Layers == 0) {
|
| 39 |
if (self::$AllowCommit) {
|
| 40 |
db_query('COMMIT');
|
| 41 |
} else {
|
| 42 |
db_query('ROLLBACK');
|
| 43 |
}
|
| 44 |
|
| 45 |
// Reset the ROLLBACK propagator
|
| 46 |
self::$AllowCommit = TRUE;
|
| 47 |
}
|
| 48 |
}
|
| 49 |
|
| 50 |
/**
|
| 51 |
* If the given value is FALSE (value AND type), roll back the current
|
| 52 |
* transaction.
|
| 53 |
*
|
| 54 |
* @param boolean $var Roll back the current transaction iff TRUE.
|
| 55 |
*/
|
| 56 |
public static function rollbackIfFalse($var)
|
| 57 |
{
|
| 58 |
if ($var === FALSE) {
|
| 59 |
self::rollback();
|
| 60 |
}
|
| 61 |
}
|
| 62 |
|
| 63 |
/**
|
| 64 |
* Force this transaction to roll back when finished.
|
| 65 |
*/
|
| 66 |
public static function rollback()
|
| 67 |
{
|
| 68 |
self::$AllowCommit = FALSE;
|
| 69 |
}
|
| 70 |
|
| 71 |
/**
|
| 72 |
* Will this transaction roll back instead of committing?
|
| 73 |
*
|
| 74 |
* @return boolean TRUE if the transaction will roll back, FALSE if it
|
| 75 |
* will commit.
|
| 76 |
*/
|
| 77 |
public static function willRollback()
|
| 78 |
{
|
| 79 |
return !self::$AllowCommit;
|
| 80 |
}
|
| 81 |
}
|
| 82 |
|
| 83 |
/**
|
| 84 |
* Utility function that abstracts transactionalized updates or inserts
|
| 85 |
* on a single row.
|
| 86 |
*
|
| 87 |
* @param string $table (required) The un-prefixed table name without curly
|
| 88 |
* braces.
|
| 89 |
* @param mixed $key_field (required) The name(s) of the column(s) to use when
|
| 90 |
* identifying the row to update. If only a single column is being used
|
| 91 |
* to select the row, this may be a string. If more than one column
|
| 92 |
* is being used, this must be an array of strings.
|
| 93 |
* If this operation is an update, these columns must be present as keys
|
| 94 |
* in $fields with their desired WHERE = values.
|
| 95 |
* @param array $fields An associative array mapping column names (keys) to
|
| 96 |
* the values to be inserted/updated. For updates, only the columns
|
| 97 |
* with updated values need to be in this array.
|
| 98 |
*
|
| 99 |
* @return mixed
|
| 100 |
*/
|
| 101 |
function transaction_update($table, $key_field, $fields) {
|
| 102 |
$txn = new Transaction();
|
| 103 |
|
| 104 |
// Accept a string if the key field is one column
|
| 105 |
$return_simple_key_field = FALSE;
|
| 106 |
if (!is_array($key_field)) {
|
| 107 |
$return_simple_key_field = TRUE;
|
| 108 |
$key_field = array($key_field);
|
| 109 |
}
|
| 110 |
|
| 111 |
// Convert a passed object to an array
|
| 112 |
$fields = (array) $fields;
|
| 113 |
|
| 114 |
// Check if key columns are specified
|
| 115 |
$key_field_set = TRUE;
|
| 116 |
foreach ($key_field as $column) {
|
| 117 |
if (!array_key_exists($column, $fields)) {
|
| 118 |
$key_field_set = FALSE;
|
| 119 |
}
|
| 120 |
}
|
| 121 |
|
| 122 |
// Update
|
| 123 |
if ($key_field_set) {
|
| 124 |
$sql = 'UPDATE {' . db_escape_table($table) . '} t SET ';
|
| 125 |
$where = '';
|
| 126 |
$updates = array();
|
| 127 |
foreach ($fields as $key => $value) {
|
| 128 |
if (!in_array($key, $key_field)) {
|
| 129 |
if ($value !== NULL) {
|
| 130 |
$updates[] = 't.' . $key . ' = "' . db_escape_string($value) . '"';
|
| 131 |
}
|
| 132 |
else {
|
| 133 |
$updates[] = 't.' . $key . ' = NULL';
|
| 134 |
}
|
| 135 |
}
|
| 136 |
}
|
| 137 |
$sql .= implode(',', $updates) . ' ';
|
| 138 |
$where .= ' WHERE 1 ';
|
| 139 |
|
| 140 |
foreach ($key_field as $column) {
|
| 141 |
if ($fields[$column] !== NULL) {
|
| 142 |
$where .= 'AND ' . $column . ' = "' . $fields[$column] . '" ';
|
| 143 |
}
|
| 144 |
else {
|
| 145 |
$where .= 'AND ' . $column . ' IS NULL ';
|
| 146 |
}
|
| 147 |
}
|
| 148 |
|
| 149 |
$sql .= $where;
|
| 150 |
|
| 151 |
db_query($sql);
|
| 152 |
|
| 153 |
// Return if we've actually updated something
|
| 154 |
if (db_affected_rows()) {
|
| 155 |
// If we were passed a simple key, return the same
|
| 156 |
if ($return_simple_key_field) {
|
| 157 |
return $fields[$key_field[0]];
|
| 158 |
}
|
| 159 |
|
| 160 |
$return_fields = array();
|
| 161 |
foreach ($key_field as $column) {
|
| 162 |
$return_fields[$column] = $fields[$column];
|
| 163 |
}
|
| 164 |
return $return_fields;
|
| 165 |
}
|
| 166 |
|
| 167 |
// Return if there was nothing to change, but the row actually exists
|
| 168 |
$sql = 'SELECT COUNT(*) FROM {' . db_escape_table($table) . '} ' . $where;
|
| 169 |
$exists = db_result(db_query($sql, 0, 1));
|
| 170 |
if ($exists) {
|
| 171 |
$return_fields = array();
|
| 172 |
foreach ($key_field as $column) {
|
| 173 |
$return_fields[$column] = $fields[$column];
|
| 174 |
}
|
| 175 |
return $return_fields;
|
| 176 |
}
|
| 177 |
}
|
| 178 |
else {
|
| 179 |
// We can't autogenerate if the key is more than one column
|
| 180 |
if (count($key_field) > 1) {
|
| 181 |
return FALSE;
|
| 182 |
}
|
| 183 |
}
|
| 184 |
|
| 185 |
// Insert
|
| 186 |
$sql = 'INSERT INTO {' . db_escape_table($table) . '} ';
|
| 187 |
$keys = array_keys($fields);
|
| 188 |
$values = array();
|
| 189 |
foreach ($fields as $key => $value) {
|
| 190 |
if ($value !== NULL) {
|
| 191 |
$values[] = '"' . db_escape_string($value) . '"';
|
| 192 |
}
|
| 193 |
else {
|
| 194 |
$values[] = 'NULL';
|
| 195 |
}
|
| 196 |
}
|
| 197 |
$sql .= '({' . db_escape_table($table) . '}.' . implode(',{' . db_escape_table($table) . '}.', $keys) . ') ';
|
| 198 |
$sql .= 'VALUES (' . implode(',', $values) . ')';
|
| 199 |
db_query($sql);
|
| 200 |
|
| 201 |
if (!$key_field_set) {
|
| 202 |
$fields[$key_field[0]] = db_last_insert_id($table, $key_field[0]);
|
| 203 |
}
|
| 204 |
|
| 205 |
// If we were passed a scalar key, return the key of the new row as a scalar
|
| 206 |
if ($return_simple_key_field) {
|
| 207 |
return $fields[$key_field[0]];
|
| 208 |
}
|
| 209 |
|
| 210 |
$return_fields = array();
|
| 211 |
foreach ($key_field as $column) {
|
| 212 |
$return_fields[$column] = $fields[$column];
|
| 213 |
}
|
| 214 |
return $return_fields;
|
| 215 |
}
|