dev/core#1093 add a bulkSave action for many customFields in one go
authoreileen <emcnaughton@wikimedia.org>
Thu, 4 Jul 2019 00:08:34 +0000 (12:08 +1200)
committereileen <emcnaughton@wikimedia.org>
Thu, 11 Jul 2019 00:23:26 +0000 (12:23 +1200)
Add bulkCreate function for CustomField with a view towards this being a new protocol for how bulk create actions would look from a code POV which we could expose via apiv4

CRM/Core/BAO/CustomField.php
CRM/Utils/Migrate/Import.php
tests/phpunit/CRM/Core/BAO/CustomFieldTest.php
tests/phpunit/api/v3/CustomFieldTest.php

index b893873e964f1eca896f54940358487c59cf9e0f..4af2b20991ec024b4ba879ec48e8c9c7494e12f7 100644 (file)
@@ -154,8 +154,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
   public static function create($params) {
     $customField = self::createCustomFieldRecord($params);
     $op = empty($params['id']) ? 'add' : 'modify';
-    $indexExist = empty($params['id']) ? FALSE : CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', $params['id'], 'is_searchable');
-    self::createField($customField, $op, $indexExist, CRM_Utils_Array::value('triggerRebuild', $params, TRUE));
+    self::createField($customField, $op, CRM_Utils_Array::value('triggerRebuild', $params, TRUE));
 
     CRM_Utils_Hook::post(($op === 'add' ? 'create' : 'edit'), 'CustomField', $customField->id, $customField);
 
@@ -164,6 +163,53 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
     return $customField;
   }
 
+  /**
+   * Create/ update several fields at once in a mysql efficient way.
+   *
+   * https://lab.civicrm.org/dev/core/issues/1093
+   *
+   * The intention is that apiv4 would expose any BAO with bulkSave as a new action.
+   *
+   * @param array $bulkParams
+   *   Array of arrays as would be passed into create
+   * @param array $defaults
+   *  Default parameters to be be merged into each of the params.
+   */
+  public static function bulkSave($bulkParams, $defaults) {
+    $sql = [];
+    $tables = [];
+    $customFields = [];
+    foreach ($bulkParams as $fieldParams) {
+      $params = array_merge($defaults, $fieldParams);
+      $operation = empty($params['id']) ? 'add' : 'modify';
+      $customField = self::createCustomFieldRecord($params);
+      $fieldSQL = self::getAlterFieldSQL($customField, $operation);
+      if (!isset($params['custom_group_id'])) {
+        $params['custom_group_id'] = civicrm_api3('CustomField', 'getvalue', ['id' => $customField->id, 'return' => 'custom_group_id']);
+      }
+      if (!isset($params['table_name'])) {
+        if (!isset($tables[$params['custom_group_id']])) {
+          $tables[$params['custom_group_id']] = civicrm_api3('CustomGroup', 'getvalue', [
+            'id' => $params['custom_group_id'],
+            'return' => 'table_name',
+          ]);
+        }
+        $params['table_name'] = $tables[$params['custom_group_id']];
+      }
+      $sql[$params['table_name']][] = $fieldSQL;
+      $customFields[] = $customField;
+    }
+    foreach ($sql as $tableName => $statements) {
+      // CRM-7007: do not i18n-rewrite this query
+      CRM_Core_DAO::executeQuery("ALTER TABLE $tableName " . implode(', ', $statements), [], TRUE, NULL, FALSE, FALSE);
+      Civi::service('sql_triggers')->rebuild($params['table_name'], TRUE);
+    }
+    CRM_Utils_System::flushCache();
+    foreach ($customFields as $customField) {
+      CRM_Utils_Hook::post($operation === 'add' ? 'create' : 'edit', 'CustomField', $customField->id, $customField);
+    }
+  }
+
   /**
    * Fetch object based on array of properties.
    *
@@ -1646,16 +1692,13 @@ SELECT $columnName
    *
    * @param CRM_Core_DAO_CustomField $field
    * @param string $operation
-   * @param bool $indexExist
    * @param bool $triggerRebuild
    */
-  public static function createField($field, $operation, $indexExist = FALSE, $triggerRebuild = TRUE) {
-    $params = [
-      'table_name' => CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $field->custom_group_id, 'table_name'),
-    ];
+  public static function createField($field, $operation, $triggerRebuild = TRUE) {
     $sql = str_repeat(' ', 8);
-    $sql .= "ALTER TABLE {$params['table_name']}";
-    $sql .= self::getAlterFieldSQL($field, $operation, $params, $indexExist);
+    $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $field->custom_group_id, 'table_name');
+    $sql .= "ALTER TABLE " . $tableName;
+    $sql .= self::getAlterFieldSQL($field, $operation);
 
     // CRM-7007: do not i18n-rewrite this query
     CRM_Core_DAO::executeQuery($sql, [], TRUE, NULL, FALSE, FALSE);
@@ -1665,14 +1708,14 @@ SELECT $columnName
       // CRM-16717 not sure why this was originally limited to add.
       // For example custom tables can have field length changes - which need to flow through to logging.
       // Are there any modifies we DON'T was to call this function for (& shouldn't it be clever enough to cope?)
-      if ($operation == 'add' || $operation == 'modify') {
+      if ($operation === 'add' || $operation === 'modify') {
         $logging = new CRM_Logging_Schema();
-        $logging->fixSchemaDifferencesFor($params['table_name'], [trim(strtoupper($operation)) => [$params['name']]]);
+        $logging->fixSchemaDifferencesFor($tableName, [trim(strtoupper($operation)) => [$field->column_name]]);
       }
     }
 
     if ($triggerRebuild) {
-      Civi::service('sql_triggers')->rebuild($params['table_name'], TRUE);
+      Civi::service('sql_triggers')->rebuild($tableName, TRUE);
     }
 
   }
@@ -1680,16 +1723,15 @@ SELECT $columnName
   /**
    * @param CRM_Core_DAO_CustomField $field
    * @param string $operation
-   * @param array $params
-   * @param bool $indexExist
    *
    * @return bool
    */
-  public static function getAlterFieldSQL($field, $operation, &$params, $indexExist = FALSE) {
+  public static function getAlterFieldSQL($field, $operation) {
+    $indexExist = $operation === 'add' ? FALSE : CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', $field->id, 'is_searchable');
     $params = self::prepareCreateParams($field, $operation);
-    // lets suppress the required flag, since that can cause sql issue
+    // Let's suppress the required flag, since that can cause an sql issue... for unknown reasons since we are calling
+    // a function only used by Custom Field creation...
     $params['required'] = FALSE;
-
     return CRM_Core_BAO_SchemaHandler::getFieldAlterSQL($params, $indexExist);
   }
 
index 340e10d51a6d154471cea5f5f05a44d0434f820e..590a924c70546468fed9e2ed2e58276052ad4913 100644 (file)
@@ -386,6 +386,7 @@ AND        v.name = %1
 
         // Only rebuild the table's trigger on the last field added to avoid un-necessary
         // and slow rebuilds when adding many fields at the same time.
+        // @todo - call bulkSave instead.
         $triggerRebuild = FALSE;
         if ($count == $total) {
           $triggerRebuild = TRUE;
index 70449f8d6ccb9254ab35721f375adea896ca2306..ffb3e19a91f94346f858ff5b2e203f0cbd1cced9 100644 (file)
@@ -636,4 +636,37 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
     $this->assertEquals($expected, CRM_Core_BAO_CustomField::getFieldsForImport());
   }
 
+  /**
+   * Test the bulk create function works.
+   */
+  public function testBulkCreate() {
+    $customGroup = $this->customGroupCreate([
+      'extends' => 'Individual',
+      'title' => 'my bulk group',
+    ]);
+    CRM_Core_BAO_CustomField::bulkSave([
+      [
+        'label' => 'Test',
+        'data_type' => 'String',
+        'html_type' => 'Text',
+        'column_name' => 'my_text',
+      ],
+      [
+        'label' => 'test_link',
+        'data_type' => 'Link',
+        'html_type' => 'Link',
+        'is_search_range' => '0',
+      ],
+    ],
+    [
+      'custom_group_id' => $customGroup['id'],
+      'is_active' => 1,
+      'is_searchable' => 1,
+    ]);
+    $dao = CRM_Core_DAO::executeQuery(('SHOW CREATE TABLE ' . $customGroup['values'][$customGroup['id']]['table_name']));
+    $dao->fetch();
+    $this->assertContains('`test_link_2` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL', $dao->Create_Table);
+    $this->assertContains('KEY `INDEX_my_text` (`my_text`)', $dao->Create_Table);
+  }
+
 }
index b05a439e118c16623be2f60609b3e571ba8277cc..1e7ad9d728fabfda35872ed1fabdb6ff68d59a1b 100644 (file)
@@ -628,6 +628,9 @@ class api_v3_CustomFieldTest extends CiviUnitTestCase {
     $result = $this->callAPISuccess('CustomField', 'create', $params);
   }
 
+  /**
+   * Test disabling a searchable contact reference field.
+   */
   public function testDisableSearchableContactReferenceField() {
     $customGroup = $this->customGroupCreate([
       'name' => 'testCustomGroup',
@@ -647,7 +650,7 @@ class api_v3_CustomFieldTest extends CiviUnitTestCase {
       'id' => $result['id'],
       'is_active' => 0,
     ];
-    $result = $this->callAPISuccess('CustomField', 'create', $params);
+    $this->callAPISuccess('CustomField', 'create', $params);
   }
 
 }