[HPOS] Add support for ordering by metadata in order queries (#36403)
This commit is contained in:
commit
f84042a823
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add support for sorting by order metadata in HPOS queries.
|
|
@ -73,6 +73,13 @@ class OrdersTableMetaQuery {
|
|||
*/
|
||||
private $queries = array();
|
||||
|
||||
/**
|
||||
* Flat list of clauses by name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $flattened_clauses = array();
|
||||
|
||||
/**
|
||||
* JOIN clauses to add to the main SQL query.
|
||||
*
|
||||
|
@ -129,6 +136,73 @@ class OrdersTableMetaQuery {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of names (corresponding to meta_query clauses) that can be used as an 'orderby' arg.
|
||||
*
|
||||
* @since 7.4
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_orderby_keys(): array {
|
||||
if ( ! $this->flattened_clauses ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$keys = array();
|
||||
$keys[] = 'meta_value';
|
||||
$keys[] = 'meta_value_num';
|
||||
|
||||
$first_clause = reset( $this->flattened_clauses );
|
||||
if ( $first_clause && ! empty( $first_clause['key'] ) ) {
|
||||
$keys[] = $first_clause['key'];
|
||||
}
|
||||
|
||||
$keys = array_merge(
|
||||
$keys,
|
||||
array_keys( $this->flattened_clauses )
|
||||
);
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an SQL fragment for the given meta_query key that can be used in an ORDER BY clause.
|
||||
* Call {@see 'get_orderby_keys'} to obtain a list of valid keys.
|
||||
*
|
||||
* @since 7.4
|
||||
*
|
||||
* @param string $key The key name.
|
||||
* @return string
|
||||
*
|
||||
* @throws \Exception When an invalid key is passed.
|
||||
*/
|
||||
public function get_orderby_clause_for_key( string $key ): string {
|
||||
$clause = false;
|
||||
|
||||
if ( isset( $this->flattened_clauses[ $key ] ) ) {
|
||||
$clause = $this->flattened_clauses[ $key ];
|
||||
} else {
|
||||
$first_clause = reset( $this->flattened_clauses );
|
||||
|
||||
if ( $first_clause && ! empty( $first_clause['key'] ) ) {
|
||||
if ( 'meta_value_num' === $key ) {
|
||||
return "{$first_clause['alias']}.meta_value+0";
|
||||
}
|
||||
|
||||
if ( 'meta_value' === $key || $first_clause['key'] === $key ) {
|
||||
$clause = $first_clause;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $clause ) {
|
||||
// translators: %s is a meta_query key.
|
||||
throw new \Exception( sprintf( __( 'Invalid meta_query clause key: %s.', 'woocommerce' ), $key ) );
|
||||
}
|
||||
|
||||
return "CAST({$clause['alias']}.meta_value AS {$clause['cast']})";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given meta_query clause is atomic or not (i.e. not nested).
|
||||
*
|
||||
|
@ -170,6 +244,7 @@ class OrdersTableMetaQuery {
|
|||
}
|
||||
|
||||
$sanitized[ $key ] = $arg;
|
||||
$sanitized[ $key ]['index'] = $key;
|
||||
} else {
|
||||
$sanitized_arg = $this->sanitize_meta_query( $arg );
|
||||
|
||||
|
@ -298,12 +373,24 @@ class OrdersTableMetaQuery {
|
|||
$this->generate_where_for_clause_value( $arg ),
|
||||
)
|
||||
);
|
||||
|
||||
// Store clauses by their key for ORDER BY purposes.
|
||||
$flat_clause_key = is_int( $arg['index'] ) ? $arg['alias'] : $arg['index'];
|
||||
|
||||
$unique_flat_key = $flat_clause_key;
|
||||
$i = 1;
|
||||
while ( isset( $this->flattened_clauses[ $unique_flat_key ] ) ) {
|
||||
$unique_flat_key = $flat_clause_key . '-' . $i;
|
||||
$i++;
|
||||
}
|
||||
|
||||
$this->flattened_clauses[ $unique_flat_key ] =& $arg;
|
||||
} else {
|
||||
// Nested.
|
||||
$relation = $arg['relation'];
|
||||
unset( $arg['relation'] );
|
||||
|
||||
foreach ( $arg as $key => &$clause ) {
|
||||
foreach ( $arg as $index => &$clause ) {
|
||||
$chunks[] = $this->process( $clause, $arg );
|
||||
}
|
||||
|
||||
|
|
|
@ -522,11 +522,23 @@ class OrdersTableQuery {
|
|||
}
|
||||
}
|
||||
|
||||
$allowed_orderby = array_merge(
|
||||
array_keys( $mapping ),
|
||||
array_values( $mapping ),
|
||||
$this->meta_query ? $this->meta_query->get_orderby_keys() : array()
|
||||
);
|
||||
|
||||
$this->args['orderby'] = array();
|
||||
foreach ( $orderby as $order_key => $order ) {
|
||||
if ( isset( $mapping[ $order_key ] ) ) {
|
||||
$this->args['orderby'][ $mapping[ $order_key ] ] = $this->sanitize_order( $order );
|
||||
if ( ! in_array( $order_key, $allowed_orderby, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( isset( $mapping[ $order_key ] ) ) {
|
||||
$order_key = $mapping[ $order_key ];
|
||||
}
|
||||
|
||||
$this->args['orderby'][ $order_key ] = $this->sanitize_order( $order );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -991,8 +1003,14 @@ class OrdersTableQuery {
|
|||
return;
|
||||
}
|
||||
|
||||
$meta_orderby_keys = $this->meta_query ? $this->meta_query->get_orderby_keys() : array();
|
||||
|
||||
$orderby_array = array();
|
||||
foreach ( $this->args['orderby'] as $_orderby => $order ) {
|
||||
if ( in_array( $_orderby, $meta_orderby_keys, true ) ) {
|
||||
$_orderby = $this->meta_query->get_orderby_clause_for_key( $_orderby );
|
||||
}
|
||||
|
||||
$orderby_array[] = "{$_orderby} {$order}";
|
||||
}
|
||||
|
||||
|
|
|
@ -722,6 +722,76 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case {
|
|||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* @testDox Tests queries involving 'orderby' and meta queries.
|
||||
*/
|
||||
public function test_cot_query_meta_orderby() {
|
||||
$this->toggle_cot( true );
|
||||
|
||||
$order1 = new \WC_Order();
|
||||
$order1->add_meta_data( 'color', 'red' );
|
||||
$order1->add_meta_data( 'animal', 'lion' );
|
||||
$order1->add_meta_data( 'numeric_meta', '1000' );
|
||||
$order1->save();
|
||||
|
||||
$order2 = new \WC_Order();
|
||||
$order2->add_meta_data( 'color', 'green' );
|
||||
$order2->add_meta_data( 'animal', 'lion' );
|
||||
$order2->add_meta_data( 'numeric_meta', '500' );
|
||||
$order2->save();
|
||||
|
||||
$query_args = array(
|
||||
'orderby' => 'id',
|
||||
'order' => 'ASC',
|
||||
'meta_key' => 'color', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
);
|
||||
|
||||
// Check that orders are in order (when no meta ordering is involved).
|
||||
$q = new OrdersTableQuery( $query_args );
|
||||
$this->assertEquals( $q->orders, array( $order1->get_id(), $order2->get_id() ) );
|
||||
|
||||
// When ordering by color $order2 should come first.
|
||||
// Also tests that the key name is a valid synonym for the primary meta query.
|
||||
$query_args['orderby'] = 'color';
|
||||
$q = new OrdersTableQuery( $query_args );
|
||||
$this->assertEquals( $q->orders, array( $order2->get_id(), $order1->get_id() ) );
|
||||
|
||||
// When ordering by 'numeric_meta' 1000 < 500 (due to alphabetical sorting by default).
|
||||
// Also tests that 'meta_value' is a valid synonym for the primary meta query.
|
||||
$query_args['meta_key'] = 'numeric_meta'; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
$query_args['orderby'] = 'meta_value';
|
||||
$q = new OrdersTableQuery( $query_args );
|
||||
$this->assertEquals( $q->orders, array( $order1->get_id(), $order2->get_id() ) );
|
||||
|
||||
// Forcing numeric sorting with 'meta_value_num' reverses the order above.
|
||||
$query_args['orderby'] = 'meta_value_num';
|
||||
$q = new OrdersTableQuery( $query_args );
|
||||
$this->assertEquals( $q->orders, array( $order2->get_id(), $order1->get_id() ) );
|
||||
|
||||
// Sorting by 'animal' meta is ambiguous. Test that we can order by various meta fields (and use the names in 'orderby').
|
||||
unset( $query_args['meta_key'] );
|
||||
$query_args['meta_query'] = array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
'animal_meta' => array(
|
||||
'key' => 'animal',
|
||||
),
|
||||
'color_meta' => array(
|
||||
'key' => 'color',
|
||||
),
|
||||
);
|
||||
$query_args['orderby'] = array(
|
||||
'animal_meta' => 'ASC',
|
||||
'color_meta' => 'DESC',
|
||||
);
|
||||
$q = new OrdersTableQuery( $query_args );
|
||||
$this->assertEquals( $q->orders, array( $order1->get_id(), $order2->get_id() ) );
|
||||
|
||||
// Order is reversed when changing the sort order for 'color_meta'.
|
||||
$query_args['orderby']['color_meta'] = 'ASC';
|
||||
$q = new OrdersTableQuery( $query_args );
|
||||
$this->assertEquals( $q->orders, array( $order2->get_id(), $order1->get_id() ) );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @testDox Tests queries involving the 'customer' query var.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue