Merge pull request #15817 from colemanw/Fix
[civicrm-core.git] / CRM / Utils / SQL / TempTable.php
CommitLineData
40c8b829
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
40c8b829 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
40c8b829
TO
9 +--------------------------------------------------------------------+
10 */
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
40c8b829
TO
16 *
17 * Table naming rules:
18 * - MySQL imposes a 64 char limit.
19 * - All temp tables start with "civicrm_tmp".
20 * - Durable temp tables: "civicrm_tmp_d_{12}_{32}"
21 * - Ephemeral temp tables: "civicrm_tmp_e_{12}_{32}"
22 *
0038409f
TO
23 * To use `TempTable`:
24 * - Begin by calling `CRM_Utils_SQL_TempTable::build()`.
25 * - Optionally, describe the table with `setDurable()`, `setCategory()`, `setId()`.
26 * - Finally, call `getName()` or `createWithQuery()` or `createWithColumns()`.
27 *
28 * Example 1: Just create a table name. You'll be responsible for CREATE/DROP actions.
40c8b829
TO
29 *
30 * $name = CRM_Utils_SQL_TempTable::build()->getName();
31 * $name = CRM_Utils_SQL_TempTable::build()->setDurable()->getName();
0038409f 32 * $name = CRM_Utils_SQL_TempTable::build()->setCategory('contactstats')->setId($contact['id'])->getName();
40c8b829
TO
33 *
34 * Example 2: Create a temp table using the results of a SELECT query.
35 *
36 * $tmpTbl = CRM_Utils_SQL_TempTable::build()->createWithQuery('SELECT id, display_name FROM civicrm_contact');
37 * $tmpTbl = CRM_Utils_SQL_TempTable::build()->createWithQuery(CRM_Utils_SQL_Select::from('civicrm_contact')->select('display_name'));
38 *
39 * Example 3: Create an empty temp table with list of columns.
40 *
0033aaaf 41 * $tmpTbl = CRM_Utils_SQL_TempTable::build()->setDurable()->createWithColumns('id int(10, name varchar(64)');
40c8b829
TO
42 *
43 * Example 4: Drop a table that you previously created.
44 *
45 * $tmpTbl->drop();
46 *
47 * Example 5: Auto-drop a temp table when $tmpTbl falls out of scope
48 *
49 * $tmpTbl->setAutodrop();
50 *
51 */
52class CRM_Utils_SQL_TempTable {
53
54 const UTF8 = 'DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci';
55 const CATEGORY_LENGTH = 12;
56 const CATEGORY_REGEXP = ';^[a-zA-Z0-9]+$;';
6714d8d2
SL
57 // MAX{64} - CATEGORY_LENGTH{12} - CONST_LENGHTH{15} = 37
58 const ID_LENGTH = 37;
40c8b829 59 const ID_REGEXP = ';^[a-zA-Z0-9_]+$;';
8ccee4bf 60 const INNODB = 'ENGINE=InnoDB';
61 const MEMORY = 'ENGINE=MEMORY';
40c8b829
TO
62
63 /**
64 * @var bool
65 */
6714d8d2
SL
66 protected $durable;
67
68 /**
69 * @var bool
70 */
71 protected $utf8;
40c8b829
TO
72
73 protected $category;
74
75 protected $id;
76
77 protected $autodrop;
78
8ccee4bf 79 protected $memory;
80
90e5222f 81 protected $createSql;
82
40c8b829
TO
83 /**
84 * @return CRM_Utils_SQL_TempTable
85 */
86 public static function build() {
87 $t = new CRM_Utils_SQL_TempTable();
88 $t->category = NULL;
89 $t->id = md5(uniqid('', TRUE));
90 // The constant CIVICRM_TEMP_FORCE_DURABLE is for local debugging.
91 $t->durable = CRM_Utils_Constant::value('CIVICRM_TEMP_FORCE_DURABLE', FALSE);
0033aaaf 92 $t->utf8 = TRUE;
40c8b829 93 $t->autodrop = FALSE;
8ccee4bf 94 $t->memory = FALSE;
40c8b829
TO
95 return $t;
96 }
97
98 public function __destruct() {
99 if ($this->autodrop) {
100 $this->drop();
101 }
102 }
103
104 /**
105 * Determine the full table name.
106 *
107 * @return string
108 * Ex: 'civicrm_tmp_d_foo_abcd1234abcd1234'
109 */
110 public function getName() {
111 $parts = ['civicrm', 'tmp'];
112 $parts[] = ($this->durable ? 'd' : 'e');
113 $parts[] = $this->category ? $this->category : 'dflt';
114 $parts[] = $this->id ? $this->id : 'dflt';
115 return implode('_', $parts);
116 }
117
118 /**
119 * Create the table using results from a SELECT query.
120 *
121 * @param string|CRM_Utils_SQL_Select $selectQuery
122 * @return CRM_Utils_SQL_TempTable
123 */
124 public function createWithQuery($selectQuery) {
8ccee4bf 125 $sql = sprintf('%s %s %s AS %s',
40c8b829 126 $this->toSQL('CREATE'),
8ccee4bf 127 $this->memory ? self::MEMORY : self::INNODB,
40c8b829
TO
128 $this->utf8 ? self::UTF8 : '',
129 ($selectQuery instanceof CRM_Utils_SQL_Select ? $selectQuery->toSQL() : $selectQuery)
130 );
be2fb01f 131 CRM_Core_DAO::executeQuery($sql, [], TRUE, NULL, TRUE, FALSE);
90e5222f 132 $this->createSql = $sql;
40c8b829
TO
133 return $this;
134 }
135
136 /**
137 * Create the empty table.
138 *
139 * @parma string $columns
140 * SQL column listing.
141 * Ex: 'id int(10), name varchar(64)'.
142 * @return CRM_Utils_SQL_TempTable
143 */
144 public function createWithColumns($columns) {
8ccee4bf 145 $sql = sprintf('%s (%s) %s %s',
40c8b829
TO
146 $this->toSQL('CREATE'),
147 $columns,
8ccee4bf 148 $this->memory ? self::MEMORY : self::INNODB,
40c8b829
TO
149 $this->utf8 ? self::UTF8 : ''
150 );
be2fb01f 151 CRM_Core_DAO::executeQuery($sql, [], TRUE, NULL, TRUE, FALSE);
90e5222f 152 $this->createSql = $sql;
40c8b829
TO
153 return $this;
154 }
155
156 /**
157 * Drop the table.
158 *
159 * @return CRM_Utils_SQL_TempTable
160 */
161 public function drop() {
162 $sql = $this->toSQL('DROP', 'IF EXISTS');
be2fb01f 163 CRM_Core_DAO::executeQuery($sql, [], TRUE, NULL, TRUE, FALSE);
40c8b829
TO
164 return $this;
165 }
166
167 /**
168 * @param string $action
169 * Ex: 'CREATE', 'DROP'
170 * @param string|NULL $ifne
171 * Ex: 'IF EXISTS', 'IF NOT EXISTS'.
172 * @return string
173 * Ex: 'CREATE TEMPORARY TABLE `civicrm_tmp_e_foo_abcd1234`'
174 * Ex: 'CREATE TABLE IF NOT EXISTS `civicrm_tmp_d_foo_abcd1234`'
175 */
176 private function toSQL($action, $ifne = NULL) {
177 $parts = [];
178 $parts[] = $action;
179 if (!$this->durable) {
180 $parts[] = 'TEMPORARY';
181 }
182 $parts[] = 'TABLE';
183 if ($ifne) {
184 $parts[] = $ifne;
185 }
186 $parts[] = '`' . $this->getName() . '`';
187 return implode(' ', $parts);
188 }
189
190 /**
191 * @return string|NULL
192 */
193 public function getCategory() {
194 return $this->category;
195 }
196
197 /**
198 * @return string|NULL
199 */
200 public function getId() {
201 return $this->id;
202 }
203
90e5222f 204 /**
205 * @return string|NULL
206 */
207 public function getCreateSql() {
208 return $this->createSql;
209 }
210
40c8b829
TO
211 /**
212 * @return bool
213 */
214 public function isAutodrop() {
215 return $this->autodrop;
216 }
217
218 /**
219 * @return bool
220 */
221 public function isDurable() {
222 return $this->durable;
223 }
224
8ccee4bf 225 /**
226 * @return bool
227 */
228 public function isMemory() {
229 return $this->memory;
230 }
231
40c8b829
TO
232 /**
233 * @return bool
234 */
235 public function isUtf8() {
236 return $this->utf8;
237 }
238
239 /**
240 * @param bool $autodrop
241 * @return CRM_Utils_SQL_TempTable
242 */
243 public function setAutodrop($autodrop = TRUE) {
244 $this->autodrop = $autodrop;
245 return $this;
246 }
247
248 /**
249 * @param string|NULL $category
f0197a3d 250 *
40c8b829
TO
251 * @return CRM_Utils_SQL_TempTable
252 */
253 public function setCategory($category) {
254 if ($category && !preg_match(self::CATEGORY_REGEXP, $category) || strlen($category) > self::CATEGORY_LENGTH) {
255 throw new \RuntimeException("Malformed temp table category");
256 }
257 $this->category = $category;
258 return $this;
259 }
260
261 /**
f0197a3d 262 * Set whether the table should be durable.
263 *
264 * Durable tables are not TEMPORARY in the mysql sense.
265 *
266 * @param bool $durable
267 *
40c8b829
TO
268 * @return CRM_Utils_SQL_TempTable
269 */
270 public function setDurable($durable = TRUE) {
271 $this->durable = $durable;
272 return $this;
273 }
274
275 /**
f0197a3d 276 * Setter for id
277 *
40c8b829 278 * @param mixed $id
f0197a3d 279 *
40c8b829
TO
280 * @return CRM_Utils_SQL_TempTable
281 */
282 public function setId($id) {
283 if ($id && !preg_match(self::ID_REGEXP, $id) || strlen($id) > self::ID_LENGTH) {
284 throw new \RuntimeException("Malformed temp table id");
285 }
286 $this->id = $id;
287 return $this;
288 }
289
8ccee4bf 290 /**
291 * Set table engine to MEMORY.
292 *
293 * @param bool $value
294 *
295 * @return $this
296 */
297 public function setMemory($value = TRUE) {
298 $this->memory = $value;
299 return $this;
300 }
301
f0197a3d 302 /**
303 * Set table collation to UTF8.
304 *
0033aaaf 305 * @deprecated This method is deprecated as tables should be assumed to have
306 * UTF-8 as the default character set and collation; some other character set
307 * or collation may be specified in the column definition.
f0197a3d 308 *
309 * @param bool $value
310 *
311 * @return $this
312 */
40c8b829
TO
313 public function setUtf8($value = TRUE) {
314 $this->utf8 = $value;
315 return $this;
316 }
317
318}