elkarte /
Elkarte
| 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 = ['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 = [], $parameters = []): bool |
||
| 97 | { |
||
| 98 | $parameters = array_merge([ |
||
| 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[] = ['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
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 | [ |
||
| 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): bool |
||
| 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 = [], $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 = []); |
||
| 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 = []); |
||
| 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 = [], $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 = []); |
||
| 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): bool |
||
| 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 = []); |
||
| 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 = []); |
||
| 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; |
||
| 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 | [ |
||
| 401 | 'security_override' => true, |
||
| 402 | ] |
||
| 403 | ); |
||
| 404 | } |
||
| 405 | } |
||
| 406 |