Index: wp-includes/taxonomy.php
===================================================================
--- wp-includes/taxonomy.php	(revision 15595)
+++ wp-includes/taxonomy.php	(working copy)
@@ -23,12 +23,12 @@
 		'public' => true,
 		'show_ui' => true,
 		'_builtin' => true,
-	) ) ;
+	) );
 
 	register_taxonomy( 'post_tag', 'post', array(
 	 	'hierarchical' => false,
 		'update_count_callback' => '_update_post_term_count',
-		'query_var' => false,
+		'query_var' => 'tag',
 		'rewrite' => false,
 		'public' => true,
 		'show_ui' => true,
Index: wp-includes/theme.php
===================================================================
--- wp-includes/theme.php	(revision 15595)
+++ wp-includes/theme.php	(working copy)
@@ -813,9 +813,12 @@
  * @return string
  */
 function get_tag_template() {
-	$tag_id = absint( get_query_var('tag_id') );
-	$tag_name = get_query_var('tag');
+	global $wp_query;
 
+	$tag = $wp_query->get_queried_object();
+	$tag_name = $tag->slug;
+	$tag_id = $tag->term_id;
+
 	$templates = array();
 
 	if ( $tag_name )
Index: wp-includes/functions.php
===================================================================
--- wp-includes/functions.php	(revision 15595)
+++ wp-includes/functions.php	(working copy)
@@ -4293,6 +4293,81 @@
 }
 
 /*
+ * Used internally to generate an SQL string for searching across multiple taxonomies
+ *
+ * @access private
+ * @since 3.1.0
+ *
+ * @param array $queries An array of queries
+ * @return string SQL string
+ */
+function _wp_tax_sql( $queries ) {
+	global $wpdb;
+
+	$sql = array();
+	foreach ( $queries as $query ) {
+		$taxonomy = $query['taxonomy'];
+		$terms = array_unique( $query['terms'] );
+		$field = $query['field'];
+
+		if ( !in_array( $field, array( 'term_id', 'slug', 'name' ) ) )
+			$field = 'term_id';
+
+		$operator = $query['operator'];
+		if ( !in_array( $operator, array( 'IN', 'NOT IN' ) ) )
+			$operator = 'IN';
+
+		switch ( $field ) {
+			case 'term_id':
+				$terms = array_map( 'intval', $terms );
+
+				if ( is_taxonomy_hierarchical( $taxonomy ) ) {
+					$children = $terms;
+					foreach ( $terms as $term )
+						$children = array_merge( $children, get_term_children( $term, $taxonomy ) );
+					$terms = $children;
+				}
+
+				$terms = implode( ',', $terms );
+				$sql[] = $wpdb->prepare( "
+					SELECT object_id
+					FROM $wpdb->term_relationships
+					INNER JOIN $wpdb->term_taxonomy USING (term_taxonomy_id)
+					WHERE taxonomy = %s
+					AND term_id $operator ($terms)
+				", $taxonomy );
+			break;
+
+			case 'slug':
+			case 'name':
+				foreach ( $terms as $i => $term ) {
+					$terms[$i] = sanitize_term_field('slug', $term, 0, $taxonomy, 'db');
+				}
+				$terms = array_filter($terms);
+
+				$terms = "'" . implode( "','", $terms ) . "'";
+				$sql[] = $wpdb->prepare( "
+					SELECT object_id
+					FROM $wpdb->term_relationships
+					INNER JOIN $wpdb->term_taxonomy USING (term_taxonomy_id)
+					INNER JOIN $wpdb->terms USING (term_id)
+					WHERE taxonomy = %s
+					AND $field $operator ($terms)
+				", $taxonomy );
+		}
+	}
+
+	if ( 1 == count( $sql ) )
+		return $sql[0];
+
+	$r = "SELECT object_id FROM $wpdb->term_relationships WHERE 1=1";
+	foreach ( $sql as $query )
+		$r .= " AND object_id IN ($query)";
+
+	return $r;
+}
+
+/*
  * Used internally to tidy up the search terms
  *
  * @access private
Index: wp-includes/query.php
===================================================================
--- wp-includes/query.php	(revision 15595)
+++ wp-includes/query.php	(working copy)
@@ -662,6 +662,15 @@
 	var $query_vars = array();
 
 	/**
+	 * Taxonomy query, after parsing
+	 *
+	 * @since 3.1.0
+	 * @access public
+	 * @var array
+	 */
+	var $tax_query = array();
+
+	/**
 	 * Holds the data for a single object that is queried.
 	 *
 	 * Holds the contents of a post, page, category, attachment.
@@ -1528,7 +1537,6 @@
 
 		// First let's clear some variables
 		$distinct = '';
-		$whichcat = '';
 		$whichauthor = '';
 		$whichmimetype = '';
 		$where = '';
@@ -1785,13 +1793,38 @@
 		// Allow plugins to contextually add/remove/modify the search section of the database query
 		$search = apply_filters_ref_array('posts_search', array( $search, &$this ) );
 
-		// Category stuff
+		// Taxonomies
+		$tax_query = array();
 
-		if ( empty($q['cat']) || ($q['cat'] == '0') ||
-				// Bypass cat checks if fetching specific posts
-				$this->is_singular ) {
-			$whichcat = '';
-		} else {
+		if ( $this->is_tax ) {
+			foreach ( $GLOBALS['wp_taxonomies'] as $taxonomy => $t ) {
+				if ( $t->query_var && !empty( $q[$t->query_var] ) ) {
+					$tax_query_defaults = array(
+						'taxonomy' => $taxonomy,
+						'field' => 'slug',
+						'operator' => 'IN'
+					);
+
+					$term = str_replace( ' ', '+', $q[$t->query_var] );
+
+					if ( strpos($term, '+') !== false ) {
+						$terms = preg_split( '/[+\s]+/', $term );
+						foreach ( $terms as $term ) {
+							$tax_query[] = array_merge( $tax_query_defaults, array(
+								'terms' => array( $term )
+							) );
+						}
+					} else {
+						$tax_query[] = array_merge( $tax_query_defaults, array(
+							'terms' => preg_split('/[,\s]+/', $term)
+						) );
+					}
+				}
+			}
+		}
+
+		// Category stuff
+		if ( !empty($q['cat']) && '0' != $q['cat'] && !$this->is_singular ) {
 			$q['cat'] = ''.urldecode($q['cat']).'';
 			$q['cat'] = addslashes_gpc($q['cat']);
 			$cat_array = preg_split('/[,\s]+/', $q['cat']);
@@ -1804,25 +1837,29 @@
 				$cat = abs($cat);
 				if ( $in ) {
 					$q['category__in'][] = $cat;
-					$q['category__in'] = array_merge($q['category__in'], get_term_children($cat, 'category'));
 				} else {
 					$q['category__not_in'][] = $cat;
-					$q['category__not_in'] = array_merge($q['category__not_in'], get_term_children($cat, 'category'));
 				}
 			}
 			$q['cat'] = implode(',', $req_cats);
 		}
 
 		if ( !empty($q['category__in']) ) {
-			$join = " INNER JOIN $wpdb->term_relationships ON ($wpdb->posts.ID = $wpdb->term_relationships.object_id) INNER JOIN $wpdb->term_taxonomy ON ($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id) ";
-			$whichcat .= " AND $wpdb->term_taxonomy.taxonomy = 'category' ";
-			$include_cats = "'" . implode("', '", $q['category__in']) . "'";
-			$whichcat .= " AND $wpdb->term_taxonomy.term_id IN ($include_cats) ";
+			$tax_query[] = array(
+				'taxonomy' => 'category',
+				'terms' => $q['category__in'],
+				'operator' => 'IN',
+				'field' => 'term_id'
+			);
 		}
 
 		if ( !empty($q['category__not_in']) ) {
-			$cat_string = "'" . implode("', '", $q['category__not_in']) . "'";
-			$whichcat .= " AND $wpdb->posts.ID NOT IN ( SELECT tr.object_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy = 'category' AND tt.term_id IN ($cat_string) )";
+			$tax_query[] = array(
+				'taxonomy' => 'category',
+				'terms' => $q['category__not_in'],
+				'operator' => 'NOT IN',
+				'field' => 'term_id'
+			);
 		}
 
 		// Category stuff for nice URLs
@@ -1851,137 +1888,51 @@
 
 			$q['cat'] = $reqcat;
 
-			$join = " INNER JOIN $wpdb->term_relationships ON ($wpdb->posts.ID = $wpdb->term_relationships.object_id) INNER JOIN $wpdb->term_taxonomy ON ($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id) ";
-			$whichcat = " AND $wpdb->term_taxonomy.taxonomy = 'category' ";
-			$in_cats = array($q['cat']);
-			$in_cats = array_merge($in_cats, get_term_children($q['cat'], 'category'));
-			$in_cats = "'" . implode("', '", $in_cats) . "'";
-			$whichcat .= "AND $wpdb->term_taxonomy.term_id IN ($in_cats)";
-			$groupby = "{$wpdb->posts}.ID";
+			$tax_query[] = array(
+				'taxonomy' => 'category',
+				'terms' => array( $q['cat'] ),
+				'operator' => 'IN',
+				'field' => 'term_id'
+			);
 		}
 
-		// Tags
-		if ( '' != $q['tag'] ) {
-			if ( strpos($q['tag'], ',') !== false ) {
-				$tags = preg_split('/[,\s]+/', $q['tag']);
-				foreach ( (array) $tags as $tag ) {
-					$tag = sanitize_term_field('slug', $tag, 0, 'post_tag', 'db');
-					$q['tag_slug__in'][] = $tag;
-				}
-			} else if ( preg_match('/[+\s]+/', $q['tag']) || !empty($q['cat']) ) {
-				$tags = preg_split('/[+\s]+/', $q['tag']);
-				foreach ( (array) $tags as $tag ) {
-					$tag = sanitize_term_field('slug', $tag, 0, 'post_tag', 'db');
-					$q['tag_slug__and'][] = $tag;
-				}
-			} else {
-				$q['tag'] = sanitize_term_field('slug', $q['tag'], 0, 'post_tag', 'db');
-				$q['tag_slug__in'][] = $q['tag'];
-			}
+		// Tag stuff
+		if ( !empty($qv['tag_id']) ) {
+			$tax_query[] = array(
+				'taxonomy' => 'post_tag',
+				'terms' => $qv['tag_id'],
+				'operator' => 'IN',
+				'field' => 'term_id'
+			);
 		}
 
-		if ( !empty($q['category__in']) || !empty($q['meta_key']) || !empty($q['tag__in']) || !empty($q['tag_slug__in']) ) {
-			$groupby = "{$wpdb->posts}.ID";
+		if ( !empty($q['tag__in']) ) {
+			$tax_query[] = array(
+				'taxonomy' => 'post_tag',
+				'terms' => $q['tag__in'],
+				'operator' => 'IN',
+				'field' => 'term_id'
+			);
 		}
 
-		if ( !empty($q['tag__in']) && empty($q['cat']) ) {
-			$join = " INNER JOIN $wpdb->term_relationships ON ($wpdb->posts.ID = $wpdb->term_relationships.object_id) INNER JOIN $wpdb->term_taxonomy ON ($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id) ";
-			$whichcat .= " AND $wpdb->term_taxonomy.taxonomy = 'post_tag' ";
-			$include_tags = "'" . implode("', '", $q['tag__in']) . "'";
-			$whichcat .= " AND $wpdb->term_taxonomy.term_id IN ($include_tags) ";
-			$reqtag = term_exists( $q['tag__in'][0], 'post_tag' );
-			if ( !empty($reqtag) )
-				$q['tag_id'] = $reqtag['term_id'];
-		}
-
-		if ( !empty($q['tag_slug__in']) && empty($q['cat']) ) {
-			$join = " INNER JOIN $wpdb->term_relationships ON ($wpdb->posts.ID = $wpdb->term_relationships.object_id) INNER JOIN $wpdb->term_taxonomy ON ($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id) INNER JOIN $wpdb->terms ON ($wpdb->term_taxonomy.term_id = $wpdb->terms.term_id) ";
-			$whichcat .= " AND $wpdb->term_taxonomy.taxonomy = 'post_tag' ";
-			$include_tags = "'" . implode("', '", $q['tag_slug__in']) . "'";
-			$whichcat .= " AND $wpdb->terms.slug IN ($include_tags) ";
-			$reqtag = get_term_by( 'slug', $q['tag_slug__in'][0], 'post_tag' );
-			if ( !empty($reqtag) )
-				$q['tag_id'] = $reqtag->term_id;
-		}
-
 		if ( !empty($q['tag__not_in']) ) {
-			$tag_string = "'" . implode("', '", $q['tag__not_in']) . "'";
-			$whichcat .= " AND $wpdb->posts.ID NOT IN ( SELECT tr.object_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy = 'post_tag' AND tt.term_id IN ($tag_string) )";
+			$tax_query[] = array(
+				'taxonomy' => 'post_tag',
+				'terms' => $q['tag__not_in'],
+				'operator' => 'NOT IN',
+				'field' => 'term_id'
+			);
 		}
 
-		// Tag and slug intersections.
-		$intersections = array('category__and' => 'category', 'tag__and' => 'post_tag', 'tag_slug__and' => 'post_tag', 'tag__in' => 'post_tag', 'tag_slug__in' => 'post_tag');
-		$tagin = array('tag__in', 'tag_slug__in'); // These are used to make some exceptions below
-		foreach ( $intersections as $item => $taxonomy ) {
-			if ( empty($q[$item]) ) continue;
-			if ( in_array($item, $tagin) && empty($q['cat']) ) continue; // We should already have what we need if categories aren't being used
+		if ( !empty( $tax_query ) ) {
+			$this->tax_query = $tax_query;
+			$tax_query_sql = _wp_tax_sql( $tax_query );
 
-			if ( $item != 'category__and' ) {
-				$reqtag = term_exists( $q[$item][0], 'post_tag' );
-				if ( !empty($reqtag) )
-					$q['tag_id'] = $reqtag['term_id'];
-			}
-
-			if ( in_array( $item, array('tag_slug__and', 'tag_slug__in' ) ) )
-				$taxonomy_field = 'slug';
-			else
-				$taxonomy_field = 'term_id';
-
-			$q[$item] = array_unique($q[$item]);
-			$tsql = "SELECT p.ID FROM $wpdb->posts p INNER JOIN $wpdb->term_relationships tr ON (p.ID = tr.object_id) INNER JOIN $wpdb->term_taxonomy tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) INNER JOIN $wpdb->terms t ON (tt.term_id = t.term_id)";
-			$tsql .= " WHERE tt.taxonomy = '$taxonomy' AND t.$taxonomy_field IN ('" . implode("', '", $q[$item]) . "')";
-			if ( !in_array($item, $tagin) ) { // This next line is only helpful if we are doing an and relationship
-				$tsql .= " GROUP BY p.ID HAVING count(p.ID) = " . count($q[$item]);
-			}
-			$post_ids = $wpdb->get_col($tsql);
-
-			if ( count($post_ids) )
-				$whichcat .= " AND $wpdb->posts.ID IN (" . implode(', ', $post_ids) . ") ";
-			else {
-				$whichcat = " AND 0 = 1";
-				break;
-			}
+			$where .= " AND $wpdb->posts.ID IN( " . implode( ', ', $wpdb->get_col( $tax_query_sql ) ) . ")";
 		}
 
-		// Taxonomies
-		if ( $this->is_tax ) {
-			if ( '' != $q['taxonomy'] ) {
-				$taxonomy = $q['taxonomy'];
-				$tt[$taxonomy] = $q['term'];
-			} else {
-				foreach ( $GLOBALS['wp_taxonomies'] as $taxonomy => $t ) {
-					if ( $t->query_var && '' != $q[$t->query_var] ) {
-						$tt[$taxonomy] = $q[$t->query_var];
-						break;
-					}
-				}
-			}
-
-			$terms = get_terms($taxonomy, array('slug' => $tt[$taxonomy], 'hide_empty' => !is_taxonomy_hierarchical($taxonomy)));
-
-			if ( is_wp_error($terms) || empty($terms) ) {
-				$whichcat = " AND 0 ";
-			} else {
-				foreach ( $terms as $term ) {
-					$term_ids[] = $term->term_id;
-					if ( is_taxonomy_hierarchical($taxonomy) ) {
-						$children = get_term_children($term->term_id, $taxonomy);
-						$term_ids = array_merge($term_ids, $children);
-					}
-				}
-				$post_ids = get_objects_in_term($term_ids, $taxonomy);
-				if ( !is_wp_error($post_ids) && !empty($post_ids) ) {
-					$whichcat .= " AND $wpdb->posts.ID IN (" . implode(', ', $post_ids) . ") ";
-					if ( empty($post_type) ) {
-						$post_type = 'any';
-						$post_status_join = true;
-					} elseif ( in_array('attachment', (array)$post_type) ) {
-						$post_status_join = true;
-					}
-				} else {
-					$whichcat = " AND 0 ";
-				}
-			}
+		if ( !empty($q['meta_key']) ) {
+			$groupby = "{$wpdb->posts}.ID";
 		}
 
 		// Author/user stuff
@@ -2033,7 +1984,7 @@
 			$whichmimetype = wp_post_mime_type_where($q['post_mime_type'], $table_alias);
 		}
 
-		$where .= $search . $whichcat . $whichauthor . $whichmimetype;
+		$where .= $search . $whichauthor . $whichmimetype;
 
 		if ( empty($q['order']) || ((strtoupper($q['order']) != 'ASC') && (strtoupper($q['order']) != 'DESC')) )
 			$q['order'] = 'DESC';
@@ -2625,27 +2576,13 @@
 		$this->queried_object = NULL;
 		$this->queried_object_id = 0;
 
-		if ( $this->is_category ) {
-			$cat = $this->get('cat');
-			$category = &get_category($cat);
-			if ( is_wp_error( $category ) )
-				return NULL;
-			$this->queried_object = &$category;
-			$this->queried_object_id = (int) $cat;
-		} elseif ( $this->is_tag ) {
-			$tag_id = $this->get('tag_id');
-			$tag = &get_term($tag_id, 'post_tag');
-			if ( is_wp_error( $tag ) )
-				return NULL;
-			$this->queried_object = &$tag;
-			$this->queried_object_id = (int) $tag_id;
-		} elseif ( $this->is_tax ) {
-			$tax = $this->get('taxonomy');
-			$slug = $this->get('term');
-			$term = &get_terms($tax, array( 'slug' => $slug, 'hide_empty' => false ) );
-			if ( is_wp_error($term) || empty($term) )
-				return NULL;
-			$term = $term[0];
+		if ( $this->tax_query ) {
+			$query = reset( $this->tax_query );
+			if ( 'term_id' == $query['field']  )
+				$term = get_term( reset( $query['terms'] ), $query['taxonomy'] );
+			else
+				$term = get_term_by( $query['field'], reset( $query['terms'] ), $query['taxonomy'] );
+
 			$this->queried_object = $term;
 			$this->queried_object_id = $term->term_id;
 		} elseif ( $this->is_posts_page ) {
Index: wp-includes/general-template.php
===================================================================
--- wp-includes/general-template.php	(revision 15599)
+++ wp-includes/general-template.php	(working copy)
@@ -1589,6 +1589,8 @@
  * @param array $args Optional arguments.
  */
 function feed_links_extra( $args = array() ) {
+	global $wp_query;
+
 	$defaults = array(
 		/* translators: Separator between blog name and feed type in feed links */
 		'separator'   => _x('&raquo;', 'feed link'),
@@ -1614,16 +1616,15 @@
 			$href = get_post_comments_feed_link( $post->ID );
 		}
 	} elseif ( is_category() ) {
-		$cat_id = intval( get_query_var('cat') );
+		$term = $wp_query->get_queried_object();
 
-		$title = esc_attr(sprintf( $args['cattitle'], get_bloginfo('name'), $args['separator'], get_cat_name( $cat_id ) ));
-		$href = get_category_feed_link( $cat_id );
+		$title = esc_attr(sprintf( $args['cattitle'], get_bloginfo('name'), $args['separator'], $term->name ));
+		$href = get_category_feed_link( $term->term_id );
 	} elseif ( is_tag() ) {
-		$tag_id = intval( get_query_var('tag_id') );
-		$tag = get_tag( $tag_id );
+		$term = $wp_query->get_queried_object();
 
-		$title = esc_attr(sprintf( $args['tagtitle'], get_bloginfo('name'), $args['separator'], $tag->name ));
-		$href = get_tag_feed_link( $tag_id );
+		$title = esc_attr(sprintf( $args['tagtitle'], get_bloginfo('name'), $args['separator'], $term->name ));
+		$href = get_tag_feed_link( $term->term_id );
 	} elseif ( is_author() ) {
 		$author_id = intval( get_query_var('author') );
 
