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

ReferenceDynamic - Save lots of irrelevant queries when finding backreferences #29381

Merged
merged 1 commit into from
Mar 1, 2024
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
10 changes: 3 additions & 7 deletions CRM/Core/DAO.php
Original file line number Diff line number Diff line change
Expand Up @@ -2699,13 +2699,9 @@ public function getReferenceCounts() {
}

/**
* List all tables which have hard foreign keys to this table.
*
* For now, this returns a description of every entity_id/entity_table
* reference.
* TODO: filter dynamic entity references on the $tableName, based on
* schema metadata in dynamicForeignKey which enumerates a restricted
* set of possible entity_table's.
* List all tables which have either:
* - hard foreign keys to this table, or
* - a dynamic foreign key that includes this table as a possible target.
*
* @param string $tableName
* Table referred to.
Expand Down
54 changes: 31 additions & 23 deletions CRM/Core/Reference/Dynamic.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ class CRM_Core_Reference_Dynamic extends CRM_Core_Reference_Basic {
* @return bool
*/
public function matchesTargetTable($tableName) {
// FIXME: Shouldn't this check against keys returned by getTargetEntities?
return TRUE;
$targetEntities = $this->getTargetEntities();
if (!$targetEntities) {
// Missing whitelist! That's not good, but we'll grandfather it in by accepting all entities.
return TRUE;
}
return in_array(CRM_Core_DAO_AllCoreTables::getEntityNameForTable($tableName), $targetEntities, TRUE);
}

/**
* Returns a list of all allowed values for $this->refTypeColumn
*
* @return array
* [option_value => EntityName]
* [ref_column_value => EntityName]
* Keys are the value stored in $this->refTypeColumn,
* Values are the name of the corresponding entity.
*/
Expand Down Expand Up @@ -53,23 +57,14 @@ public function getTargetEntities(): array {
* a query-handle (like the result of CRM_Core_DAO::executeQuery)
*/
public function findReferences($targetDao) {
$refColumn = $this->getReferenceKey();
$targetColumn = $this->getTargetKey();

$params = [
1 => [$targetDao->$targetColumn, 'String'],
// If anyone complains about $targetDao::getTableName(), then could use
// "{get_class($targetDao)}::getTableName();"
2 => [$targetDao::getTableName(), 'String'],
];

$sql = <<<EOS
SELECT id
FROM {$this->getReferenceTable()}
WHERE {$refColumn} = %1
WHERE {$this->getReferenceKey()} = %1
AND {$this->getTypeColumn()} = %2
EOS;

$params = $this->getQueryParams($targetDao);
$daoName = CRM_Core_DAO_AllCoreTables::getClassForTable($this->getReferenceTable());
$result = CRM_Core_DAO::executeQuery($sql, $params, TRUE, $daoName);
return $result;
Expand All @@ -81,14 +76,6 @@ public function findReferences($targetDao) {
* @return array
*/
public function getReferenceCount($targetDao) {
$targetColumn = $this->getTargetKey();
$params = [
1 => [$targetDao->$targetColumn, 'String'],
// If anyone complains about $targetDao::getTableName(), then could use
// "{get_class($targetDao)}::getTableName();"
2 => [$targetDao::getTableName(), 'String'],
];

$sql = <<<EOS
SELECT count(id)
FROM {$this->getReferenceTable()}
Expand All @@ -101,7 +88,28 @@ public function getReferenceCount($targetDao) {
'type' => get_class($this),
'table' => $this->getReferenceTable(),
'key' => $this->getReferenceKey(),
'count' => CRM_Core_DAO::singleValueQuery($sql, $params),
'count' => CRM_Core_DAO::singleValueQuery($sql, $this->getQueryParams($targetDao)),
];
}

/**
* Gets query params needed by the find reference query
* @param CRM_Core_DAO $targetDao
* @return array[]
*/
private function getQueryParams($targetDao): array {
$targetColumn = $this->getTargetKey();

// Look up option value for this entity. It's usually the table name, but not always.
// If the lookup fails (some entities are missing the option list for the ref column),
// then fall back on the table name.
$targetEntity = CRM_Core_DAO_AllCoreTables::getBriefName(get_class($targetDao));
$targetEntities = $this->getTargetEntities();
$targetValue = array_search($targetEntity, $targetEntities) ?: $targetDao::getTableName();

return [
1 => [$targetDao->$targetColumn, 'String'],
2 => [$targetValue, 'String'],
];
}

Expand Down
9 changes: 9 additions & 0 deletions tests/phpunit/api/v3/ContactTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3386,6 +3386,13 @@ public function testGetReferenceCounts(): void {
],
],
]);
foreach ([1, 2, 3] as $num) {
$this->callAPISuccess('EntityTag', 'create', [
'entity_table' => 'civicrm_contact',
'entity_id' => $result['id'],
'tag_id' => $this->tagCreate(['name' => "taggy $num"])['id'],
]);
}

//$dao = new CRM_Contact_BAO_Contact();
//$dao->id = $result['id'];
Expand All @@ -3404,6 +3411,8 @@ public function testGetReferenceCounts(): void {
$this->assertEquals('civicrm_email', $refCountsIdx['sql:civicrm_email:contact_id']['table']);
$this->assertEquals(2, $refCountsIdx['sql:civicrm_phone:contact_id']['count']);
$this->assertEquals('civicrm_phone', $refCountsIdx['sql:civicrm_phone:contact_id']['table']);
$this->assertEquals(3, $refCountsIdx['sql:civicrm_entity_tag:entity_id']['count']);
$this->assertEquals('civicrm_entity_tag', $refCountsIdx['sql:civicrm_entity_tag:entity_id']['table']);
$this->assertNotTrue(isset($refCountsIdx['sql:civicrm_address:contact_id']));
}

Expand Down