From 7187c8dff08cc5a5aafbcefeb5f6bf7fbcb3c10e Mon Sep 17 00:00:00 2001 From: Vedanshu Jain Date: Mon, 12 Dec 2022 13:39:44 +0530 Subject: [PATCH] Split CALC_FOUND_ROW query into seperate count query for better performance (#35468) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: NĂ©stor Soriano --- plugins/woocommerce/changelog/fix-35464 | 4 ++ .../DataStores/Orders/OrdersTableQuery.php | 32 +++++++++++--- .../Orders/OrdersTableDataStoreTests.php | 42 +++++++++++++++++++ 3 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-35464 diff --git a/plugins/woocommerce/changelog/fix-35464 b/plugins/woocommerce/changelog/fix-35464 new file mode 100644 index 00000000000..f6d26253ab5 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-35464 @@ -0,0 +1,4 @@ +Significance: patch +Type: performance + +Split CALC_FOUND_ROW query into seperate count query for better performance. diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php index 56805c8fd9e..8b71df2aed5 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php @@ -115,6 +115,13 @@ class OrdersTableQuery { */ private $sql = ''; + /** + * Final SQL query to count results after processing of args. + * + * @var string + */ + private $count_sql = ''; + /** * The number of pages (when pagination is enabled). * @@ -586,11 +593,7 @@ class OrdersTableQuery { $fields = $this->fields; // SQL_CALC_FOUND_ROWS. - if ( ( ! $this->arg_isset( 'no_found_rows' ) || ! $this->args['no_found_rows'] ) && $this->limits ) { - $found_rows = 'SQL_CALC_FOUND_ROWS'; - } else { - $found_rows = ''; - } + $found_rows = ''; // JOIN. $join = implode( ' ', array_unique( array_filter( array_map( 'trim', $this->join ) ) ) ); @@ -617,6 +620,23 @@ class OrdersTableQuery { $groupby = $this->groupby ? 'GROUP BY ' . implode( ', ', (array) $this->groupby ) : ''; $this->sql = "SELECT $found_rows DISTINCT $fields FROM $orders_table $join WHERE $where $groupby $orderby $limits"; + $this->build_count_query( $fields, $join, $where, $groupby ); + } + + /** + * Build SQL query for counting total number of results. + * + * @param string $fields Prepared fields for SELECT clause. + * @param string $join Prepared JOIN clause. + * @param string $where Prepared WHERE clause. + * @param string $groupby Prepared GROUP BY clause. + */ + private function build_count_query( $fields, $join, $where, $groupby ) { + if ( ! isset( $this->sql ) || '' === $this->sql ) { + wc_doing_it_wrong( __FUNCTION__, 'Count query can only be build after main query is built.', '7.3.0' ); + } + $orders_table = $this->tables['orders']; + $this->count_sql = "SELECT COUNT(DISTINCT $fields) FROM $orders_table $join WHERE $where $groupby"; } /** @@ -1014,7 +1034,7 @@ class OrdersTableQuery { } if ( $this->limits ) { - $this->found_orders = absint( $wpdb->get_var( 'SELECT FOUND_ROWS()' ) ); + $this->found_orders = absint( $wpdb->get_var( $this->count_sql ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $this->max_num_pages = (int) ceil( $this->found_orders / $this->args['limit'] ); } else { $this->found_orders = count( $this->orders ); diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php index 3b512c14328..765095fe095 100644 --- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php +++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php @@ -1003,6 +1003,48 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { $this->assertEquals( array_slice( $test_orders, 5, 5 ), $query->orders, 'The expected dataset is supplied when paginating through orders.' ); } + /** + * @testdox Test that the query counts works as expected. + * + * @return void + */ + public function test_cot_query_count() { + $this->assertEquals( 0, ( new OrdersTableQuery() )->found_orders, 'We initially have zero orders within our custom order tables.' ); + + for ( $i = 0; $i < 30; $i ++ ) { + $order = new WC_Order(); + $this->switch_data_store( $order, $this->sut ); + if ( 0 === $i % 2 ) { + $order->set_billing_address_2( 'Test' ); + } + $order->save(); + } + + $query = new OrdersTableQuery( array( 'limit' => 5 ) ); + $this->assertEquals( 30, $query->found_orders, 'Specifying limits still calculate all found orders.' ); + + // Count does not change based on the fields that we are fetching. + $query = new OrdersTableQuery( + array( + 'fields' => 'ids', + 'limit' => 5, + ) + ); + $this->assertEquals( 30, $query->found_orders, 'Fetching specific field does not change query count.' ); + + $query = new OrdersTableQuery( + array( + 'field_query' => array( + array( + 'field' => 'billing_address_2', + 'value' => 'Test', + ), + ), + ) + ); + $this->assertEquals( 15, $query->found_orders, 'Counting orders with a field query works.' ); + } + /** * @testDox Test the `get_order_count()` method. */