diff --git modules/simpletest/simpletest.info modules/simpletest/simpletest.info
index 63f61e6..076c606 100644
--- modules/simpletest/simpletest.info
+++ modules/simpletest/simpletest.info
@@ -40,3 +40,4 @@ files[] = tests/update.test
 files[] = tests/xmlrpc.test
 files[] = tests/upgrade/upgrade.test
 files[] = tests/upgrade/upgrade.poll.test
+files[] = tests/upgrade/upgrade.taxonomy.test
diff --git modules/simpletest/tests/upgrade/upgrade.taxonomy.test modules/simpletest/tests/upgrade/upgrade.taxonomy.test
new file mode 100644
index 0000000..5c3eec2
--- /dev/null
+++ modules/simpletest/tests/upgrade/upgrade.taxonomy.test
@@ -0,0 +1,122 @@
+<?php
+// $Id$
+
+/**
+ * Test taxonomy upgrades.
+ */
+class UpgradePathTaxonomyTestCase extends UpgradePathTestCase {
+  public static function getInfo() {
+    return array(
+      'name'  => 'Taxonomy upgrade path',
+      'description'  => 'Taxonomy upgrade path tests.',
+      'group' => 'Upgrade path',
+    );
+  }
+
+  public function setUp() {
+    // Path to the database dump.
+    $this->databaseDumpFile = drupal_get_path('module', 'simpletest') . '/tests/upgrade/drupal-6.filled.database.php';
+    parent::setUp();
+  }
+
+  /**
+   * Basic tests for the taxonomy upgrade.
+   */
+  public function testTaxonomyUpgrade() {
+    $this->assertTrue($this->performUpgrade(), t('The upgrade was completed successfully.'));
+
+    // Visit the front page to assert for PHP warning and errors.
+    $this->drupalGet('');
+
+    // Check that taxonomy_vocabulary_node_type and taxonomy_term_node have been
+    // removed.
+    $this->assertFalse(db_table_exists('taxonomy_vocabulary_node_type'), t('taxonomy_vocabulary_node_type has been removed.'));
+    $this->assertFalse(db_table_exists('taxonomy_term_node'), t('taxonomy_term_node has been removed.'));
+
+    $vocabularies = taxonomy_get_vocabularies();
+
+    // Check that the node type 'page' has been associated to a taxonomy
+    // reference field for each vocabulary.
+    foreach (field_info_instances('node', 'page') as $instance) {
+      $field = field_info_field($instance['field_name']);
+      if ($field['type'] == 'taxonomy_term_reference') {
+        foreach ($field['settings']['allowed_values'] as $tree) {
+
+          // Prefer valid taxonomy term reference fields for a given vocabulary
+          // when they exist.
+          if (empty($instances[$tree['vid']]) || $instances[$tree['vid']] == 'taxonomyextra') {
+            $instances[$tree['vid']] = $field['field_name'];
+          }
+        }
+      }
+    }
+    $this->assertEqual(sort(array_keys($vocabularies)), sort(array_keys($instances)), t('Node type page has instances for every vocabulary.'));
+
+    // Check that the node type 'story' has been associated to a taxonomy
+    // reference field for each vocabulary. It was not explicitely in
+    // $vocabulary->nodes but each node of type 'story' was associated to
+    // one or more terms.
+    $instances = array();
+    foreach (field_info_instances('node', 'story') as $instance) {
+      $field = field_info_field($instance['field_name']);
+      if ($field['type'] == 'taxonomy_term_reference') {
+        foreach ($field['settings']['allowed_values'] as $tree) {
+          if (empty($instances[$tree['vid']]) || $instances[$tree['vid']] == 'taxonomyextra') {
+            $instances[$tree['vid']] = $field['field_name'];
+          }
+        }
+      }
+    }
+    $field_names = array_flip($instances);
+    $this->assertEqual(count($field_names), 1, t('Only one taxonomy term field is used for on story nodes'));
+    $this->assertEqual(key($field_names), 'taxonomyextra', t('Only the excess taxonomy term field is used for on story nodes'));
+
+    // Check that the node type 'poll' has been associated to no taxonomy
+    // reference field.
+    $instances = array();
+    foreach (field_info_instances('node', 'poll') as $instance) {
+      $field = field_info_field($instance['field_name']);
+      if ($field['type'] == 'taxonomy_term_reference') {
+        foreach ($field['settings']['allowed_values'] as $tree) {
+          if (empty($instances[$tree['vid']]) || $instances[$tree['vid']] == 'taxonomyextra') {
+            $instances[$tree['vid']] = $field['field_name'];
+          }
+        }
+      }
+    }
+    $this->assertTrue(empty($instances), t('Node type poll has no taxonomy term reference field instances.'));
+
+    // Check that each node of type 'page' and 'story' is associated to all the
+    // terms except terms whose ID is equal to the node ID or is equal to the
+    // node ID subtracted from 49.
+    $nodes = node_load_multiple(array(), array('type' => 'page'));
+    $nodes += node_load_multiple(array(), array('type' => 'story'));
+    $terms = db_select('taxonomy_term_data', 'td')
+      ->fields('td')
+      ->execute()
+      ->fetchAllAssoc('tid');
+    field_attach_prepare_view('node', $nodes, 'full');
+    foreach ($nodes as $nid => $node) {
+      $node->content = field_attach_view('node', $node, 'full');
+      $this->drupalSetContent(drupal_render($node->content));
+      foreach ($terms as $tid => $term) {
+        $args = array(
+          '%name' => $term->name,
+          '@tid' => $tid,
+          '%nid' => $nid,
+        );
+
+        // Use link rather than term name because migrated term names can be
+        // substrings of other term names. e.g. "term 1 of vocabulary 2" is
+        // found when "term 1 of vocabulary 20" is output.
+        $link = l($term->name, 'taxonomy/term/' . $term->tid);
+        if (($tid == $nid) || ($tid + $nid == 49)) {
+          $this->assertNoRaw($link, t('Term %name (@tid) is not displayed on node %nid', $args));
+        }
+        else {
+          $this->assertRaw($link, t('Term %name (@tid) is displayed on node %nid', $args));
+        }
+      }
+    }
+  }
+}
diff --git modules/taxonomy/taxonomy.install modules/taxonomy/taxonomy.install
index 680403d..a95f5ca 100644
--- modules/taxonomy/taxonomy.install
+++ modules/taxonomy/taxonomy.install
@@ -412,7 +412,54 @@ function taxonomy_update_7004() {
       field_create_instance($instance);
     }
   }
-  db_drop_table('taxonomy_vocabulary_node_type');
+
+  // Some contrib projects stored term node associations without regard for the
+  // selections in the taxonomy_vocabulary_node_types table.
+
+  // Allowed values for this extra vocabs field is every vocabulary.
+  foreach (taxonomy_get_vocabularies() as $vocabulary) {
+    $allowed_values[] = array(
+      'vid' => $vocabulary->vid,
+      'parent' => 0,
+    );
+  }
+
+  $field_name = 'taxonomyextra';
+  $field = array(
+    'field_name' => $field_name,
+    'type' => 'taxonomy_term_reference',
+    'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+    'settings' => array(
+      'required' => FALSE,
+      'allowed_values' => $allowed_values,
+    ),
+  );
+  field_create_field($field);
+
+  foreach (node_type_get_types() as $bundle) {
+    $instance = array(
+      'label' => 'Taxonomy upgrade extras',
+      'field_name' => $field_name,
+      'bundle' => $bundle->type,
+      'entity_type' => 'node',
+      'description' => 'Debris left over after upgrade from Drupal 6',
+      'widget' => array(
+        'type' => 'taxonomy_autocomplete',
+      ),
+      'display' => array(
+        'default' => array(
+          'type' => 'taxonomy_term_reference_link',
+          'weight' => 10,
+        ),
+        'teaser' => array(
+          'type' => 'taxonomy_term_reference_link',
+          'weight' => 10,
+        ),
+      ),
+    );
+    field_create_instance($instance);
+  }
+
   $fields = array('help', 'multiple', 'required', 'tags');
   foreach ($fields as $field) {
     db_drop_field('taxonomy_vocabulary', $field);
@@ -423,14 +470,6 @@ function taxonomy_update_7004() {
  * Migrate {taxonomy_term_node} table to field storage.
  */
 function taxonomy_update_7005(&$sandbox) {
-  // Since we are upgrading from Drupal 6, we know that only
-  // field_sql_storage.module will be enabled.
-  $field = field_info_field($field['field_name']);
-  $data_table = _field_sql_storage_tablename($field);
-  $revision_table = _field_sql_storage_revision_tablename($field);
-  $etid = _field_sql_storage_etid('node');
-  $value_column = $field['field_name'] . '_value';
-  $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta', $value_column);
 
   // This is a multi-pass update. On the first call we need to initialize some
   // variables.
@@ -440,47 +479,144 @@ function taxonomy_update_7005(&$sandbox) {
 
     $query = db_select('taxonomy_term_node', 't');
     $sandbox['total'] = $query->countQuery()->execute()->fetchField();
-    $found = (bool) $sandbox['total'];
-  }
-  else {
-    // We do each pass in batches of 1000, this should result in a
-    // maximum of 2000 insert queries each operation.
-    $batch = 1000 + $sandbox['last'];
 
-    // Query and save data for the current revision.
-    $result = db_query_range('SELECT td.tid, tn.nid, td.weight, tn.vid, n2.type, n2.created, n2.sticky FROM {taxonomy_term_data} td INNER JOIN {taxonomy_term_node} tn ON td.tid = tn.tid INNER JOIN {node} n2 ON tn.nid = n2.nid INNER JOIN {node} n ON tn.vid = n.vid AND td.vid = :vocabulary_id ORDER BY td.weight ASC', array(':vocabulary_id' => $vocabulary->vid), $sandbox['last'], $batch);
-    $deltas = array();
+    // Use an inline version of Drupal 6 taxonomy_get_vocabularies() here since
+    // we can no longer rely on $vocabulary->nodes from the API function.
+    $result = db_query('SELECT v.vid, v.machine_name, n.type FROM {taxonomy_vocabulary} v INNER JOIN {taxonomy_vocabulary_node_type} n ON v.vid = n.vid');
+    $vocabularies = array();
     foreach ($result as $record) {
-      $found = TRUE;
-      $sandbox['count'] += 1;
-      // Start deltas from 0, and increment by one for each
-      // term attached to a node.
-      $deltas[$record->nid] = isset($deltas[$record->nid]) ? ++$deltas[$record->nid] : 0;
-      $values = array($etid, $record->nid, $record->vid, $record->type, $deltas[$record->nid], $record->tid);
-      db_insert($data_table)->fields($columns)->values($values)->execute();
-
-      // Update the {taxonomy_index} table.
-      db_insert('taxonomy_index')
-        ->fields(array('nid', 'tid', 'sticky', 'created',))
-        ->values(array($record->nid, $record->tid, $record->sticky, $record->created))
-        ->execute();
+
+      // If no node types are associated with a vocabulary, the LEFT JOIN will
+      // return a NULL value for type.
+      if (isset($record->type)) {
+        $vocabularies[$record->vid][$record->type] = 'taxonomy_'. $record->machine_name;
+      }
+    }
+
+    if (!empty($vocabularies)) {
+      $sandbox['vocabularies'] = $vocabularies;
     }
+  }
+  else {
+    $field_info = field_info_fields();
+    $etid = _field_sql_storage_etid('node');
 
-    // Query and save data for all revisions.
-    $result = db_query('SELECT td.tid, tn.nid, td.weight, tn.vid, n.type FROM {taxonomy_term_data} td INNER JOIN {taxonomy_term_node} tn ON td.tid = tn.tid AND td.vid = :vocabulary_id INNER JOIN {node} n ON tn.nid = n.nid ORDER BY td.weight ASC', array(':vocabulary_id' => $vocabulary->vid), $sandbox['last'][$batch]);
-    $deltas = array();
+    // We do each pass in batches of 1000.
+    $batch = 1000;
+
+    // Query selects all revisions at once and processes them in revision and
+    // term weight order.
+    $query = 'SELECT td.vid AS vocab_id, td.tid, tn.nid, tn.vid, n.type, n2.created, n2.sticky, n2.nid AS is_current FROM {taxonomy_term_data} td INNER JOIN {taxonomy_term_node} tn ON td.tid = tn.tid LEFT JOIN {node} n ON tn.nid = n.nid LEFT JOIN {node} n2 ON tn.vid = n2.vid ORDER BY tn.vid, td.weight ASC';
+    $result = db_query_range($query, $sandbox['last'], $batch);
+    if (isset($sandbox['cursor'])) {
+      $values = $sandbox['cursor']['values'];
+      $deltas = $sandbox['cursor']['deltas'];
+    }
+    else {
+      $deltas = array();
+    }
     foreach ($result as $record) {
-      $found = TRUE;
       $sandbox['count'] += 1;
-      // Start deltas at 0, and increment by one for each term attached to a revision.
-      $deltas[$record->vid] = isset($deltas[$record->vid]) ? ++$deltas[$record->vid] : 0;
-      $values = array($etid, $record->nid, $record->vid, $record->type, $deltas[$record->vid], $record->tid);
-      db_insert($revision_table)->fields($columns)->values($values)->execute();
+
+      // Use the valid field for this vocabulary and node type or use the
+      // overflow vocabulary if there is no valid field.
+      $field_name = isset($sandbox['vocabularies'][$record->vocab_id][$record->type]) ? $sandbox['vocabularies'][$record->vocab_id][$record->type] : 'taxonomyextra';
+      $field = $field_info[$field_name];
+
+      // Start deltas from 0, and increment by one for each term attached to a
+      // node.
+      if (!isset($deltas[$field_name])) {
+        $deltas[$field_name] = 0;
+      }
+
+      if (isset($values)) {
+
+        // If the last inserted revision_id is the same as the current record,
+        // use the previous deltas to calculate the next delta.
+        if ($record->vid == $values[2]) {
+
+          // see field_default_validate().
+          if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ($deltas[$field_name] + 1) >= $field['cardinality']) {
+
+            // For excess values for a single value field, switch over to the
+            // overflow field.
+            $field_name = 'taxonomyextra';
+            $field = $field_info[$field_name];
+            if (!isset($deltas[$field_name])) {
+              $deltas[$field_name] = 0;
+            }
+          }
+        }
+        else {
+
+          // When the record is a new revision, empty the deltas array.
+          $deltas = array($field_name => 0);
+        }
+      }
+
+      // Table and column found in the field's storage details. During upgrades,
+      // it's always SQL.
+      $table = key($field['storage']['details']['sql'][FIELD_LOAD_REVISION]);
+      $value_column = $field['storage']['details']['sql'][FIELD_LOAD_REVISION][$table]['tid'];
+
+      // Column names and values in field storage are the same for current and
+      // revision.
+      $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'language', 'delta', $value_column);
+      $values = array($etid, $record->nid, $record->vid, $record->type, LANGUAGE_NONE, $deltas[$field_name]++, $record->tid);
+
+      // Insert rows into the revision table.
+      db_insert($table)->fields($columns)->values($values)->execute();
+
+      // is_current column is a node ID if this revision is also current.
+      if ($record->is_current) {
+        $table = key($field['storage']['details']['sql'][FIELD_LOAD_CURRENT]);
+        db_insert($table)->fields($columns)->values($values)->execute();
+
+        // Update the {taxonomy_index} table.
+        db_insert('taxonomy_index')
+          ->fields(array('nid', 'tid', 'sticky', 'created',))
+          ->values(array($record->nid, $record->tid, $record->sticky, $record->created))
+          ->execute();
+      }
     }
-    $sandbox['last'] = $batch;
+
+    // Store the set of inserted values and the current revision's deltas in the
+    // sandbox.
+    $sandbox['cursor'] = array(
+      'values' => $values,
+      'deltas' => $deltas,
+    );
+    $sandbox['last'] += $batch;
+  }
+
+  if ($sandbox['count'] < $sandbox['total']) {
+    $sandbox['#finished'] = FALSE;
   }
-  if (!$found) {
-   db_drop_table('taxonomy_term_node');
+  else {
+    db_drop_table('taxonomy_vocabulary_node_type');
+    db_drop_table('taxonomy_term_node');
+
+    // If there are no vocabs, we're done.
+    $sandbox['#finished'] = TRUE;
+
+    // Determine necessity of taxonomyextras field.
+    $field = $field_info['taxonomyextra'];
+    $table = key($field['storage']['details']['sql'][FIELD_LOAD_REVISION]);
+    $node_types = db_select($table)->distinct()->fields($table, array('bundle'))
+      ->execute()->fetchCol();
+
+    if (empty($node_types)) {
+      // Delete the overflow field if there are no rows in the revision table.
+      field_delete_field('taxonomyextra');
+    }
+    else {
+      // Remove instances which are not actually used.
+      $bundles = array_diff($field['bundles']['node'], $node_types);
+      foreach ($bundles as $bundle) {
+        $instance = field_info_instance('node', 'taxonomyextra', $bundle);
+        field_delete_instance($instance);
+      }
+    }
   }
 }
 
