Index: wp-includes/taxonomy.php
===================================================================
--- wp-includes/taxonomy.php	(revision 17662)
+++ wp-includes/taxonomy.php	(working copy)
@@ -539,7 +539,7 @@
 	 *		Possible values: 'term_id', 'slug' or 'name'
 	 *		Default: 'term_id'
 	 * - 'operator' string (optional)
-	 *		Possible values: 'IN' and 'NOT IN'.
+	 *		Possible values: 'AND', 'IN' or 'NOT IN'.
 	 *		Default: 'IN'
 	 * - 'include_children' bool (optional) Whether to include child terms.
 	 *		Default: true
@@ -548,7 +548,7 @@
 	 * @access public
 	 * @var array
 	 */
-	var $queries = array();
+	public $queries = array();
 
 	/**
 	 * The relation between the queries. Can be one of 'AND' or 'OR'.
@@ -557,11 +557,20 @@
 	 * @access public
 	 * @var string
 	 */
-	var $relation;
+	public $relation;
 
 	/**
-	 * PHP4 type constructor.
+	 * Standard response when the query should not return any rows.
 	 *
+	 * @since 3.2.0
+	 * @access private
+	 * @var string
+	 */
+	private static $no_results = array( 'join' => '', 'where' => ' AND 0 = 1' );
+
+	/**
+	 * Constructor.
+	 *
 	 * Parses a compact tax query and sets defaults.
 	 *
 	 * @since 3.1.0
@@ -581,10 +590,8 @@
 	 *      'field' => 'slug',
 	 *    ),
 	 *  )
-	 *
-	 * @return WP_Tax_Query
 	 */
-	function WP_Tax_Query( $tax_query ) {
+	public function __construct( $tax_query ) {
 		if ( isset( $tax_query['relation'] ) && strtoupper( $tax_query['relation'] ) == 'OR' ) {
 			$this->relation = 'OR';
 		} else {
@@ -621,7 +628,7 @@
 	 * @param string $primary_id_column
 	 * @return array
 	 */
-	function get_sql( $primary_table, $primary_id_column ) {
+	public function get_sql( $primary_table, $primary_id_column ) {
 		global $wpdb;
 
 		$join = '';
@@ -629,39 +636,21 @@
 		$i = 0;
 
 		foreach ( $this->queries as $query ) {
-			extract( $query );
+			$this->clean_query( $query );
 
-			if ( ! taxonomy_exists( $taxonomy ) )
-				return array( 'join' => '', 'where' => ' AND 0 = 1');
+			if ( is_wp_error( $query ) ) {
+				return self::$no_results;
+			}
 
-			$terms = array_unique( (array) $terms );
+			extract( $query );
 
-			if ( empty( $terms ) )
-				continue;
-
-			if ( is_taxonomy_hierarchical( $taxonomy ) && $include_children ) {
-				$this->_transform_terms( $terms, $taxonomy, $field, 'term_id' );
-
-				$children = array();
-				foreach ( $terms as $term ) {
-					$children = array_merge( $children, get_term_children( $term, $taxonomy ) );
-					$children[] = $term;
-				}
-				$terms = $children;
-
-				$this->_transform_terms( $terms, $taxonomy, 'term_id', 'term_taxonomy_id' );
-			}
-			else {
-				$this->_transform_terms( $terms, $taxonomy, $field, 'term_taxonomy_id' );
-			}
-
 			if ( 'IN' == $operator ) {
 
 				if ( empty( $terms ) ) {
 					if ( 'OR' == $this->relation )
 						continue;
 					else
-						return array( 'join' => '', 'where' => ' AND 0 = 1' );
+						return self::$no_results;
 				}
 
 				$terms = implode( ',', $terms );
@@ -714,49 +703,89 @@
 	}
 
 	/**
-	 * Transforms a list of terms, from one field to another.
+	 * Validates a single query.
 	 *
-	 * @since 3.1.0
+	 * @since 3.2.0
 	 * @access private
 	 *
-	 * @param array &$terms The list of terms
-	 * @param string $taxonomy The taxonomy of the terms
-	 * @param string $field The initial field
+	 * @param array &$query The single query
+	 */
+	private function clean_query( &$query ) {
+		if ( ! taxonomy_exists( $query['taxonomy'] ) ) {
+			$query = new WP_Error( 'Invalid taxonomy' );
+			return;
+		}
+
+		$query['terms'] = array_unique( (array) $query['terms'] );
+
+		if ( is_taxonomy_hierarchical( $query['taxonomy'] ) && $query['include_children'] ) {
+			$this->transform_query( $query, 'term_id' );
+
+			if ( is_wp_error( $query ) ) {
+				return;
+			}
+
+			$children = array();
+			foreach ( $query['terms'] as $term ) {
+				$children = array_merge( $children, get_term_children( $term, $query['taxonomy'] ) );
+				$children[] = $term;
+			}
+			$query['terms'] = $children;
+		}
+
+		$this->transform_query( $query, 'term_taxonomy_id' );
+	}
+
+	/**
+	 * Transforms a single query, from one field to another.
+	 *
+	 * @since 3.2.0
+	 * @access private
+	 *
+	 * @param array &$query The single query
 	 * @param string $resulting_field The resulting field
 	 */
-	function _transform_terms( &$terms, $taxonomy, $field, $resulting_field ) {
+	private function transform_query( &$query, $resulting_field ) {
 		global $wpdb;
 
-		if ( empty( $terms ) )
+		if ( empty( $query['terms'] ) )
 			return;
 
-		if ( $field == $resulting_field )
+		if ( $query['field'] == $resulting_field )
 			return;
 
 		$resulting_field = esc_sql( $resulting_field );
 
-		switch ( $field ) {
+		switch ( $query['field'] ) {
 			case 'slug':
 			case 'name':
-				$terms = "'" . implode( "','", array_map( 'sanitize_title_for_query', $terms ) ) . "'";
+				$terms = "'" . implode( "','", array_map( 'sanitize_title_for_query', $query['terms'] ) ) . "'";
 				$terms = $wpdb->get_col( "
 					SELECT $wpdb->term_taxonomy.$resulting_field
 					FROM $wpdb->term_taxonomy
 					INNER JOIN $wpdb->terms USING (term_id)
-					WHERE taxonomy = '$taxonomy'
-					AND $wpdb->terms.$field IN ($terms)
+					WHERE taxonomy = '{$query['taxonomy']}'
+					AND $wpdb->terms.{$query['field']} IN ($terms)
 				" );
 				break;
 
 			default:
-				$terms = implode( ',', array_map( 'intval', $terms ) );
+				$terms = implode( ',', array_map( 'intval', $query['terms'] ) );
 				$terms = $wpdb->get_col( "
 					SELECT $resulting_field
 					FROM $wpdb->term_taxonomy
-					WHERE taxonomy = '$taxonomy'
+					WHERE taxonomy = '{$query['taxonomy']}'
 					AND term_id IN ($terms)
 				" );
 		}
+
+		if ( 'AND' == $query['operator'] && count( $terms ) < count( $query['terms'] ) ) {
+			$query = new WP_Error( 'Inexistent terms' );
+			return;
+		}
+
+		$query['terms'] = $terms;
+		$query['field'] = $resulting_field;
 	}
 }
 
