From a5d7af60bf71a9f9171b40a3ef754e2c7e79a4bc Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Wed, 23 Dec 2020 22:04:39 -0500 Subject: [PATCH] dev/core#2271 - APIv4 - Smarter check for adding default ON clause to explicit joins When adding explicit joins in APIv4, the API will automacially add an ON clause linking it to the base entity unless it detects some other link already present in the ON clause supplied. Previously it would determine this by checking for the join entity ID; now it looks for the join entity ID OR any other field in the join entity with an FK. This gives more leeway for people to add creative joins without the defaults getting in the way. --- Civi/Api4/Query/Api4SelectQuery.php | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Civi/Api4/Query/Api4SelectQuery.php b/Civi/Api4/Query/Api4SelectQuery.php index 922f0954a203..c02568433a82 100644 --- a/Civi/Api4/Query/Api4SelectQuery.php +++ b/Civi/Api4/Query/Api4SelectQuery.php @@ -535,7 +535,8 @@ private function addExplicitJoins() { $side = array_shift($join) ? 'INNER' : 'LEFT'; // Add all fields from joined entity to spec $joinEntityGet = \Civi\API\Request::create($entity, 'get', ['version' => 4, 'checkPermissions' => $this->getCheckPermissions()]); - foreach ($joinEntityGet->entityFields() as $field) { + $joinEntityFields = $joinEntityGet->entityFields(); + foreach ($joinEntityFields as $field) { $field['sql_name'] = '`' . $alias . '`.`' . $field['column_name'] . '`'; $this->addSpecField($alias . '.' . $field['name'], $field); } @@ -543,7 +544,7 @@ private function addExplicitJoins() { $conditions = $this->getBridgeJoin($join, $entity, $alias); } else { - $conditions = $this->getJoinConditions($join, $entity, $alias); + $conditions = $this->getJoinConditions($join, $entity, $alias, $joinEntityFields); } foreach (array_filter($join) as $clause) { $conditions[] = $this->treeWalkClauses($clause, 'ON'); @@ -559,20 +560,30 @@ private function addExplicitJoins() { * @param array $joinTree * @param string $joinEntity * @param string $alias + * @param array $joinEntityFields * @return array */ - private function getJoinConditions($joinTree, $joinEntity, $alias) { + private function getJoinConditions($joinTree, $joinEntity, $alias, $joinEntityFields) { $conditions = []; // getAclClause() expects a stack of 1-to-1 join fields to help it dedupe, but this is more flexible, // so unless this is a direct 1-to-1 join with the main entity, we'll just hack it // with a padded empty stack to bypass its deduping. $stack = [NULL, NULL]; - // If we're not explicitly referencing the joinEntity ID in the ON clause, search for a default - $explicitId = array_filter($joinTree, function($clause) use ($alias) { + // See if the ON clause already contains an FK reference to joinEntity + $explicitFK = array_filter($joinTree, function($clause) use ($alias, $joinEntityFields) { list($sideA, $op, $sideB) = array_pad((array) $clause, 3, NULL); - return $op === '=' && ($sideA === "$alias.id" || $sideB === "$alias.id"); + if ($op !== '=' || !$sideB) { + return FALSE; + } + foreach ([$sideA, $sideB] as $expr) { + if ($expr === "$alias.id" || !empty($joinEntityFields["$alias.$expr"]['fk_entity'])) { + return TRUE; + } + } + return FALSE; }); - if (!$explicitId) { + // If we're not explicitly referencing the ID (or some other FK field) of the joinEntity, search for a default + if (!$explicitFK) { foreach ($this->apiFieldSpec as $name => $field) { if ($field['entity'] !== $joinEntity && $field['fk_entity'] === $joinEntity) { $conditions[] = $this->treeWalkClauses([$name, '=', "$alias.id"], 'ON');