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: 3 additions & 1 deletion CRM/Core/PrevNextCache/Sql.php
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,9 @@ public function fetch($cacheKey, $offset, $rowCount) {
$dao = CRM_Utils_SQL_Select::from('civicrm_prevnext_cache pnc')
->where('pnc.cachekey = @cacheKey', ['cacheKey' => $cacheKey])
->select('pnc.entity_id1 as cid')
->orderBy('pnc.id')
// Prevent duplicates – dev/core#6638
->groupBy('pnc.entity_id1')
->orderBy('MIN(pnc.id)')
->limit($rowCount, $offset)
->execute();
while ($dao->fetch()) {
Expand Down
77 changes: 77 additions & 0 deletions tests/phpunit/CRM/Contact/SelectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,83 @@ public function testPrevNextCache(): void {
$this->checkArrayEquals($contacts[0], $expectedEntry);
}

/**
* Test that the prevnext cache handles duplicates and limit/offset sorting correctly.
*/
public function testPrevNextCacheDuplicatesWithSort(): void {
Civi::settings()->set('searchPrimaryDetailsOnly', 0);
$contactIDs = [];
// Create 30 contacts named Natalie
for ($i = 0; $i < 30; $i++) {
$contactID = $this->individualCreate(['first_name' => 'Natalie', 'last_name' => 'Test' . $i]);
$contactIDs[] = $contactID;
// Add multiple Home addresses
$this->callAPISuccess('Address', 'create', [
'contact_id' => $contactID,
'location_type_id' => 'Home',
'street_address' => "Home St A $i",
'city' => "City A",
'is_primary' => 1,
]);
$this->callAPISuccess('Address', 'create', [
'contact_id' => $contactID,
'location_type_id' => 'Home',
'street_address' => "Home St B $i",
'city' => "City B",
'is_primary' => 0,
]);
// Add multiple Home emails
$this->callAPISuccess('Email', 'create', [
'contact_id' => $contactID,
'location_type_id' => 'Home',
'email' => "natalie.home.a.$i@example.com",
'is_primary' => 1,
]);
$this->callAPISuccess('Email', 'create', [
'contact_id' => $contactID,
'location_type_id' => 'Home',
'email' => "natalie.home.b.$i@example.com",
'is_primary' => 0,
]);
// Add multiple Home phones
$this->callAPISuccess('Phone', 'create', [
'contact_id' => $contactID,
'location_type_id' => 'Home',
'phone' => "111-111-$i",
'is_primary' => 1,
]);
$this->callAPISuccess('Phone', 'create', [
'contact_id' => $contactID,
'location_type_id' => 'Home',
'phone' => "222-222-$i",
'is_primary' => 0,
]);
}

$formValues = [
'first_name' => 'Natalie',
'email' => 'natalie',
'phone_numeric' => '111',
];
$params = CRM_Contact_BAO_Query::convertFormValues($formValues, 0, FALSE, NULL, []);

$selector = new CRM_Contact_Selector(
'CRM_Contact_Selector',
$formValues,
$params,
NULL,
CRM_Core_Action::ADVANCED,
NULL,
FALSE,
'advanced'
);
$selector->setKey('natalie_test');

$rows = $selector->getRows(CRM_Core_Action::VIEW, 0, 50, 'city');

$this->assertCount(30, $rows, 'Should return all 30 contacts even when they have duplicates in the prevnext cache due to left joins.');
}

/**
* Data sets for testing.
*/
Expand Down