wp/wp-includes/class-wp-meta-query.php
changeset 9 177826044cd9
parent 7 cf61fcea0001
child 16 a86126ab1dd4
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
    97 	/**
    97 	/**
    98 	 * Constructor.
    98 	 * Constructor.
    99 	 *
    99 	 *
   100 	 * @since 3.2.0
   100 	 * @since 3.2.0
   101 	 * @since 4.2.0 Introduced support for naming query clauses by associative array keys.
   101 	 * @since 4.2.0 Introduced support for naming query clauses by associative array keys.
   102 	 *
   102 	 * @since 5.1.0 Introduced $compare_key clause parameter, which enables LIKE key matches.
   103 	 *
   103 	 *
   104 	 * @param array $meta_query {
   104 	 * @param array $meta_query {
   105 	 *     Array of meta query clauses. When first-order clauses or sub-clauses use strings as
   105 	 *     Array of meta query clauses. When first-order clauses or sub-clauses use strings as
   106 	 *     their array keys, they may be referenced in the 'orderby' parameter of the parent query.
   106 	 *     their array keys, they may be referenced in the 'orderby' parameter of the parent query.
   107 	 *
   107 	 *
   108 	 *     @type string $relation Optional. The MySQL keyword used to join
   108 	 *     @type string $relation Optional. The MySQL keyword used to join
   109 	 *                            the clauses of the query. Accepts 'AND', or 'OR'. Default 'AND'.
   109 	 *                            the clauses of the query. Accepts 'AND', or 'OR'. Default 'AND'.
   110 	 *     @type array {
   110 	 *     @type array {
   111 	 *         Optional. An array of first-order clause parameters, or another fully-formed meta query.
   111 	 *         Optional. An array of first-order clause parameters, or another fully-formed meta query.
   112 	 *
   112 	 *
   113 	 *         @type string $key     Meta key to filter by.
   113 	 *         @type string $key         Meta key to filter by.
   114 	 *         @type string $value   Meta value to filter by.
   114 	 *         @type string $compare_key MySQL operator used for comparing the $key. Accepts '=' and 'LIKE'.
   115 	 *         @type string $compare MySQL operator used for comparing the $value. Accepts '=',
   115 	 *                                   Default '='.
   116 	 *                               '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE',
   116 	 *         @type string $value       Meta value to filter by.
   117 	 *                               'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'REGEXP',
   117 	 *         @type string $compare     MySQL operator used for comparing the $value. Accepts '=',
   118 	 *                               'NOT REGEXP', 'RLIKE', 'EXISTS' or 'NOT EXISTS'.
   118 	 *                                   '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE',
   119 	 *                               Default is 'IN' when `$value` is an array, '=' otherwise.
   119 	 *                                   'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'REGEXP',
   120 	 *         @type string $type    MySQL data type that the meta_value column will be CAST to for
   120 	 *                                   'NOT REGEXP', 'RLIKE', 'EXISTS' or 'NOT EXISTS'.
   121 	 *                               comparisons. Accepts 'NUMERIC', 'BINARY', 'CHAR', 'DATE',
   121 	 *                                   Default is 'IN' when `$value` is an array, '=' otherwise.
   122 	 *                               'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', or 'UNSIGNED'.
   122 	 *         @type string $type        MySQL data type that the meta_value column will be CAST to for
   123 	 *                               Default is 'CHAR'.
   123 	 *                                   comparisons. Accepts 'NUMERIC', 'BINARY', 'CHAR', 'DATE',
       
   124 	 *                                   'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', or 'UNSIGNED'.
       
   125 	 *                                   Default is 'CHAR'.
   124 	 *     }
   126 	 *     }
   125 	 * }
   127 	 * }
   126 	 */
   128 	 */
   127 	public function __construct( $meta_query = false ) {
   129 	public function __construct( $meta_query = false ) {
   128 		if ( !$meta_query )
   130 		if ( ! $meta_query ) {
   129 			return;
   131 			return;
       
   132 		}
   130 
   133 
   131 		if ( isset( $meta_query['relation'] ) && strtoupper( $meta_query['relation'] ) == 'OR' ) {
   134 		if ( isset( $meta_query['relation'] ) && strtoupper( $meta_query['relation'] ) == 'OR' ) {
   132 			$this->relation = 'OR';
   135 			$this->relation = 'OR';
   133 		} else {
   136 		} else {
   134 			$this->relation = 'AND';
   137 			$this->relation = 'AND';
   159 				$relation = $query;
   162 				$relation = $query;
   160 
   163 
   161 			} elseif ( ! is_array( $query ) ) {
   164 			} elseif ( ! is_array( $query ) ) {
   162 				continue;
   165 				continue;
   163 
   166 
   164 			// First-order clause.
   167 				// First-order clause.
   165 			} elseif ( $this->is_first_order_clause( $query ) ) {
   168 			} elseif ( $this->is_first_order_clause( $query ) ) {
   166 				if ( isset( $query['value'] ) && array() === $query['value'] ) {
   169 				if ( isset( $query['value'] ) && array() === $query['value'] ) {
   167 					unset( $query['value'] );
   170 					unset( $query['value'] );
   168 				}
   171 				}
   169 
   172 
   170 				$clean_queries[ $key ] = $query;
   173 				$clean_queries[ $key ] = $query;
   171 
   174 
   172 			// Otherwise, it's a nested query, so we recurse.
   175 				// Otherwise, it's a nested query, so we recurse.
   173 			} else {
   176 			} else {
   174 				$cleaned_query = $this->sanitize_query( $query );
   177 				$cleaned_query = $this->sanitize_query( $query );
   175 
   178 
   176 				if ( ! empty( $cleaned_query ) ) {
   179 				if ( ! empty( $cleaned_query ) ) {
   177 					$clean_queries[ $key ] = $cleaned_query;
   180 					$clean_queries[ $key ] = $cleaned_query;
   184 		}
   187 		}
   185 
   188 
   186 		// Sanitize the 'relation' key provided in the query.
   189 		// Sanitize the 'relation' key provided in the query.
   187 		if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) {
   190 		if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) {
   188 			$clean_queries['relation'] = 'OR';
   191 			$clean_queries['relation'] = 'OR';
   189 			$this->has_or_relation = true;
   192 			$this->has_or_relation     = true;
   190 
   193 
   191 		/*
   194 			/*
   192 		 * If there is only a single clause, call the relation 'OR'.
   195 			* If there is only a single clause, call the relation 'OR'.
   193 		 * This value will not actually be used to join clauses, but it
   196 			* This value will not actually be used to join clauses, but it
   194 		 * simplifies the logic around combining key-only queries.
   197 			* simplifies the logic around combining key-only queries.
   195 		 */
   198 			*/
   196 		} elseif ( 1 === count( $clean_queries ) ) {
   199 		} elseif ( 1 === count( $clean_queries ) ) {
   197 			$clean_queries['relation'] = 'OR';
   200 			$clean_queries['relation'] = 'OR';
   198 
   201 
   199 		// Default to AND.
   202 			// Default to AND.
   200 		} else {
   203 		} else {
   201 			$clean_queries['relation'] = 'AND';
   204 			$clean_queries['relation'] = 'AND';
   202 		}
   205 		}
   203 
   206 
   204 		return $clean_queries;
   207 		return $clean_queries;
   234 		 * first (so that its table join is against an unaliased meta table) and
   237 		 * first (so that its table join is against an unaliased meta table) and
   235 		 * needs to be its own clause (so it doesn't interfere with the logic of
   238 		 * needs to be its own clause (so it doesn't interfere with the logic of
   236 		 * the rest of the meta_query).
   239 		 * the rest of the meta_query).
   237 		 */
   240 		 */
   238 		$primary_meta_query = array();
   241 		$primary_meta_query = array();
   239 		foreach ( array( 'key', 'compare', 'type' ) as $key ) {
   242 		foreach ( array( 'key', 'compare', 'type', 'compare_key' ) as $key ) {
   240 			if ( ! empty( $qv[ "meta_$key" ] ) ) {
   243 			if ( ! empty( $qv[ "meta_$key" ] ) ) {
   241 				$primary_meta_query[ $key ] = $qv[ "meta_$key" ];
   244 				$primary_meta_query[ $key ] = $qv[ "meta_$key" ];
   242 			}
   245 			}
   243 		}
   246 		}
   244 
   247 
   273 	 *
   276 	 *
   274 	 * @param string $type MySQL type to cast meta_value.
   277 	 * @param string $type MySQL type to cast meta_value.
   275 	 * @return string MySQL type.
   278 	 * @return string MySQL type.
   276 	 */
   279 	 */
   277 	public function get_cast_for_type( $type = '' ) {
   280 	public function get_cast_for_type( $type = '' ) {
   278 		if ( empty( $type ) )
   281 		if ( empty( $type ) ) {
   279 			return 'CHAR';
   282 			return 'CHAR';
       
   283 		}
   280 
   284 
   281 		$meta_type = strtoupper( $type );
   285 		$meta_type = strtoupper( $type );
   282 
   286 
   283 		if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) )
   287 		if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) ) {
   284 			return 'CHAR';
   288 			return 'CHAR';
   285 
   289 		}
   286 		if ( 'NUMERIC' == $meta_type )
   290 
       
   291 		if ( 'NUMERIC' == $meta_type ) {
   287 			$meta_type = 'SIGNED';
   292 			$meta_type = 'SIGNED';
       
   293 		}
   288 
   294 
   289 		return $meta_type;
   295 		return $meta_type;
   290 	}
   296 	}
   291 
   297 
   292 	/**
   298 	/**
   331 		/**
   337 		/**
   332 		 * Filters the meta query's generated SQL.
   338 		 * Filters the meta query's generated SQL.
   333 		 *
   339 		 *
   334 		 * @since 3.1.0
   340 		 * @since 3.1.0
   335 		 *
   341 		 *
   336 		 * @param array  $clauses           Array containing the query's JOIN and WHERE clauses.
   342 		 * @param array  $sql               Array containing the query's JOIN and WHERE clauses.
   337 		 * @param array  $queries           Array of meta queries.
   343 		 * @param array  $queries           Array of meta queries.
   338 		 * @param string $type              Type of meta.
   344 		 * @param string $type              Type of meta.
   339 		 * @param string $primary_table     Primary table.
   345 		 * @param string $primary_table     Primary table.
   340 		 * @param string $primary_id_column Primary column ID.
   346 		 * @param string $primary_id_column Primary column ID.
   341 		 * @param object $context           The main query object.
   347 		 * @param object $context           The main query object.
   362 		/*
   368 		/*
   363 		 * $queries are passed by reference to get_sql_for_query() for recursion.
   369 		 * $queries are passed by reference to get_sql_for_query() for recursion.
   364 		 * To keep $this->queries unaltered, pass a copy.
   370 		 * To keep $this->queries unaltered, pass a copy.
   365 		 */
   371 		 */
   366 		$queries = $this->queries;
   372 		$queries = $this->queries;
   367 		$sql = $this->get_sql_for_query( $queries );
   373 		$sql     = $this->get_sql_for_query( $queries );
   368 
   374 
   369 		if ( ! empty( $sql['where'] ) ) {
   375 		if ( ! empty( $sql['where'] ) ) {
   370 			$sql['where'] = ' AND ' . $sql['where'];
   376 			$sql['where'] = ' AND ' . $sql['where'];
   371 		}
   377 		}
   372 
   378 
   402 			'where' => '',
   408 			'where' => '',
   403 		);
   409 		);
   404 
   410 
   405 		$indent = '';
   411 		$indent = '';
   406 		for ( $i = 0; $i < $depth; $i++ ) {
   412 		for ( $i = 0; $i < $depth; $i++ ) {
   407 			$indent .= "  ";
   413 			$indent .= '  ';
   408 		}
   414 		}
   409 
   415 
   410 		foreach ( $query as $key => &$clause ) {
   416 		foreach ( $query as $key => &$clause ) {
   411 			if ( 'relation' === $key ) {
   417 			if ( 'relation' === $key ) {
   412 				$relation = $query['relation'];
   418 				$relation = $query['relation'];
   424 					} else {
   430 					} else {
   425 						$sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
   431 						$sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
   426 					}
   432 					}
   427 
   433 
   428 					$sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
   434 					$sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
   429 				// This is a subquery, so we recurse.
   435 					// This is a subquery, so we recurse.
   430 				} else {
   436 				} else {
   431 					$clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
   437 					$clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
   432 
   438 
   433 					$sql_chunks['where'][] = $clause_sql['where'];
   439 					$sql_chunks['where'][] = $clause_sql['where'];
   434 					$sql_chunks['join'][]  = $clause_sql['join'];
   440 					$sql_chunks['join'][]  = $clause_sql['join'];
   480 	public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) {
   486 	public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) {
   481 		global $wpdb;
   487 		global $wpdb;
   482 
   488 
   483 		$sql_chunks = array(
   489 		$sql_chunks = array(
   484 			'where' => array(),
   490 			'where' => array(),
   485 			'join' => array(),
   491 			'join'  => array(),
   486 		);
   492 		);
   487 
   493 
   488 		if ( isset( $clause['compare'] ) ) {
   494 		if ( isset( $clause['compare'] ) ) {
   489 			$clause['compare'] = strtoupper( $clause['compare'] );
   495 			$clause['compare'] = strtoupper( $clause['compare'] );
   490 		} else {
   496 		} else {
   491 			$clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
   497 			$clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
   492 		}
   498 		}
   493 
   499 
   494 		if ( ! in_array( $clause['compare'], array(
   500 		if ( ! in_array(
   495 			'=', '!=', '>', '>=', '<', '<=',
   501 			$clause['compare'],
   496 			'LIKE', 'NOT LIKE',
   502 			array(
   497 			'IN', 'NOT IN',
   503 				'=',
   498 			'BETWEEN', 'NOT BETWEEN',
   504 				'!=',
   499 			'EXISTS', 'NOT EXISTS',
   505 				'>',
   500 			'REGEXP', 'NOT REGEXP', 'RLIKE'
   506 				'>=',
   501 		) ) ) {
   507 				'<',
       
   508 				'<=',
       
   509 				'LIKE',
       
   510 				'NOT LIKE',
       
   511 				'IN',
       
   512 				'NOT IN',
       
   513 				'BETWEEN',
       
   514 				'NOT BETWEEN',
       
   515 				'EXISTS',
       
   516 				'NOT EXISTS',
       
   517 				'REGEXP',
       
   518 				'NOT REGEXP',
       
   519 				'RLIKE',
       
   520 			)
       
   521 		) ) {
   502 			$clause['compare'] = '=';
   522 			$clause['compare'] = '=';
   503 		}
   523 		}
   504 
   524 
   505 		$meta_compare = $clause['compare'];
   525 		if ( isset( $clause['compare_key'] ) && 'LIKE' === strtoupper( $clause['compare_key'] ) ) {
       
   526 			$clause['compare_key'] = strtoupper( $clause['compare_key'] );
       
   527 		} else {
       
   528 			$clause['compare_key'] = '=';
       
   529 		}
       
   530 
       
   531 		$meta_compare     = $clause['compare'];
       
   532 		$meta_compare_key = $clause['compare_key'];
   506 
   533 
   507 		// First build the JOIN clause, if one is required.
   534 		// First build the JOIN clause, if one is required.
   508 		$join = '';
   535 		$join = '';
   509 
   536 
   510 		// We prefer to avoid joins if possible. Look for an existing join compatible with this clause.
   537 		// We prefer to avoid joins if possible. Look for an existing join compatible with this clause.
   511 		$alias = $this->find_compatible_table_alias( $clause, $parent_query );
   538 		$alias = $this->find_compatible_table_alias( $clause, $parent_query );
   512 		if ( false === $alias ) {
   539 		if ( false === $alias ) {
   513 			$i = count( $this->table_aliases );
   540 			$i     = count( $this->table_aliases );
   514 			$alias = $i ? 'mt' . $i : $this->meta_table;
   541 			$alias = $i ? 'mt' . $i : $this->meta_table;
   515 
   542 
   516 			// JOIN clauses for NOT EXISTS have their own syntax.
   543 			// JOIN clauses for NOT EXISTS have their own syntax.
   517 			if ( 'NOT EXISTS' === $meta_compare ) {
   544 			if ( 'NOT EXISTS' === $meta_compare ) {
   518 				$join .= " LEFT JOIN $this->meta_table";
   545 				$join .= " LEFT JOIN $this->meta_table";
   519 				$join .= $i ? " AS $alias" : '';
   546 				$join .= $i ? " AS $alias" : '';
   520 				$join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] );
   547 
   521 
   548 				if ( 'LIKE' === $meta_compare_key ) {
   522 			// All other JOIN clauses.
   549 					$join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key LIKE %s )", '%' . $wpdb->esc_like( $clause['key'] ) . '%' );
       
   550 				} else {
       
   551 					$join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] );
       
   552 				}
       
   553 
       
   554 				// All other JOIN clauses.
   523 			} else {
   555 			} else {
   524 				$join .= " INNER JOIN $this->meta_table";
   556 				$join .= " INNER JOIN $this->meta_table";
   525 				$join .= $i ? " AS $alias" : '';
   557 				$join .= $i ? " AS $alias" : '';
   526 				$join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )";
   558 				$join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )";
   527 			}
   559 			}
   528 
   560 
   529 			$this->table_aliases[] = $alias;
   561 			$this->table_aliases[] = $alias;
   530 			$sql_chunks['join'][] = $join;
   562 			$sql_chunks['join'][]  = $join;
   531 		}
   563 		}
   532 
   564 
   533 		// Save the alias to this clause, for future siblings to find.
   565 		// Save the alias to this clause, for future siblings to find.
   534 		$clause['alias'] = $alias;
   566 		$clause['alias'] = $alias;
   535 
   567 
   536 		// Determine the data type.
   568 		// Determine the data type.
   537 		$_meta_type = isset( $clause['type'] ) ? $clause['type'] : '';
   569 		$_meta_type     = isset( $clause['type'] ) ? $clause['type'] : '';
   538 		$meta_type  = $this->get_cast_for_type( $_meta_type );
   570 		$meta_type      = $this->get_cast_for_type( $_meta_type );
   539 		$clause['cast'] = $meta_type;
   571 		$clause['cast'] = $meta_type;
   540 
   572 
   541 		// Fallback for clause keys is the table alias. Key must be a string.
   573 		// Fallback for clause keys is the table alias. Key must be a string.
   542 		if ( is_int( $clause_key ) || ! $clause_key ) {
   574 		if ( is_int( $clause_key ) || ! $clause_key ) {
   543 			$clause_key = $clause['alias'];
   575 			$clause_key = $clause['alias'];
   544 		}
   576 		}
   545 
   577 
   546 		// Ensure unique clause keys, so none are overwritten.
   578 		// Ensure unique clause keys, so none are overwritten.
   547 		$iterator = 1;
   579 		$iterator        = 1;
   548 		$clause_key_base = $clause_key;
   580 		$clause_key_base = $clause_key;
   549 		while ( isset( $this->clauses[ $clause_key ] ) ) {
   581 		while ( isset( $this->clauses[ $clause_key ] ) ) {
   550 			$clause_key = $clause_key_base . '-' . $iterator;
   582 			$clause_key = $clause_key_base . '-' . $iterator;
   551 			$iterator++;
   583 			$iterator++;
   552 		}
   584 		}
   559 		// meta_key.
   591 		// meta_key.
   560 		if ( array_key_exists( 'key', $clause ) ) {
   592 		if ( array_key_exists( 'key', $clause ) ) {
   561 			if ( 'NOT EXISTS' === $meta_compare ) {
   593 			if ( 'NOT EXISTS' === $meta_compare ) {
   562 				$sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL';
   594 				$sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL';
   563 			} else {
   595 			} else {
   564 				$sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) );
   596 				if ( 'LIKE' === $meta_compare_key ) {
       
   597 					$sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key LIKE %s", '%' . $wpdb->esc_like( trim( $clause['key'] ) ) . '%' );
       
   598 				} else {
       
   599 					$sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) );
       
   600 				}
   565 			}
   601 			}
   566 		}
   602 		}
   567 
   603 
   568 		// meta_value.
   604 		// meta_value.
   569 		if ( array_key_exists( 'value', $clause ) ) {
   605 		if ( array_key_exists( 'value', $clause ) ) {
   576 			} else {
   612 			} else {
   577 				$meta_value = trim( $meta_value );
   613 				$meta_value = trim( $meta_value );
   578 			}
   614 			}
   579 
   615 
   580 			switch ( $meta_compare ) {
   616 			switch ( $meta_compare ) {
   581 				case 'IN' :
   617 				case 'IN':
   582 				case 'NOT IN' :
   618 				case 'NOT IN':
   583 					$meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')';
   619 					$meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')';
   584 					$where = $wpdb->prepare( $meta_compare_string, $meta_value );
   620 					$where               = $wpdb->prepare( $meta_compare_string, $meta_value );
   585 					break;
   621 					break;
   586 
   622 
   587 				case 'BETWEEN' :
   623 				case 'BETWEEN':
   588 				case 'NOT BETWEEN' :
   624 				case 'NOT BETWEEN':
   589 					$meta_value = array_slice( $meta_value, 0, 2 );
   625 					$meta_value = array_slice( $meta_value, 0, 2 );
   590 					$where = $wpdb->prepare( '%s AND %s', $meta_value );
   626 					$where      = $wpdb->prepare( '%s AND %s', $meta_value );
   591 					break;
   627 					break;
   592 
   628 
   593 				case 'LIKE' :
   629 				case 'LIKE':
   594 				case 'NOT LIKE' :
   630 				case 'NOT LIKE':
   595 					$meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%';
   631 					$meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%';
   596 					$where = $wpdb->prepare( '%s', $meta_value );
   632 					$where      = $wpdb->prepare( '%s', $meta_value );
   597 					break;
   633 					break;
   598 
   634 
   599 				// EXISTS with a value is interpreted as '='.
   635 				// EXISTS with a value is interpreted as '='.
   600 				case 'EXISTS' :
   636 				case 'EXISTS':
   601 					$meta_compare = '=';
   637 					$meta_compare = '=';
   602 					$where = $wpdb->prepare( '%s', $meta_value );
   638 					$where        = $wpdb->prepare( '%s', $meta_value );
   603 					break;
   639 					break;
   604 
   640 
   605 				// 'value' is ignored for NOT EXISTS.
   641 				// 'value' is ignored for NOT EXISTS.
   606 				case 'NOT EXISTS' :
   642 				case 'NOT EXISTS':
   607 					$where = '';
   643 					$where = '';
   608 					break;
   644 					break;
   609 
   645 
   610 				default :
   646 				default:
   611 					$where = $wpdb->prepare( '%s', $meta_value );
   647 					$where = $wpdb->prepare( '%s', $meta_value );
   612 					break;
   648 					break;
   613 
   649 
   614 			}
   650 			}
   615 
   651 
   685 
   721 
   686 			// Clauses connected by OR can share joins as long as they have "positive" operators.
   722 			// Clauses connected by OR can share joins as long as they have "positive" operators.
   687 			if ( 'OR' === $parent_query['relation'] ) {
   723 			if ( 'OR' === $parent_query['relation'] ) {
   688 				$compatible_compares = array( '=', 'IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', '>', '>=', '<', '<=' );
   724 				$compatible_compares = array( '=', 'IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', '>', '>=', '<', '<=' );
   689 
   725 
   690 			// Clauses joined by AND with "negative" operators share a join only if they also share a key.
   726 				// Clauses joined by AND with "negative" operators share a join only if they also share a key.
   691 			} elseif ( isset( $sibling['key'] ) && isset( $clause['key'] ) && $sibling['key'] === $clause['key'] ) {
   727 			} elseif ( isset( $sibling['key'] ) && isset( $clause['key'] ) && $sibling['key'] === $clause['key'] ) {
   692 				$compatible_compares = array( '!=', 'NOT IN', 'NOT LIKE' );
   728 				$compatible_compares = array( '!=', 'NOT IN', 'NOT LIKE' );
   693 			}
   729 			}
   694 
   730 
   695 			$clause_compare  = strtoupper( $clause['compare'] );
   731 			$clause_compare  = strtoupper( $clause['compare'] );
   708 		 * @param string|bool $alias        Table alias, or false if none was found.
   744 		 * @param string|bool $alias        Table alias, or false if none was found.
   709 		 * @param array       $clause       First-order query clause.
   745 		 * @param array       $clause       First-order query clause.
   710 		 * @param array       $parent_query Parent of $clause.
   746 		 * @param array       $parent_query Parent of $clause.
   711 		 * @param object      $this         WP_Meta_Query object.
   747 		 * @param object      $this         WP_Meta_Query object.
   712 		 */
   748 		 */
   713 		return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this ) ;
   749 		return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this );
   714 	}
   750 	}
   715 
   751 
   716 	/**
   752 	/**
   717 	 * Checks whether the current query has any OR relations.
   753 	 * Checks whether the current query has any OR relations.
   718 	 *
   754 	 *