Remove use of SQL_CALC_FOUND_ROWS in low stock query (#40289)

* Separate count query

* Fix total_results var

* Add count-low-in-stock endpoint

* Use new count-low-in-stock endpoint

* Fix inconsistent in function names

* Type cast to int

* Add changefile(s) from automation for the following project(s): woocommerce

* Fix spelling

* Separate count query function

* Add changefile(s) from automation for the following project(s): woocommerce

* Use get_count_query

* Use count-low-in-stock endpoint

* Remove condition where it only updates product count when there is less than 2 items -- no longer needed as we are using a new endpoint to count the products

* Fix spelling

* Add changefile(s) from automation for the following project(s): woocommerce

* Lint fixes

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Moon 2023-11-08 20:49:50 -08:00 committed by GitHub
parent 65e533bedd
commit 670e2c0669
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 194 additions and 39 deletions

View File

@ -57,11 +57,7 @@ export function getOrderStatuses( select ) {
}
export const getLowStockCountQuery = {
page: 1,
per_page: 1,
low_in_stock: true,
status: 'publish',
_fields: [ 'id' ],
};
export function getLowStockCount( select ) {
@ -74,16 +70,16 @@ export function getLowStockCount( select ) {
// depend on `getItemsTotalCount` to have been called.
// eslint-disable-next-line @wordpress/no-unused-vars-before-return
const totalLowStockProducts = getItemsTotalCount(
'products/low-in-stock',
'products/count-low-in-stock',
getLowStockCountQuery,
defaultValue
);
const isError = Boolean(
getItemsError( 'products/low-in-stock', getLowStockCountQuery )
getItemsError( 'products/count-low-in-stock', getLowStockCountQuery )
);
const isRequesting = isResolving( 'getItemsTotalCount', [
'products/low-in-stock',
'products/count-low-in-stock',
getLowStockCountQuery,
defaultValue,
] );

View File

@ -40,9 +40,7 @@ export class StockPanel extends Component {
}
async updateStock( product, quantity ) {
const { invalidateResolution, updateProductStock, products } =
this.props;
const { invalidateResolution, updateProductStock } = this.props;
const success = await updateProductStock( product, quantity );
if ( success ) {
@ -51,13 +49,11 @@ export class StockPanel extends Component {
'products/low-in-stock',
productsQuery,
] );
if ( products.length < 2 ) {
invalidateResolution( 'getItemsTotalCount', [
'products/low-in-stock',
getLowStockCountQuery,
null,
] );
}
invalidateResolution( 'getItemsTotalCount', [
'products/count-low-in-stock',
getLowStockCountQuery,
null,
] );
}
return success;

View File

@ -0,0 +1,4 @@
Significance: patch
Type: performance
Remove use SQL_CALC_FOUND_ROWS from the query. SQL_CALC_FOUND_ROWS can cause inconsistent performance depending on the # of rows or DB version.

View File

@ -0,0 +1,4 @@
Significance: patch
Type: performance
Remove use SQL_CALC_FOUND_ROWS from the query. SQL_CALC_FOUND_ROWS can cause inconsistent performance depending on the # of rows or DB version.

View File

@ -42,6 +42,47 @@ final class ProductsLowInStock extends \WC_REST_Products_Controller {
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'products/count-low-in-stock',
array(
'args' => array(),
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'get_low_in_stock_count' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_low_in_stock_count_params(),
),
'schema' => array( $this, 'get_low_in_stock_count_schema' ),
)
);
}
/**
* Return # of low in stock count.
*
* @param WP_REST_Request $request request object.
*
* @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response
*/
public function get_low_in_stock_count( $request ) {
global $wpdb;
$status = $request->get_param( 'status' );
$low_stock_threshold = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
$sidewide_stock_threshold_only = $this->is_using_sitewide_stock_threshold_only();
$count_query_string = $this->get_count_query( $sidewide_stock_threshold_only );
$count_query_results = $wpdb->get_results(
// phpcs:ignore -- not sure why phpcs complains about this line when prepare() is used here.
$wpdb->prepare( $count_query_string, $status, $low_stock_threshold ),
);
$total_results = (int) $count_query_results[0]->total;
$response = rest_ensure_response( array( 'total' => $total_results ) );
$response->header( 'X-WP-Total', $total_results );
$response->header( 'X-WP-TotalPages', 0 );
return $response;
}
/**
@ -153,7 +194,9 @@ final class ProductsLowInStock extends \WC_REST_Products_Controller {
$offset = ( $page - 1 ) * $per_page;
$low_stock_threshold = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
$query_string = $this->get_query( $this->is_using_sitewide_stock_threshold_only() );
$sidewide_stock_threshold_only = $this->is_using_sitewide_stock_threshold_only();
$query_string = $this->get_query( $sidewide_stock_threshold_only );
$query_results = $wpdb->get_results(
// phpcs:ignore -- not sure why phpcs complains about this line when prepare() is used here.
@ -161,7 +204,13 @@ final class ProductsLowInStock extends \WC_REST_Products_Controller {
OBJECT_K
);
$total_results = $wpdb->get_var( 'SELECT FOUND_ROWS()' );
$count_query_string = $this->get_count_query( $sidewide_stock_threshold_only );
$count_query_results = $wpdb->get_results(
// phpcs:ignore -- not sure why phpcs complains about this line when prepare() is used here.
$wpdb->prepare( $count_query_string, $status, $low_stock_threshold ),
);
$total_results = $count_query_results[0]->total;
return array(
'results' => $query_results,
@ -213,19 +262,22 @@ final class ProductsLowInStock extends \WC_REST_Products_Controller {
}
/**
* Generate a query.
* Return a query string for low in stock products.
* The query string incldues the following replacement strings:
* - :selects
* - :postmeta_join
* - :postmeta_wheres
* - :orderAndLimit
*
* @param bool $siteside_only generates a query for sitewide low stock threshold only query.
* @param array $replacements of replacement strings.
*
* @return string
*/
protected function get_query( $siteside_only = false ) {
private function get_base_query( $replacements = array() ) {
global $wpdb;
$query = "
SELECT
SQL_CALC_FOUND_ROWS wp_posts.*,
:postmeta_select
wc_product_meta_lookup.stock_quantity
:selects
FROM
{$wpdb->wc_product_meta_lookup} wc_product_meta_lookup
LEFT JOIN {$wpdb->posts} wp_posts ON wp_posts.ID = wc_product_meta_lookup.product_id
@ -236,21 +288,26 @@ final class ProductsLowInStock extends \WC_REST_Products_Controller {
AND wc_product_meta_lookup.stock_quantity IS NOT NULL
AND wc_product_meta_lookup.stock_status IN('instock', 'outofstock')
:postmeta_wheres
order by wc_product_meta_lookup.product_id DESC
limit %d, %d
:orderAndLimit
";
$postmeta = array(
'select' => '',
'join' => '',
'wheres' => 'AND wc_product_meta_lookup.stock_quantity <= %d',
);
return strtr( $query, $replacements );
}
if ( ! $siteside_only ) {
$postmeta['select'] = 'meta.meta_value AS low_stock_amount,';
$postmeta['join'] = "LEFT JOIN {$wpdb->postmeta} AS meta ON wp_posts.ID = meta.post_id
AND meta.meta_key = '_low_stock_amount'";
$postmeta['wheres'] = "AND (
/**
* Add sitewide stock query string to base query string.
*
* @param string $query Base query string.
*
* @return string
*/
private function add_sitewide_stock_query_str( $query ) {
global $wpdb;
$postmeta = array(
'select' => 'meta.meta_value AS low_stock_amount,',
'join' => "LEFT JOIN {$wpdb->postmeta} AS meta ON wp_posts.ID = meta.post_id
AND meta.meta_key = '_low_stock_amount'",
'wheres' => "AND (
(
meta.meta_value > ''
AND wc_product_meta_lookup.stock_quantity <= CAST(
@ -264,8 +321,8 @@ final class ProductsLowInStock extends \WC_REST_Products_Controller {
)
AND wc_product_meta_lookup.stock_quantity <= %d
)
)";
}
)",
);
return strtr(
$query,
@ -277,6 +334,64 @@ final class ProductsLowInStock extends \WC_REST_Products_Controller {
);
}
/**
* Generate a query.
*
* @param bool $sitewide_only generates a query for sitewide low stock threshold only query.
*
* @return string
*/
protected function get_query( $sitewide_only = false ) {
$query = $this->get_base_query(
array(
':selects' => 'wp_posts.*, :postmeta_select wc_product_meta_lookup.stock_quantity',
':orderAndLimit' => 'order by wc_product_meta_lookup.product_id DESC limit %d, %d',
)
);
if ( ! $sitewide_only ) {
return $this->add_sitewide_stock_query_str( $query );
}
return strtr(
$query,
array(
':postmeta_select' => '',
':postmeta_join' => '',
':postmeta_wheres' => 'AND wc_product_meta_lookup.stock_quantity <= %d',
)
);
}
/**
* Generate a count query.
*
* @param bool $sitewide_only generates a query for sitewide low stock threshold only query.
*
* @return string
*/
protected function get_count_query( $sitewide_only = false ) {
$query = $this->get_base_query(
array(
':selects' => 'count(*) as total',
':orderAndLimit' => '',
)
);
if ( ! $sitewide_only ) {
return $this->add_sitewide_stock_query_str( $query );
}
return strtr(
$query,
array(
':postmeta_select' => '',
':postmeta_join' => '',
':postmeta_wheres' => 'AND wc_product_meta_lookup.stock_quantity <= %d',
)
);
}
/**
* Get the query params for collections of attachments.
*
@ -316,4 +431,44 @@ final class ProductsLowInStock extends \WC_REST_Products_Controller {
return $params;
}
/**
* Get the query params for collections for /count-low-in-stock endpoint.
*
* @return array
*/
public function get_low_in_stock_count_params() {
$params = array();
$params['context'] = $this->get_context_param();
$params['context']['default'] = 'view';
$params['status'] = array(
'default' => 'publish',
'description' => __( 'Limit result set to products assigned a specific status.', 'woocommerce' ),
'type' => 'string',
'enum' => array_merge( array_keys( get_post_statuses() ), array( 'future' ) ),
'sanitize_callback' => 'sanitize_key',
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
/**
* Get the schema for /count-low-in-stock response.
*
* @return array
*/
public function get_low_in_stock_count_schema() {
return array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'Count Low in Stock Items',
'type' => 'object',
'properties' => array(
'type' => 'object',
'properties' => array(
'total' => 'integer',
),
),
);
}
}