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..6666f49 100644
--- modules/taxonomy/taxonomy.install
+++ modules/taxonomy/taxonomy.install
@@ -412,7 +412,78 @@ function taxonomy_update_7004() {
       field_create_instance($instance);
     }
   }
-  db_drop_table('taxonomy_vocabulary_node_type');
+
+  $node_types = array();
+
+  // Some contrib projects stored term node associations without regard for the
+  // selections in the taxonomy_vocabulary_node_types table.
+
+  // This query is unnecessary if we just decide to create an overflow field by
+  // default and set its allowed values to include every vocabulary.
+  $result = db_query('SELECT DISTINCT td.vid, n.type FROM {taxonomy_term_node} tn LEFT JOIN {node} n ON tn.nid = n.nid LEFT JOIN {taxonomy_term_data} td ON tn.tid = td.tid');
+  foreach ($result as $record) {
+    if (empty($vocabularies[$record->vid]->nodes[$record->type])) {
+      $node_types[$record->vid][$record->type] = $record->type;
+      unset($record->type);
+      $record->nodes = $node_types[$record->vid];
+      $extra_vocabularies[$record->vid] = $record;
+    }
+  }
+
+  if (!empty($extra_vocabularies)) {
+
+    $node_types = array();
+
+    // Allowed values for this extra vocabs field is every vocabulary not
+    // included by a core node type association.
+    foreach ($extra_vocabularies as $vid => $record) {
+      $allowed_values[] = array(
+        'vid' => $vid,
+        'parent' => 0,
+      );
+      $node_types = array_merge($node_types, $record->nodes);
+    }
+
+    // TODO: Bike shed this field name.
+    $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_types as $bundle) {
+      $instance = array(
+
+        // There is no valid instance.
+        'label' => 'Taxonomy upgrade extras',
+        'field_name' => $field_name,
+        'bundle' => $bundle,
+        '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 +494,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 +503,121 @@ 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;
+      }
     }
 
-    // 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();
+    if (!empty($vocabularies)) {
+      $sandbox['vocabularies'] = $vocabularies;
+    }
+  }
+  else {
+    $field_info = field_info_fields();
+    $etid = _field_sql_storage_etid('node');
+
+    // 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];
+          }
+        }
+        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;
   }
 }
 
