Issues (1686)

sources/ElkArte/Database/AbstractTable.php (2 issues)

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
The method _db_create_query_column() does not exist on ElkArte\Database\AbstractTable. Since it exists in all sub-types, consider adding an abstract or default implementation to ElkArte\Database\AbstractTable. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

148
			$table_query .= "\n\t" . $this->/** @scrutinizer ignore-call */ _db_create_query_column($column, $table_name) . ',';
Loading history...
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
Bug Best Practice introduced by
The expression return $columns returns the type string[] which is incompatible with the documented return type string.
Loading history...
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