Index: wp-includes/wp-db.php
===================================================================
--- wp-includes/wp-db.php	(revision 5496)
+++ wp-includes/wp-db.php	(working copy)
@@ -34,6 +34,9 @@
 	var $optiongroups;
 	var $optiongroup_options;
 	var $postmeta;
+	var $terms;
+	var $term_taxonomy;
+	var $term_relationships;
 
 	var $charset;
 	var $collate;
Index: wp-includes/taxonomy.php
===================================================================
--- wp-includes/taxonomy.php	(revision 0)
+++ wp-includes/taxonomy.php	(revision 0)
@@ -0,0 +1,165 @@
+<?php
+
+/**
+ * Adds a new term to the database.  Optionally marks it as an alias of an existing term.
+ * @param string $term The term to add.
+ * @param string $taxonomy The taxonomy to which to add the term
+ * @param int|string $alias_of The id or slug of the new term's alias.
+ */
+function add_term( $term, $taxonomy, $args = array() ) {
+	global $wpdb;
+	$term_slug = sanitize_title($term);
+	$defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0);
+	$args = wp_parse_args($args, $defaults);
+	extract($args);
+
+	$term_group = 0;	
+	if ($alias_of) {
+		$alias = $wpdb->fetch_row("SELECT term_id, term_group FROM $wpdb->terms WHERE term_slug = '$alias_of'");
+		if ($alias->term_group) {
+			// The alias we want is already in a group, so let's use that one.
+			$term_group = $alias->term_group;
+		} else {
+			// The alias isn't in a group, so let's create a new one and firstly add the alias term to it.
+			$term_group = $wpdb->get_var("SELECT MAX() term_group FROM $wpdb->terms GROUP BY term_group") + 1;
+			$wpdb->query("UPDATE $wpdb->terms SET term_group = $term_group WHERE term_id = $alias->term_id");
+		}
+	}
+
+	// Now add or replace the term.  This works because we have a UNIQUE key on term_slug.
+	$wpdb->query("REPLACE INTO $wpdb->terms (term_name, term_slug, term_group) VALUES ('$term', '$term_slug', '$term_group')");
+	$term_id = (int) $wpdb->insert_id;
+	
+	$tt_id = $wpdb->get_var("SELECT tt.term_taxonomy_id FROM $wpdb->term_taxonomy AS tt INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = '$taxonomy' AND t.term_id = $term_id");
+
+	if ( ! empty($tt_id) )
+		return $tt_id;
+			
+	$wpdb->query("INSERT INTO $wpdb->term_taxonomy (term_id, taxonomy, term_description, parent, count) VALUES ('$term_id', '$taxonomy', '$description', '$parent', '0')");
+}
+
+/**
+ * Removes a term from the database.
+ */
+function remove_term() {}
+	
+	
+/**
+ * Returns the index of a defined term, or 0 (false) if the term doesn't exist.
+ */
+function is_term($term, $taxonomy = '') {
+	global $wpdb;
+	$term = sanitize_title($term);
+	return $wpdb->get_var("SELECT term_id FROM $wpdb->terms WHERE term_slug = '$term'");
+}
+	
+/**
+ * Given an array of terms, returns those that are defined term slugs.  Ignores integers.
+ * @param array $terms The term slugs to check for a definition.
+ */
+function get_defined_terms($terms) {
+	global $wpdb;
+
+	foreach ($terms as $term) {
+		if (!is_int($term)) {
+			$searches[] = $term;
+		}
+	}
+	$terms = "'" . implode("', '", $searches) . "'";
+	return $wpdb->get_col("SELECT term_slug FROM $wpdb->terms WHERE term_slug IN ($terms)");
+}
+	
+/**
+ * Relates an object (post, link etc) to a term and taxonomy type.  Creates the term and taxonomy
+ * relationship if it doesn't already exist.  Creates a term if it doesn't exist (using the term_slug).
+ * @param array|int|string $term The slug or id of the term.
+ * @param int $object_id The object to relate to.
+ * @param array|string $taxonomies The context(s) in which to relate the term to the object.
+ */
+function add_term_relationship($terms, $object_id, $taxonomies) {
+	global $wpdb;
+		
+	if (!is_array($taxonomies)) {
+		$taxonomies = array($taxonomies);
+	}
+	
+	if (!is_array($terms)) {
+		$terms = array($terms);
+	}
+		
+	$defined_terms = get_defined_terms($terms);
+
+	foreach ($terms as $term) {
+		if (!is_int($term)) {
+			if (!isset($defined_terms[$term])) {
+				$new_terms[] = $term;
+			}
+			$term_slugs[] = $term;
+		} else {
+			$term_ids[] = $term;
+		}
+	}
+
+	$term_clause = isset($term_ids) ? 'tt.term_id IN (' . implode(', ', $term_ids) . ')' : '';
+	if (isset($term_slugs)) {
+		if ($term_clause) {
+			$term_clause .= ' OR ';
+		}
+		$term_clause .= "t.term_slug IN ('" . implode("', '", $term_slugs) . "')";
+		$term_join = "INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id";
+	} else {
+		$term_join = '';
+	}
+		
+	// Now add or increment the term taxonomy relationships.  This is inefficient at the moment.
+	foreach ($taxonomies as $taxonomy) {
+		foreach ($terms as $term) {
+			add_term($term, $taxonomy);
+		}
+	}
+		
+	$taxonomies = "'" . implode("', '", $taxonomies) . "'";
+		
+	// Finally, relate the term and taxonomy to the object.
+	$wpdb->query("INSERT INTO $wpdb->term_relationships(object_id, term_taxonomy_id) SELECT '$object_id', term_taxonomy_id FROM $wpdb->term_taxonomy AS tt $term_join WHERE ($term_clause) AND tt.taxonomy IN ($taxonomies)");
+}
+	
+/**
+ * Returns the terms associated with the given object(s), in the supplied taxonomies.
+ * @param int|array $object_id The id of the object(s)) to retrieve for.
+ * @param string|array $taxonomies The taxonomies to retrieve terms from.
+ * @return array The requested term data.	 	 	 
+ */
+function get_object_terms($object_id, $taxonomy) {
+	global $wpdb;
+	$taxonomies = ($single_taxonomy = !is_array($taxonomy)) ? array($taxonomy) : $taxonomy;
+	$object_ids = ($single_object = !is_array($object_id)) ? array($object_id) : $object_id;
+
+	$taxonomies = "'" . implode("', '", $taxonomies) . "'";		
+	$object_ids = implode(', ', $object_ids);		
+
+	if ( $taxonomy_data = $wpdb->get_results("SELECT t.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON tt.term_id = t.term_id INNER JOIN $wpdb->term_relationships AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy IN ($taxonomies) AND tr.object_id IN ($object_ids)") ) {
+		if ($single_taxonomy && $single_object) {
+			// Just one kind of taxonomy for one object.
+			return $taxonomy_data;
+		} else {
+			foreach ($taxonomy_data as $data) {
+				if ($single_taxonomy) {
+					// Many objects, one taxonomy type.
+					$return[$data->object_id][] = $data;
+				} elseif ($single_object) {
+					// One object, many taxonomies.
+					$return[$data->taxonomy][] = $data;
+				} else {
+					// Many objects, many taxonomies.
+					$return[$data->object_id][$data->taxonomy][] = $data;
+				}
+			}
+			return $return;			
+		}
+	} else {
+		return array();
+	}		
+}	
+
+?>
Index: wp-includes/version.php
===================================================================
--- wp-includes/version.php	(revision 5496)
+++ wp-includes/version.php	(working copy)
@@ -3,6 +3,6 @@
 // This holds the version number in a separate file so we can bump it without cluttering the SVN
 
 $wp_version = '2.3-alpha';
-$wp_db_version = 5200;
+$wp_db_version = 5495;
 
 ?>
Index: wp-includes/post.php
===================================================================
--- wp-includes/post.php	(revision 5496)
+++ wp-includes/post.php	(working copy)
@@ -459,10 +459,8 @@
 
 	$post_id = (int) $post_id;
 	
-	if ( !isset( $tag_cache[$blog_id][$post_id] ) )
-		update_post_category_cache( $post_id ); // loads $tag_cache
-
-	return $tag_cache[$blog_id][$post_id];
+	$tags = get_object_terms($post_id, 'post_tag');
+	return $tags;
 }
 
 function wp_get_recent_posts($num = 10) {
@@ -792,76 +790,11 @@
 	
 	if ( !$post_id )
 		return false;
-	
-	// prevent warnings for unintialized variables
-	$tag_ids = array();
 
 	if ( empty($tags) )
 		$tags = array();
 	$tags = (is_array($tags)) ? $tags : explode( ',', $tags );
-	
-	foreach ( $tags as $tag ) {
-		$tag = trim( $tag );
-		if ( !$tag_slug = sanitize_title( $tag ) )
-			continue; // discard
-		if ( !$tag_id = tag_exists( $tag ) )
-			$tag_id = wp_create_tag( $tag );
-		$tag_ids[] = $tag_id;
-	}
-
-	if ( empty($tag_ids) && ( !empty($tags) || $append ) )
-		return false;
-	
-	$tag_ids = array_unique( $tag_ids );
-	
-	// First the old tags
-	$old_tags = $wpdb->get_col("
-		SELECT category_id
-		FROM $wpdb->post2cat
-		WHERE post_id = '$post_id' AND rel_type = 'tag'");
-	
-	if ( !$old_tags ) {
-		$old_tags = array();
-	} else {
-		$old_tags = array_unique( $old_tags );
-	}
-	
-	// Delete any?
-	$delete_tags = array_diff( $old_tags, $tag_ids);
-	if ( $delete_tags && !$append ) {
-		foreach ( $delete_tags as $del ) {
-			$wpdb->query("
-				DELETE FROM $wpdb->post2cat
-				WHERE category_id = '$del'
-					AND post_id = '$post_id'
-					AND rel_type = 'tag'
-				");
-		}
-	}
-	
-	// Add any?
-	$add_tags = array_diff( $tag_ids, $old_tags );
-	if ( $add_tags ) {
-		foreach ( $add_tags as $new_tag ) {
-			$new_tag = (int) $new_tag;
-			if ( !empty($new_tag) )
-				$wpdb->query("
-					INSERT INTO $wpdb->post2cat (post_id, category_id, rel_type) 
-					VALUES ('$post_id', '$new_tag', 'tag')");
-		}
-	}
-	
-	// Update category counts.
-	$all_affected_tags = array_unique( array_merge( $tag_ids, $old_tags ) );
-	foreach ( $all_affected_tags as $tag_id ) {
-		$count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->post2cat, $wpdb->posts WHERE $wpdb->posts.ID=$wpdb->post2cat.post_id AND post_status = 'publish' AND post_type = 'post' AND category_id = '$tag_id' AND rel_type = 'tag'" );
-		$wpdb->query( "UPDATE $wpdb->categories SET tag_count = '$count', type = type | " . TAXONOMY_TAG . " WHERE cat_ID = '$tag_id'" );
-		if ( $count == 0 )
-			$wpdb->query( "UPDATE $wpdb->categories SET type = type & ~". TAXONOMY_TAG . " WHERE cat_ID = '$tag_id'" );
-		clean_category_cache( $tag_id );
-		do_action( 'edit_category', $tag_id );
-		do_action( 'edit_tag', $tag_id );
-	}
+	add_term_relationship($tags, $post_id, 'post_tag');
 }
 
 function wp_set_post_categories($post_ID = 0, $post_categories = array()) {
Index: wp-settings.php
===================================================================
--- wp-settings.php	(revision 5496)
+++ wp-settings.php	(working copy)
@@ -116,6 +116,9 @@
 $wpdb->options        = $wpdb->prefix . 'options';
 $wpdb->postmeta       = $wpdb->prefix . 'postmeta';
 $wpdb->usermeta       = $wpdb->prefix . 'usermeta';
+$wpdb->terms          = $wpdb->prefix . 'terms';
+$wpdb->term_taxonomy  = $wpdb->prefix . 'term_taxonomy';
+$wpdb->term_relationships = $wpdb->prefix . 'term_relationships';
 
 if ( defined('CUSTOM_USER_TABLE') )
 	$wpdb->users = CUSTOM_USER_TABLE;
@@ -168,6 +171,7 @@
 require (ABSPATH . WPINC . '/version.php');
 require (ABSPATH . WPINC . '/deprecated.php');
 require (ABSPATH . WPINC . '/script-loader.php');
+require (ABSPATH . WPINC . '/taxonomy.php');
 
 if (strpos($_SERVER['PHP_SELF'], 'install.php') === false) {
     // Used to guarantee unique hash cookies
Index: wp-admin/admin-functions.php
===================================================================
--- wp-admin/admin-functions.php	(revision 5496)
+++ wp-admin/admin-functions.php	(working copy)
@@ -664,16 +664,13 @@
 	if ( !$post_id )
 		return false;
 
-	$tags = $wpdb->get_results( "
-		     SELECT category_id, cat_name
-		     FROM $wpdb->categories, $wpdb->post2cat
-		     WHERE $wpdb->post2cat.category_id = cat_ID AND $wpdb->post2cat.post_id = '$post_id' AND rel_type = 'tag'
-		     " );
+	$tags = wp_get_post_tags($post_id);
+
 	if ( !$tags )
 		return false;
 
 	foreach ( $tags as $tag )
-		$tag_names[] = $tag->cat_name;
+		$tag_names[] = $tag->term_name;
 	$tags_to_edit = join( ', ', $tag_names );
 	$tags_to_edit = attribute_escape( $tags_to_edit );
 	$tags_to_edit = apply_filters( 'tags_to_edit', $tags_to_edit );
Index: wp-admin/admin-db.php
===================================================================
--- wp-admin/admin-db.php	(revision 5496)
+++ wp-admin/admin-db.php	(working copy)
@@ -286,23 +286,14 @@
 	if (! $tag_nicename = sanitize_title($tag_name))
 		return 0;
 
-	return (int) $wpdb->get_var("SELECT cat_ID FROM $wpdb->categories WHERE category_nicename = '$tag_nicename' AND ( type & " . TAXONOMY_TAG .  " != 0 )");
+	return is_term($tag_name, 'post_tag');
 }
 
 function wp_create_tag($tag_name) {
 	if ( $id = tag_exists($tag_name) )
 		return $id;
-	$tag_array = array('cat_name' => $tag_name, 'type' => TAXONOMY_TAG);
 
-	if ( $id = category_object_exists($tag_name) ) {
-		$category = get_category($id);
-		$tag_array['type'] = $category->type | $tag_array['type'];
-		$tag_array['cat_ID'] = $id;
-		$id = wp_update_category($tag_array);
-		return $id;
-	} else {
-		return wp_insert_category($tag_array);
-	}
+	$tag_id = add_term($tag_name, 'post_tag');	
 }
 
 function wp_delete_user($id, $reassign = 'novalue') {
Index: wp-admin/upgrade-schema.php
===================================================================
--- wp-admin/upgrade-schema.php	(revision 5496)
+++ wp-admin/upgrade-schema.php	(working copy)
@@ -10,21 +10,30 @@
 		$charset_collate .= " COLLATE $wpdb->collate";
 }
 
-$wp_queries="CREATE TABLE $wpdb->categories (
-  cat_ID bigint(20) NOT NULL auto_increment,
-  cat_name varchar(55) NOT NULL default '',
-  category_nicename varchar(200) NOT NULL default '',
-  category_description longtext NOT NULL,
-  category_parent bigint(20) NOT NULL default '0',
-  category_count bigint(20) NOT NULL default '0',
-  link_count bigint(20) NOT NULL default '0',
-  tag_count bigint(20) NOT NULL default '0',
-  posts_private tinyint(1) NOT NULL default '0',
-  links_private tinyint(1) NOT NULL default '0',
-  type tinyint NOT NULL default '1',
-  PRIMARY KEY  (cat_ID),
-  KEY category_nicename (category_nicename)
+$wp_queries="CREATE TABLE $wpdb->terms (
+ term_id bigint(20) NOT NULL auto_increment,
+ term_name varchar(55) NOT NULL default '',
+ term_slug varchar(200) NOT NULL default '',
+ term_group bigint(10) NOT NULL default 0,
+ PRIMARY KEY  (term_id),
+ UNIQUE KEY term_slug (term_slug)
 ) $charset_collate;
+CREATE TABLE $wpdb->term_taxonomy (
+ term_taxonomy_id bigint(20) NOT NULL auto_increment,
+ term_id bigint(20) NOT NULL default 0,
+ taxonomy varchar(32) NOT NULL default '',
+ term_description longtext NOT NULL,
+ parent bigint(20) NOT NULL default 0,
+ count bigint(20) NOT NULL default 0,
+ PRIMARY KEY (term_taxonomy_id),
+ UNIQUE KEY (term_id, taxonomy)
+) $charset_collate;
+CREATE TABLE $wpdb->term_relationships (
+ object_id bigint(20) NOT NULL default 0,
+ term_taxonomy_id bigint(20) NOT NULL default 0,
+ PRIMARY KEY  (object_id),
+ KEY (term_taxonomy_id)
+) $charset_collate;
 CREATE TABLE $wpdb->comments (
   comment_ID bigint(20) unsigned NOT NULL auto_increment,
   comment_post_ID int(11) NOT NULL default '0',
@@ -45,13 +54,6 @@
   KEY comment_approved (comment_approved),
   KEY comment_post_ID (comment_post_ID)
 ) $charset_collate;
-CREATE TABLE $wpdb->link2cat (
-  rel_id bigint(20) NOT NULL auto_increment,
-  link_id bigint(20) NOT NULL default '0',
-  category_id bigint(20) NOT NULL default '0',
-  PRIMARY KEY  (rel_id),
-  KEY link_id (link_id,category_id)
-) $charset_collate;
 CREATE TABLE $wpdb->links (
   link_id bigint(20) NOT NULL auto_increment,
   link_url varchar(255) NOT NULL default '',
@@ -86,14 +88,6 @@
   PRIMARY KEY  (option_id,blog_id,option_name),
   KEY option_name (option_name)
 ) $charset_collate;
-CREATE TABLE $wpdb->post2cat (
-  rel_id bigint(20) NOT NULL auto_increment,
-  post_id bigint(20) NOT NULL default '0',
-  category_id bigint(20) NOT NULL default '0',
-  rel_type varchar(64) NOT NULL default 'category',
-  PRIMARY KEY  (rel_id),
-  KEY post_id (post_id,category_id)
-) $charset_collate;
 CREATE TABLE $wpdb->postmeta (
   meta_id bigint(20) NOT NULL auto_increment,
   post_id bigint(20) NOT NULL default '0',
@@ -404,4 +398,4 @@
 	}
 }
 
-?>
\ No newline at end of file
+?>
