diff --git a/CRM/Activity/BAO/Activity.php b/CRM/Activity/BAO/Activity.php index 11fcfa530d61..31c3c253d932 100644 --- a/CRM/Activity/BAO/Activity.php +++ b/CRM/Activity/BAO/Activity.php @@ -920,8 +920,7 @@ public static function deprecatedGetActivities($input) { $config = CRM_Core_Config::singleton(); - $randomNum = md5(uniqid()); - $activityTempTable = "civicrm_temp_activity_details_{$randomNum}"; + $activityTempTable = CRM_Utils_SQL_TempTable::build()->setCategory('actdetail')->getName(); $tableFields = array( 'activity_id' => 'int unsigned', @@ -1012,7 +1011,7 @@ public static function deprecatedGetActivities($input) { // step 2: Get target and assignee contacts for above activities // create temp table for target contacts - $activityContactTempTable = "civicrm_temp_activity_contact_{$randomNum}"; + $activityContactTempTable = CRM_Utils_SQL_TempTable::build()->setCategory('actcontact')->getName(); $query = "CREATE TEMPORARY TABLE {$activityContactTempTable} ( activity_id int unsigned, contact_id int unsigned, record_type_id varchar(16), contact_name varchar(255), is_deleted int unsigned, counter int unsigned, INDEX index_activity_id( activity_id ) ) diff --git a/CRM/Contact/Form/Search/Custom/ContribSYBNT.php b/CRM/Contact/Form/Search/Custom/ContribSYBNT.php index bfd961b1c275..cf2b1e950be5 100644 --- a/CRM/Contact/Form/Search/Custom/ContribSYBNT.php +++ b/CRM/Contact/Form/Search/Custom/ContribSYBNT.php @@ -192,10 +192,8 @@ public function all( "; if ($justIDs) { - CRM_Core_DAO::executeQuery("DROP TEMPORARY TABLE IF EXISTS CustomSearch_SYBNT_temp"); - $query = "CREATE TEMPORARY TABLE CustomSearch_SYBNT_temp AS ({$sql})"; - CRM_Core_DAO::executeQuery($query); - $sql = "SELECT contact_a.id as contact_id FROM CustomSearch_SYBNT_temp as contact_a"; + $tempTable = CRM_Utils_SQL_TempTable::build()->createWithQuery($sql); + $sql = "SELECT contact_a.id as contact_id FROM {$tempTable->getName()} as contact_a"; } return $sql; } diff --git a/CRM/Contact/Form/Search/Custom/DateAdded.php b/CRM/Contact/Form/Search/Custom/DateAdded.php index 3ec22422e3db..34ec772f3c76 100644 --- a/CRM/Contact/Form/Search/Custom/DateAdded.php +++ b/CRM/Contact/Form/Search/Custom/DateAdded.php @@ -36,6 +36,8 @@ class CRM_Contact_Form_Search_Custom_DateAdded extends CRM_Contact_Form_Search_C protected $_aclFrom = NULL; protected $_aclWhere = NULL; + protected $_datesTable = NULL, $_xgTable = NULL, $_igTable = NULL; + /** * Class constructor. * @@ -177,11 +179,12 @@ public function all( */ public function from() { //define table name - $randomNum = md5(uniqid()); - $this->_tableName = "civicrm_temp_custom_{$randomNum}"; + $this->_datesTable = CRM_Utils_SQL_TempTable::build()->setCategory('dates')->getName(); + $this->_xgTable = CRM_Utils_SQL_TempTable::build()->setCategory('xg')->getName(); + $this->_igTable = CRM_Utils_SQL_TempTable::build()->setCategory('ig')->getName(); //grab the contacts added in the date range first - $sql = "CREATE TEMPORARY TABLE dates_{$this->_tableName} ( id int primary key, date_added date ) ENGINE=HEAP"; + $sql = "CREATE TEMPORARY TABLE {$this->_datesTable} ( id int primary key, date_added date ) ENGINE=HEAP"; if ($this->_debug > 0) { print "-- Date range query:
";
       print "$sql;";
@@ -197,7 +200,7 @@ public function from() {
       $endDateFix = "AND date_added <= '" . substr($endDate, 0, 10) . " 23:59:00'";
     }
 
-    $dateRange = "INSERT INTO dates_{$this->_tableName} ( id, date_added )
+    $dateRange = "INSERT INTO {$this->_datesTable} ( id, date_added )
           SELECT
               civicrm_contact.id,
               min(civicrm_log.modified_date) AS date_added
@@ -249,16 +252,16 @@ public function from() {
         $xGroups = 0;
       }
 
-      $sql = "DROP TEMPORARY TABLE IF EXISTS Xg_{$this->_tableName}";
+      $sql = "DROP TEMPORARY TABLE IF EXISTS {$this->_xgTable}";
       CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray);
-      $sql = "CREATE TEMPORARY TABLE Xg_{$this->_tableName} ( contact_id int primary key) ENGINE=HEAP";
+      $sql = "CREATE TEMPORARY TABLE {$this->_xgTable} ( contact_id int primary key) ENGINE=HEAP";
       CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray);
 
       //used only when exclude group is selected
       if ($xGroups != 0) {
-        $excludeGroup = "INSERT INTO  Xg_{$this->_tableName} ( contact_id )
+        $excludeGroup = "INSERT INTO  {$this->_xgTable} ( contact_id )
                   SELECT  DISTINCT civicrm_group_contact.contact_id
-                  FROM civicrm_group_contact, dates_{$this->_tableName} AS d
+                  FROM civicrm_group_contact, {$this->_datesTable} AS d
                   WHERE
                      d.id = civicrm_group_contact.contact_id AND
                      civicrm_group_contact.status = 'Added' AND
@@ -277,16 +280,16 @@ public function from() {
                               SELECT contact_id FROM civicrm_group_contact
                               WHERE civicrm_group_contact.group_id = {$values} AND civicrm_group_contact.status = 'Removed')";
 
-            $smartGroupQuery = " INSERT IGNORE INTO Xg_{$this->_tableName}(contact_id) $smartSql";
+            $smartGroupQuery = " INSERT IGNORE INTO {$this->_xgTable}(contact_id) $smartSql";
 
             CRM_Core_DAO::executeQuery($smartGroupQuery, CRM_Core_DAO::$_nullArray);
           }
         }
       }
 
-      $sql = "DROP TEMPORARY TABLE IF EXISTS Ig_{$this->_tableName}";
+      $sql = "DROP TEMPORARY TABLE IF EXISTS {$this->_igTable}";
       CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray);
-      $sql = "CREATE TEMPORARY TABLE Ig_{$this->_tableName}
+      $sql = "CREATE TEMPORARY TABLE {$this->_igTable}
                 ( id int PRIMARY KEY AUTO_INCREMENT,
                   contact_id int,
                   group_names varchar(64)) ENGINE=HEAP";
@@ -299,9 +302,9 @@ public function from() {
 
       CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray);
 
-      $includeGroup = "INSERT INTO Ig_{$this->_tableName} (contact_id, group_names)
+      $includeGroup = "INSERT INTO {$this->_igTable} (contact_id, group_names)
                  SELECT      d.id as contact_id, civicrm_group.name as group_name
-                 FROM        dates_{$this->_tableName} AS d
+                 FROM        {$this->_datesTable} AS d
                  INNER JOIN  civicrm_group_contact
                  ON          civicrm_group_contact.contact_id = d.id
                  LEFT JOIN   civicrm_group
@@ -309,8 +312,8 @@ public function from() {
 
       //used only when exclude group is selected
       if ($xGroups != 0) {
-        $includeGroup .= " LEFT JOIN        Xg_{$this->_tableName}
-                                          ON        d.id = Xg_{$this->_tableName}.contact_id";
+        $includeGroup .= " LEFT JOIN        {$this->_xgTable}
+                                          ON        d.id = {$this->_xgTable}.contact_id";
       }
       $includeGroup .= " WHERE
                                      civicrm_group_contact.status = 'Added'  AND
@@ -318,7 +321,7 @@ public function from() {
 
       //used only when exclude group is selected
       if ($xGroups != 0) {
-        $includeGroup .= " AND  Xg_{$this->_tableName}.contact_id IS null";
+        $includeGroup .= " AND  {$this->_xgTable}.contact_id IS null";
       }
 
       if ($this->_debug > 0) {
@@ -339,7 +342,7 @@ public function from() {
 
           $smartSql .= " AND contact_a.id IN (
                                    SELECT id AS contact_id
-                                   FROM dates_{$this->_tableName} )";
+                                   FROM {$this->_datesTable} )";
 
           $smartSql .= " AND contact_a.id NOT IN (
                                    SELECT contact_id FROM civicrm_group_contact
@@ -347,11 +350,11 @@ public function from() {
 
           //used only when exclude group is selected
           if ($xGroups != 0) {
-            $smartSql .= " AND contact_a.id NOT IN (SELECT contact_id FROM  Xg_{$this->_tableName})";
+            $smartSql .= " AND contact_a.id NOT IN (SELECT contact_id FROM  {$this->_xgTable})";
           }
 
           $smartGroupQuery = " INSERT IGNORE INTO
-                        Ig_{$this->_tableName}(contact_id)
+                        {$this->_igTable}(contact_id)
                         $smartSql";
 
           CRM_Core_DAO::executeQuery($smartGroupQuery, CRM_Core_DAO::$_nullArray);
@@ -360,11 +363,11 @@ public function from() {
             print "$smartGroupQuery;";
             print "
"; } - $insertGroupNameQuery = "UPDATE IGNORE Ig_{$this->_tableName} + $insertGroupNameQuery = "UPDATE IGNORE {$this->_igTable} SET group_names = (SELECT title FROM civicrm_group WHERE civicrm_group.id = $values) - WHERE Ig_{$this->_tableName}.contact_id IS NOT NULL - AND Ig_{$this->_tableName}.group_names IS NULL"; + WHERE {$this->_igTable}.contact_id IS NOT NULL + AND {$this->_igTable}.group_names IS NULL"; CRM_Core_DAO::executeQuery($insertGroupNameQuery, CRM_Core_DAO::$_nullArray); if ($this->_debug > 0) { print "-- Smart group query:
";
@@ -380,12 +383,12 @@ public function from() {
 
     /* We need to join to this again to get the date_added value */
 
-    $from .= " INNER JOIN dates_{$this->_tableName} d ON (contact_a.id = d.id) {$this->_aclFrom}";
+    $from .= " INNER JOIN {$this->_datesTable} d ON (contact_a.id = d.id) {$this->_aclFrom}";
 
     // Only include groups in the search query of one or more Include OR Exclude groups has been selected.
     // CRM-6356
     if ($this->_groups) {
-      $from .= " INNER JOIN Ig_{$this->_tableName} temptable1 ON (contact_a.id = temptable1.contact_id)";
+      $from .= " INNER JOIN {$this->_igTable} temptable1 ON (contact_a.id = temptable1.contact_id)";
     }
 
     return $from;
@@ -437,13 +440,13 @@ public function count() {
 
   public function __destruct() {
     //drop the temp. tables if they exist
-    if (!empty($this->_includeGroups)) {
-      $sql = "DROP TEMPORARY TABLE IF EXISTS Ig_{$this->_tableName}";
+    if ($this->_igTable && !empty($this->_includeGroups)) {
+      $sql = "DROP TEMPORARY TABLE IF EXISTS {$this->_igTable}";
       CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray);
     }
 
-    if (!empty($this->_excludeGroups)) {
-      $sql = "DROP TEMPORARY TABLE IF EXISTS  Xg_{$this->_tableName}";
+    if ($this->_xgTable && !empty($this->_excludeGroups)) {
+      $sql = "DROP TEMPORARY TABLE IF EXISTS {$this->_xgTable}";
       CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray);
     }
   }
diff --git a/CRM/Contact/Form/Task.php b/CRM/Contact/Form/Task.php
index d445b761c744..27edba918fb9 100644
--- a/CRM/Contact/Form/Task.php
+++ b/CRM/Contact/Form/Task.php
@@ -150,7 +150,7 @@ public static function preProcessCommon(&$form, $useTable = FALSE) {
     $form->assign('taskName', CRM_Utils_Array::value($form->_task, $crmContactTaskTasks));
 
     if ($useTable) {
-      $form->_componentTable = CRM_Core_DAO::createTempTableName('civicrm_task_action', TRUE, $qfKey);
+      $form->_componentTable = CRM_Utils_SQL_TempTable::build()->setCategory('tskact')->setDurable()->setId($qfKey)->getName();
       $sql = " DROP TABLE IF EXISTS {$form->_componentTable}";
       CRM_Core_DAO::executeQuery($sql);
 
diff --git a/CRM/Core/Config.php b/CRM/Core/Config.php
index 349c672b83de..196ad6f4563f 100644
--- a/CRM/Core/Config.php
+++ b/CRM/Core/Config.php
@@ -389,11 +389,12 @@ public static function clearTempTables($timeInterval = FALSE) {
       WHERE  TABLE_SCHEMA = %1
       AND (
         TABLE_NAME LIKE 'civicrm_import_job_%'
-        OR TABLE_NAME LIKE 'civicrm_export_temp%'
-        OR TABLE_NAME LIKE 'civicrm_task_action_temp%'
         OR TABLE_NAME LIKE 'civicrm_report_temp%'
+        OR TABLE_NAME LIKE 'civicrm_tmp_d%'
         )
     ";
+    // NOTE: Cannot find use-cases where "civicrm_report_temp" would be durable. Could probably remove.
+
     if ($timeInterval) {
       $query .= " AND CREATE_TIME < DATE_SUB(NOW(), INTERVAL {$timeInterval})";
     }
diff --git a/CRM/Core/DAO.php b/CRM/Core/DAO.php
index 584f187e09bb..4d7a6f83fe03 100644
--- a/CRM/Core/DAO.php
+++ b/CRM/Core/DAO.php
@@ -2018,6 +2018,8 @@ public static function setCreateDefaults(&$params, $defaults) {
    * @param null $string
    *
    * @return string
+   * @deprecated
+   * @see CRM_Utils_SQL_TempTable
    */
   public static function createTempTableName($prefix = 'civicrm', $addRandomString = TRUE, $string = NULL) {
     $tableName = $prefix . "_temp";
diff --git a/CRM/Export/BAO/Export.php b/CRM/Export/BAO/Export.php
index c6fd1fabab2d..dd6fb35db9bd 100644
--- a/CRM/Export/BAO/Export.php
+++ b/CRM/Export/BAO/Export.php
@@ -1259,7 +1259,7 @@ public static function writeDetailsToTable($tableName, &$details, &$sqlColumns)
    */
   public static function createTempTable(&$sqlColumns) {
     //creating a temporary table for the search result that need be exported
-    $exportTempTable = CRM_Core_DAO::createTempTableName('civicrm_export', TRUE);
+    $exportTempTable = CRM_Utils_SQL_TempTable::build()->setDurable()->setCategory('export')->getName();
 
     // also create the sql table
     $sql = "DROP TABLE IF EXISTS {$exportTempTable}";
diff --git a/CRM/Report/Form.php b/CRM/Report/Form.php
index dc599e03b2ba..e745e93f63da 100644
--- a/CRM/Report/Form.php
+++ b/CRM/Report/Form.php
@@ -3689,7 +3689,7 @@ public function buildGroupTempTable() {
         WHERE smartgroup_contact.group_id IN ({$smartGroups}) ";
     }
 
-    $this->groupTempTable = 'civicrm_report_temp_group_' . date('Ymd_') . uniqid();
+    $this->groupTempTable = CRM_Utils_SQL_TempTable::build()->setCategory('rptgrp')->setId(date('Ymd_') . uniqid())->getName();
     $this->executeReportQuery("
       CREATE TEMPORARY TABLE $this->groupTempTable $this->_databaseAttributes
       $query
diff --git a/CRM/Report/Form/Contribute/Lybunt.php b/CRM/Report/Form/Contribute/Lybunt.php
index 0fb7f093a5d8..e5aeb0fcba2f 100644
--- a/CRM/Report/Form/Contribute/Lybunt.php
+++ b/CRM/Report/Form/Contribute/Lybunt.php
@@ -567,7 +567,7 @@ public function beginPostProcessCommon() {
     // @todo this acl has no test coverage and is very hard to test manually so could be fragile.
     $this->resetFormSqlAndWhereHavingClauses();
 
-    $this->contactTempTable = 'civicrm_report_temp_lybunt_c_' . date('Ymd_') . uniqid();
+    $this->contactTempTable = CRM_Utils_SQL_TempTable::build()->setCategory('rptlybunt')->setId(date('Ymd_') . uniqid())->getName();
     $this->limit();
     $getContacts = "
       CREATE TEMPORARY TABLE $this->contactTempTable {$this->_databaseAttributes}
diff --git a/CRM/Utils/SQL/TempTable.php b/CRM/Utils/SQL/TempTable.php
new file mode 100644
index 000000000000..cedc46c81622
--- /dev/null
+++ b/CRM/Utils/SQL/TempTable.php
@@ -0,0 +1,272 @@
+getName();
+ * $name = CRM_Utils_SQL_TempTable::build()->setDurable()->getName();
+ * $name = CRM_Utils_SQL_TempTable::build()->setCategory('contactstats')->setId($contact['id'])->getName();
+ *
+ * Example 2: Create a temp table using the results of a SELECT query.
+ *
+ * $tmpTbl = CRM_Utils_SQL_TempTable::build()->createWithQuery('SELECT id, display_name FROM civicrm_contact');
+ * $tmpTbl = CRM_Utils_SQL_TempTable::build()->createWithQuery(CRM_Utils_SQL_Select::from('civicrm_contact')->select('display_name'));
+ *
+ * Example 3: Create an empty temp table with list of columns.
+ *
+ * $tmpTbl = CRM_Utils_SQL_TempTable::build()->setDurable()->setUtf8()->createWithColumns('id int(10, name varchar(64)');
+ *
+ * Example 4: Drop a table that you previously created.
+ *
+ * $tmpTbl->drop();
+ *
+ * Example 5: Auto-drop a temp table when $tmpTbl falls out of scope
+ *
+ * $tmpTbl->setAutodrop();
+ *
+ */
+class CRM_Utils_SQL_TempTable {
+
+  const UTF8 = 'DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci';
+  const CATEGORY_LENGTH = 12;
+  const CATEGORY_REGEXP = ';^[a-zA-Z0-9]+$;';
+  const ID_LENGTH = 37; // MAX{64} - CATEGORY_LENGTH{12} - CONST_LENGHTH{15} = 37
+  const ID_REGEXP = ';^[a-zA-Z0-9_]+$;';
+
+  /**
+   * @var bool
+   */
+  protected $durable, $utf8;
+
+  protected $category;
+
+  protected $id;
+
+  protected $autodrop;
+
+  /**
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public static function build() {
+    $t = new CRM_Utils_SQL_TempTable();
+    $t->category = NULL;
+    $t->id = md5(uniqid('', TRUE));
+    // The constant CIVICRM_TEMP_FORCE_DURABLE is for local debugging.
+    $t->durable = CRM_Utils_Constant::value('CIVICRM_TEMP_FORCE_DURABLE', FALSE);
+    // I suspect it would be better to just say utf8=true, but a lot of existing queries don't do the utf8 bit.
+    $t->utf8 = CRM_Utils_Constant::value('CIVICRM_TEMP_FORCE_UTF8', FALSE);
+    $t->autodrop = FALSE;
+    return $t;
+  }
+
+  public function __destruct() {
+    if ($this->autodrop) {
+      $this->drop();
+    }
+  }
+
+  /**
+   * Determine the full table name.
+   *
+   * @return string
+   *   Ex: 'civicrm_tmp_d_foo_abcd1234abcd1234'
+   */
+  public function getName() {
+    $parts = ['civicrm', 'tmp'];
+    $parts[] = ($this->durable ? 'd' : 'e');
+    $parts[] = $this->category ? $this->category : 'dflt';
+    $parts[] = $this->id ? $this->id : 'dflt';
+    return implode('_', $parts);
+  }
+
+  /**
+   * Create the table using results from a SELECT query.
+   *
+   * @param string|CRM_Utils_SQL_Select $selectQuery
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public function createWithQuery($selectQuery) {
+    $sql = sprintf('%s %s AS %s',
+      $this->toSQL('CREATE'),
+      $this->utf8 ? self::UTF8 : '',
+      ($selectQuery instanceof CRM_Utils_SQL_Select ? $selectQuery->toSQL() : $selectQuery)
+    );
+    CRM_Core_DAO::executeQuery($sql, array(), TRUE, NULL, TRUE, FALSE);
+    return $this;
+  }
+
+  /**
+   * Create the empty table.
+   *
+   * @parma string $columns
+   *   SQL column listing.
+   *   Ex: 'id int(10), name varchar(64)'.
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public function createWithColumns($columns) {
+    $sql = sprintf('%s (%s) %s',
+      $this->toSQL('CREATE'),
+      $columns,
+      $this->utf8 ? self::UTF8 : ''
+    );
+    CRM_Core_DAO::executeQuery($sql, array(), TRUE, NULL, TRUE, FALSE);
+    return $this;
+  }
+
+  /**
+   * Drop the table.
+   *
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public function drop() {
+    $sql = $this->toSQL('DROP', 'IF EXISTS');
+    CRM_Core_DAO::executeQuery($sql, array(), TRUE, NULL, TRUE, FALSE);
+    return $this;
+  }
+
+  /**
+   * @param string $action
+   *   Ex: 'CREATE', 'DROP'
+   * @param string|NULL $ifne
+   *   Ex: 'IF EXISTS', 'IF NOT EXISTS'.
+   * @return string
+   *   Ex: 'CREATE TEMPORARY TABLE `civicrm_tmp_e_foo_abcd1234`'
+   *   Ex: 'CREATE TABLE IF NOT EXISTS `civicrm_tmp_d_foo_abcd1234`'
+   */
+  private function toSQL($action, $ifne = NULL) {
+    $parts = [];
+    $parts[] = $action;
+    if (!$this->durable) {
+      $parts[] = 'TEMPORARY';
+    }
+    $parts[] = 'TABLE';
+    if ($ifne) {
+      $parts[] = $ifne;
+    }
+    $parts[] = '`' . $this->getName() . '`';
+    return implode(' ', $parts);
+  }
+
+  /**
+   * @return string|NULL
+   */
+  public function getCategory() {
+    return $this->category;
+  }
+
+  /**
+   * @return string|NULL
+   */
+  public function getId() {
+    return $this->id;
+  }
+
+  /**
+   * @return bool
+   */
+  public function isAutodrop() {
+    return $this->autodrop;
+  }
+
+  /**
+   * @return bool
+   */
+  public function isDurable() {
+    return $this->durable;
+  }
+
+  /**
+   * @return bool
+   */
+  public function isUtf8() {
+    return $this->utf8;
+  }
+
+  /**
+   * @param bool $autodrop
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public function setAutodrop($autodrop = TRUE) {
+    $this->autodrop = $autodrop;
+    return $this;
+  }
+
+  /**
+   * @param string|NULL $category
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public function setCategory($category) {
+    if ($category && !preg_match(self::CATEGORY_REGEXP, $category) || strlen($category) > self::CATEGORY_LENGTH) {
+      throw new \RuntimeException("Malformed temp table category");
+    }
+    $this->category = $category;
+    return $this;
+  }
+
+  /**
+   * @parma bool $value
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public function setDurable($durable = TRUE) {
+    $this->durable = $durable;
+    return $this;
+  }
+
+  /**
+   * @param mixed $id
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public function setId($id) {
+    if ($id && !preg_match(self::ID_REGEXP, $id) || strlen($id) > self::ID_LENGTH) {
+      throw new \RuntimeException("Malformed temp table id");
+    }
+    $this->id = $id;
+    return $this;
+  }
+
+  public function setUtf8($value = TRUE) {
+    $this->utf8 = $value;
+    return $this;
+  }
+
+}