Skip to content

Commit

Permalink
Merge pull request #20715 from colemanw/fixFkJoins
Browse files Browse the repository at this point in the history
APIv4 - Fix same-table joins and remove unused code
  • Loading branch information
monishdeb authored Jun 30, 2021
2 parents 3c9cd4c + d2982e4 commit 962e1ff
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 63 deletions.
40 changes: 5 additions & 35 deletions Civi/Api4/Service/Schema/SchemaMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function getPath($baseTableName, $targetTableAlias) {
return $path;
}

$this->findPaths($table, $targetTableAlias, 1, $path);
$this->findPaths($table, $targetTableAlias, $path);

return $path;
}
Expand Down Expand Up @@ -88,49 +88,19 @@ public function addTables(array $tables) {
}

/**
* Recursive function to traverse the schema looking for a path
* Traverse the schema looking for a path
*
* @param Table $table
* The current table to base fromm
* @param string $target
* The target joinable table alias
* @param int $depth
* The current level of recursion which reflects the number of joins needed
* @param \Civi\Api4\Service\Schema\Joinable\Joinable[] $path
* (By-reference) The possible paths to the target table
* @param \Civi\Api4\Service\Schema\Joinable\Joinable[] $currentPath
* For internal use only to track the path to reach the target table
*/
private function findPaths(Table $table, $target, $depth, &$path, $currentPath = []
) {
static $visited = [];

// reset if new call
if ($depth === 1) {
$visited = [];
}

$canBeShorter = empty($path) || count($currentPath) + 1 < count($path);
$tooFar = $depth > self::MAX_JOIN_DEPTH;
$beenHere = in_array($table->getName(), $visited);

if ($tooFar || $beenHere || !$canBeShorter) {
return;
}

// prevent circular reference
$visited[] = $table->getName();

foreach ($table->getExternalLinks() as $link) {
private function findPaths(Table $table, $target, &$path) {
foreach ($table->getTableLinks() as $link) {
if ($link->getAlias() === $target) {
$path = array_merge($currentPath, [$link]);
}
else {
$linkTable = $this->getTableByName($link->getTargetTable());
if ($linkTable) {
$nextStep = array_merge($currentPath, [$link]);
$this->findPaths($linkTable, $target, $depth + 1, $path, $nextStep);
}
$path[] = $link;
}
}
}
Expand Down
28 changes: 28 additions & 0 deletions tests/phpunit/api/v4/Action/FkJoinTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,35 @@ public function testBridgeJoinRelationshipContactActivity() {
$this->assertNull($result[3]['rel.id']);
$this->assertEquals($cid3, $result[4]['contact.id']);
$this->assertNull($result[3]['rel.id']);
}

public function testJoinToEmployerId() {
$employer = Contact::create(FALSE)
->addValue('contact_type', 'Organization')
->addValue('organization_name', 'TesterCo')
->execute()->first()['id'];
$employee = Contact::create(FALSE)
->addValue('employer_id', $employer)
->addValue('first_name', 'TesterMan')
->execute()->first()['id'];
$email = Email::create(FALSE)
->addValue('email', 'tester@test.com')
->addValue('contact_id', $employee)
->execute()->first()['id'];

$contactGet = Contact::get(FALSE)
->addWhere('id', '=', $employee)
->addSelect('employer_id', 'employer_id.display_name')
->execute()->first();
$this->assertEquals($employer, $contactGet['employer_id']);
$this->assertEquals('TesterCo', $contactGet['employer_id.display_name']);

$emailGet = Email::get(FALSE)
->addWhere('id', '=', $email)
->addSelect('contact_id.employer_id', 'contact_id.employer_id.display_name')
->execute()->first();
$this->assertEquals($employer, $emailGet['contact_id.employer_id']);
$this->assertEquals('TesterCo', $emailGet['contact_id.employer_id.display_name']);
}

}
28 changes: 0 additions & 28 deletions tests/phpunit/api/v4/Service/Schema/SchemaMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,34 +46,6 @@ public function testWillHavePathWithSingleJump() {
$this->assertNotEmpty($map->getPath('civicrm_phone', 'location'));
}

public function testWillHavePathWithDoubleJump() {
$activity = new Table('activity');
$activityContact = new Table('activity_contact');
$middleLink = new Joinable('activity_contact', 'activity_id');
$contactLink = new Joinable('contact', 'id');
$activity->addTableLink('id', $middleLink);
$activityContact->addTableLink('contact_id', $contactLink);

$map = new SchemaMap();
$map->addTables([$activity, $activityContact]);

$this->assertNotEmpty($map->getPath('activity', 'contact'));
}

public function testPathWithTripleJoin() {
$first = new Table('first');
$second = new Table('second');
$third = new Table('third');
$first->addTableLink('id', new Joinable('second', 'id'));
$second->addTableLink('id', new Joinable('third', 'id'));
$third->addTableLink('id', new Joinable('fourth', 'id'));

$map = new SchemaMap();
$map->addTables([$first, $second, $third]);

$this->assertNotEmpty($map->getPath('first', 'fourth'));
}

public function testCircularReferenceWillNotBreakIt() {
$contactTable = new Table('contact');
$carTable = new Table('car');
Expand Down

0 comments on commit 962e1ff

Please sign in to comment.