Skip to content

Commit

Permalink
dev/core#1093 add a bulkSave action for many customFields in one go
Browse files Browse the repository at this point in the history
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
  • Loading branch information
eileenmcnaughton committed Jul 8, 2019
1 parent 11b6a39 commit 0638164
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 26 deletions.
63 changes: 46 additions & 17 deletions CRM/Core/BAO/CustomField.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,7 @@ public static function dataToHtml() {
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);

Expand All @@ -164,6 +163,40 @@ public static function create($params) {
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 = [];
$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, $params);
$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.
*
Expand Down Expand Up @@ -1646,16 +1679,13 @@ public static function defaultCustomTableSchema($params) {
*
* @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);
Expand All @@ -1665,31 +1695,30 @@ public static function createField($field, $operation, $indexExist = FALSE, $tri
// 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->name]]);
}
}

if ($triggerRebuild) {
Civi::service('sql_triggers')->rebuild($params['table_name'], TRUE);
Civi::service('sql_triggers')->rebuild($tableName, TRUE);
}

}

/**
* @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);
}

Expand Down
1 change: 1 addition & 0 deletions CRM/Utils/Migrate/Import.php
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ public function customFields(&$xml, &$idMap) {

// 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;
Expand Down
33 changes: 33 additions & 0 deletions tests/phpunit/CRM/Core/BAO/CustomFieldTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -636,4 +636,37 @@ public function testGetFieldsForImport() {
$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);
}

}
26 changes: 17 additions & 9 deletions tests/phpunit/api/v3/CustomFieldTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,19 @@ public function setUp() {
parent::setUp();
}

/**
* Cleanup after tests.
*
* @throws \CRM_Core_Exception
*/
public function tearDown() {
$tablesToTruncate = array(
// true tells quickCleanup to drop custom_value tables that might have been created in the test
$this->quickCleanup([
'civicrm_contact',
'civicrm_file',
'civicrm_entity_file',
);
// true tells quickCleanup to drop custom_value tables that might have been created in the test
$this->quickCleanup($tablesToTruncate, TRUE);
], TRUE);
parent::tearDown();
}

/**
Expand Down Expand Up @@ -606,26 +611,29 @@ public function testMakeSearchableContactReferenceFieldUnsearchable() {
$result = $this->callAPISuccess('CustomField', 'create', $params);
}

/**
* Test disabling a searchable contact reference field.
*/
public function testDisableSearchableContactReferenceField() {
$customGroup = $this->customGroupCreate(array(
$customGroup = $this->customGroupCreate([
'name' => 'testCustomGroup',
'title' => 'testCustomGroup',
'extends' => 'Individual',
));
$params = array(
]);
$params = [
'name' => 'testCustomField',
'label' => 'testCustomField',
'custom_group_id' => 'testCustomGroup',
'data_type' => 'ContactReference',
'html_type' => 'Autocomplete-Select',
'is_searchable' => '1',
);
];
$result = $this->callAPISuccess('CustomField', 'create', $params);
$params = [
'id' => $result['id'],
'is_active' => 0,
];
$result = $this->callAPISuccess('CustomField', 'create', $params);
$this->callAPISuccess('CustomField', 'create', $params);
}

}

0 comments on commit 0638164

Please sign in to comment.