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; + } + +}