APIv4 - Remove implicit multi-joins
authorColeman Watts <coleman@civicrm.org>
Thu, 7 May 2020 14:01:17 +0000 (10:01 -0400)
committerColeman Watts <coleman@civicrm.org>
Fri, 15 May 2020 17:00:22 +0000 (13:00 -0400)
These were actually run as a separate query and "joined" to the main results array in PHP.
Removing them to make way for supporting explicit multi-joins which will use SQL.

Civi/Api4/Event/Events.php
Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php [deleted file]
Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php [deleted file]
Civi/Api4/Query/Api4SelectQuery.php
Civi/Api4/Service/Schema/Joiner.php
tests/phpunit/api/v4/Action/FkJoinTest.php
tests/phpunit/api/v4/Query/Api4SelectQueryComplexJoinTest.php [deleted file]
tests/phpunit/api/v4/Query/Api4SelectQueryTest.php
tests/phpunit/api/v4/Query/OneToOneJoinTest.php
tests/phpunit/api/v4/Query/SelectQueryMultiJoinTest.php

index bb866e287b636ca03b3b3b3ebc2f4d7b54b7b6aa..4264ef2ff5fe8af53934fe48724202750c4a283b 100644 (file)
@@ -36,9 +36,4 @@ class Events {
    */
   const SCHEMA_MAP_BUILD = 'api.schema_map.build';
 
-  /**
-   * Alter query results of APIv4 select query
-   */
-  const POST_SELECT_QUERY = 'api.select_query.post';
-
 }
diff --git a/Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php b/Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php
deleted file mode 100644 (file)
index d41ab9f..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-
-/*
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC. All rights reserved.                        |
- |                                                                    |
- | This work is published under the GNU AGPLv3 license with some      |
- | permitted exceptions and without any warranty. For full license    |
- | and copyright information, see https://civicrm.org/licensing       |
- +--------------------------------------------------------------------+
- */
-
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
-namespace Civi\Api4\Event\Subscriber;
-
-use Civi\Api4\Event\Events;
-use Civi\Api4\Event\SchemaMapBuildEvent;
-use Civi\Api4\Service\Schema\Joinable\Joinable;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-
-class ContactSchemaMapSubscriber implements EventSubscriberInterface {
-
-  /**
-   * @return array
-   */
-  public static function getSubscribedEvents() {
-    return [
-      Events::SCHEMA_MAP_BUILD => 'onSchemaBuild',
-    ];
-  }
-
-  /**
-   * @param \Civi\Api4\Event\SchemaMapBuildEvent $event
-   */
-  public function onSchemaBuild(SchemaMapBuildEvent $event) {
-    $schema = $event->getSchemaMap();
-    $table = $schema->getTableByName('civicrm_contact');
-    $this->addCreatedActivitiesLink($table);
-    $this->fixPreferredLanguageAlias($table);
-  }
-
-  /**
-   * @param \Civi\Api4\Service\Schema\Table $table
-   */
-  private function addCreatedActivitiesLink($table) {
-    $alias = 'created_activities';
-    $joinable = new Joinable('civicrm_activity_contact', 'contact_id', $alias);
-    $joinable->addCondition($alias . '.record_type_id = 1');
-    $joinable->setJoinType($joinable::JOIN_TYPE_ONE_TO_MANY);
-    $table->addTableLink('id', $joinable);
-  }
-
-  /**
-   * @param \Civi\Api4\Service\Schema\Table $table
-   */
-  private function fixPreferredLanguageAlias($table) {
-    foreach ($table->getExternalLinks() as $link) {
-      if ($link->getAlias() === 'languages') {
-        $link->setAlias('preferred_language');
-        return;
-      }
-    }
-  }
-
-}
diff --git a/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php b/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php
deleted file mode 100644 (file)
index e21c2dd..0000000
+++ /dev/null
@@ -1,335 +0,0 @@
-<?php
-
-/*
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC. All rights reserved.                        |
- |                                                                    |
- | This work is published under the GNU AGPLv3 license with some      |
- | permitted exceptions and without any warranty. For full license    |
- | and copyright information, see https://civicrm.org/licensing       |
- +--------------------------------------------------------------------+
- */
-
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
-namespace Civi\Api4\Event\Subscriber;
-
-use Civi\Api4\Event\Events;
-use Civi\Api4\Event\PostSelectQueryEvent;
-use Civi\Api4\Query\Api4SelectQuery;
-use Civi\Api4\Utils\ArrayInsertionUtil;
-use Civi\Api4\Utils\FormattingUtil;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-
-/**
- * Changes the results of a select query, doing 1-n joins and unserializing data
- */
-class PostSelectQuerySubscriber implements EventSubscriberInterface {
-
-  /**
-   * @inheritDoc
-   */
-  public static function getSubscribedEvents() {
-    return [
-      Events::POST_SELECT_QUERY => 'onPostQuery',
-    ];
-  }
-
-  /**
-   * @param \Civi\Api4\Event\PostSelectQueryEvent $event
-   */
-  public function onPostQuery(PostSelectQueryEvent $event) {
-    $results = $event->getResults();
-    $event->setResults($this->postRun($results, $event->getQuery()));
-  }
-
-  /**
-   * @param array $results
-   * @param \Civi\Api4\Query\Api4SelectQuery $query
-   *
-   * @return array
-   */
-  protected function postRun(array $results, Api4SelectQuery $query) {
-    if (empty($results)) {
-      return $results;
-    }
-
-    FormattingUtil::formatOutputValues($results, $query->getApiFieldSpec(), $query->getEntity());
-
-    // Group the selects to avoid queries for each field
-    $groupedSelects = $this->getNtoManyJoinSelects($query);
-    foreach ($groupedSelects as $finalAlias => $selects) {
-      $joinPath = $query->getPathJoinTypes($selects[0]);
-      $selects = $this->formatSelects($finalAlias, $selects, $query);
-      $joinResults = $this->getJoinResults($query, $finalAlias, $selects);
-      $this->formatJoinResults($joinResults, $query, $finalAlias);
-
-      // Insert join results into original result
-      foreach ($results as &$primaryResult) {
-        $baseId = $primaryResult['id'];
-        $filtered = array_filter($joinResults, function ($res) use ($baseId) {
-          return ($res['_base_id'] == $baseId);
-        });
-        $filtered = array_values($filtered);
-        ArrayInsertionUtil::insert($primaryResult, $joinPath, $filtered);
-      }
-    }
-
-    return array_values($results);
-  }
-
-  /**
-   * @param array $joinResults
-   * @param \Civi\Api4\Query\Api4SelectQuery $query
-   * @param string $alias
-   */
-  private function formatJoinResults(&$joinResults, $query, $alias) {
-    $join = $query->getJoinedTable($alias);
-    $fields = [];
-    foreach ($join->getEntityFields() as $field) {
-      $name = explode('.', $field->getName());
-      $fields[array_pop($name)] = $field->toArray();
-    }
-    if ($fields) {
-      FormattingUtil::formatOutputValues($joinResults, $fields, $join->getEntity());
-    }
-  }
-
-  /**
-   * Find only those joins that need to be handled by a separate query and weren't done in the main query.
-   *
-   * @param \Civi\Api4\Query\Api4SelectQuery $query
-   *
-   * @return array
-   */
-  private function getNtoManyJoinSelects(Api4SelectQuery $query) {
-    $joinedDotSelects = array_filter(
-      $query->getSelect(),
-      function ($select) use ($query) {
-        return strpos($select, '.') && array_filter($query->getPathJoinTypes($select));
-      }
-    );
-
-    $selects = [];
-    // group related selects by alias so they can be executed in one query
-    foreach ($joinedDotSelects as $select) {
-      $parts = explode('.', $select);
-      $finalAlias = $parts[count($parts) - 2];
-      $selects[$finalAlias][] = $select;
-    }
-
-    // sort by depth, e.g. email selects should be done before email.location
-    uasort($selects, function ($a, $b) {
-      $aFirst = $a[0];
-      $bFirst = $b[0];
-      return substr_count($aFirst, '.') > substr_count($bFirst, '.');
-    });
-
-    return $selects;
-  }
-
-  /**
-   * @param array $selects
-   * @param $serializationType
-   * @param \Civi\Api4\Query\Api4SelectQuery $query
-   *
-   * @return array
-   */
-  private function getResultsForSerializedField(
-    array $selects,
-    $serializationType,
-    Api4SelectQuery $query
-  ) {
-    // Get the alias (Selects are grouped and all target the same table)
-    $sampleField = current($selects);
-    $alias = strstr($sampleField, '.', TRUE);
-
-    // Fetch the results with the serialized field
-    $selects['serialized'] = $query::MAIN_TABLE_ALIAS . '.' . $alias;
-    $serializedResults = $this->runWithNewSelects($selects, $query);
-    $newResults = [];
-
-    // Create a new results array, with a separate entry for each option value
-    foreach ($serializedResults as $result) {
-      $optionValues = \CRM_Core_DAO::unSerializeField(
-        $result['serialized'],
-        $serializationType
-      );
-      unset($result['serialized']);
-      foreach ($optionValues as $value) {
-        $newResults[] = array_merge($result, ['value' => $value]);
-      }
-    }
-
-    $optionValueValues = array_unique(array_column($newResults, 'value'));
-    $optionValues = $this->getOptionValuesFromValues(
-      $selects,
-      $query,
-      $optionValueValues
-    );
-    $valueField = $alias . '.value';
-
-    // Index by value
-    foreach ($optionValues as $key => $subResult) {
-      $optionValues[$subResult['value']] = $subResult;
-      unset($subResult[$key]);
-
-      // Exclude 'value' if not in original selects
-      if (!in_array($valueField, $selects)) {
-        unset($optionValues[$subResult['value']]['value']);
-      }
-    }
-
-    // Replace serialized with the sub-select results
-    foreach ($newResults as &$result) {
-      $result = array_merge($result, $optionValues[$result['value']]);
-      unset($result['value']);
-    }
-
-    return $newResults;
-  }
-
-  /**
-   * Prepares selects for the subquery to fetch join results
-   *
-   * @param string $alias
-   * @param array $selects
-   * @param \Civi\Api4\Query\Api4SelectQuery $query
-   *
-   * @return array
-   */
-  private function formatSelects($alias, $selects, Api4SelectQuery $query) {
-    $mainAlias = $query::MAIN_TABLE_ALIAS;
-    $selectFields = [];
-
-    foreach ($selects as $select) {
-      $selectAlias = str_replace('`', '', $query->getField($select)['sql_name']);
-      $fieldAlias = substr($select, strrpos($select, '.') + 1);
-      $selectFields[$fieldAlias] = $selectAlias;
-    }
-
-    $firstSelect = $selects[0];
-    $pathParts = explode('.', $firstSelect);
-    $numParts = count($pathParts);
-    $parentAlias = $numParts > 2 ? $pathParts[$numParts - 3] : $mainAlias;
-
-    $selectFields['id'] = sprintf('%s.id', $alias);
-    $selectFields['_parent_id'] = $parentAlias . '.id';
-    $selectFields['_base_id'] = $mainAlias . '.id';
-
-    return $selectFields;
-  }
-
-  /**
-   * @param array $selects
-   * @param \Civi\Api4\Query\Api4SelectQuery $query
-   *
-   * @return array
-   */
-  private function runWithNewSelects(array $selects, Api4SelectQuery $query) {
-    $aliasedSelects = array_map(function ($field, $alias) {
-      return sprintf('%s as "%s"', $field, $alias);
-    }, $selects, array_keys($selects));
-
-    $newSelect = sprintf('SELECT DISTINCT %s', implode(", ", $aliasedSelects));
-    $sql = $query->getQuery()->toSQL();
-    // Replace the "SELECT" clause
-    $sql = $newSelect . substr($sql, strpos($sql, "\nFROM"));
-
-    if (is_array($query->debugOutput)) {
-      $query->debugOutput['sql'][] = $sql;
-    }
-
-    $relatedResults = [];
-    $resultDAO = \CRM_Core_DAO::executeQuery($sql);
-    while ($resultDAO->fetch()) {
-      $relatedResult = [];
-      foreach ($selects as $alias => $column) {
-        $returnName = $alias;
-        $alias = str_replace('.', '_', $alias);
-        if (property_exists($resultDAO, $alias)) {
-          $relatedResult[$returnName] = $resultDAO->$alias;
-        }
-      };
-      $relatedResults[] = $relatedResult;
-    }
-
-    return $relatedResults;
-  }
-
-  /**
-   * @param \Civi\Api4\Query\Api4SelectQuery $query
-   * @param $alias
-   * @param $selects
-   * @return array
-   */
-  protected function getJoinResults(Api4SelectQuery $query, $alias, $selects) {
-    $apiFieldSpec = $query->getApiFieldSpec();
-    if (!empty($apiFieldSpec[$alias]['serialize'])) {
-      $type = $apiFieldSpec[$alias]['serialize'];
-      $joinResults = $this->getResultsForSerializedField($selects, $type, $query);
-    }
-    else {
-      $joinResults = $this->runWithNewSelects($selects, $query);
-    }
-
-    // Remove results with no matching entries
-    $joinResults = array_filter($joinResults, function ($result) {
-      return !empty($result['id']);
-    });
-
-    return $joinResults;
-  }
-
-  /**
-   * Get all the option_value values required in the query
-   *
-   * @param array $selects
-   * @param \Civi\Api4\Query\Api4SelectQuery $query
-   * @param array $values
-   *
-   * @return array
-   */
-  private function getOptionValuesFromValues(
-    array $selects,
-    Api4SelectQuery $query,
-    array $values
-  ) {
-    $sampleField = current($selects);
-    $alias = strstr($sampleField, '.', TRUE);
-
-    // Get the option value table that was joined
-    $relatedTable = NULL;
-    foreach ($query->getJoinedTables() as $joinedTable) {
-      if ($joinedTable->getAlias() === $alias) {
-        $relatedTable = $joinedTable;
-      }
-    }
-
-    // We only want subselects related to the joined table
-    $subSelects = array_filter($selects, function ($select) use ($alias) {
-      return strpos($select, $alias) === 0;
-    });
-
-    // Fetch all related option_value entries
-    $valueField = $alias . '.value';
-    $subSelects[] = $valueField;
-    $tableName = $relatedTable->getTargetTable();
-    $conditions = $relatedTable->getExtraJoinConditions();
-    $conditions[] = $valueField . ' IN ("' . implode('", "', $values) . '")';
-    $subQuery = new \CRM_Utils_SQL_Select($tableName . ' ' . $alias);
-    $subQuery->where($conditions);
-    $subQuery->select($subSelects);
-    $subResults = $subQuery->execute()->fetchAll();
-
-    return $subResults;
-  }
-
-}
index 63cba4e2807b06bbf60e3a25e904177da3ea6841..26e12fedbc796a28bcfce503c18cccad4182863f 100644 (file)
@@ -12,8 +12,6 @@
 namespace Civi\Api4\Query;
 
 use Civi\API\SelectQuery;
-use Civi\Api4\Event\Events;
-use Civi\Api4\Event\PostSelectQueryEvent;
 use Civi\Api4\Service\Schema\Joinable\CustomGroupJoinable;
 use Civi\Api4\Service\Schema\Joinable\Joinable;
 use Civi\Api4\Utils\FormattingUtil;
@@ -133,24 +131,21 @@ class Api4SelectQuery extends SelectQuery {
       $this->debugOutput['sql'][] = $sql;
     }
     $query = \CRM_Core_DAO::executeQuery($sql);
-    $i = 0;
     while ($query->fetch()) {
-      $id = $query->id ?? $i++;
       if (in_array('row_count', $this->select)) {
         $results[]['row_count'] = (int) $query->c;
         break;
       }
-      $results[$id] = [];
+      $result = [];
       foreach ($this->selectAliases as $alias => $expr) {
         $returnName = $alias;
         $alias = str_replace('.', '_', $alias);
-        $results[$id][$returnName] = property_exists($query, $alias) ? $query->$alias : NULL;
+        $result[$returnName] = property_exists($query, $alias) ? $query->$alias : NULL;
       }
+      $results[] = $result;
     }
-    $event = new PostSelectQueryEvent($results, $this);
-    \Civi::dispatcher()->dispatch(Events::POST_SELECT_QUERY, $event);
-
-    return $event->getResults();
+    FormattingUtil::formatOutputValues($results, $this->getApiFieldSpec(), $this->getEntity());
+    return $results;
   }
 
   protected function buildSelectClause() {
@@ -173,7 +168,7 @@ class Api4SelectQuery extends SelectQuery {
       });
       foreach ($wildFields as $item) {
         $pos = array_search($item, array_values($this->select));
-        $this->joinFK($item);
+        $this->autoJoinFK($item);
         $matches = SelectUtil::getMatchingFields($item, array_keys($this->apiFieldSpec));
         array_splice($this->select, $pos, 1, $matches);
       }
@@ -192,9 +187,6 @@ class Api4SelectQuery extends SelectQuery {
           }
           $valid = FALSE;
         }
-        elseif ($field['is_many']) {
-          $valid = FALSE;
-        }
       }
       if ($valid) {
         $alias = $expr->getAlias();
@@ -388,7 +380,7 @@ class Api4SelectQuery extends SelectQuery {
     $fieldName = $col ? substr($expr, 0, $col) : $expr;
     // Perform join if field not yet available - this will add it to apiFieldSpec
     if (!isset($this->apiFieldSpec[$fieldName]) && strpos($fieldName, '.')) {
-      $this->joinFK($fieldName);
+      $this->autoJoinFK($fieldName);
     }
     $field = $this->apiFieldSpec[$fieldName] ?? NULL;
     if ($strict && !$field) {
@@ -399,13 +391,13 @@ class Api4SelectQuery extends SelectQuery {
   }
 
   /**
-   * Joins a path and adds all fields in the joined eneity to apiFieldSpec
+   * Joins a path and adds all fields in the joined entity to apiFieldSpec
    *
    * @param $key
    * @throws \API_Exception
    * @throws \Exception
    */
-  protected function joinFK($key) {
+  protected function autoJoinFK($key) {
     if (isset($this->apiFieldSpec[$key])) {
       return;
     }
@@ -418,20 +410,12 @@ class Api4SelectQuery extends SelectQuery {
     array_pop($pathArray);
     $pathString = implode('.', $pathArray);
 
-    if (!$joiner->canJoin($this, $pathString)) {
+    if (!$joiner->canAutoJoin($this->getFrom(), $pathString)) {
       return;
     }
 
     $joinPath = $joiner->join($this, $pathString);
 
-    $isMany = FALSE;
-    foreach ($joinPath as $joinable) {
-      if ($joinable->getJoinType() === Joinable::JOIN_TYPE_ONE_TO_MANY) {
-        $isMany = TRUE;
-      }
-    }
-
-    /** @var \Civi\Api4\Service\Schema\Joinable\Joinable $lastLink */
     $lastLink = array_pop($joinPath);
 
     // Custom field names are already prefixed
@@ -447,7 +431,6 @@ class Api4SelectQuery extends SelectQuery {
       $fieldArray['sql_name'] = '`' . $lastLink->getAlias() . '`.`' . $fieldArray['column_name'] . '`';
       $fieldArray['is_custom'] = $isCustom;
       $fieldArray['is_join'] = TRUE;
-      $fieldArray['is_many'] = $isMany;
       $this->addSpecField($prefix . $fieldArray['name'], $fieldArray);
     }
   }
@@ -611,51 +594,6 @@ class Api4SelectQuery extends SelectQuery {
     }
   }
 
-  /**
-   * Checks if a field either belongs to the main entity or is joinable 1-to-1.
-   *
-   * Used to determine if a field can be added to the SELECT of the main query,
-   * or if it must be fetched post-query.
-   *
-   * @param string $fieldPath
-   * @return bool
-   */
-  public function isOneToOneField(string $fieldPath) {
-    return strpos($fieldPath, '.') === FALSE || !array_filter($this->getPathJoinTypes($fieldPath));
-  }
-
-  /**
-   * Separates a string like 'emails.location_type.label' into an array, where
-   * each value in the array tells whether it is 1-1 or 1-n join type
-   *
-   * @param string $pathString
-   *   Dot separated path to the field
-   *
-   * @return array
-   *   Index is table alias and value is boolean whether is 1-to-many join
-   */
-  public function getPathJoinTypes($pathString) {
-    $pathParts = explode('.', $pathString);
-    // remove field
-    array_pop($pathParts);
-    $path = [];
-    $query = $this;
-    $isMultipleChecker = function($alias) use ($query) {
-      foreach ($query->getJoinedTables() as $table) {
-        if ($table->getAlias() === $alias) {
-          return $table->getJoinType() === Joinable::JOIN_TYPE_ONE_TO_MANY;
-        }
-      }
-      return FALSE;
-    };
-
-    foreach ($pathParts as $part) {
-      $path[$part] = $isMultipleChecker($part);
-    }
-
-    return $path;
-  }
-
   /**
    * @param $path
    * @param $field
@@ -667,7 +605,7 @@ class Api4SelectQuery extends SelectQuery {
       return;
     }
     $defaults = [];
-    $defaults['is_custom'] = $defaults['is_join'] = $defaults['is_many'] = FALSE;
+    $defaults['is_custom'] = $defaults['is_join'] = FALSE;
     $field += $defaults;
     $this->apiFieldSpec[$path] = $field;
   }
index 6333e14a779e743b0a0de847b0eae59692e9f527..58bb850ce6c8112904dc57cf7f0011d35a9f5006 100644 (file)
@@ -72,20 +72,33 @@ class Joiner {
   }
 
   /**
-   * @param \Civi\Api4\Query\Api4SelectQuery $query
+   * Determines if path string points to a simple n-1 join that can be automatically added
+   *
+   * @param string $baseTable
    * @param $joinPath
    *
    * @return bool
    */
-  public function canJoin(Api4SelectQuery $query, $joinPath) {
-    return !empty($this->getPath($query->getFrom(), $joinPath));
+  public function canAutoJoin($baseTable, $joinPath) {
+    try {
+      $path = $this->getPath($baseTable, $joinPath);
+      foreach ($path as $joinable) {
+        if ($joinable->getJoinType() === $joinable::JOIN_TYPE_ONE_TO_MANY) {
+          return FALSE;
+        }
+      }
+      return TRUE;
+    }
+    catch (\Exception $e) {
+      return FALSE;
+    }
   }
 
   /**
    * @param string $baseTable
    * @param string $joinPath
    *
-   * @return array
+   * @return \Civi\Api4\Service\Schema\Joinable\Joinable[]
    * @throws \Exception
    */
   protected function getPath($baseTable, $joinPath) {
index 66e2c9dfe6ee67e063f7f7bd25f512e68a498e94..f23109a479c0603144cc2dcd8a87c72a95b4f841 100644 (file)
@@ -55,40 +55,4 @@ class FkJoinTest extends UnitTestCase {
     $this->assertCount(1, $results);
   }
 
-  public function testActivityContactJoin() {
-    $results = Activity::get()
-      ->setCheckPermissions(FALSE)
-      ->addSelect('assignees.id')
-      ->addSelect('assignees.first_name')
-      ->addSelect('assignees.display_name')
-      ->addWhere('assignees.first_name', '=', 'Phoney')
-      ->execute();
-
-    $firstResult = $results->first();
-
-    $this->assertCount(1, $results);
-    $this->assertTrue(is_array($firstResult['assignees']));
-
-    $firstAssignee = array_shift($firstResult['assignees']);
-    $this->assertEquals($firstAssignee['first_name'], 'Phoney');
-  }
-
-  public function testContactPhonesJoin() {
-    $testContact = $this->getReference('test_contact_1');
-    $testPhone = $this->getReference('test_phone_1');
-
-    $results = Contact::get()
-      ->setCheckPermissions(FALSE)
-      ->addSelect('phones.phone')
-      ->addWhere('id', '=', $testContact['id'])
-      ->addWhere('phones.location_type_id:name', '=', 'Home')
-      ->execute()
-      ->first();
-
-    $this->assertArrayHasKey('phones', $results);
-    $this->assertCount(1, $results['phones']);
-    $firstPhone = array_shift($results['phones']);
-    $this->assertEquals($testPhone['phone'], $firstPhone['phone']);
-  }
-
 }
diff --git a/tests/phpunit/api/v4/Query/Api4SelectQueryComplexJoinTest.php b/tests/phpunit/api/v4/Query/Api4SelectQueryComplexJoinTest.php
deleted file mode 100644 (file)
index eaae446..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-
-/*
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC. All rights reserved.                        |
- |                                                                    |
- | This work is published under the GNU AGPLv3 license with some      |
- | permitted exceptions and without any warranty. For full license    |
- | and copyright information, see https://civicrm.org/licensing       |
- +--------------------------------------------------------------------+
- */
-
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
-namespace api\v4\Query;
-
-use Civi\Api4\Query\Api4SelectQuery;
-use api\v4\UnitTestCase;
-
-/**
- * @group headless
- */
-class Api4SelectQueryComplexJoinTest extends UnitTestCase {
-
-  public function setUpHeadless() {
-    $relatedTables = [
-      'civicrm_address',
-      'civicrm_email',
-      'civicrm_phone',
-      'civicrm_openid',
-      'civicrm_im',
-      'civicrm_website',
-      'civicrm_activity',
-      'civicrm_activity_contact',
-    ];
-    $this->cleanup(['tablesToTruncate' => $relatedTables]);
-    $this->loadDataSet('SingleContact');
-    return parent::setUpHeadless();
-  }
-
-  public function testWithComplexRelatedEntitySelect() {
-    $api = \Civi\API\Request::create('Contact', 'get', ['version' => 4, 'checkPermissions' => FALSE]);
-    $query = new Api4SelectQuery($api);    $query->select[] = 'id';
-    $query->select[] = 'display_name';
-    $query->select[] = 'phones.*_id';
-    $query->select[] = 'emails.email';
-    $query->select[] = 'emails.location_type_id:name';
-    $query->select[] = 'created_activities.contact_id';
-    $query->select[] = 'created_activities.activity.subject';
-    $query->select[] = 'created_activities.activity.activity_type_id:name';
-    $query->where[] = ['first_name', '=', 'Single'];
-    $query->where[] = ['id', '=', $this->getReference('test_contact_1')['id']];
-    $results = $query->run();
-
-    $testActivities = [
-      $this->getReference('test_activity_1'),
-      $this->getReference('test_activity_2'),
-    ];
-    $activitySubjects = array_column($testActivities, 'subject');
-
-    $this->assertCount(1, $results);
-    $firstResult = array_shift($results);
-    $this->assertArrayHasKey('created_activities', $firstResult);
-    $firstCreatedActivity = array_shift($firstResult['created_activities']);
-    $this->assertArrayHasKey('activity', $firstCreatedActivity);
-    $firstActivity = $firstCreatedActivity['activity'];
-    $this->assertContains($firstActivity['subject'], $activitySubjects);
-    $this->assertArrayHasKey('activity_type_id:name', $firstActivity);
-
-    $this->assertArrayHasKey('location_type_id:name', $firstResult['emails'][0]);
-    $this->assertArrayHasKey('location_type_id', $firstResult['phones'][0]);
-    $this->assertArrayHasKey('id', $firstResult['phones'][0]);
-    $this->assertArrayNotHasKey('phone', $firstResult['phones'][0]);
-  }
-
-  public function testWithSelectOfOrphanDeepValues() {
-    $api = \Civi\API\Request::create('Contact', 'get', ['version' => 4, 'checkPermissions' => FALSE]);
-    $query = new Api4SelectQuery($api);    $query->select[] = 'id';
-    $query->select[] = 'first_name';
-    // emails not selected
-    $query->select[] = 'emails.location_type.name';
-    $results = $query->run();
-    $firstResult = array_shift($results);
-
-    $this->assertEmpty($firstResult['emails']);
-  }
-
-  public function testOrderDoesNotMatter() {
-    $api = \Civi\API\Request::create('Contact', 'get', ['version' => 4, 'checkPermissions' => FALSE]);
-    $query = new Api4SelectQuery($api);
-    $query->select[] = 'id';
-    $query->select[] = 'first_name';
-    // before emails selection
-    $query->select[] = 'emails.location_type_id:name';
-    $query->select[] = 'emails.email';
-    $query->where[] = ['emails.email', 'IS NOT NULL'];
-    $results = $query->run();
-    $firstResult = array_shift($results);
-
-    $this->assertNotEmpty($firstResult['emails'][0]['location_type_id:name']);
-  }
-
-}
index 3a11b5e8b0a51ff7f6b3fd93edac11cdd8d372f5..a628b89106fa8d52cc1c51bd4c0f091c4f221149 100644 (file)
@@ -48,35 +48,6 @@ class Api4SelectQueryTest extends UnitTestCase {
     return parent::setUpHeadless();
   }
 
-  public function testWithSingleWhereJoin() {
-    $phoneNum = $this->getReference('test_phone_1')['phone'];
-
-    $api = \Civi\API\Request::create('Contact', 'get', ['version' => 4, 'checkPermissions' => FALSE]);
-    $query = new Api4SelectQuery($api);
-    $query->where[] = ['phones.phone', '=', $phoneNum];
-    $results = $query->run();
-
-    $this->assertCount(1, $results);
-  }
-
-  public function testOneToManyJoin() {
-    $phoneNum = $this->getReference('test_phone_1')['phone'];
-
-    $api = \Civi\API\Request::create('Contact', 'get', ['version' => 4, 'checkPermissions' => FALSE]);
-    $query = new Api4SelectQuery($api);
-    $query->select[] = 'id';
-    $query->select[] = 'first_name';
-    $query->select[] = 'phones.phone';
-    $query->where[] = ['phones.phone', '=', $phoneNum];
-    $results = $query->run();
-
-    $this->assertCount(1, $results);
-    $firstResult = array_shift($results);
-    $this->assertArrayHasKey('phones', $firstResult);
-    $firstPhone = array_shift($firstResult['phones']);
-    $this->assertEquals($phoneNum, $firstPhone['phone']);
-  }
-
   public function testManyToOneJoin() {
     $phoneNum = $this->getReference('test_phone_1')['phone'];
     $contact = $this->getReference('test_contact_1');
@@ -95,20 +66,6 @@ class Api4SelectQueryTest extends UnitTestCase {
     $this->assertEquals($contact['display_name'], $firstResult['contact.display_name']);
   }
 
-  public function testOneToManyMultipleJoin() {
-    $api = \Civi\API\Request::create('Contact', 'get', ['version' => 4, 'checkPermissions' => FALSE]);
-    $query = new Api4SelectQuery($api);
-    $query->select[] = 'id';
-    $query->select[] = 'first_name';
-    $query->select[] = 'phones.phone';
-    $query->where[] = ['first_name', '=', 'Phoney'];
-    $results = $query->run();
-    $result = array_pop($results);
-
-    $this->assertEquals('Phoney', $result['first_name']);
-    $this->assertCount(2, $result['phones']);
-  }
-
   public function testInvaidSort() {
     $api = \Civi\API\Request::create('Contact', 'get', ['version' => 4, 'checkPermissions' => FALSE]);
     $query = new Api4SelectQuery($api);
index 138034c77b69bfdd6975c27ba2e74ea2396e2a90..416289db2c1ec76949bfb2317ff3f4e203245dbb 100644 (file)
@@ -14,7 +14,6 @@
  *
  * @package CRM
  * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
  *
  */
 
@@ -53,8 +52,7 @@ class OneToOneJoinTest extends UnitTestCase {
       ->addSelect('preferred_language:label')
       ->addSelect('last_name')
       ->execute()
-      ->indexBy('last_name')
-      ->getArrayCopy();
+      ->indexBy('last_name');
 
     $this->assertEquals($contacts['One']['preferred_language:label'], 'Armenian');
     $this->assertEquals($contacts['Two']['preferred_language:label'], 'Basque');
index 4f0f22416d0bc986042ea7bf1b2e21f7d5602315..d8af222c684f324f6667e47c7716fa1bba441ec9 100644 (file)
@@ -21,7 +21,6 @@
 
 namespace api\v4\Query;
 
-use Civi\Api4\Contact;
 use Civi\Api4\Email;
 use api\v4\UnitTestCase;
 
@@ -38,40 +37,11 @@ class SelectQueryMultiJoinTest extends UnitTestCase {
     return parent::setUpHeadless();
   }
 
-  public function testOneToManySelect() {
-    $results = Contact::get()
-      ->addSelect('emails.email')
-      ->execute()
-      ->indexBy('id')
-      ->getArrayCopy();
-
-    $firstContactId = $this->getReference('test_contact_1')['id'];
-    $secondContactId = $this->getReference('test_contact_2')['id'];
-
-    $firstContact = $results[$firstContactId];
-    $secondContact = $results[$secondContactId];
-    $firstContactEmails = array_column($firstContact['emails'], 'email');
-    $secondContactEmails = array_column($secondContact['emails'], 'email');
-
-    $expectedFirstEmails = [
-      'test_contact_one_home@fakedomain.com',
-      'test_contact_one_work@fakedomain.com',
-    ];
-    $expectedSecondEmails = [
-      'test_contact_two_home@fakedomain.com',
-      'test_contact_two_work@fakedomain.com',
-    ];
-
-    $this->assertEquals($expectedFirstEmails, $firstContactEmails);
-    $this->assertEquals($expectedSecondEmails, $secondContactEmails);
-  }
-
   public function testManyToOneSelect() {
     $results = Email::get()
       ->addSelect('contact.display_name')
       ->execute()
-      ->indexBy('id')
-      ->getArrayCopy();
+      ->indexBy('id');
 
     $firstEmail = $this->getReference('test_email_1');
     $secondEmail = $this->getReference('test_email_2');