APIv4 - Preserve field data type when aggregating into an array using GROUP_CONCAT
authorColeman Watts <coleman@civicrm.org>
Tue, 15 Jun 2021 06:22:08 +0000 (02:22 -0400)
committerColeman Watts <coleman@civicrm.org>
Tue, 15 Jun 2021 10:56:20 +0000 (06:56 -0400)
Ensures that e.g. an array of integer fields will be returned as integers and not an array of strings

Civi/Api4/Query/SqlFunctionCOUNT.php
Civi/Api4/Query/SqlFunctionGROUP_CONCAT.php
Civi/Api4/Utils/FormattingUtil.php
tests/phpunit/api/v4/Action/SqlFunctionTest.php

index 2ab3d661630f9da57e8944502ddfb8f70e854c60..19ddd4ceba6f1fa61730bd9b212b954c51926e4d 100644 (file)
@@ -32,9 +32,12 @@ class SqlFunctionCOUNT extends SqlFunction {
    *
    * @see \Civi\Api4\Utils\FormattingUtil::formatOutputValues
    * @param string $value
+   * @param string $dataType
    * @return string|array
    */
-  public function formatOutputValue($value) {
+  public function formatOutputValue($value, &$dataType) {
+    // Count is always an integer
+    $dataType = 'Integer';
     return (int) $value;
   }
 
index fb618e0ac7c02eab3b56e865a960847635007fcb..1b52b941b5faaf23150bc720dcb2ee6d78e2cbe2 100644 (file)
@@ -51,13 +51,19 @@ class SqlFunctionGROUP_CONCAT extends SqlFunction {
    *
    * @see \Civi\Api4\Utils\FormattingUtil::formatOutputValues
    * @param string $value
+   * @param string $dataType
    * @return string|array
    */
-  public function formatOutputValue($value) {
+  public function formatOutputValue($value, &$dataType) {
     $exprArgs = $this->getArgs();
+    // By default, values are split into an array and formatted according to the field's dataType
     if (!$exprArgs[2]['prefix']) {
       $value = explode(\CRM_Core_DAO::VALUE_SEPARATOR, $value);
     }
+    // If using custom separator, unset $dataType to preserve raw string
+    else {
+      $dataType = NULL;
+    }
     return $value;
   }
 
index 888aafb4b795cdff87b5d86f04dae9cf8ccb47ea..340870e77f99fe4918272926a8a32ebf22ba08e4 100644 (file)
@@ -201,10 +201,9 @@ class FormattingUtil {
         $fieldName = \CRM_Utils_Array::first($fieldExpr->getFields());
         $field = $fieldName && isset($fields[$fieldName]) ? $fields[$fieldName] : NULL;
         $dataType = $field['data_type'] ?? ($fieldName == 'id' ? 'Integer' : NULL);
-        // If Sql Function e.g. GROUP_CONCAT or COUNT wants to do its own formatting, apply and skip dataType conversion
+        // If Sql Function e.g. GROUP_CONCAT or COUNT wants to do its own formatting, apply
         if (method_exists($fieldExpr, 'formatOutputValue') && is_string($value)) {
-          $result[$key] = $value = $fieldExpr->formatOutputValue($value);
-          $dataType = NULL;
+          $result[$key] = $value = $fieldExpr->formatOutputValue($value, $dataType);
         }
         if (!$field) {
           continue;
index a4ecd8d11f0bf582b4fa7bf4d775058cd12a85fd..585a796a1f1dec45d8d7efa48f0d19f57bea32b5 100644 (file)
@@ -71,12 +71,16 @@ class SqlFunctionTest extends UnitTestCase {
       ->addGroupBy('contact_id')
       ->addWhere('contact_id', '=', $cid)
       ->addSelect('GROUP_CONCAT(financial_type_id:name)')
+      ->addSelect('GROUP_CONCAT(financial_type_id)')
       ->addSelect('COUNT(*) AS count')
       ->execute()
       ->first();
 
     $this->assertTrue(4 === $agg['count']);
     $this->assertContains('Donation', $agg['GROUP_CONCAT:financial_type_id:name']);
+    foreach ($agg['GROUP_CONCAT:financial_type_id'] as $type) {
+      $this->assertTrue(is_int($type));
+    }
 
     // Test GROUP_CONCAT with a CONCAT as well
     $agg = Contribution::get(FALSE)