1 | <?php |
||
2 | |||
3 | /** |
||
4 | * This is the base class for DbTable functionality. |
||
5 | * It contains abstract methods to be implemented for the specific database system, |
||
6 | * related to a table structure. |
||
7 | * Add-ons will need this, to change the database for their needs. |
||
8 | * |
||
9 | * @package ElkArte Forum |
||
10 | * @copyright ElkArte Forum contributors |
||
11 | * @license BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file) |
||
12 | * |
||
13 | * @version 2.0 dev |
||
14 | * |
||
15 | */ |
||
16 | |||
17 | namespace ElkArte\Database; |
||
18 | |||
19 | use ElkArte\Exceptions\Exception; |
||
20 | |||
21 | /** |
||
22 | * This is used to create a table without worrying about schema compatibilities |
||
23 | * across supported database systems. |
||
24 | */ |
||
25 | abstract class AbstractTable |
||
26 | { |
||
27 | /** @var array Array of table names we don't allow to be removed by addons. */ |
||
28 | protected $_reservedTables; |
||
29 | |||
30 | /** @var array Keeps a (reverse) log of changes to the table structure, to be undone. |
||
31 | * This is used by Packages admin installation/uninstalling/upgrade. */ |
||
32 | protected $_package_log; |
||
33 | |||
34 | /** |
||
35 | * DbTable::construct |
||
36 | * |
||
37 | * @param object $_db - An implementation of the abstract DbTable |
||
38 | * @param string $_db_prefix - Database tables prefix |
||
39 | */ |
||
40 | public function __construct(protected $_db, protected $_db_prefix) |
||
41 | { |
||
42 | // We won't do any remove on these |
||
43 | $this->_reservedTables = array('admin_info_files', 'approval_queue', 'attachments', 'ban_groups', 'ban_items', |
||
44 | 'board_permissions', 'boards', 'calendar', 'calendar_holidays', 'categories', 'collapsed_categories', |
||
45 | 'custom_fields', 'group_moderators', 'log_actions', 'log_activity', 'log_banned', 'log_boards', |
||
46 | 'log_digest', 'log_errors', 'log_floodcontrol', 'log_group_requests', 'log_karma', 'log_mark_read', |
||
47 | 'log_notify', 'log_online', 'log_packages', 'log_polls', 'log_reported', 'log_reported_comments', |
||
48 | 'log_scheduled_tasks', 'log_search_messages', 'log_search_results', 'log_search_subjects', |
||
49 | 'log_search_topics', 'log_topics', 'mail_queue', 'membergroups', 'members', 'message_icons', |
||
50 | 'messages', 'moderators', 'package_servers', 'permission_profiles', 'permissions', 'personal_messages', |
||
51 | 'pm_recipients', 'poll_choices', 'polls', 'scheduled_tasks', 'sessions', 'settings', 'smileys', |
||
52 | 'themes', 'topics'); |
||
53 | |||
54 | foreach ($this->_reservedTables as $k => $table_name) |
||
55 | { |
||
56 | $this->_reservedTables[$k] = strtolower($this->_db_prefix . $table_name); |
||
57 | } |
||
58 | |||
59 | 1 | // let's be sure. |
|
60 | $this->_package_log = []; |
||
61 | 1 | } |
|
62 | |||
63 | /** |
||
64 | 1 | * This function can be used to create a table without worrying about schema |
|
65 | * compatibilities across supported database systems. |
||
66 | * - If the table exists will, by default, do nothing. |
||
67 | * - Builds table with columns as passed to it - at least one column must be sent. |
||
68 | * The columns array should have one sub-array for each column - these sub arrays contain: |
||
69 | * 'name' = Column name |
||
70 | * 'type' = Type of column - values from (smallint, mediumint, int, text, varchar, char, tinytext, mediumtext, largetext) |
||
71 | * 'size' => Size of column (If applicable) - for example 255 for a large varchar, 10 for an int etc. |
||
72 | * If not set it will pick a size. |
||
73 | * - 'default' = Default value - do not set if no default required. |
||
74 | * - 'null' => Can it be null (true or false) - if not set default will be false. |
||
75 | 1 | * - 'auto' => Set to true to make it an auto incrementing column. Set to a numerical value to set from what |
|
76 | * it should begin counting. |
||
77 | 1 | * - Adds indexes as specified within indexes parameter. Each index should be a member of $indexes. Values are: |
|
78 | * - 'name' => Index name (If left empty it will be generated). |
||
79 | * - 'type' => Type of index. Choose from 'primary', 'unique' or 'index'. If not set will default to 'index'. |
||
80 | * - 'columns' => Array containing columns that form part of key - in the order the index is to be created. |
||
81 | 1 | * - parameters: (None yet) |
|
82 | * - if_exists values: |
||
83 | * - 'ignore' will do nothing if the table exists. (And will return true) |
||
84 | 1 | * - 'overwrite' will drop any existing table of the same name. |
|
85 | 1 | * - 'error' will return false if the table already exists. |
|
86 | * |
||
87 | * @param string $table_name |
||
88 | * @param array $columns in the format specified. |
||
89 | * @param array $indexes default array(), in the format specified. |
||
90 | * @param array $parameters default array( |
||
91 | * 'if_exists' => 'ignore', |
||
92 | * 'temporary' => false, |
||
93 | * ) |
||
94 | * @return bool |
||
95 | */ |
||
96 | public function create_table($table_name, $columns, $indexes = array(), $parameters = array()) |
||
97 | { |
||
98 | $parameters = array_merge(array( |
||
99 | 'if_exists' => 'ignore', |
||
100 | 'temporary' => false, |
||
101 | ), $parameters); |
||
102 | |||
103 | // With or without the database name, the fullname looks like this. |
||
104 | $full_table_name = str_replace('{db_prefix}', $this->_real_prefix(), $table_name); |
||
105 | $table_name = str_replace('{db_prefix}', $this->_db_prefix, $table_name); |
||
106 | |||
107 | // First - no way do we touch our tables. |
||
108 | if (in_array(strtolower($table_name), $this->_reservedTables, true)) |
||
109 | { |
||
110 | return false; |
||
111 | } |
||
112 | |||
113 | // Log that we'll want to remove this on uninstall. |
||
114 | $this->_package_log[] = array('remove_table', $table_name); |
||
115 | |||
116 | // This... my friends... is a function in a half - let's start by checking if the table exists! |
||
117 | if ($parameters['if_exists'] === 'force_drop') |
||
118 | { |
||
119 | $this->drop_table($table_name, true); |
||
120 | } |
||
121 | 2 | elseif ($this->table_exists($full_table_name)) |
|
122 | { |
||
123 | 2 | // This is a sad day... drop the table? If not, return false (error) by default. |
|
124 | 2 | if ($parameters['if_exists'] === 'overwrite') |
|
125 | { |
||
126 | 1 | $this->drop_table($table_name); |
|
127 | } |
||
128 | else |
||
129 | 2 | { |
|
130 | 2 | return $parameters['if_exists'] === 'ignore'; |
|
131 | } |
||
132 | } |
||
133 | 2 | ||
134 | // If we've got this far - good news - no table exists. We can build our own! |
||
135 | $this->_db->transaction('begin'); |
||
136 | |||
137 | if ($parameters['temporary'] !== true) |
||
138 | { |
||
139 | 2 | $table_query = 'CREATE TABLE ' . $table_name . "\n" . '('; |
|
140 | } |
||
141 | else |
||
142 | 2 | { |
|
143 | $table_query = 'CREATE TEMPORARY TABLE ' . $table_name . "\n" . '('; |
||
144 | 2 | } |
|
145 | |||
146 | foreach ($columns as $column) |
||
147 | { |
||
148 | $table_query .= "\n\t" . $this->_db_create_query_column($column, $table_name) . ','; |
||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
149 | } |
||
150 | |||
151 | $table_query .= $this->_create_query_indexes($indexes, $table_name); |
||
152 | |||
153 | // No trailing commas! |
||
154 | if (substr($table_query, -1) === ',') |
||
155 | { |
||
156 | $table_query = substr($table_query, 0, -1); |
||
157 | } |
||
158 | |||
159 | $table_query .= $this->_close_table_query($parameters['temporary']); |
||
160 | 2 | ||
161 | // Create the table! |
||
162 | 2 | $this->_db->query('', $table_query, |
|
163 | array( |
||
164 | 'security_override' => true, |
||
165 | ) |
||
166 | ); |
||
167 | |||
168 | 2 | // And the indexes... if any |
|
169 | $this->_build_indexes(); |
||
170 | 2 | ||
171 | // Go, go power rangers! |
||
172 | 2 | $this->_db->transaction('commit'); |
|
173 | |||
174 | return true; |
||
175 | 2 | } |
|
176 | |||
177 | /** |
||
178 | 2 | * Strips out the table name, we might not need it in some cases |
|
179 | */ |
||
180 | 2 | abstract protected function _real_prefix(); |
|
181 | |||
182 | /** |
||
183 | 2 | * Drop a table. |
|
184 | * |
||
185 | * @param string $table_name |
||
186 | 2 | * @param bool $force If forcing the drop or not. Useful in case of temporary |
|
187 | * tables that may not be detected as existing. |
||
188 | 2 | */ |
|
189 | abstract public function drop_table($table_name, $force = false); |
||
190 | |||
191 | /** |
||
192 | * Checks if a table exists |
||
193 | 2 | * |
|
194 | * @param string $table_name |
||
195 | * @return bool |
||
196 | 2 | */ |
|
197 | public function table_exists($table_name) |
||
198 | 2 | { |
|
199 | $filter = $this->_db->list_tables(false, $table_name); |
||
200 | |||
201 | return !empty($filter); |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * It is mean to parse the indexes array of a create_table function |
||
206 | * to prepare for the indexes creation |
||
207 | * |
||
208 | * @param string[] $indexes |
||
209 | * @param string $table_name |
||
210 | * @return string |
||
211 | */ |
||
212 | abstract protected function _create_query_indexes($indexes, $table_name); |
||
213 | |||
214 | /** |
||
215 | * Adds the closing "touch" to the CREATE TABLE query |
||
216 | * |
||
217 | * @param bool $temporary - If the table is temporary |
||
218 | * @return string |
||
219 | */ |
||
220 | abstract protected function _close_table_query($temporary); |
||
221 | |||
222 | /** |
||
223 | * In certain cases it is necessary to create the indexes of a |
||
224 | * newly created table with new queries after the table has been created. |
||
225 | * |
||
226 | * @return void |
||
227 | */ |
||
228 | protected function _build_indexes() |
||
229 | { |
||
230 | } |
||
231 | |||
232 | /** |
||
233 | * This function adds a column. |
||
234 | * |
||
235 | * @param string $table_name the name of the table |
||
236 | * @param array $column_info with column information |
||
237 | * @param array $parameters default array() |
||
238 | * @param string $if_exists default 'update' |
||
239 | */ |
||
240 | abstract public function add_column($table_name, $column_info, $parameters = array(), $if_exists = 'update'); |
||
241 | |||
242 | /** |
||
243 | * Removes a column. |
||
244 | * |
||
245 | * @param string $table_name |
||
246 | * @param string $column_name |
||
247 | * @param array $parameters default array() |
||
248 | */ |
||
249 | abstract public function remove_column($table_name, $column_name, $parameters = array()); |
||
250 | |||
251 | /** |
||
252 | * Change a column. |
||
253 | 1 | * |
|
254 | * @param string $table_name |
||
255 | 1 | * @param string $old_column |
|
256 | * @param array $column_info |
||
257 | * @param array $parameters default array() |
||
258 | */ |
||
259 | abstract public function change_column($table_name, $old_column, $column_info, $parameters = array()); |
||
260 | |||
261 | /** |
||
262 | * Add an index. |
||
263 | * |
||
264 | * @param string $table_name |
||
265 | * @param array $index_info |
||
266 | * @param array $parameters default array() |
||
267 | * @param string $if_exists default 'update' |
||
268 | */ |
||
269 | abstract public function add_index($table_name, $index_info, $parameters = array(), $if_exists = 'update'); |
||
270 | |||
271 | /** |
||
272 | * Remove an index. |
||
273 | * |
||
274 | * @param string $table_name |
||
275 | * @param string $index_name |
||
276 | * @param array $parameters default array() |
||
277 | */ |
||
278 | abstract public function remove_index($table_name, $index_name, $parameters = array()); |
||
279 | |||
280 | /** |
||
281 | * Get the schema formatted name for a type. |
||
282 | * |
||
283 | * @param string $type_name |
||
284 | * @param int|null $type_size |
||
285 | * @param bool $reverse |
||
286 | */ |
||
287 | abstract public function calculate_type($type_name, $type_size = null, $reverse = false); |
||
288 | |||
289 | /** |
||
290 | * Optimize a table |
||
291 | * |
||
292 | * @param string $table - the table to be optimized |
||
293 | * @return int - how much it was gained |
||
294 | */ |
||
295 | abstract public function optimize($table); |
||
296 | |||
297 | /** |
||
298 | * Return a copy of this instance package log |
||
299 | */ |
||
300 | public function package_log() |
||
301 | { |
||
302 | return $this->_package_log; |
||
303 | } |
||
304 | |||
305 | /** |
||
306 | * Checks if a column exists in a table |
||
307 | * |
||
308 | * @param string $table_name |
||
309 | * @param string $column_name |
||
310 | * @return bool |
||
311 | */ |
||
312 | public function column_exists($table_name, $column_name) |
||
313 | { |
||
314 | return $this->_get_column_info($table_name, $column_name) !== false; |
||
315 | } |
||
316 | |||
317 | /** |
||
318 | * Finds a column by name in a table and returns some info. |
||
319 | * |
||
320 | * @param string $table_name |
||
321 | * @param string $column_name |
||
322 | * @return array|false |
||
323 | */ |
||
324 | protected function _get_column_info($table_name, $column_name) |
||
325 | { |
||
326 | $columns = $this->list_columns($table_name, true); |
||
327 | |||
328 | foreach ($columns as $column) |
||
329 | { |
||
330 | if ($column_name === $column['name']) |
||
331 | { |
||
332 | return $column; |
||
333 | } |
||
334 | } |
||
335 | |||
336 | return false; |
||
337 | } |
||
338 | |||
339 | /** |
||
340 | * Return column information for a table. |
||
341 | * |
||
342 | * @param string $table_name |
||
343 | * @param bool $detail |
||
344 | * @param array $parameters default array() |
||
345 | * @return mixed |
||
346 | */ |
||
347 | abstract public function list_columns($table_name, $detail = false, $parameters = array()); |
||
348 | |||
349 | /** |
||
350 | * Returns name, columns and indexes of a table |
||
351 | * |
||
352 | * @param string $table_name |
||
353 | * @return array |
||
354 | */ |
||
355 | public function table_structure($table_name) |
||
356 | { |
||
357 | $table_name = str_replace('{db_prefix}', $this->_db_prefix, $table_name); |
||
358 | |||
359 | return [ |
||
360 | 'name' => $table_name, |
||
361 | 'columns' => $this->list_columns($table_name, true), |
||
362 | 'indexes' => $this->list_indexes($table_name, true), |
||
363 | ]; |
||
364 | } |
||
365 | |||
366 | /** |
||
367 | * Get index information. |
||
368 | * |
||
369 | * @param string $table_name |
||
370 | * @param bool $detail |
||
371 | * @param array $parameters |
||
372 | * @return mixed |
||
373 | */ |
||
374 | abstract public function list_indexes($table_name, $detail = false, $parameters = array()); |
||
375 | |||
376 | /** |
||
377 | * Clean the indexes strings (e.g. PostgreSQL doesn't support max length) |
||
378 | * |
||
379 | * @param string[] $columns |
||
380 | * @return string |
||
381 | */ |
||
382 | protected function _clean_indexes($columns) |
||
383 | { |
||
384 | return $columns; |
||
0 ignored issues
–
show
|
|||
385 | } |
||
386 | |||
387 | /** |
||
388 | * A very simple wrapper around the ALTER TABLE SQL statement. |
||
389 | * |
||
390 | * @param string $table_name |
||
391 | * @param string $statement |
||
392 | * @return bool|AbstractResult |
||
393 | * @throws Exception |
||
394 | */ |
||
395 | protected function _alter_table($table_name, $statement) |
||
396 | { |
||
397 | return $this->_db->query('', ' |
||
398 | ALTER TABLE ' . $table_name . ' |
||
399 | ' . $statement, |
||
400 | array( |
||
401 | 'security_override' => true, |
||
402 | ) |
||
403 | ); |
||
404 | } |
||
405 | } |
||
406 |