Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/stable 3 3 0/8700 improve slow query #8702

Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3033629
pkp/pkp-lib#8700 Updated UserGroupDAO::_getSearchSql() to sort users …
jonasraoni Feb 25, 2023
e96b2fd
pkp/pkp-lib#8700 Updated DAO::countRecords() to optimize the query be…
jonasraoni Feb 25, 2023
ee9f26c
pkp/pkp-lib#8700 Updated code to drop the params after the ORDER BY
jonasraoni Feb 25, 2023
4742748
pkp/pkp-lib#8700 Fixed check to ensure the user was properly created
jonasraoni Feb 27, 2023
f0a721d
pkp/pkp-lib#8700 Give Cypress a chance to retry
jonasraoni Feb 28, 2023
d64de0e
pkp/pkp-lib#8700 Dropped query optimizer
jonasraoni Mar 23, 2023
269a3c2
pkp/pkp-lib#8700 Fixed query
jonasraoni Mar 23, 2023
802899f
pkp/pkp-lib#8700 Updated DAO::countRecords() to accept a Laravel Builder
jonasraoni Mar 23, 2023
9ef2515
pkp/pkp-lib#8700 Optimized DAOResultFactory instances which are built…
jonasraoni Mar 23, 2023
1abb51f
pkp/pkp-lib#8700 Optimized remaining DAOResultFactory instances
jonasraoni Mar 23, 2023
ace373a
pkp/pkp-lib#8700 Added Builder macro to count records in a safe and p…
jonasraoni Mar 23, 2023
fde386f
pkp/pkp-lib#8700 Updated code to make use of the new safeCount() macro
jonasraoni Mar 23, 2023
9d9fad4
pkp/pkp-lib#8700 Fixed flaky test
jonasraoni Mar 26, 2023
fe5ee56
pkp/pkp-lib#8700 Updated countRecords() to use the Builder::safeCount()
jonasraoni Mar 26, 2023
30bc59a
pkp/pkp-lib#8700 Dropped safeCount() workaround in favor of getCountF…
jonasraoni May 23, 2024
88a287e
pkp/pkp-lib#8700 Removed not-needed using
jonasraoni May 28, 2024
b6759cc
pkp/pkp-lib#8700 Improved documentation
jonasraoni May 28, 2024
b790311
pkp/pkp-lib#8700 Updated _getSearchSql() method not to generate a SQL…
jonasraoni May 28, 2024
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: 2 additions & 2 deletions api/v1/_email/PKPEmailHandler.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ public function process(ServerRequestInterface $slimRequest, APIResponse $respon
$countRunning = Capsule::table('jobs')
->where('queue', $args['queueId'])
->whereNotNull('reserved_at')
->count();
->getCountForPagination();
$countPending = $this->countPending($args['queueId']);

// Don't run another job if one is already running.
Expand Down Expand Up @@ -230,6 +230,6 @@ function() {
protected function countPending(string $queueId) : int {
return Capsule::table('jobs')
->where('queue', $queueId)
->count();
->getCountForPagination();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ public function getMany($slimRequest, $response, $args) {
$metricsDao = \DAORegistry::getDAO('MetricsDAO'); /** @var MetricsDAO */
return $response->withJson([
'items' => $items,
'itemsMax' => $metricsDao->countRecords($statsQO->toSql(), $statsQO->getBindings()),
'itemsMax' => $metricsDao->countRecords($statsQO),
], 200);
}

Expand Down
2 changes: 1 addition & 1 deletion classes/core/PKPApplication.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
define('WORKFLOW_TYPE_AUTHOR', 'author');

use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Database\Query\Builder;
jonasraoni marked this conversation as resolved.
Show resolved Hide resolved

interface iPKPApplicationInfoProvider {
/**
Expand Down Expand Up @@ -221,7 +222,6 @@ public function initializeDatabaseConnection() {
error_log("Database query\n$query->sql\n" . json_encode($query->bindings));//\n Bindings: " . print_r($query->bindings, true));
});


// Set up Laravel queue handling
$laravelContainer->bind('exception.handler', function () {
return new class implements Illuminate\Contracts\Debug\ExceptionHandler {
Expand Down
7 changes: 6 additions & 1 deletion classes/db/DAO.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
define('SORT_DIRECTION_DESC', 0x00002);

use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Database\Query\Builder;

class DAO {
/**
Expand Down Expand Up @@ -98,11 +99,15 @@ function retrieveRange($sql, $params = [], $dbResultRange = null, $callHooks = t

/**
* Count the number of records in the supplied SQL statement (with optional bind parameters parameters)
* @param $sql string SQL query to be counted
* @param $sql string|Builder SQL query to be counted
* @param $params array Optional SQL query bind parameters
jonasraoni marked this conversation as resolved.
Show resolved Hide resolved
* @return int
*/
public function countRecords($sql, $params = []) {
// In case a Laravel Builder has been received, drop its SELECT and ORDER BY clauses for optimization purposes
if ($sql instanceof Builder) {
return $sql->getCountForPagination();
}
$result = $this->retrieve('SELECT COUNT(*) AS row_count FROM (' . $sql . ') AS count_subquery', $params);
return $result->current()->row_count;
}
Expand Down
5 changes: 3 additions & 2 deletions classes/db/DAOResultFactory.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import('lib.pkp.classes.core.ItemIterator');
import('lib.pkp.classes.db.DAOResultIterator');

use Illuminate\Database\Query\Builder;
use Illuminate\Support\Enumerable;

class DAOResultFactory extends ItemIterator {
Expand All @@ -37,7 +38,7 @@ class DAOResultFactory extends ItemIterator {
var $records;

/**
* @var string|null Fetch SQL
* @var string|Builder|null Fetch SQL
*/
var $sql;

Expand All @@ -63,7 +64,7 @@ class DAOResultFactory extends ItemIterator {
* @param $dao object DAO class for factory
* @param $functionName The function to call on $dao to create an object
* @param $idFields array an array of primary key field names that uniquely identify a result row in the record set. Should be data object _data array key, not database column name
* @param $sql string Optional SQL query used to generate paged result set. Necessary when total row counts will be needed (e.g. when paging). WARNING: New code should not use this.
* @param $sql string|Builder Optional SQL query used to generate paged result set. Necessary when total row counts will be needed (e.g. when paging). WARNING: New code should not use this.
* @param $params array Optional parameters for SQL query used to generate paged result set. Necessary when total row counts will be needed (e.g. when paging). WARNING: New code should not use this.
* @param $rangeInfo DBResultRange Optional pagination information. WARNING: New code should not use this.
*/
Expand Down
17 changes: 10 additions & 7 deletions classes/log/EmailLogDAO.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,22 @@ function _getByEventType($assocType, $assocId, $eventType, $userId = null, $rang
];
if ($userId) $params[] = $userId;

$result = $this->retrieveRange(
$sql = 'SELECT e.*
FROM email_log e' .
($userId ? ' LEFT JOIN email_log_users u ON e.log_id = u.email_log_id' : '') .
' WHERE e.assoc_type = ? AND
$baseSql = '
FROM email_log e' .
jonasraoni marked this conversation as resolved.
Show resolved Hide resolved
($userId ? ' LEFT JOIN email_log_users u ON e.log_id = u.email_log_id' : '') . '
WHERE e.assoc_type = ? AND
e.assoc_id = ? AND
e.event_type = ?' .
($userId ? ' AND u.user_id = ?' : ''),
($userId ? ' AND u.user_id = ?' : '') . '
';

$result = $this->retrieveRange(
"SELECT e.* {$baseSql}",
$params,
$rangeInfo
);

return new DAOResultFactory($result, $this, 'build', [], $sql, $params, $rangeInfo); // Counted in submissionEmails.tpl
return new DAOResultFactory($result, $this, 'build', [], "SELECT 0 {$baseSql}", $params, $rangeInfo); // Counted in submissionEmails.tpl
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ function (Builder $q) use ($row, $lastInsertedFileId) {
// be unique.
$count = Capsule::table('review_round_files')
->where('file_id', '=', $row->file_id)
->count();
->getCountForPagination();
if ($count > 1) {
Capsule::table('review_round_files')
->where('file_id', '=', $row->file_id)
Expand Down
17 changes: 9 additions & 8 deletions classes/note/NoteDAO.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,19 @@ function getByAssoc($assocType, $assocId, $userId = null, $orderBy = NOTE_ORDER_
$directionSanitized = 'DESC';
}

$result = $this->retrieve(
$sql = 'SELECT *
FROM notes
WHERE assoc_id = ?
$baseSql = '
FROM notes
WHERE assoc_id = ?
AND assoc_type = ?
' . ($userId?' AND user_id = ?':'') .
($isAdmin?'':'
AND (title IS NOT NULL OR contents IS NOT NULL)') . '
ORDER BY ' . $orderSanitized . ' ' . $directionSanitized,
($isAdmin?'':' AND (title IS NOT NULL OR contents IS NOT NULL)') . '
';

$result = $this->retrieve(
"SELECT * {$baseSql} ORDER BY {$orderSanitized} {$directionSanitized}",
$params
);
return new DAOResultFactory($result, $this, '_fromRow', [], $sql, $params); // Counted in QueriesGridCellProvider
return new DAOResultFactory($result, $this, '_fromRow', [], "SELECT 0 {$baseSql}", $params); // Counted in QueriesGridCellProvider
}

/**
Expand Down
71 changes: 43 additions & 28 deletions classes/security/UserGroupDAO.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -250,18 +250,21 @@ function isDefault($userGroupId) {
function getByRoleId($contextId, $roleId, $default = false, $dbResultRange = null) {
$params = [(int) $contextId, (int) $roleId];
if ($default) $params[] = 1; // true
$result = $this->retrieveRange(
$sql = 'SELECT *
FROM user_groups
WHERE context_id = ? AND

$baseSql = '
FROM user_groups
WHERE context_id = ? AND
role_id = ?
' . ($default?' AND is_default = ?':'')
. ' ORDER BY user_group_id',
' . ($default?' AND is_default = ?':'') . '
';

$result = $this->retrieveRange(
"SELECT * {$baseSql} ORDER BY user_group_id",
$params,
$dbResultRange
);

return new DAOResultFactory($result, $this, '_returnFromRow', [], $sql, $params, $dbResultRange);
return new DAOResultFactory($result, $this, '_returnFromRow', [], "SELECT 0 {$baseSql}", $params, $dbResultRange);
}

/**
Expand Down Expand Up @@ -381,15 +384,18 @@ function getByContextId($contextId = null, $dbResultRange = null) {
$params = [];
if ($contextId) $params[] = (int) $contextId;

$baseSql = '
FROM user_groups ug' .
($contextId?' WHERE ug.context_id = ?':'') . '
';

$result = $this->retrieveRange(
$sql = 'SELECT ug.*
FROM user_groups ug' .
($contextId?' WHERE ug.context_id = ?':''),
"SELECT ug.* {$baseSql}",
$params,
$dbResultRange
);

return new DAOResultFactory($result, $this, '_returnFromRow', [], $sql, $params, $dbResultRange);
return new DAOResultFactory($result, $this, '_returnFromRow', [], "SELECT 0 {$baseSql}", $params, $dbResultRange);
}

/**
Expand Down Expand Up @@ -509,14 +515,15 @@ function getUsersById($userGroupId = null, $contextId = null, $searchType = null
COALESCE(us.setting_value, '') = '', us.locale <> ?
LIMIT 1
)";
$params = [
$selectParams = [
IDENTITY_SETTING_GIVENNAME, $locale, $primaryLocale, $locale,
IDENTITY_SETTING_FAMILYNAME, $locale, $primaryLocale, $locale
];

$sql = "SELECT u.*, $settingValue AS user_given, $settingValue AS user_family
$baseSql = '
FROM users AS u
WHERE 1 = 1";
WHERE 1 = 1
';

// Has user group
if ($contextId || $userGroupId) {
Expand All @@ -526,7 +533,7 @@ function getUsersById($userGroupId = null, $contextId = null, $searchType = null
if ($userGroupId) {
$params[] = (int) $userGroupId;
}
$sql .= ' AND EXISTS (
$baseSql .= ' AND EXISTS (
SELECT 0
FROM user_user_groups uug
INNER JOIN user_groups ug
Expand All @@ -537,12 +544,16 @@ function getUsersById($userGroupId = null, $contextId = null, $searchType = null
' . ($userGroupId ? 'AND ug.user_group_id = ?' : '') . '
)';
}
$sql .= ' ' . $this->_getSearchSql($searchType, $search, $searchMatch, $params);
$baseSql .= ' ' . $this->_getSearchSql($searchType, $search, $searchMatch, $params, '');
jonasraoni marked this conversation as resolved.
Show resolved Hide resolved

// Get the result set
$result = $this->retrieveRange($sql, $params, $dbResultRange);
$result = $this->retrieveRange(
"SELECT u.*, $settingValue AS user_given, $settingValue AS user_family {$baseSql} " . $this->userDao->getOrderBy(),
array_merge($selectParams, $params),
$dbResultRange
);

return new DAOResultFactory($result, $this->userDao, '_returnUserFromRowWithData', [], $sql, $params, $dbResultRange);
return new DAOResultFactory($result, $this->userDao, '_returnUserFromRowWithData', [], "SELECT 0 {$baseSql}", $params, $dbResultRange);
}

//
Expand Down Expand Up @@ -819,9 +830,10 @@ function deleteSettingsByLocale($locale) {
* @param string $search the keywords to search for.
* @param string $searchMatch where to match (is, contains, startsWith).
* @param array $params SQL parameter array reference
* @param ?string $querySuffix When null adds an ORDER BY clause using the UserDAO::getOrderBy()
* @return string SQL search snippet
*/
function _getSearchSql($searchType, $search, $searchMatch, &$params) {
function _getSearchSql($searchType, $search, $searchMatch, &$params, $querySuffix = null) {
$hasUserSetting = "EXISTS(
SELECT 0
FROM user_settings
Expand Down Expand Up @@ -910,7 +922,7 @@ function _getSearchSql($searchType, $search, $searchMatch, &$params) {
}
}

$searchSql .= $this->userDao->getOrderBy(); // FIXME Add "sort field" parameter?
$searchSql .= $querySuffix ?? $this->userDao->getOrderBy(); // FIXME Add "sort field" parameter?

return $searchSql;
}
Expand All @@ -930,22 +942,25 @@ function _getSearchSql($searchType, $search, $searchMatch, &$params) {
function getUserGroupsByStage($contextId, $stageId, $roleId = null, $dbResultRange = null) {
$params = [(int) $contextId, (int) $stageId];
if ($roleId) $params[] = (int) $roleId;

$baseSql = '
FROM user_groups ug
JOIN user_group_stage ugs ON (ug.user_group_id = ugs.user_group_id AND ug.context_id = ugs.context_id)
WHERE ugs.context_id = ? AND
ugs.stage_id = ?
' . ($roleId?'AND ug.role_id = ?':'') . '
';

return new DAOResultFactory(
$this->retrieveRange(
$sql = 'SELECT ug.*
FROM user_groups ug
JOIN user_group_stage ugs ON (ug.user_group_id = ugs.user_group_id AND ug.context_id = ugs.context_id)
WHERE ugs.context_id = ? AND
ugs.stage_id = ?
' . ($roleId?'AND ug.role_id = ?':'') . '
ORDER BY ug.role_id ASC',
"SELECT ug.* {$baseSql} ORDER BY ug.role_id ASC",
$params,
$dbResultRange
),
$this,
'_returnFromRow',
[],
$sql, $params, $dbResultRange
"SELECT 0 {$baseSql}", $params, $dbResultRange
);
}

Expand Down
4 changes: 2 additions & 2 deletions classes/services/PKPSubmissionService.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ public function getMany($args = []) {
if (isset($args['offset'])) unset($args['offset']);
$submissionListQO = $this->getQueryBuilder($args)->getQuery();
$submissionDao = DAORegistry::getDAO('SubmissionDAO'); /* @var $submissionDao SubmissionDAO */
$result = $submissionDao->retrieveRange($sql = $submissionListQO->toSql(), $params = $submissionListQO->getBindings(), $range);
$queryResults = new DAOResultFactory($result, $submissionDao, '_fromRow', [], $sql, $params, $range);
$result = $submissionDao->retrieveRange($submissionListQO->toSql(), $submissionListQO->getBindings(), $range);
$queryResults = new DAOResultFactory($result, $submissionDao, '_fromRow', [], $submissionListQO, [], $range);

return $queryResults->toIterator();
}
Expand Down
7 changes: 4 additions & 3 deletions classes/services/PKPUserService.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public function getMany($args = []) {
$userListQO = $this->getQueryBuilder($args)->getQuery();
$userDao = DAORegistry::getDAO('UserDAO'); /* @var $userDao UserDAO */
$result = $userDao->retrieveRange($userListQO->toSql(), $userListQO->getBindings(), $range);
$queryResults = new DAOResultFactory($result, $userDao, '_returnUserFromRowWithData', [], $userListQO->toSql(), $userListQO->getBindings());
$queryResults = new DAOResultFactory($result, $userDao, '_returnUserFromRowWithData', [], $userListQO);

return $queryResults->toIterator();
}
Expand Down Expand Up @@ -592,8 +592,9 @@ public function getAccessibleWorkflowStages($userId, $contextId, &$submission, $
* @param array $args See self::getMany()
*/
public function count($args = []) {
$qb = $this->getQueryBuilder($args);
return $qb->getQuery()->get()->count();
return $this->getQueryBuilder($args)
->getQuery()
->getCountForPagination();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ public function searchPhrase($phrase) {
public function getCount() {
return $this
->getQuery()
->select('a.announcement_id')
->get()
->count();
->getCountForPagination();
}

/**
Expand Down
4 changes: 1 addition & 3 deletions classes/services/queryBuilders/PKPAuthorQueryBuilder.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,7 @@ public function filterByPublicationIds($publicationIds) {
public function getCount() {
return $this
->getQuery()
->select('a.author_id')
->get()
->count();
->getCountForPagination();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,7 @@ public function searchPhrase($phrase) {
public function getCount() {
return $this
->getQuery()
->select('c.' . $this->dbIdColumn)
->get()
->count();
->getCountForPagination();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ public function getCount() {
$compiledQuery = $this->getCompiledQuery();
return Capsule::table(Capsule::raw('(' . $compiledQuery[0] . ') as email_template_count'))
->setBindings($compiledQuery[1])
->count();
->getCountForPagination();
}

/**
Expand Down
Loading