Index: database/database.mysql
===================================================================
RCS file: /cvs/drupal/drupal/database/database.mysql,v
retrieving revision 1.170
diff -u -F^f -r1.170 database.mysql
--- database/database.mysql	21 Feb 2005 19:47:44 -0000	1.170
+++ database/database.mysql	5 Mar 2005 19:16:28 -0000
@@ -135,11 +135,12 @@
 --
 
 CREATE TABLE book (
+  vid int(10) unsigned NOT NULL default '0',
   nid int(10) unsigned NOT NULL default '0',
   parent int(10) NOT NULL default '0',
   weight tinyint(3) NOT NULL default '0',
-  log longtext,
-  PRIMARY KEY  (nid),
+  PRIMARY KEY  (vid),
+  KEY nid (nid),
   KEY parent (parent)
 ) TYPE=MyISAM;
 
@@ -232,6 +233,7 @@
 CREATE TABLE files (
   fid int(10) unsigned NOT NULL default '0',
   nid int(10) unsigned NOT NULL default '0',
+  vid int(10) unsigned NOT NULL default '0',
   filename varchar(255) NOT NULL default '',
   filepath varchar(255) NOT NULL default '',
   filemime varchar(255) NOT NULL default '',
@@ -279,9 +281,11 @@
 --
 
 CREATE TABLE forum (
+  vid int(10) unsigned NOT NULL default '0',
   nid int(10) unsigned NOT NULL default '0',
   tid int(10) unsigned NOT NULL default  '0',
-  PRIMARY KEY  (nid),
+  PRIMARY KEY  (vid),
+  KEY nid (nid),
   KEY tid (tid)
 ) TYPE=MyISAM;
 
@@ -392,8 +396,8 @@
 
 CREATE TABLE node (
   nid int(10) unsigned NOT NULL auto_increment,
+  vid int(10) unsigned NOT NULL default '1',
   type varchar(16) NOT NULL default '',
-  title varchar(128) NOT NULL default '',
   uid int(10) NOT NULL default '0',
   status int(4) NOT NULL default '1',
   created int(11) NOT NULL default '0',
@@ -401,16 +405,12 @@
   comment int(2) NOT NULL default '0',
   promote int(2) NOT NULL default '0',
   moderate int(2) NOT NULL default '0',
-  teaser longtext NOT NULL,
-  body longtext NOT NULL,
-  revisions longtext NOT NULL,
   sticky int(2) NOT NULL default '0',
-  format int(4) NOT NULL default '0',
   PRIMARY KEY  (nid),
   KEY node_type (type(4)),
-  KEY node_title_type (title,type(4)),
   KEY status (status),
   KEY uid (uid),
+  KEY vid (vid),
   KEY node_moderate (moderate),
   KEY node_promote_status (promote, status),
   KEY node_created (created),
@@ -431,6 +431,24 @@
   grant_delete tinyint(1) unsigned NOT NULL default '0',
   PRIMARY KEY  (nid,gid,realm)
 ) TYPE=MyISAM;
+ 
+--
+-- Table structure for table 'node_revisions'
+--
+ 
+CREATE TABLE node_revisions (
+  nid int(10) unsigned NOT NULL,
+  vid int(10) unsigned NOT NULL,
+  uid int(10) NOT NULL default '0',
+  title varchar(128) NOT NULL default '',
+  body longtext NOT NULL default '',
+  teaser longtext NOT NULL default '',
+  log longtext NOT NULL default '',
+  timestamp int(11) NOT NULL default '0',
+  format int(4) NOT NULL default '0',
+  PRIMARY KEY  (nid,vid),
+  KEY uid (uid)
+) TYPE=MyISAM;
 
 --
 -- Table structure for table 'profile_fields'
Index: database/database.pgsql
===================================================================
RCS file: /cvs/drupal/drupal/database/database.pgsql,v
retrieving revision 1.106
diff -u -F^f -r1.106 database.pgsql
--- database/database.pgsql	27 Feb 2005 15:40:35 -0000	1.106
+++ database/database.pgsql	5 Mar 2005 19:16:32 -0000
@@ -133,10 +133,10 @@
 --
 
 CREATE TABLE book (
+  vid integer NOT NULL default '0',
   nid integer NOT NULL default '0',
   parent integer NOT NULL default '0',
   weight smallint NOT NULL default '0',
-  log text default '',
   PRIMARY KEY (nid)
 );
 CREATE INDEX book_nid_idx ON book(nid);
@@ -231,6 +231,7 @@
 CREATE TABLE files (
   fid SERIAL,
   nid integer NOT NULL default '0',
+  vid integer NOT NULL default '0',
   filename varchar(255) NOT NULL default '',
   filepath varchar(255) NOT NULL default '',
   filemime varchar(255) NOT NULL default '',
@@ -279,11 +280,13 @@
 --
 
 CREATE TABLE forum (
+  vid integer NOT NULL default '0',
   nid integer NOT NULL default '0',
   tid integer NOT NULL default '0',
-  shadow integer NOT NULL default '0',
-  PRIMARY KEY  (nid)
+  PRIMARY KEY  (vid)
 );
+CREATE INDEX forum_vid_idx ON forum(vid);
+CREATE INDEX forum_nid_idx ON forum(nid);
 CREATE INDEX forum_tid_idx ON forum(tid);
 
 --
@@ -434,6 +437,26 @@
   PRIMARY KEY  (nid,gid,realm)
 );
 
+--
+-- Table structure for table 'node_revisions'
+--
+
+CREATE TABLE node_revisions (
+  vid integer NOT NULL default '0',
+  nid integer NOT NULL default '0',
+  uid integer NOT NULL default '0',
+  title varchar(128) NOT NULL default '',
+  body text NOT NULL default '',
+  teaser text NOT NULL default '',
+  log text NOT NULL default '',
+  format smallint NOT NULL default '0',
+  timestamp integer NOT NULL default '0',
+  data text NOT NULL default '',
+  PRIMARY KEY (vid)
+);
+
+CREATE INDEX node_revisions_nid_idx ON node_revisions(nid);
+CREATE INDEX node_revisions_uid_idx ON node_revisions(uid);
 
 --
 -- Table structure for table 'node_counter'
Index: database/updates.inc
===================================================================
RCS file: /cvs/drupal/drupal/database/updates.inc,v
retrieving revision 1.98
diff -u -F^f -r1.98 updates.inc
--- database/updates.inc	3 Mar 2005 20:21:51 -0000	1.98
+++ database/updates.inc	5 Mar 2005 19:16:53 -0000
@@ -102,7 +102,9 @@
   "2005-01-28" => "update_123",
   "2005-02-11" => "update_124",
   "2005-02-23" => "update_125",
-  "2005-03-03" => "update_126"
+  "2005-03-03" => "update_126",
+  "2005-03-04" => "update_127",
+  "2005-03-05" => "update_128"
 );
 
 function update_32() {
@@ -2313,6 +2315,154 @@ function update_126() {
   return array();
 }
 
+function update_127() {
+  $ret = array();
+
+  if ($GLOBALS['db_type'] == 'mysql') {
+    $ret[] = update_sql("CREATE TABLE {node_revisions} (
+                                nid int(10) unsigned NOT NULL,
+                                vid int(10) unsigned NOT NULL,
+                                uid int(10) NOT NULL default '0',
+                                title varchar(128) NOT NULL default '',
+                                body longtext NOT NULL default '',
+                                teaser longtext NOT NULL default '',
+                                log longtext NOT NULL default '',
+                                timestamp int(11) NOT NULL default '0',
+                                format int(4) NOT NULL default '0',
+                                PRIMARY KEY  (vid),
+                                KEY nid (nid),
+                                KEY uid (uid)
+                        )");
+    $ret[] = update_sql("ALTER TABLE {node} ADD vid int(10) unsigned NOT NULL default '0'");
+    $ret[] = update_sql("ALTER TABLE {files} ADD vid int(10) unsigned NOT NULL default '0'");
+    $ret[] = update_sql("ALTER TABLE {book} ADD vid int(10) unsigned NOT NULL default '0'");
+    $ret[] = update_sql("ALTER TABLE {forum} ADD vid int(10) unsigned NOT NULL default '0'");
+    $ret[] = update_sql("ALTER TABLE {book} DROP PRIMARY KEY");
+    $ret[] = update_sql("ALTER TABLE {forum} DROP PRIMARY KEY");
+    $ret[] = update_sql("ALTER TABLE {book} ADD KEY nid (nid)");
+    $ret[] = update_sql("ALTER TABLE {forum} ADD KEY nid (nid)");
+  } 
+  else {
+    $ret[] = update_sql("CREATE TABLE {node_revisions} (
+                                 vid integer NOT NULL default '0',
+                                 nid integer NOT NULL default '0',
+                                 uid integer NOT NULL default '0',
+                                 title varchar(128) NOT NULL default '',
+                                 body text NOT NULL default '',
+                                 teaser text NOT NULL default '',
+                                 log text NOT NULL default '',
+                                 timestamp integer NOT NULL default '0',
+                                 format smallint NOT NULL default '0',
+                                 PRIMARY KEY  (vid)
+                        )");
+    $ret[] = update_sql("ALTER TABLE {node} ADD vid integer NOT NULL default '0'");
+    $ret[] = update_sql("ALTER TABLE {files} ADD vid integer NOT NULL default '0'");
+    $ret[] = update_sql("ALTER TABLE {book} ADD vid integer NOT NULL default '0'");
+    $ret[] = update_sql("ALTER TABLE {forum} ADD vid integer NOT NULL default '0'");
+    $ret[] = update_sql("CREATE INDEX node_revisions_uid_idx ON node_revisions(uid)");
+    $ret[] = update_sql("CREATE INDEX node_revisions_nid_idx ON node_revisions(nid)");
+    $ret[] = update_sql("ALTER TABLE {book} DROP INDEX nid");
+    $ret[] = update_sql("ALTER TABLE {forum} DROP INDEX nid");
+/* This needs to be ported to pgsql
+    $ret[] = update_sql("ALTER TABLE {book} ADD PRIMARY KEY vid (vid)");
+    $ret[] = update_sql("ALTER TABLE {forum} ADD PRIMARY KEY vid (vid)");
+    $ret[] = update_sql("ALTER TABLE {book} ADD KEY nid (nid)");
+    $ret[] = update_sql("ALTER TABLE {forum} ADD KEY nid (nid)");
+*/
+  }
+
+  return $ret;
+}
+
+function update_128() {
+  $ret = array();
+
+  $vid = 0;
+  $result = db_query("SELECT nid, uid, type, title, body, teaser, changed, format, revisions FROM {node}");
+  while ($row = db_fetch_array($result)) {
+    $nid = $row['nid'];
+    $vid++;
+    db_query("INSERT INTO {node_revisions} (nid, vid, uid, title, body, teaser, timestamp, format) VALUES (%d, %d, %d, '%s', '%s', '%s', %d, %d)", $nid, $vid, $row['uid'], $row['title'], $row['body'], $row['teaser'], $row['changed'], $row['format']);
+    switch ($row['type']) {
+      case 'forum':
+        db_query("UPDATE {forum} SET vid = %d WHERE nid = %d", $vid, $nid);
+        break;
+      case 'book':
+        db_query("UPDATE {book} SET vid = %d WHERE nid = %d", $vid, $nid);
+        break;
+    }
+
+    db_query("UPDATE {node} SET vid = %d WHERE nid = %d", $vid, $nid);
+
+    $revisions = unserialize($row['revisions']);
+    if (is_array($revisions) && count($revisions) > 0) {
+      foreach ($revisions as $version) {
+        $revision = array();
+        foreach ($version['node'] as $node_field => $node_value) {
+          $revision[$node_field] = $node_value;
+        }
+        $revision['uid'] = $version['uid'];
+        $revision['timestamp'] = $version['timestamp'];
+
+        $vid++;
+        db_query("INSERT INTO {node_revisions} (nid, vid, uid, title, body, teaser, log, timestamp, format) VALUES (%d, %d, %d, '%s', '%s', '%s', '%s', %d, %d)", $nid, $vid, $revision['uid'], $revision['title'], $revision['body'], $revision['teaser'], $revision['log'], $revision['timestamp'], $revision['format']);
+        switch ($row['type']) {
+          case 'forum':
+            db_query("INSERT INTO {forum} (vid, nid, tid) VALUES (%d, %d, %d)", $vid, $nid, $revision['tid']);
+            break;
+          case 'book':
+            db_query("INSERT INTO {book} (vid, nid, parent, weight) VALUES (%d, %d, %d, %d, '%s')", $vid, $nid, $revision['parent'], $revision['weight']);
+            break;
+        }
+      }
+    }
+  }
+
+  // Other node types might be added to the book. Move logs too.
+  $result = db_query("SELECT n.nid, n.vid, n.type, b.log FROM {book} b INNER JOIN {node} n ON b.nid = n.nid");
+  while ($row = db_fetch_object($result)) {
+    if ($row->type != 'book') {
+      db_query("UPDATE {book} SET vid = %d WHERE nid = %d", $row->vid, $row->nid);
+    }
+    if ($row->log != '') {
+      db_query("UPDATE {node_revisions} SET log = '%s' WHERE vid = %d", $row->log, $row->vid);
+    }
+  }
+
+  // Assign existing files to current vid
+  $result = db_query("SELECT n.nid, n.vid FROM {node} INNER JOIN {files} f ON n.nid = f.nid");
+  while ($row = db_fetch_object($result)) {
+    db_query("UPDATE {files} SET vid = %d WHERE nid = %d", $row->vid, $row->nid);
+  }
+
+  if ($GLOBALS['db_type'] == 'mysql') {
+    $ret[] = update_sql("ALTER TABLE {book} ADD PRIMARY KEY vid (vid)");
+    $ret[] = update_sql("ALTER TABLE {forum} ADD PRIMARY KEY vid (vid)");
+    $ret[] = update_sql("CREATE TABLE {old_revisions} (
+                            nid integer NOT NULL default '0',
+                            revisions text NOT NULL default '',
+                            PRIMARY KEY (nid)
+                         )");
+  }
+  else { // pgsql update
+  }
+
+  // Move old revisions to old_revisions table.
+  $result = db_query("SELECT nid, revisions FROM {node} WHERE revisions != ''");
+  while ($row = db_fetch_object($result)) {
+    db_query("INSERT INTO {old_revisions} (nid, revisions) VALUES (%d, '%s')", $row->nid, $row->revisions);
+  }
+
+  $ret[] = update_sql("INSERT INTO {sequences} (name, id) VALUES ('{node_revisions}_vid', $vid)");
+  $ret[] = update_sql("ALTER TABLE {book} DROP log");
+  $ret[] = update_sql("ALTER TABLE {node} DROP teaser");
+  $ret[] = update_sql("ALTER TABLE {node} DROP body");
+  $ret[] = update_sql("ALTER TABLE {node} DROP format");
+  $ret[] = update_sql("ALTER TABLE {node} DROP revisions");
+
+  return $ret;
+}
+
 function update_sql($sql) {
   $edit = $_POST["edit"];
   $result = db_query($sql);
Index: includes/database.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/database.inc,v
retrieving revision 1.39
diff -u -F^f -r1.39 database.inc
--- includes/database.inc	19 Feb 2005 22:24:23 -0000	1.39
+++ includes/database.inc	5 Mar 2005 19:16:56 -0000
@@ -129,8 +129,13 @@ function db_set_active($name = 'default'
  *   A string containing an SQL query.
  * @param ...
  *   A variable number of arguments which are substituted into the query using
- *   printf() syntax. Instead of a variable number of query arguments, you may
- *   also pass a single array containing the query arguments.
+ *   printf() syntax. The query arguments can be enclosed in one array instead.
+ *   For INSERT and UPDATE queries the arguments should be present in an 
+ *   array in which the keys name the database columns. For UPDATE queries
+ *   additional parameters can be passed as single arguments after the array.
+ *   Examples:
+ *   db_query('INSERT INTO {node} %a', array('nid' => 42, 'title' => 'foo bar', ...));
+ *   db_query('UPDATE {node} SET %a WHERE nid = %d', array('uid' => 42, 'title' => 'foo bar'), $node->nid);
  * @return
  *   A database query result resource, or FALSE if the query was not executed
  *   correctly.
@@ -139,8 +144,20 @@ function db_query($query) {
   $args = func_get_args();
   $query = db_prefix_tables($query);
   if (count($args) > 1) {
+    if (strpos($query, '%a') !== FALSE) {
+      if (strpos($query, 'INSERT') !== FALSE) {
+        $keys = array_keys($args[1]);
+        $insert = '('. implode(', ', $keys) .') VALUES ('. implode(', ', array_pad(array(), count($keys), "'%s'")) .')';
+        $query = str_replace('%a', $insert, $query);
+      }
+      else if (strpos($query, 'UPDATE') !== FALSE) {
+        $keys = array_keys($args[1]);
+        $update = implode(" = '%s', ", $keys) ." = '%s'";
+        $query = str_replace('%a', $update, $query);
+      }
+    }
     if (is_array($args[1])) {
-      $args = array_merge(array($query), $args[1]);
+      $args = array_merge(array($query), $args[1], array_slice($args, 2));
     }
     $args = array_map('db_escape_string', $args);
     $args[0] = $query;
Index: includes/database.mysql.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/database.mysql.inc,v
retrieving revision 1.27
diff -u -F^f -r1.27 database.mysql.inc
--- includes/database.mysql.inc	29 Nov 2004 13:10:43 -0000	1.27
+++ includes/database.mysql.inc	5 Mar 2005 19:16:59 -0000
@@ -236,6 +236,92 @@ function db_escape_string($text) {
 }
 
 /**
+ * Begins a transaction/lock
+ *
+ * Use this when you need to update multiple tables in such a way that
+ * either all of the updates go through or none do at all. This is
+ * especially useful for the improved revisions system.
+ *
+ * In order to accomodate older versions of MySQL that don't support
+ * table types that have transactions, rollback is not used, and we
+ * must specify which tables are going to be
+ *
+ * So, your code will look something like this:
+ * db_begin_transaction(array('table1', 'table2'));
+ *   // Check if conditions are okay for this update
+ * if ($conditions_ok) {
+ *   // run queries
+ * }
+ * db_commit_transaction();
+ *
+ * You code should ALWAYS call both db_begin_transaction() and
+ * db_commit_transaction() and ALWAYS call them in the same order
+ *
+ * @param $tables array of tables to lock
+ * @return a DB_Result object or a DB_Error
+ */
+function db_begin_transaction($tables) {
+  foreach ($tables as $num => $table) {
+    $tables[$num]= $table .' WRITE';
+  }
+  return db_query('LOCK TABLES '. implode(', ', $tables));
+}
+  
+/**
+ * Commits a transaction/lock
+ *
+ * See db_begin_transaction() for usage details
+ *
+ * @return a DB_Result object or a DB_Error
+ */
+function db_commit_transaction() {
+  return db_query('UNLOCK TABLES');    
+}
+
+/**
+ * Begins a transaction/lock
+ *
+ * Use this when you need to update multiple tables in such a way that
+ * either all of the updates go through or none do at all. This is
+ * especially useful for the improved revisions system.
+ *
+ * In order to accomodate older versions of MySQL that don't support
+ * table types that have transactions, rollback is not used, and we
+ * must specify which tables are going to be
+ *
+ * So, your code will look something like this:
+ * db_begin_transaction(array('table1', 'table2'));
+ *   // Check if conditions are okay for this update
+ * if ($conditions_ok) {
+ *   // run queries
+ * }
+ * db_commit_transaction();
+ *
+ * You code should ALWAYS call both db_begin_transaction() and
+ * db_commit_transaction() and ALWAYS call them in the same order
+ *
+ * @param $tables array of tables to lock
+ * @return a DB_Result object or a DB_Error
+ */
+function db_begin_transaction($tables) {
+  foreach ($tables as $num => $table) {
+    $tables[$num]= '{'. $table .'} WRITE';
+  }
+  return db_query('LOCK TABLES '. implode(', ', $tables));
+}
+  
+/**
+ * Commits a transaction/lock
+ *
+ * See db_begin_transaction() for usage details
+ *
+ * @return a DB_Result object or a DB_Error
+ */
+function db_commit_transaction() {
+  return db_query('UNLOCK TABLES');    
+}
+
+/**
  * @} End of "ingroup database".
  */
 
Index: includes/database.pgsql.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/database.pgsql.inc,v
retrieving revision 1.6
diff -u -F^f -r1.6 database.pgsql.inc
--- includes/database.pgsql.inc	7 Jan 2005 19:18:05 -0000	1.6
+++ includes/database.pgsql.inc	5 Mar 2005 19:17:01 -0000
@@ -231,6 +231,45 @@ function db_escape_string($text) {
 }
 
 /**
+ * Begins a transaction/lock
+ *
+ * Use this when you need to update multiple tables in such a way that
+ * either all of the updates go through or none do at all. This is
+ * especially useful for the improved revisions system.
+ *
+ * In order to accomodate older versions of MySQL that don't support
+ * table types that have transactions, rollback is not used, and we
+ * must specify which tables are going to be
+ *
+ * So, your code will look something like this:
+ * db_begin_transaction(array('table1', 'table2'));
+ *   // Check if conditions are okay for this update
+ * if ($conditions_ok) {
+ *   // run queries
+ * }
+ * db_commit_transaction();
+ *
+ * You code should ALWAYS call both db_begin_transaction() and
+ * db_commit_transaction() and ALWAYS call them in the same order
+ *
+ * @param $tables array of tables to lock
+ * @return a DB_Result object or a DB_Error
+ */
+function db_begin_transaction($tables) {
+  return db_query('START TRANSACTION');
+}
+  
+/**
+ * Commits a transaction/lock
+ *
+ * See db_begin_transaction() for usage details
+ *
+ * @return a DB_Result object or a DB_Error
+ */
+function db_commit_transaction() {
+  return db_query('COMMIT');
+}
+/**
  * @} End of "ingroup database".
  */
 
Index: modules/blogapi.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/blogapi.module,v
retrieving revision 1.37
diff -u -F^f -r1.37 blogapi.module
--- modules/blogapi.module	31 Jan 2005 19:36:20 -0000	1.37
+++ modules/blogapi.module	5 Mar 2005 19:17:10 -0000
@@ -370,7 +370,12 @@ function blogapi_get_recent_posts($req_p
   }
 
   $type = _blogapi_blogid($params[0]);
-  $result = db_query_range('SELECT n.nid, n.title,'. ($bodies ? ' n.body,' : '') ." n.created, u.name FROM {node} n, {users} u WHERE n.uid=u.uid AND n.type = '%s' AND n.uid = %d ORDER BY n.created DESC",  $type, $user->uid, 0, $params[3]);
+  if ($bodies) {
+    $result = db_query_range("SELECT n.nid, n.title, r.body, n.created, u.name FROM {node} n, {node_revisions} r, {users} u WHERE n.uid = u.uid AND n.vid = r.vid AND n.type = '%s' AND n.uid = %d ORDER BY n.created DESC",  $type, $user->uid, 0, $params[3]);
+  }
+  else {
+    $result = db_query_range("SELECT n.nid, n.title, n.created, u.name FROM {node} n, {users} u WHERE n.uid = u.uid AND n.type = '%s' AND n.uid = %d ORDER BY n.created DESC", $type, $user->uid, 0, $params[3]);
+  }
   while ($blog = db_fetch_object($result)) {
     $xmlrpcval = _blogapi_get_post($blog, $bodies);
     $blogs[] = $xmlrpcval;
Index: modules/book.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/book.module,v
retrieving revision 1.285
diff -u -F^f -r1.285 book.module
--- modules/book.module	12 Feb 2005 07:51:14 -0000	1.285
+++ modules/book.module	5 Mar 2005 19:17:20 -0000
@@ -111,7 +111,7 @@ function book_menu($may_cache) {
     // We don't want to cache these menu items because they could change whenever
     // a book page or outline node is edited.
     if (arg(0) == 'admin' && arg(1) == 'node' && arg(2) == 'book') {
-      $result = db_query(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE b.parent = 0 ORDER BY b.weight, n.title'));
+      $result = db_query(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = 0 ORDER BY b.weight, n.title'));
       while ($book = db_fetch_object($result)) {
         $items[] = array('path' => 'admin/node/book/'. $book->nid, 'title' => t('"%title" book', array('%title' => $book->title)));
       }
@@ -136,7 +136,7 @@ function book_block($op = 'list', $delta
   else if ($op == 'view') {
     // Only display this block when the user is browsing a book:
     if (arg(0) == 'node' && is_numeric(arg(1))) {
-      $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE n.nid = %d'), arg(1));
+      $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d'), arg(1));
       if (db_num_rows($result) > 0) {
         $node = db_fetch_object($result);
 
@@ -163,7 +163,7 @@ function book_block($op = 'list', $delta
 function book_load($node) {
   global $user;
 
-  $book = db_fetch_object(db_query('SELECT parent, weight, log FROM {book} WHERE nid = %d', $node->nid));
+  $book = db_fetch_object(db_query('SELECT parent, weight FROM {book} WHERE vid = %d', $node->vid));
 
   if (arg(2) == 'edit' && !user_access('administer nodes')) {
     // If a user is about to update a book page, we overload some
@@ -185,14 +185,19 @@ function book_load($node) {
  * Implementation of hook_insert().
  */
 function book_insert($node) {
-  db_query("INSERT INTO {book} (nid, parent, weight, log) VALUES (%d, %d, %d, '%s')", $node->nid, $node->parent, $node->weight, $node->log);
+  db_query("INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)", $node->nid, $node->vid, $node->parent, $node->weight);
 }
 
 /**
  * Implementation of hook_update().
  */
 function book_update($node) {
-  db_query("UPDATE {book} SET parent = %d, weight = %d, log = '%s' WHERE nid = %d", $node->parent, $node->weight, $node->log, $node->nid);
+  if ($node->is_new || $node->revision) {
+    db_query("INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d, '%s')", $node->nid, $node->vid, $node->parent, $node->weight);
+  }
+  else {
+    db_query("UPDATE {book} SET parent = %d, weight = %d WHERE vid = %d", $node->parent, $node->weight, $node->vid);
+  }
 }
 
 /**
@@ -229,6 +234,7 @@ function book_form(&$node) {
 
   $output .= form_textarea(t('Body'), 'body', $node->body, 60, 20, '', NULL, TRUE);
   $output .= filter_form('format', $node->format);
+
   $output .= form_textarea(t('Log message'), 'log', $node->log, 60, 5, t('An explanation of the additions or updates being made to help other authors understand your motivations.'));
 
   if (user_access('administer nodes')) {
@@ -256,13 +262,15 @@ function book_outline() {
   if ($node->nid) {
     switch ($op) {
       case t('Add to book outline'):
-        db_query('INSERT INTO {book} (nid, parent, weight) VALUES (%d, %d, %d)', $node->nid, $edit['parent'], $edit['weight']);
+        db_query('INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)', $node->nid, $node->vid, $edit['parent'], $edit['weight']);
+        db_query("UPDATE {node_revisions} SET log = '%s' WHERE vid = %d", $edit['log'], $node->vid);
         drupal_set_message(t('Added the post to the book.'));
         drupal_goto("node/$node->nid");
         break;
 
       case t('Update book outline'):
-        db_query('UPDATE {book} SET parent = %d, weight = %d WHERE nid = %d', $edit['parent'], $edit['weight'], $node->nid);
+        db_query('UPDATE {book} SET parent = %d, weight = %d WHERE vid = %d', $edit['parent'], $edit['weight'], $node->vid);
+        db_query("UPDATE {node_revisions} SET log = '%s' WHERE vid = %d", $edit['log'], $node->vid);
         drupal_set_message(t('Updated the book outline.'));
         drupal_goto("node/$node->nid");
         break;
@@ -274,10 +282,11 @@ function book_outline() {
         break;
 
       default:
-        $page = db_fetch_object(db_query('SELECT * FROM {book} WHERE nid = %d', $node->nid));
+        $page = db_fetch_object(db_query('SELECT * FROM {book} WHERE vid = %d', $node->vid));
 
         $output  = form_select(t('Parent'), 'parent', $page->parent, book_toc($node->nid), t('The parent page in the book.'));
-        $output .= form_weight(t('Weight'), 'weight', $node->weight, 15, t('Pages at a given level are ordered first by weight and then by title.'));
+        $output .= form_weight(t('Weight'), 'weight', $page->weight, 15, t('Pages at a given level are ordered first by weight and then by title.'));
+        $output .= form_textarea(t('Log message'), 'log', $node->log, 60, 5, t('An explanation of the additions or updates being made to help other authors understand your motivations.'));
 
         if ($page->nid) {
           $output .= form_submit(t('Update book outline'));
@@ -325,7 +334,7 @@ function book_revision_load($page, $cond
  * Return the path (call stack) to a certain book page.
  */
 function book_location($node, $nodes = array()) {
-  $parent = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE n.nid = %d'), $node->parent));
+  $parent = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d'), $node->parent));
   if ($parent->title) {
     $nodes = book_location($parent, $nodes);
     array_push($nodes, $parent);
@@ -334,7 +343,7 @@ function book_location($node, $nodes = a
 }
 
 function book_location_down($node, $nodes = array()) {
-  $last_direct_child = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE b.parent = %d ORDER BY b.weight DESC, n.title DESC'), $node->nid));
+  $last_direct_child = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d ORDER BY b.weight DESC, n.title DESC'), $node->nid));
   if ($last_direct_child) {
     array_push($nodes, $last_direct_child);
     $nodes = book_location_down($last_direct_child, $nodes);
@@ -352,7 +361,7 @@ function book_prev($node) {
   }
 
   // Previous on the same level:
-  $direct_above = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE b.parent = %d AND n.status = 1 AND n.moderate = 0 AND (b.weight < %d OR (b.weight = %d AND n.title < '%s')) ORDER BY b.weight DESC, n.title DESC"), $node->parent, $node->weight, $node->weight, $node->title));
+  $direct_above = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND n.moderate = 0 AND (b.weight < %d OR (b.weight = %d AND n.title < '%s')) ORDER BY b.weight DESC, n.title DESC"), $node->parent, $node->weight, $node->weight, $node->title));
   if ($direct_above) {
     // Get last leaf of $above.
     $path = book_location_down($direct_above);
@@ -361,7 +370,7 @@ function book_prev($node) {
   }
   else {
     // Direct parent:
-    $prev = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE n.nid = %d AND n.status = 1 AND n.moderate = 0'), $node->parent));
+    $prev = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d AND n.status = 1 AND n.moderate = 0'), $node->parent));
     return $prev;
   }
 }
@@ -371,7 +380,7 @@ function book_prev($node) {
  */
 function book_next($node) {
   // get first direct child
-  $child = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE b.parent = %d AND n.status = 1 AND n.moderate = 0 ORDER BY b.weight ASC, n.title ASC'), $node->nid));
+  $child = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND n.moderate = 0 ORDER BY b.weight ASC, n.title ASC'), $node->nid));
   if ($child) {
     return $child;
   }
@@ -380,7 +389,7 @@ function book_next($node) {
   array_push($path = book_location($node), $node); // Path to top-level node including this one.
 
   while (($leaf = array_pop($path)) && count($path)) {
-    $next = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE b.parent = %d AND n.status = 1 AND n.moderate = 0 AND (b.weight > %d OR (b.weight = %d AND n.title > '%s')) ORDER BY b.weight ASC, n.title ASC"), $leaf->parent, $leaf->weight, $leaf->weight, $leaf->title));
+    $next = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND n.moderate = 0 AND (b.weight > %d OR (b.weight = %d AND n.title > '%s')) ORDER BY b.weight ASC, n.title ASC"), $leaf->parent, $leaf->weight, $leaf->weight, $leaf->title));
     if ($next) {
       return $next;
     }
@@ -415,10 +424,6 @@ function book_content($node, $teaser = F
  */
 function book_view(&$node, $teaser = FALSE, $page = FALSE) {
   $node = book_content($node, $teaser);
-
-  if (!$teaser && $node->moderate) {
-    $node->body .= '<div class="log"><div class="title">'. t('Log') .':</div>'. $node->log .'</div>';
-  }
 }
 
 /**
@@ -430,7 +435,7 @@ function book_nodeapi(&$node, $op, $teas
   switch ($op) {
     case 'view':
       if (!$teaser) {
-        $book = db_fetch_array(db_query('SELECT * FROM {book} WHERE nid = %d', $node->nid));
+        $book = db_fetch_array(db_query('SELECT * FROM {book} WHERE vid = %d', $node->vid));
         if ($book) {
           if ($node->moderate && user_access('administer nodes')) {
             drupal_set_message(t("This update/post awaits moderation and won't be accessible until approved."));
@@ -521,7 +526,7 @@ function book_toc_recurse($nid, $indent,
 }
 
 function book_toc($exclude = 0) {
-  $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE n.status = 1 ORDER BY b.weight, n.title'));
+  $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 ORDER BY b.weight, n.title'));
 
   while ($node = db_fetch_object($result)) {
     if (!$children[$node->parent]) {
@@ -574,7 +579,7 @@ function book_tree_recurse($nid, $depth,
 }
 
 function book_tree($parent = 0, $depth = 3, $unfold = array()) {
-  $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE n.status = 1 AND n.moderate = 0 ORDER BY b.weight, n.title'));
+  $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND n.moderate = 0 ORDER BY b.weight, n.title'));
 
   while ($node = db_fetch_object($result)) {
     $list = $children[$node->parent] ? $children[$node->parent] : array();
@@ -591,7 +596,7 @@ function book_tree($parent = 0, $depth =
  * Menu callback; prints a listing of all books.
  */
 function book_render() {
-  $result = db_query(db_rewrite_sql('SELECT n.nid FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE b.parent = 0 AND n.status = 1 AND n.moderate = 0 ORDER BY b.weight, n.title'));
+  $result = db_query(db_rewrite_sql('SELECT n.nid FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = 0 AND n.status = 1 AND n.moderate = 0 ORDER BY b.weight, n.title'));
 
   while ($page = db_fetch_object($result)) {
     // Load the node:
@@ -617,7 +622,7 @@ function book_render() {
  */
 function book_print($nid = 0, $depth = 1) {
   global $base_url;
-  $result = db_query(db_rewrite_sql('SELECT DISTINCT(n.nid), n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE n.status = 1 AND n.nid = %d AND n.moderate = 0 ORDER BY b.weight, n.title'), $nid);
+  $result = db_query(db_rewrite_sql('SELECT DISTINCT(n.nid), n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND n.nid = %d AND n.moderate = 0 ORDER BY b.weight, n.title'), $nid);
 
   while ($page = db_fetch_object($result)) {
     // load the node:
@@ -650,7 +655,7 @@ function book_print($nid = 0, $depth = 1
 }
 
 function book_print_recurse($parent = '', $depth = 1) {
-  $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE n.status = 1 AND b.parent = %d AND n.moderate = 0 ORDER BY b.weight, n.title'), $parent);
+  $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND b.parent = %d AND n.moderate = 0 ORDER BY b.weight, n.title'), $parent);
 
   while ($page = db_fetch_object($result)) {
     // Load the node:
@@ -687,7 +692,7 @@ function book_admin_view_line($node, $de
 }
 
 function book_admin_view_book($nid, $depth = 1) {
-  $result = db_query(db_rewrite_sql('SELECT n.nid FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE b.parent = %d ORDER BY b.weight, n.title'), $nid);
+  $result = db_query(db_rewrite_sql('SELECT n.nid FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d ORDER BY b.weight, n.title'), $nid);
 
   while ($node = db_fetch_object($result)) {
     $node = node_load(array('nid' => $node->nid));
@@ -724,15 +729,16 @@ function book_admin_save($nid, $edit = a
 
     foreach ($edit as $nid => $value) {
       // Check to see whether the title needs updating:
-      $title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $nid));
-      if ($title != $value['title']) {
+      $node = db_fetch_object(db_query('SELECT title, vid FROM {node} WHERE nid = %d', $nid));
+      if ($node->title != $value['title']) {
         db_query("UPDATE {node} SET title = '%s' WHERE nid = %d", $value['title'], $nid);
+        db_query("UPDATE {book} SET title = '%s' WHERE vid = %d", $value['title'], $node->vid);
       }
 
       // Check to see whether the weight needs updating:
-      $weight = db_result(db_query('SELECT weight FROM {book} WHERE nid = %d', $nid));
-      if ($weight != $value['weight']) {
-        db_query('UPDATE {book} SET weight = %d WHERE nid = %d', $value['weight'], $nid);
+      $node = db_fetch_object(db_query('SELECT b.vid, b.weight FROM {book} b INNER JOIN {node} n ON n.vid = b.vid WHERE n.nid = %d', $nid));
+      if ($node->weight != $value['weight']) {
+        db_query('UPDATE {book} SET weight = %d WHERE vid = %d', $value['weight'], $node->vid);
       }
     }
 
@@ -747,7 +753,7 @@ function book_admin_save($nid, $edit = a
  * Menu callback; displays a listing of all orphaned book pages.
  */
 function book_admin_orphan() {
-  $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, n.status, b.parent FROM {node} n INNER JOIN {book} b ON n.nid = b.nid'));
+  $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, n.status, b.parent FROM {node} n INNER JOIN {book} b ON v.nid = b.vid'));
 
   while ($page = db_fetch_object($result)) {
     $pages[$page->nid] = $page;
Index: modules/forum.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/forum.module,v
retrieving revision 1.233
diff -u -F^f -r1.233 forum.module
--- modules/forum.module	3 Mar 2005 20:51:27 -0000	1.233
+++ modules/forum.module	5 Mar 2005 19:17:37 -0000
@@ -292,7 +292,7 @@ function forum_admin_configure() {
  * Implementation of hook_load().
  */
 function forum_load($node) {
-  $forum = db_fetch_object(db_query('SELECT * FROM {forum} WHERE nid = %d', $node->nid));
+  $forum = db_fetch_object(db_query('SELECT * FROM {forum} WHERE vid = %d', $node->vid));
 
   return $forum;
 }
@@ -497,7 +497,12 @@ function forum_validate(&$node) {
  * Implementation of hook_update().
  */
 function forum_update($node) {
-  db_query('UPDATE {forum} SET tid = %d WHERE nid = %d', $node->tid, $node->nid);
+  if ($node->is_new || $node->revision) {
+    db_query("INSERT INTO {forum} (nid, vid, tid) VALUES (%d, %d, %d)", $node->nid, $node->vid, $node->tid);
+  }
+  else {
+    db_query('UPDATE {forum} SET tid = %d WHERE vid = %d', $node->tid, $node->vid);
+  }
 }
 
 /**
@@ -530,7 +535,7 @@ function forum_form(&$node) {
  * Implementation of hook_insert().
  */
 function forum_insert($node) {
-  db_query('INSERT INTO {forum} (nid, tid) VALUES (%d, %d)', $node->nid, $node->tid);
+  db_query('INSERT INTO {forum} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $node->tid);
 }
 
 /**
@@ -655,7 +660,7 @@ function forum_get_topics($tid, $sortby,
 
   $term = taxonomy_get_term($tid);
 
-  $sql = db_rewrite_sql("SELECT n.nid, f.tid, n.title, n.sticky, u.name, u.uid, n.created AS timestamp, n.comment AS comment_mode, l.last_comment_timestamp, IF(l.last_comment_uid, cu.name, l.last_comment_name) AS last_comment_name, l.last_comment_uid, l.comment_count AS num_comments FROM {node} n, {node_comment_statistics} l, {users} cu, {term_node} r, {users} u, {forum} f WHERE n.status = 1 AND l.last_comment_uid = cu.uid AND n.nid = l.nid AND n.nid = r.nid AND r.tid = %d AND n.uid = u.uid AND n.nid = f.nid");
+  $sql = db_rewrite_sql("SELECT n.nid, f.tid, n.title, n.sticky, u.name, u.uid, n.created AS timestamp, n.comment AS comment_mode, l.last_comment_timestamp, IF(l.last_comment_uid, cu.name, l.last_comment_name) AS last_comment_name, l.last_comment_uid, l.comment_count AS num_comments FROM {node} n, {node_comment_statistics} l, {users} cu, {term_node} r, {users} u, {forum} f WHERE n.status = 1 AND l.last_comment_uid = cu.uid AND n.nid = l.nid AND n.nid = r.nid AND r.tid = %d AND n.uid = u.uid AND n.vid = f.vid");
   $sql .= tablesort_sql($forum_topic_list_header, 'n.sticky DESC,');
 
   $sql_count = db_rewrite_sql("SELECT COUNT(n.nid) FROM {node} n INNER JOIN {term_node} r ON n.nid = r.nid AND r.tid = %d WHERE n.status = 1 AND n.type = 'forum'");
Index: modules/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node.module,v
retrieving revision 1.474
diff -u -F^f -r1.474 node.module
--- modules/node.module	5 Mar 2005 11:05:07 -0000	1.474
+++ modules/node.module	5 Mar 2005 19:18:00 -0000
@@ -389,31 +389,27 @@ function node_load($conditions, $revisio
   }
 
   // Retrieve the node.
-  $node = db_fetch_object(db_query('SELECT n.*, u.uid, u.name, u.picture, u.data FROM {node} n INNER JOIN {users} u ON u.uid = n.uid WHERE '. implode(' AND ', $cond)));
-  $node = drupal_unpack($node);
-
-  // Unserialize the revisions and user data fields.
-  if ($node->revisions) {
-    $node->revisions = unserialize($node->revisions);
+  if ($revision) {
+    $node = db_fetch_object(db_query('SELECT n.nid, n.vid, n.type, n.status, n.created, n.changed, n.comment, n.promote, n.moderate, n.sticky, r.timestamp AS revision_timestamp, r.title, r.body, r.teaser, r.format, u.uid, u.name, u.picture, u.data FROM {node} n INNER JOIN {users} u ON u.uid = n.uid INNER JOIN {node_revisions} r ON r.nid = n.nid AND r.vid = %d WHERE '. implode(' AND ', $cond), $revision));
   }
-
-  // Call the node specific callback (if any) and piggy-back the
-  // results to the node or overwrite some values.
-  if ($extra = node_invoke($node, 'load')) {
-    foreach ($extra as $key => $value) {
-      $node->$key = $value;
-    }
+  else {
+    $node = db_fetch_object(db_query('SELECT n.nid, n.vid, n.type, n.status, n.created, n.changed, n.comment, n.promote, n.moderate, n.sticky, r.timestamp AS revision_timestamp, r.title, r.body, r.teaser, r.format, u.uid, u.name, u.picture, u.data FROM {node} n INNER JOIN {users} u ON u.uid = n.uid INNER JOIN {node_revisions} r ON r.vid = n.vid WHERE '. implode(' AND ', $cond)));
   }
 
-  if ($extra = node_invoke_nodeapi($node, 'load')) {
-    foreach ($extra as $key => $value) {
-      $node->$key = $value;
+  if ($node->nid) {
+    // Call the node specific callback (if any) and piggy-back the
+    // results to the node or overwrite some values.
+    if ($extra = node_invoke($node, 'load')) {
+      foreach ($extra as $key => $value) {
+        $node->$key = $value;
+      }
+    }
+  
+    if ($extra = node_invoke_nodeapi($node, 'load')) {
+      foreach ($extra as $key => $value) {
+        $node->$key = $value;
+      }
     }
-  }
-
-  // Return the desired revision.
-  if (!is_null($revision) && is_array($node->revisions[$revision])) {
-   $node = $node->revisions[$revision]['node'];
   }
 
   if ($cachable) {
@@ -427,61 +423,100 @@ function node_load($conditions, $revisio
  * Save a node object into the database.
  */
 function node_save($node) {
-  // Fetch fields to save to node table:
-  $fields = node_invoke_nodeapi($node, 'fields');
+  global $user;
 
-  // Serialize the revisions field:
-  if ($node->revisions) {
-    $node->revisions = serialize($node->revisions);
-  }
+  $node->is_new = false;
 
   // Apply filters to some default node fields:
   if (empty($node->nid)) {
     // Insert a new node.
+    $node->is_new = true;
 
     // Set some required fields:
     if (!$node->created) {
       $node->created = time();
     }
-    if (!$node->changed) {
-      $node->changed = time();
-    }
     $node->nid = db_next_id('{node}_nid');
-
-    // Prepare the query:
-    foreach ($node as $key => $value) {
-      if (in_array((string) $key, $fields)) {
-        $k[] = db_escape_string($key);
-        $v[] = $value;
-        $s[] = "'%s'";
-      }
+    $node->vid = db_next_id('{node_revisions}_vid');;
+  }
+  else {
+    // We need to ensure that all node fields are filled.
+    $node_current = node_load(array('nid' => $node->nid));
+    foreach ($node as $field => $data) {
+      $node_current->$field = $data;
     }
+    $node = $node_current;
 
-    // Insert the node into the database:
-    db_query("INSERT INTO {node} (". implode(", ", $k) .") VALUES(". implode(", ", $s) .")", $v);
-
-    // Call the node specific callback (if any):
-    node_invoke($node, 'insert');
-    node_invoke_nodeapi($node, 'insert');
+    if ($node->revision) {
+      $node->vid = db_next_id('{node_revisions}_vid');;
+    }
   }
-  else {
-    // Update an existing node.
 
-    // Set some required fields:
+  if (!$node->changed) {
     $node->changed = time();
+  }
 
-    // Prepare the query:
-    foreach ($node as $key => $value) {
-      if (in_array($key, $fields)) {
-        $q[] = db_escape_string($key) ." = '%s'";
-        $v[] = $value;
+  // Split off revisions data to another structure
+  $revisions_table_values = array('nid' => $node->nid, 'vid' => $node->vid, 
+                     'title' => $node->title, 'body' => $node->body,
+                     'teaser' => $node->teaser, 'log' => $node->log, 'timestamp' => $node->changed,
+                     'uid' => $user->uid, 'format' => $node->format);
+  $revisions_table_types = array('nid' => '%d', 'vid' => '%d', 
+                     'title' => "'%s'", 'body' => "'%s'",
+                     'teaser' => "'%s'", 'log' => "'%s'", 'timestamp' => '%d',
+                     'uid' => '%d', 'format' => '%d');
+  $node_table_values = array('nid' => $node->nid, 'vid' => $node->vid, 
+                    'title' => $node->title, 'type' => $node->type, 'uid' => $node->uid,
+                    'status' => $node->status, 'created' => $node->created,
+                    'changed' => $node->changed, 'comment' => $node->comment,
+                    'promote' => $node->promote, 'moderate' => $node->moderate,
+                    'sticky' => $node->sticky);
+  $node_table_types = array('nid' => '%d', 'vid' => '%d', 
+                    'title' => "'%s'", 'type' => "'%s'", 'uid' => '%d',
+                    'status' => '%d', 'created' => '%d',
+                    'changed' => '%d', 'comment' => '%d',
+                    'promote' => '%d', 'moderate' => '%d',
+                    'sticky' => '%d');
+
+  //Generate the node table query
+  if ($node->is_new) {
+    $node_query = 'INSERT INTO {node} ('. implode(', ', array_keys($node_table_types)) .') VALUES ('. implode(', ', $node_table_types) .')';
+    $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')';
+  }
+  else {
+    $arr = array();
+    foreach ($node_table_types as $key => $value) {
+      $arr[] = $key .' = '. $value;
+    }
+    $node_table_values[] = $node->nid;
+    $node_query = 'UPDATE {node} SET '. implode(', ', $arr) .' WHERE nid = %d';
+    if ($node->revision) {
+      $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')';
+    }
+    else {
+      $arr = array();
+      foreach ($revisions_table_types as $key => $value) {
+        $arr[] = $key .' = '. $value;
       }
+      $revisions_table_values[] = $node->vid;
+      $revisions_query = 'UPDATE {node_revisions} SET '. implode(', ', $arr) .' WHERE vid = %d';
     }
+  }
+
+  //Generate the node_revisions table query
 
-    // Update the node in the database:
-    db_query("UPDATE {node} SET ". implode(', ', $q) ." WHERE nid = '$node->nid'", $v);
+  // Insert the node into the database:
+  db_begin_transaction(array('node', 'node_revisions', 'watchdog', 'sessions'));
+  db_query($node_query, $node_table_values);
+  db_query($revisions_query, $revisions_table_values);
+  db_commit_transaction();
 
-    // Call the node specific callback (if any):
+  // Call the node specific callback (if any):
+  if ($node->is_new) {
+    node_invoke($node, 'insert');
+    node_invoke_nodeapi($node, 'insert');
+  }
+  else {
     node_invoke($node, 'update');
     node_invoke_nodeapi($node, 'update');
   }
@@ -515,6 +550,10 @@ function node_view($node, $teaser = FALS
   // TODO: this strips legitimate uses of '<!--break-->' also.
   $node->body = str_replace('<!--break-->', '', $node->body);
 
+  if ($node->log != '' && !$teaser && $node->moderate) {
+    $node->body .= '<div class="log"><div class="title">'. t('Log') .':</div>'. $node->log .'</div>';
+  }
+
   // The 'view' hook can be implemented to overwrite the default function
   // to display nodes.
   if (node_hook($node, 'view')) {
@@ -704,19 +743,16 @@ function node_menu($may_cache) {
           'access' => node_access('update', $node),
           'weight' => 1,
           'type' => MENU_LOCAL_TASK);
+        $items[] = array('path' => 'node/'. arg(1) .'/revisions', 'title' => t('revisions'),
+          'callback' => 'node_page',
+          'access' => user_access('administer nodes'),
+          'weight' => 2,
+          'type' => MENU_LOCAL_TASK);
         $items[] = array('path' => 'node/'. arg(1) .'/delete', 'title' => t('delete'),
           'callback' => 'node_page',
           'access' => node_access('delete', $node),
           'weight' => 1,
           'type' => MENU_CALLBACK);
-
-        if ($node->revisions) {
-          $items[] = array('path' => 'node/'. arg(1) .'/revisions', 'title' => t('revisions'),
-            'callback' => 'node_page',
-            'access' => user_access('administer nodes'),
-            'weight' => 2,
-            'type' => MENU_LOCAL_TASK);
-        }
       }
     }
     else if (arg(0) == 'admin' && arg(1) == 'node' && arg(2) == 'configure' && arg(3) == 'types' && is_string(arg(4))) {
@@ -985,13 +1021,26 @@ function node_revision_overview($nid) {
   if (user_access('administer nodes')) {
     $node = node_load(array('nid' => $nid));
 
-    drupal_set_title($node->title);
+    drupal_set_title(t('Revisions for %title', array('%title' => $node->title)));
+
+    if ($node->vid) {
+      $header = array('', t('Author'), t('Title'), t('Date'), array('colspan' => '3', 'data' => t('Operations')));
 
-    if ($node->revisions) {
-      $header = array(t('Older revisions'), array('colspan' => '3', 'data' => t('Operations')));
+      $revisions = node_revision_list($node);
 
-      foreach ($node->revisions as $key => $revision) {
-        $rows[] = array(t('revision #%r revised by %u on %d', array('%r' => $key, '%u' => format_name(user_load(array('uid' => $revision['uid']))), '%d' => format_date($revision['timestamp'], 'small'))) . ($revision['history'] ? '<br /><small>'. $revision['history'] .'</small>' : ''), l(t('view'), "node/$node->nid", array(), "revision=$key"), l(t('rollback'), "node/$node->nid/rollback-revision/$key"), l(t('delete'), "node/$node->nid/delete-revision/$key"));
+      $i = 0;
+      foreach ($revisions as $key => $revision) {
+        $rows[] = array(
+          array('data' => ++$i, 'rowspan' => ($revision->log != '') ? 2 : 1),
+          format_name($revision),
+          $revision->title,
+          format_date($revision->timestamp, 'small'),
+          l(t('view'), "node/$node->nid/revision/". $revision->vid),
+          l(t('rollback'), "node/$node->nid/rollback-revision/". $revision->vid),
+          l(t('delete'), "node/$node->nid/delete-revision/". $revision->vid));
+        if ($revision->log != '') {
+          $rows[] = array('', t('Log message:'), $revision->log, array('colspan' => 5));
+        }
       }
       $output .= theme('table', $header, $rows);
     }
@@ -1000,32 +1049,6 @@ function node_revision_overview($nid) {
   return $output;
 }
 
-
-/**
- * Return the revision with the specified revision number.
- */
-function node_revision_load($node, $revision) {
-  return $node->revisions[$revision]['node'];
-}
-
-/**
- * Create and return a new revision of the given node.
- */
-function node_revision_create($node) {
-  global $user;
-
-  // "Revision" is the name of the field used to indicate that we have to
-  // create a new revision of a node.
-  if ($node->nid && $node->revision) {
-    $prev = node_load(array('nid' => $node->nid));
-    $node->revisions = $prev->revisions;
-    unset($prev->revisions);
-    $node->revisions[] = array('uid' => $user->uid, 'timestamp' => time(), 'node' => $prev, 'history' => $node->history);
-  }
-
-  return $node;
-}
-
 /**
  * Roll back to the revision with the specified revision number.
  */
@@ -1033,29 +1056,13 @@ function node_revision_rollback($nid, $r
   global $user;
 
   if (user_access('administer nodes')) {
-    $node = node_load(array('nid' => $nid));
-
-    // Extract the specified revision:
-    $rev = $node->revisions[$revision]['node'];
 
-    // Inherit all the past revisions:
-    $rev->revisions = $node->revisions;
+    $node = node_load(array('nid' => $nid), $revision);
 
-    // Save the original/current node:
-    $rev->revisions[] = array('uid' => $user->uid, 'timestamp' => time(), 'node' => $node);
+    node_save($node);
 
-    // Remove the specified revision:
-    unset($rev->revisions[$revision]);
-
-    // Save the node:
-    foreach ($node as $key => $value) {
-      $filter[] = $key;
-    }
-
-    node_save($rev, $filter);
-
-    drupal_set_message(t('Rolled back to revision %revision of %title', array('%revision' => "<em>#$revision</em>", '%title' => "<em>$node->title</em>")));
-    drupal_goto('node/'. $nid .'/revisions');
+    drupal_set_message(t('Rolled back to the revision with the ID %revision of %title', array('%revision' => "<em>$revision</em>", '%title' => "<em>$node->title</em>")));
+    drupal_goto("node/$nid/revisions");
   }
 }
 
@@ -1064,14 +1071,22 @@ function node_revision_rollback($nid, $r
  */
 function node_revision_delete($nid, $revision) {
   if (user_access('administer nodes')) {
+    // Start a transaction so that the current node and node count
+    // don't change between the time we load it from the database and
+    // delete the node
+    db_begin_transaction(array('node', 'node_revisions', 'watchdog', 'sessions'));
     $node = node_load(array('nid' => $nid));
+    $revisions = node_revision_list($node);
 
-    unset($node->revisions[$revision]);
+    // Don't delete the last revision of the node or the current revision
+    if ((count($revisions) > 0) && ($revision != $node->vid)) {
+      db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", array($nid, $revision));
+    }
 
-    node_save($node, array('nid', 'revisions'));
+    db_commit_transaction();
 
-    drupal_set_message(t('Deleted revision %revision of %title', array('%revision' => "<em>#$revision</em>", '%title' => "<em>$node->title</em>")));
-    drupal_goto('node/'. $nid . (count($node->revisions) ? '/revisions' : ''));
+    drupal_set_message(t('Deleted revision with the ID %revision of %title', array('%revision' => "<em>$revision</em>", '%title' => "<em>$node->title</em>")));
+    drupal_goto("node/$nid/revisions");
   }
 }
 
@@ -1079,12 +1094,13 @@ function node_revision_delete($nid, $rev
  * Return a list of all the existing revision numbers.
  */
 function node_revision_list($node) {
-  if (is_array($node->revisions)) {
-    return array_keys($node->revisions);
-  }
-  else {
-    return array();
+  $revisions = array();
+  $result = db_query('SELECT r.vid, r.title, r.log, r.uid, r.timestamp, u.name FROM {node_revisions} r INNER JOIN {users} u ON u.uid = r.uid WHERE r.nid = %d ORDER BY r.timestamp ASC', $node->nid);
+  while ($revision = db_fetch_object($result)) {
+    $revisions[] = $revision;
   }
+
+  return $revisions;
 }
 
 /**
@@ -1216,9 +1232,6 @@ function node_validate($node) {
     form_set_error('changed', t('This content has been modified by another user, unable to save changes.'));
   }
 
-  // Create a new revision when required.
-  $node = node_revision_create($node);
-
   if (user_access('administer nodes')) {
     // Set up default values, if required.
     if (!$node->created) {
@@ -1588,8 +1601,12 @@ function node_delete($edit) {
   if (node_access('delete', $node)) {
 
     if ($edit['confirm']) {
-      // Delete the specified node:
+      // Delete the specified node: 
+      // We don't need to lock this in a transaction because once the
+      // entry in node disappears, the entry in node_revisions won't be
+      // accessable
       db_query('DELETE FROM {node} WHERE nid = %d', $node->nid);
+      db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);
 
       // Call the node-specific callback (if any):
       node_invoke($node, 'delete');
@@ -1677,9 +1694,21 @@ function node_page() {
     case 'delete-revision':
       node_revision_delete(arg(1), arg(3));
       break;
+    case 'revision':
+      if (is_numeric(arg(1)) && is_numeric(arg(3))) {
+        $node = node_load(array('nid' => arg(1)), arg(3));
+        if ($node->nid) {
+          drupal_set_title($node->title);
+          print theme('page', node_show($node, arg(2)));
+        }
+        else {
+          drupal_not_found();
+        }
+      }
+      break;
     case 'view':
       if (is_numeric(arg(1))) {
-        $node = node_load(array('nid' => arg(1)), $_GET['revision']);
+        $node = node_load(array('nid' => arg(1)));
         if ($node->nid) {
           drupal_set_title($node->title);
           print theme('page', node_show($node, arg(2)));
@@ -1779,9 +1808,6 @@ function node_nodeapi(&$node, $op, $arg 
     case 'settings':
       // $node contains the type name in this operation
       return form_checkboxes(t('Default options'), 'node_options_'. $node, variable_get('node_options_'. $node, array('status', 'promote')), array('status' => t('Published'), 'moderate' => t('In moderation queue'), 'promote' => t('Promoted to front page'), 'sticky' => t('Sticky at top of lists'), 'revision' => t('Create new revision')), t('Users with the <em>administer nodes</em> permission will be able to override these options.'));
-
-    case 'fields':
-      return array('nid', 'uid', 'type', 'title', 'teaser', 'body', 'revisions', 'status', 'promote', 'moderate', 'sticky', 'created', 'changed', 'format');
   }
 }
 
Index: modules/page.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/page.module,v
retrieving revision 1.132
diff -u -F^f -r1.132 page.module
--- modules/page.module	8 Feb 2005 19:44:39 -0000	1.132
+++ modules/page.module	5 Mar 2005 19:18:00 -0000
@@ -74,6 +74,8 @@ function page_form(&$node) {
   $output .= form_textarea(t('Body'), 'body', $node->body, 60, 20, '', NULL, TRUE);
   $output .= filter_form('format', $node->format);
 
+  $output .= form_textarea(t('Log message'), 'log', $node->log, 60, 5, t('An explanation of the additions or updates being made to help other authors understand your motivations.'));
+
   return $output;
 }
 
Index: modules/upload.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload.module,v
retrieving revision 1.28
diff -u -F^f -r1.28 upload.module
--- modules/upload.module	8 Feb 2005 19:43:02 -0000	1.28
+++ modules/upload.module	5 Mar 2005 19:18:20 -0000
@@ -282,10 +282,10 @@ function upload_nodeapi(&$node, $op, $ar
 
 function upload_count_size($uid = 0) {
   if ($uid) {
-    $result = db_query("SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node} n ON f.nid = n.nid WHERE uid = %d", $uid);
+    $result = db_query("SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node_revisions} n ON f.vid = n.vid WHERE uid = %d", $uid);
   }
   else {
-    $result = db_query("SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node} n ON f.nid = n.nid");
+    $result = db_query("SELECT SUM(f.filesize) FROM {files} f INNER JOIN {node_revisions} n ON f.vid = n.vid");
   }
 
   return db_result($result);
@@ -300,8 +300,8 @@ function upload_save($node) {
       // Insert new files:
       if ($file = file_save_upload($file, $file->filename)) {
         $fid = db_next_id('{files}_fid');
-        db_query("INSERT INTO {files} (fid, nid, filename, filepath, filemime, filesize, list) VALUES (%d, %d, '%s', '%s', '%s', %d, %d)",
-                 $fid, $node->nid, $file->filename, $file->filepath, $file->filemime, $file->filesize, $node->list[$key]);
+        db_query("INSERT INTO {files} (fid, nid, vid, filename, filepath, filemime, filesize, list) VALUES (%d, %d, %d, '%s', '%s', '%s', %d, %d)",
+                 $fid, $node->nid, $node->vid, $file->filename, $file->filepath, $file->filemime, $file->filesize, $node->list[$key]);
       }
     }
     else {
@@ -355,8 +355,8 @@ function upload_form($node) {
 function upload_load($node) {
   $files = array();
 
-  if ($node->nid) {
-    $result = db_query("SELECT * FROM {files} WHERE nid = %d", $node->nid);
+  if ($node->vid) {
+    $result = db_query("SELECT * FROM {files} WHERE vid = %d", $node->vid);
     while ($file = db_fetch_object($result)) {
       $files[$file->fid] = $file;
     }
