Export merge to household - fix DB error relating to fields too long for table.
authoreileen <emcnaughton@wikimedia.org>
Fri, 21 Dec 2018 03:35:57 +0000 (16:35 +1300)
committereileen <emcnaughton@wikimedia.org>
Mon, 31 Dec 2018 20:00:36 +0000 (09:00 +1300)
The key fix here is that the fields for merging to household are no longer added to the temporary table.

They are fully loaded in php anyway so there is no performance gain using a temp table.
Instead we iterate them in php as we process each row

CRM/Export/BAO/Export.php
CRM/Export/BAO/ExportProcessor.php
tests/phpunit/CRM/Export/BAO/ExportTest.php

index 565838b385ad10a9a53863ff1f80339469a4cdce..cc7a41749eb52d8f1cc5acc30934ab86fa08b735 100644 (file)
@@ -41,15 +41,6 @@ class CRM_Export_BAO_Export {
   // CRM-7675
   const EXPORT_ROW_COUNT = 100000;
 
-  /**
-   * Key representing the head of household in the relationship array.
-   *
-   * e.g. ['8_b_a' => 'Household Member Is', '8_a_b = 'Household Member Of'.....]
-   *
-   * @var
-   */
-  protected static $relationshipTypes = [];
-
   /**
    * Get default return property for export based on mode
    *
@@ -226,12 +217,11 @@ class CRM_Export_BAO_Export {
 
     $processor = new CRM_Export_BAO_ExportProcessor($exportMode, $fields, $queryOperator, $mergeSameHousehold, $isPostalOnly);
     $returnProperties = array();
-    self::$relationshipTypes = $processor->getRelationshipTypes();
 
     if ($fields) {
       foreach ($fields as $key => $value) {
         $fieldName = CRM_Utils_Array::value(1, $value);
-        if (!$fieldName) {
+        if (!$fieldName || $processor->isHouseholdMergeRelationshipTypeKey($fieldName)) {
           continue;
         }
 
@@ -341,17 +331,7 @@ INSERT INTO {$componentTable} SELECT distinct gc.contact_id FROM civicrm_group_c
         $returnProperties['id'] = 1;
       }
 
-      foreach ($returnProperties as $key => $value) {
-        if (!$processor->isRelationshipTypeKey($key)) {
-          foreach ($processor->getHouseholdRelationshipTypes() as $householdRelationshipType) {
-            if (!in_array($key, ['location_type', 'im_provider'])) {
-              $returnProperties[$householdRelationshipType][$key] = $value;
-            }
-          }
-          // @todo - don't use returnProperties above.
-          $processor->setHouseholdMergeReturnProperties($returnProperties[$householdRelationshipType]);
-        }
-      }
+      $processor->setHouseholdMergeReturnProperties(array_diff_key($returnProperties, array_fill_keys(['location_type', 'im_provider'], 1)));
     }
 
     self::buildRelatedContactArray($selectAll, $ids, $processor, $componentTable);
@@ -475,6 +455,9 @@ INSERT INTO {$componentTable} SELECT distinct gc.contact_id FROM civicrm_group_c
         $count++;
         $rowsThisIteration++;
         $row = $processor->buildRow($query, $iterationDAO, $outputColumns, $metadata, $paymentDetails, $addPaymentHeader, $paymentTableId);
+        if ($row === FALSE) {
+          continue;
+        }
 
         // add component info
         // write the row to a file
@@ -500,13 +483,6 @@ INSERT INTO {$componentTable} SELECT distinct gc.contact_id FROM civicrm_group_c
         self::mergeSameAddress($exportTempTable, $sqlColumns, $exportParams);
       }
 
-      // merge the records if they have corresponding households
-      if ($mergeSameHousehold) {
-        foreach ($processor->getHouseholdRelationshipTypes() as $householdRelationshipType) {
-          self::mergeSameHousehold($exportTempTable, $sqlColumns, $householdRelationshipType);
-        }
-      }
-
       // call export hook
       CRM_Utils_Hook::export($exportTempTable, $headerRows, $sqlColumns, $exportMode, $componentTable, $ids);
 
@@ -996,82 +972,6 @@ WHERE  id IN ( $deleteIDString )
     return $merge;
   }
 
-  /**
-   * Merge household record into the individual record
-   * if exists
-   *
-   * @param string $exportTempTable
-   *   Temporary temp table that stores the records.
-   * @param array $sqlColumns
-   *   Array of names of the table columns of the temp table.
-   * @param string $prefix
-   *   Name of the relationship type that is prefixed to the table columns.
-   */
-  public static function mergeSameHousehold($exportTempTable, &$sqlColumns, $prefix) {
-    $prefixColumn = $prefix . '_';
-    $replaced = array();
-
-    // name map of the non standard fields in header rows & sql columns
-    $mappingFields = array(
-      'civicrm_primary_id' => 'id',
-      'provider_id' => 'im_service_provider',
-    );
-
-    //figure out which columns are to be replaced by which ones
-    foreach ($sqlColumns as $columnNames => $dontCare) {
-      if ($rep = CRM_Utils_Array::value($columnNames, $mappingFields)) {
-        $replaced[$columnNames] = CRM_Utils_String::munge($prefixColumn . $rep, '_', 64);
-      }
-      else {
-        $householdColName = CRM_Utils_String::munge($prefixColumn . $columnNames, '_', 64);
-
-        if (!empty($sqlColumns[$householdColName])) {
-          $replaced[$columnNames] = $householdColName;
-        }
-      }
-    }
-    $query = "UPDATE $exportTempTable SET ";
-
-    $clause = array();
-    foreach ($replaced as $from => $to) {
-      $clause[] = "$from = $to ";
-      unset($sqlColumns[$to]);
-    }
-    $query .= implode(",\n", $clause);
-    $query .= " WHERE {$replaced['civicrm_primary_id']} != ''";
-
-    CRM_Core_DAO::executeQuery($query);
-
-    //drop the table columns that store redundant household info
-    $dropQuery = "ALTER TABLE $exportTempTable ";
-    foreach ($replaced as $householdColumns) {
-      $dropClause[] = " DROP $householdColumns ";
-    }
-    $dropQuery .= implode(",\n", $dropClause);
-
-    CRM_Core_DAO::executeQuery($dropQuery);
-
-    // also drop the temp table if exists
-    $sql = "DROP TABLE IF EXISTS {$exportTempTable}_temp";
-    CRM_Core_DAO::executeQuery($sql);
-
-    // clean up duplicate records
-    $query = "
-CREATE TABLE {$exportTempTable}_temp SELECT *
-FROM {$exportTempTable}
-GROUP BY civicrm_primary_id ";
-
-    CRM_Core_DAO::disableFullGroupByMode();
-    CRM_Core_DAO::executeQuery($query);
-    CRM_Core_DAO::reenableFullGroupByMode();
-
-    $query = "DROP TABLE $exportTempTable";
-    CRM_Core_DAO::executeQuery($query);
-
-    $query = "ALTER TABLE {$exportTempTable}_temp RENAME TO {$exportTempTable}";
-    CRM_Core_DAO::executeQuery($query);
-  }
-
   /**
    * @param $exportTempTable
    * @param $headerRows
index 02382cba8e1dfdb96d4a5a6a0145d55f4c3329ae..35968fdd67e0c24f035b7d75c5c75f7a166fac5f 100644 (file)
@@ -104,6 +104,13 @@ class CRM_Export_BAO_ExportProcessor {
    */
   protected $relationshipReturnProperties = [];
 
+  /**
+   * IDs of households that have already been exported.
+   *
+   * @var array
+   */
+  protected $exportedHouseholds = [];
+
   /**
    * Get return properties by relationship.
    * @return array
@@ -245,6 +252,30 @@ class CRM_Export_BAO_ExportProcessor {
     return isset($this->relatedContactValues[$relationshipType][$contactID][$field]) ? $this->relatedContactValues[$relationshipType][$contactID][$field] : '';
   }
 
+  /**
+   * Get the id of the related household.
+   *
+   * @param int $contactID
+   * @param string $relationshipType
+   *
+   * @return int
+   */
+  public function getRelatedHouseholdID($contactID, $relationshipType) {
+    return $this->relatedContactValues[$relationshipType][$contactID]['id'];
+  }
+
+  /**
+   * Has the household already been exported.
+   *
+   * @param int $housholdContactID
+   *
+   * @return bool
+   */
+  public function isHouseholdExported($housholdContactID) {
+    return isset($this->exportedHouseholds[$housholdContactID]);
+
+  }
+
   /**
    * @return bool
    */
@@ -606,18 +637,30 @@ class CRM_Export_BAO_ExportProcessor {
    * @param $addPaymentHeader
    * @param $paymentTableId
    *
-   * @return array
+   * @return array|bool
    */
   public function buildRow($query, $iterationDAO, $outputColumns, $metadata, $paymentDetails, $addPaymentHeader, $paymentTableId) {
     $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id');
     $imProviders = CRM_Core_PseudoConstant::get('CRM_Core_DAO_IM', 'provider_id');
 
     $row = [];
+    $householdMergeRelationshipType = $this->getHouseholdMergeTypeForRow($iterationDAO->contact_id);
+    if ($householdMergeRelationshipType) {
+      $householdID = $this->getRelatedHouseholdID($iterationDAO->contact_id, $householdMergeRelationshipType);
+      if ($this->isHouseholdExported($householdID)) {
+        return FALSE;
+      }
+      foreach (array_keys($outputColumns) as $column) {
+        $row[$column] = $this->getRelationshipValue($householdMergeRelationshipType, $iterationDAO->contact_id, $column);
+      }
+      $this->markHouseholdExported($householdID);
+      return $row;
+    }
+
     $query->convertToPseudoNames($iterationDAO);
 
     //first loop through output columns so that we return what is required, and in same order.
     foreach ($outputColumns as $field => $value) {
-
       // add im_provider to $dao object
       if ($field == 'im_provider' && property_exists($iterationDAO, 'provider_id')) {
         $iterationDAO->im_provider = $iterationDAO->provider_id;
@@ -645,20 +688,7 @@ class CRM_Export_BAO_ExportProcessor {
       }
 
       if ($this->isRelationshipTypeKey($field)) {
-        foreach (array_keys($value) as $property) {
-          if ($property === 'location') {
-            // @todo just undo all this nasty location wrangling!
-            foreach ($value['location'] as $locationKey => $locationFields) {
-              foreach (array_keys($locationFields) as $locationField) {
-                $fieldKey = str_replace(' ', '_', $locationKey . '-' . $locationField);
-                $row[$field . '_' . $fieldKey] = $this->getRelationshipValue($field, $iterationDAO->contact_id, $fieldKey);
-              }
-            }
-          }
-          else {
-            $row[$field . '_' . $property] = $this->getRelationshipValue($field, $iterationDAO->contact_id, $property);
-          }
-        }
+        $this->buildRelationshipFieldsForRow($row, $iterationDAO->contact_id, $value, $field);
       }
       else {
         $row[$field] = $this->getTransformedFieldValue($field, $iterationDAO, $fieldValue, $metadata, $paymentDetails);
@@ -692,6 +722,33 @@ class CRM_Export_BAO_ExportProcessor {
     return $row;
   }
 
+  /**
+   * If this row has a household whose details we should use get the relationship type key.
+   *
+   * @param $contactID
+   *
+   * @return bool
+   */
+  public function getHouseholdMergeTypeForRow($contactID) {
+    if (!$this->isMergeSameHousehold()) {
+      return FALSE;
+    }
+    foreach ($this->getHouseholdRelationshipTypes() as $relationshipType) {
+      if (isset($this->relatedContactValues[$relationshipType][$contactID])) {
+        return $relationshipType;
+      }
+    }
+  }
+
+  /**
+   * Mark the given household as already exported.
+   *
+   * @param $householdID
+   */
+  public function markHouseholdExported($householdID) {
+    $this->exportedHouseholds[$householdID] = $householdID;
+  }
+
   /**
    * @param $field
    * @param $iterationDAO
@@ -1276,4 +1333,27 @@ class CRM_Export_BAO_ExportProcessor {
     return $params;
   }
 
+  /**
+   * @param $row
+   * @param $contactID
+   * @param $value
+   * @param $field
+   */
+  protected function buildRelationshipFieldsForRow(&$row, $contactID, $value, $field) {
+    foreach (array_keys($value) as $property) {
+      if ($property === 'location') {
+        // @todo just undo all this nasty location wrangling!
+        foreach ($value['location'] as $locationKey => $locationFields) {
+          foreach (array_keys($locationFields) as $locationField) {
+            $fieldKey = str_replace(' ', '_', $locationKey . '-' . $locationField);
+            $row[$field . '_' . $fieldKey] = $this->getRelationshipValue($field, $contactID, $fieldKey);
+          }
+        }
+      }
+      else {
+        $row[$field . '_' . $property] = $this->getRelationshipValue($field, $contactID, $property);
+      }
+    }
+  }
+
 }
index 2d850895629a5300ba4c0a02dd24c121d4516eb7..e698fc550b668db38d2d91f1b89fa55aad7c8c28 100644 (file)
@@ -562,7 +562,6 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
    * Test exporting relationships.
    */
   public function testExportRelationshipsMergeToHouseholdAllFields() {
-    $this->markTestIncomplete('Does not yet work under CI due to mysql limitation (number of columns in table). Works on some boxes');
     list($householdID) = $this->setUpHousehold();
     list($tableName) = CRM_Export_BAO_Export::exportComponents(
       FALSE,
@@ -583,12 +582,13 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
     );
     $dao = CRM_Core_DAO::executeQuery("SELECT * FROM {$tableName}");
     while ($dao->fetch()) {
+      $this->assertEquals('Unit Test household', $dao->display_name);
       $this->assertEquals('Portland', $dao->city);
       $this->assertEquals('ME', $dao->state_province);
       $this->assertEquals($householdID, $dao->civicrm_primary_id);
       $this->assertEquals($householdID, $dao->civicrm_primary_id);
-      $this->assertEquals('Unit Test Household', $dao->addressee);
-      $this->assertEquals('Unit Test Household', $dao->display_name);
+      $this->assertEquals('Unit Test household', $dao->addressee);
+      $this->assertEquals(1, $dao->N);
     }
   }