Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changelogs/remove_sql-calc-found-rows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
significance: major
type: changed
entry: Removing use of SQL_CALC_FOUND_ROWS due to depreciation in MySQL and
observed unreliability of count results.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"lifterlms/lifterlms-blocks": "2.7.2",
"lifterlms/lifterlms-cli": "0.0.5",
"lifterlms/lifterlms-helper": "3.5.9",
"lifterlms/lifterlms-rest": "1.0.4",
"lifterlms/lifterlms-rest": "1.0.5",
"woocommerce/action-scheduler": "3.5.4",
"gocodebox/banner-notifications": "1.1.1"
},
Expand Down
87 changes: 81 additions & 6 deletions includes/abstracts/abstract.llms.database.query.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ abstract class LLMS_Database_Query extends LLMS_Abstract_Query {
*/
protected $id = 'database';

/**
* SQL query used to count total found results.
*
* Set by subclasses in prepare_query() from the same clause
* variables (FROM, JOIN, WHERE) used for the main query.
*
* @since [version]
*
* @var string
*/
protected $count_query = '';

/**
* Retrieve query argument default values.
*
Expand Down Expand Up @@ -152,32 +164,38 @@ protected function set_found_results() {
}

/**
* Perform a SQL to retrieve the total number of found results for the given query.
* Retrieve the total number of found results for the given query.
*
* Uses a separate COUNT(*) query built from the same SQL clauses as the
* main query, set by subclasses in prepare_query().
*
* @since 6.0.0
* @since [version] Replaced FOUND_ROWS() with $this->count_query.
*
* @return int
*/
protected function found_results() {

global $wpdb;
return (int) $wpdb->get_var( 'SELECT FOUND_ROWS()' ); // db call ok; no-cache ok.

if ( empty( $this->count_query ) ) {
return 0;
}

return (int) $wpdb->get_var( $this->count_query ); // db call ok; no-cache ok.
}

/**
* Retrieve the prepared SQL for the SELECT clause.
*
* @since 4.5.1
* @since [version] Removed SQL_CALC_FOUND_ROWS; found results are now counted via a separate query.
*
* @param string $select_columns Optional. Columns to select. Default '*'.
* @return string
*/
protected function sql_select_columns( $select_columns = '*' ) {

if ( ! $this->get( 'count_only' ) && ! $this->get( 'no_found_rows' ) ) {
$select_columns = 'SQL_CALC_FOUND_ROWS ' . $select_columns;
}

if ( $this->get( 'suppress_filters' ) ) {
return $select_columns;
}
Expand All @@ -200,11 +218,16 @@ protected function sql_select_columns( $select_columns = '*' ) {
*
* @since 3.16.0
* @since 4.5.1 Drop use of `$this->get_filter('limit')` in favor of `"llms_{$this->id}_query_limit"`.
* @since [version] Returns empty string for count_only queries.
*
* @return string
*/
protected function sql_limit() {

if ( $this->get( 'count_only' ) ) {
return '';
}

global $wpdb;

$sql = $wpdb->prepare( 'LIMIT %d, %d', $this->get_skip(), $this->get( 'per_page' ) );
Expand Down Expand Up @@ -270,6 +293,58 @@ protected function sql_orderby() {
return apply_filters( "llms_{$this->id}_query_orderby", $sql, $this );
}

/**
* Execute a query.
*
* Overrides the parent to detect if a filter re-added SQL_CALC_FOUND_ROWS
* to the query, and falls back to FOUND_ROWS() if so.
*
* Also warns when a subclass does not set $this->count_query, which means
* get_found_results() and get_max_pages() will return 0.
*
* @since [version]
*
* @return void
*/
public function query() {

parent::query();

$has_sql_calc = ! $this->get( 'suppress_filters' ) &&
is_string( $this->query ) &&
str_contains( $this->query, 'SQL_CALC_FOUND_ROWS' );

if ( $has_sql_calc ) {
_deprecated_argument(
"llms_{$this->id}_query_prepare_query",
'[version]',
'SQL_CALC_FOUND_ROWS should no longer be added via filters. Results are now counted with a separate COUNT query.'
);

global $wpdb;
$this->found_results = (int) $wpdb->get_var( 'SELECT FOUND_ROWS()' ); // db call ok; no-cache ok.
$this->max_pages = absint( ceil( $this->found_results / $this->get( 'per_page' ) ) );
}

if (
$this->number_results &&
! $this->get( 'no_found_rows' ) &&
! $this->get( 'count_only' ) &&
empty( $this->count_query ) &&
! $has_sql_calc
) {
_doing_it_wrong(
get_class( $this ) . '::prepare_query',
sprintf(
/* translators: %s: The query subclass name. */
'Subclasses of LLMS_Database_Query should set $this->count_query in prepare_query() when no_found_rows is not true. %s does not set count_query, so get_found_results() and get_max_pages() will return 0.',
get_class( $this )
),
'[version]'
);
}
}

/**
* Gets information about properties that used to be public and have been replaced with public getters.
*
Expand Down
3 changes: 2 additions & 1 deletion includes/abstracts/llms.abstract.post.data.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,8 @@ public function recent_events( $args = array() ) {
)
);

$query_args['post_id'] = $this->post_id;
$query_args['post_id'] = $this->post_id;
$query_args['no_found_rows'] = true;

$query = new LLMS_Query_User_Postmeta( $query_args );

Expand Down
49 changes: 29 additions & 20 deletions includes/admin/reporting/tables/llms.table.quiz.non.attempts.php
Original file line number Diff line number Diff line change
Expand Up @@ -252,18 +252,7 @@ public function get_results( $args = array() ) {

$offset = ( $this->current_page - 1 ) * $per;

$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT SQL_CALC_FOUND_ROWS DISTINCT
u.ID as user_id,
u.user_email,
u.display_name,
u.user_registered,
m_first.meta_value as first_name,
m_last.meta_value as last_name,
upm.meta_value as enrollment_status,
upm.updated_date as enrollment_date
FROM {$wpdb->users} u
$from_joins_where = "FROM {$wpdb->users} u
INNER JOIN {$wpdb->prefix}lifterlms_user_postmeta upm
ON u.ID = upm.user_id
AND upm.post_id = %d
Expand All @@ -286,19 +275,39 @@ public function get_results( $args = array() ) {
AND upm2.meta_key = '_status'
)
AND qa.id IS NULL
{$search_sql}
{$search_sql}";

$prepare_args = array(
$course->get( 'id' ),
$this->quiz_id,
$course->get( 'id' ),
);

$total_results = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(DISTINCT u.ID) {$from_joins_where}",
$prepare_args
)
); // db call ok; no-cache ok.

$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT DISTINCT
u.ID as user_id,
u.user_email,
u.display_name,
u.user_registered,
m_first.meta_value as first_name,
m_last.meta_value as last_name,
upm.meta_value as enrollment_status,
upm.updated_date as enrollment_date
{$from_joins_where}
{$order_sql}
LIMIT %d, %d",
$course->get( 'id' ), // Course ID for enrollment check
$this->quiz_id, // Quiz ID for attempt check
$course->get( 'id' ), // Course ID for latest enrollment status
$offset,
$per
array_merge( $prepare_args, array( $offset, $per ) )
)
);

$total_results = $wpdb->get_var( 'SELECT FOUND_ROWS()' );

$this->max_pages = ceil( $total_results / $per );
$this->is_last_page = ( $this->current_page >= $this->max_pages );

Expand Down
6 changes: 3 additions & 3 deletions includes/admin/reporting/tables/llms.table.quizzes.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ protected function get_data( $key, $data ) {
case 'attempts':
$query = new LLMS_Query_Quiz_Attempt(
array(
'quiz_id' => $quiz->get( 'id' ),
'per_page' => 1,
'quiz_id' => $quiz->get( 'id' ),
'count_only' => true,
)
);

Expand All @@ -175,7 +175,7 @@ protected function get_data( $key, $data ) {
'quiz_id' => $quiz->get( 'id' ),
)
);
$value = '<a href="' . $url . '">' . $query->get_found_results() . '</a>';
$value = '<a href="' . $url . '">' . $query->get_count_only_result() . '</a>';

break;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,15 @@ protected function handle() {
*
* @since 4.6.0
* @since 4.7.0 Added `SQL_CALC_FOUND_ROWS` and improved query to exclude results with a completed payment plan.
* @since [version] Replaced SQL_CALC_FOUND_ROWS with a separate COUNT(*) query.
*
* @return object[]
*/
protected function query_orders() {

global $wpdb;

$orders = $wpdb->get_results(
"SELECT SQL_CALC_FOUND_ROWS p.ID
FROM {$wpdb->posts} AS p
$from_joins_where = "FROM {$wpdb->posts} AS p
LEFT JOIN {$wpdb->postmeta} AS m
ON p.ID = m.post_ID
AND m.meta_key = '_llms_plan_ended'
Expand All @@ -161,14 +160,19 @@ protected function query_orders() {
AND p.post_type = 'llms_order'
AND p.post_status = 'llms-active'
AND a.action_id IS NULL
AND m.meta_value IS NULL
AND m.meta_value IS NULL";

$total = $wpdb->get_var( "SELECT COUNT(*) {$from_joins_where}" ); // no-cache ok.
wp_cache_set( sprintf( '%s-total-results', $this->id ), $total, 'llms_tool_data' );

$orders = $wpdb->get_results(
"SELECT p.ID
{$from_joins_where}
ORDER BY p.ID ASC
LIMIT 50
;"
); // no-cache ok -- Caching implemented in `get_orders()`.

wp_cache_set( sprintf( '%s-total-results', $this->id ), $wpdb->get_var( 'SELECT FOUND_ROWS()' ), 'llms_tool_data' );

return $orders;

}
Expand Down
12 changes: 10 additions & 2 deletions includes/class-llms-events-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,16 +137,24 @@ protected function parse_args() {
* @since 3.36.0
* @since 4.7.0 Use `$this->sql_select_columns({columns})` to determine the columns to select.
* @since 6.0.0 Renamed from `preprare_query()`.
* @since [version] Build count_query from shared clauses instead of using SQL_CALC_FOUND_ROWS.
*
* @return string
*/
protected function prepare_query() {

global $wpdb;

$from = "FROM {$wpdb->prefix}lifterlms_events";
$where = $this->sql_where();

if ( ! $this->get( 'no_found_rows' ) ) {
$this->count_query = "SELECT COUNT(*) {$from} {$where}";
}

return "SELECT {$this->sql_select_columns( 'id' )}
FROM {$wpdb->prefix}lifterlms_events
{$this->sql_where()}
{$from}
{$where}
{$this->sql_orderby()}
{$this->sql_limit()};";

Expand Down
1 change: 1 addition & 0 deletions includes/class-llms-sessions.php
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ public function get_session_events( $start, $args = array() ) {
$args['exclude'][] = $end->get( 'id' );
}

$args['no_found_rows'] = true;
$query = new LLMS_Events_Query( $args );
return $query->get_events();

Expand Down
17 changes: 13 additions & 4 deletions includes/class.llms.query.quiz.attempt.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,18 +137,27 @@ protected function parse_args() {
*
* @since 3.16.0
* @since 6.0.0 Renamed from `preprare_query()`.
* @since [version] Build count_query from shared clauses instead of using SQL_CALC_FOUND_ROWS.
*
* @return string
*/
protected function prepare_query() {

global $wpdb;

$select = 'SELECT SQL_CALC_FOUND_ROWS qa.id';
$from = "FROM {$wpdb->prefix}lifterlms_quiz_attempts qa";
$joins = $this->sql_joins();
$from = "FROM {$wpdb->prefix}lifterlms_quiz_attempts qa";
$joins = $this->sql_joins();
$where = $this->sql_where();

return "{$select} {$from} {$joins} {$this->sql_where()} {$this->sql_orderby()} {$this->sql_limit()};";
if ( $this->get( 'count_only' ) ) {
return "SELECT COUNT(*) AS total {$from} {$joins} {$where};";
}

if ( ! $this->get( 'no_found_rows' ) ) {
$this->count_query = "SELECT COUNT(*) {$from} {$joins} {$where}";
}

return "SELECT qa.id {$from} {$joins} {$where} {$this->sql_orderby()} {$this->sql_limit()};";
}

/**
Expand Down
Loading
Loading