Index: wp-includes/functions.php
===================================================================
--- wp-includes/functions.php	(revision 15581)
+++ wp-includes/functions.php	(working copy)
@@ -4293,6 +4293,60 @@
 }
 
 /*
+ * 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) $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 = implode( ',', array_map( 'intval', $terms ) );
+				$terms = $wpdb->prepare( "
+					SELECT term_taxonomy_id
+					FROM $wpdb->term_taxonomy
+					WHERE taxonomy = %s
+					AND term_id IN ( $terms )
+				", $taxonomy );
+			break;
+
+			case 'slug':
+			case 'name':
+				$terms = "'" . implode( "','", esc_sql( $terms ) ) . "'";
+				$terms = $wpdb->prepare( "
+					SELECT term_taxonomy_id
+					FROM $wpdb->term_taxonomy
+					INNER JOIN $wpdb->terms USING ( term_id )
+					WHERE taxonomy = %s
+					AND $field IN ( $terms )
+				", $taxonomy );
+		}
+
+		$sql[] = " AND term_taxonomy_id $operator ( $terms )";
+	}
+
+	return "SELECT object_id FROM $wpdb->term_relationships WHERE 1=1" .  implode( '', $sql );
+}
+
+/*
  * Used internally to tidy up the search terms
  *
  * @access private
Index: wp-includes/query.php
===================================================================
--- wp-includes/query.php	(revision 15581)
+++ wp-includes/query.php	(working copy)
@@ -1780,7 +1780,6 @@
 		$search = apply_filters_ref_array('posts_search', array( $search, &$this ) );
 
 		// Category stuff
-
 		if ( empty($q['cat']) || ($q['cat'] == '0') ||
 				// Bypass cat checks if fetching specific posts
 				$this->is_singular ) {
@@ -1808,15 +1807,21 @@
 		}
 
 		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
@@ -1845,13 +1850,15 @@
 
 			$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' => $in_cats,
+				'operator' => 'IN',
+				'field' => 'term_id'
+			);
 		}
 
 		// Tags
@@ -1874,35 +1881,47 @@
 			}
 		}
 
-		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']) && empty($q['cat']) ) {
+			$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) ";
+			$tax_query[] = array(
+				'taxonomy' => 'post_tag',
+				'terms' => $q['tag_slug__in'],
+				'operator' => 'IN',
+				'field' => 'slug'
+			);
+
 			$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'
+			);
 		}
 
+		if ( !empty( $tax_query ) ) {
+			$tax_query_sql = _wp_tax_sql( $tax_query );
+
+			$where .= " AND $wpdb->posts.ID IN( " . implode( ', ', $wpdb->get_col( $tax_query_sql ) ) . ")";
+		}
+
 		// 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
@@ -1978,6 +1997,10 @@
 			}
 		}
 
+		if ( !empty($q['meta_key']) ) {
+			$groupby = "{$wpdb->posts}.ID";
+		}
+
 		// Author/user stuff
 
 		if ( empty($q['author']) || ($q['author'] == '0') ) {
