Passed
Pull Request — release-2.1 (#7424)
by Jeremy
05:17
created

smf_db_add_index()   D

Complexity

Conditions 18
Paths 86

Size

Total Lines 65
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 18
eloc 34
c 1
b 0
f 0
nc 86
nop 5
dl 0
loc 65
rs 4.8666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file contains database functionality specifically designed for packages (mods) to utilize.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines https://www.simplemachines.org
10
 * @copyright 2022 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1.2
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Add the file functions to the $smcFunc array.
21
 */
22
function db_packages_init()
23
{
24
	global $smcFunc, $reservedTables, $db_package_log, $db_prefix;
25
26
	if (!isset($smcFunc['db_create_table']) || $smcFunc['db_create_table'] != 'smf_db_create_table')
27
	{
28
		$smcFunc += array(
29
			'db_add_column' => 'smf_db_add_column',
30
			'db_add_index' => 'smf_db_add_index',
31
			'db_calculate_type' => 'smf_db_calculate_type',
32
			'db_change_column' => 'smf_db_change_column',
33
			'db_create_table' => 'smf_db_create_table',
34
			'db_drop_table' => 'smf_db_drop_table',
35
			'db_table_structure' => 'smf_db_table_structure',
36
			'db_list_columns' => 'smf_db_list_columns',
37
			'db_list_indexes' => 'smf_db_list_indexes',
38
			'db_remove_column' => 'smf_db_remove_column',
39
			'db_remove_index' => 'smf_db_remove_index',
40
		);
41
		$db_package_log = array();
42
	}
43
44
	// We setup an array of SMF tables we can't do auto-remove on - in case a mod writer cocks it up!
45
	$reservedTables = array(
46
		'admin_info_files', 'approval_queue', 'attachments',
47
		'background_tasks', 'ban_groups', 'ban_items', 'board_permissions',
48
		'board_permissions_view', 'boards', 'calendar', 'calendar_holidays',
49
		'categories', 'custom_fields', 'group_moderators', 'log_actions',
50
		'log_activity', 'log_banned', 'log_boards', 'log_comments',
51
		'log_digest', 'log_errors', 'log_floodcontrol', 'log_group_requests',
52
		'log_mark_read', 'log_member_notices', 'log_notify', 'log_online',
53
		'log_packages', 'log_polls', 'log_reported', 'log_reported_comments',
54
		'log_scheduled_tasks', 'log_search_messages', 'log_search_results',
55
		'log_search_subjects', 'log_search_topics', 'log_spider_hits',
56
		'log_spider_stats', 'log_subscribed', 'log_topics', 'mail_queue',
57
		'member_logins', 'membergroups', 'members', 'mentions',
58
		'message_icons', 'messages', 'moderator_groups', 'moderators',
59
		'package_servers', 'permission_profiles', 'permissions',
60
		'personal_messages', 'pm_labeled_messages', 'pm_labels',
61
		'pm_recipients', 'pm_rules', 'poll_choices', 'polls', 'qanda',
62
		'scheduled_tasks', 'sessions', 'settings', 'smiley_files', 'smileys',
63
		'spiders', 'subscriptions', 'themes', 'topics', 'user_alerts',
64
		'user_alerts_prefs', 'user_drafts', 'user_likes',
65
	);
66
	foreach ($reservedTables as $k => $table_name)
67
		$reservedTables[$k] = strtolower($db_prefix . $table_name);
68
69
	// We in turn may need the extra stuff.
70
	db_extend('extra');
71
}
72
73
/**
74
 * This function can be used to create a table without worrying about schema
75
 *  compatibilities across supported database systems.
76
 *  - If the table exists will, by default, do nothing.
77
 *  - Builds table with columns as passed to it - at least one column must be sent.
78
 *  The columns array should have one sub-array for each column - these sub arrays contain:
79
 *  	'name' = Column name
80
 *  	'type' = Type of column - values from (smallint, mediumint, int, text, varchar, char, tinytext, mediumtext, largetext)
81
 *  	'size' => Size of column (If applicable) - for example 255 for a large varchar, 10 for an int etc.
82
 *  		If not set SMF will pick a size.
83
 *  	- 'default' = Default value - do not set if no default required.
84
 *  	- 'not_null' => Can it be null (true or false) - if not set default will be false.
85
 *  	- 'auto' => Set to true to make it an auto incrementing column. Set to a numerical value to set from what
86
 *  		 it should begin counting.
87
 *  - Adds indexes as specified within indexes parameter. Each index should be a member of $indexes. Values are:
88
 *  	- 'name' => Index name (If left empty SMF will generate).
89
 *  	- 'type' => Type of index. Choose from 'primary', 'unique' or 'index'. If not set will default to 'index'.
90
 *  	- 'columns' => Array containing columns that form part of key - in the order the index is to be created.
91
 *  - parameters: (None yet)
92
 *  - if_exists values:
93
 *  	- 'ignore' will do nothing if the table exists. (And will return true)
94
 *  	- 'overwrite' will drop any existing table of the same name.
95
 *  	- 'error' will return false if the table already exists.
96
 *  	- 'update' will update the table if the table already exists (no change of ai field and only colums with the same name keep the data)
97
 *
98
 * @param string $table_name The name of the table to create
99
 * @param array $columns An array of column info in the specified format
100
 * @param array $indexes An array of index info in the specified format
101
 * @param array $parameters Currently not used
102
 * @param string $if_exists What to do if the table exists.
103
 * @param string $error
104
 */
105
function smf_db_create_table($table_name, $columns, $indexes = array(), $parameters = array(), $if_exists = 'ignore', $error = 'fatal')
106
{
107
	global $reservedTables, $smcFunc, $db_package_log, $db_prefix, $db_name;
108
109
	$db_trans = false;
110
	$old_table_exists = false;
111
112
	// Strip out the table name, we might not need it in some cases
113
	$real_prefix = preg_match('~^("?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix;
114
	$database = !empty($match[2]) ? $match[2] : $db_name;
115
116
	// With or without the database name, the fullname looks like this.
117
	$full_table_name = str_replace('{db_prefix}', $real_prefix, $table_name);
118
	$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
119
120
	// First - no way do we touch SMF tables.
121
	if (in_array(strtolower($short_table_name), $reservedTables))
122
		return false;
123
124
	// Log that we'll want to remove this on uninstall.
125
	$db_package_log[] = array('remove_table', $short_table_name);
126
127
	// This... my friends... is a function in a half - let's start by checking if the table exists!
128
	$tables = $smcFunc['db_list_tables']($database);
129
	if (in_array($full_table_name, $tables))
130
	{
131
		// This is a sad day... drop the table? If not, return false (error) by default.
132
		if ($if_exists == 'overwrite')
133
			$smcFunc['db_drop_table']($table_name);
134
		elseif ($if_exists == 'update')
135
		{
136
			$smcFunc['db_drop_table']($table_name . '_old');
137
			$smcFunc['db_transaction']('begin');
138
			$db_trans = true;
139
			$smcFunc['db_query']('', '
140
				ALTER TABLE ' . $short_table_name . ' RENAME TO ' . $short_table_name . '_old',
141
				array(
142
					'security_override' => true,
143
				)
144
			);
145
			$old_table_exists = true;
146
		}
147
		else
148
			return $if_exists == 'ignore';
149
	}
150
151
	// If we've got this far - good news - no table exists. We can build our own!
152
	if (!$db_trans)
153
		$smcFunc['db_transaction']('begin');
154
	$table_query = 'CREATE TABLE ' . $short_table_name . "\n" . '(';
155
	foreach ($columns as $column)
156
	{
157
		// If we have an auto increment do it!
158
		if (!empty($column['auto']))
159
		{
160
			if (!$old_table_exists)
161
				$smcFunc['db_query']('', '
162
					DROP SEQUENCE IF EXISTS ' . $short_table_name . '_seq',
163
					array(
164
						'security_override' => true,
165
					)
166
				);
167
168
			if (!$old_table_exists)
169
				$smcFunc['db_query']('', '
170
					CREATE SEQUENCE ' . $short_table_name . '_seq',
171
					array(
172
						'security_override' => true,
173
					)
174
				);
175
			$default = 'default nextval(\'' . $short_table_name . '_seq\')';
176
		}
177
		elseif (isset($column['default']) && $column['default'] !== null)
178
			$default = 'default \'' . $smcFunc['db_escape_string']($column['default']) . '\'';
179
		else
180
			$default = '';
181
182
		// Sort out the size...
183
		$column['size'] = isset($column['size']) && is_numeric($column['size']) ? $column['size'] : null;
184
		list ($type, $size) = $smcFunc['db_calculate_type']($column['type'], $column['size']);
185
		if ($size !== null)
186
			$type = $type . '(' . $size . ')';
187
188
		// backward compatibility
189
		if (isset($column['null']) && !isset($column['not_null']))
190
			$column['not_null'] = !$column['null'];
191
192
		// Now just put it together!
193
		$table_query .= "\n\t\"" . $column['name'] . '" ' . $type . ' ' . (!empty($column['not_null']) ? 'NOT NULL' : '') . ' ' . $default . ',';
194
	}
195
196
	// Loop through the indexes a sec...
197
	$index_queries = array();
198
	foreach ($indexes as $index)
199
	{
200
		// MySQL you can do a "column_name (length)", postgresql does not allow this.  Strip it.
201
		foreach ($index['columns'] as &$c)
202
			$c = preg_replace('~\s+(\(\d+\))~', '', $c);
203
204
		$columns = implode(',', $index['columns']);
205
206
		// Primary goes in the table...
207
		if (isset($index['type']) && $index['type'] == 'primary')
208
			$table_query .= "\n\t" . 'PRIMARY KEY (' . implode(',', $index['columns']) . '),';
209
		else
210
		{
211
			if (empty($index['name']))
212
				$index['name'] = trim(implode('_', preg_replace('~(\(\d+\))~', '', $index['columns'])));
213
214
			$index_queries[] = 'CREATE ' . (isset($index['type']) && $index['type'] == 'unique' ? 'UNIQUE' : '') . ' INDEX ' . $short_table_name . '_' . $index['name'] . ' ON ' . $short_table_name . ' (' . $columns . ')';
215
		}
216
	}
217
218
	// No trailing commas!
219
	if (substr($table_query, -1) == ',')
220
		$table_query = substr($table_query, 0, -1);
221
222
	$table_query .= ')';
223
224
	// Create the table!
225
	$smcFunc['db_query']('', $table_query,
226
		array(
227
			'security_override' => true,
228
		)
229
	);
230
231
	// Fill the old data
232
	if ($old_table_exists)
233
	{
234
		$same_col = array();
235
236
		$request = $smcFunc['db_query']('', '
237
			SELECT count(*), column_name
238
			FROM information_schema.columns
239
			WHERE table_name in ({string:table1},{string:table2}) AND table_schema = {string:schema}
240
			GROUP BY column_name
241
			HAVING count(*) > 1',
242
			array(
243
				'table1' => $short_table_name,
244
				'table2' => $short_table_name . '_old',
245
				'schema' => 'public',
246
			)
247
		);
248
249
		while ($row = $smcFunc['db_fetch_assoc']($request))
250
		{
251
			$same_col[] = $row['column_name'];
252
		}
253
254
		$smcFunc['db_query']('', '
255
			INSERT INTO ' . $short_table_name . '('
256
			. implode(',', $same_col) .
257
			')
258
			SELECT ' . implode(',', $same_col) . '
259
			FROM ' . $short_table_name . '_old',
260
			array()
261
		);
262
	}
263
264
	// And the indexes...
265
	foreach ($index_queries as $query)
266
		$smcFunc['db_query']('', $query,
267
			array(
268
				'security_override' => true,
269
			)
270
		);
271
272
	// Go, go power rangers!
273
	$smcFunc['db_transaction']('commit');
274
275
	if ($old_table_exists)
276
		$smcFunc['db_drop_table']($table_name . '_old');
277
278
	return true;
279
}
280
281
/**
282
 * Drop a table and its associated sequences.
283
 *
284
 * @param string $table_name The name of the table to drop
285
 * @param array $parameters Not used at the moment
286
 * @param string $error
287
 * @return boolean Whether or not the operation was successful
288
 */
289
function smf_db_drop_table($table_name, $parameters = array(), $error = 'fatal')
290
{
291
	global $reservedTables, $smcFunc, $db_prefix, $db_name;
292
293
	// After stripping away the database name, this is what's left.
294
	$real_prefix = preg_match('~^("?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix;
295
	$database = !empty($match[2]) ? $match[2] : $db_name;
296
297
	// Get some aliases.
298
	$full_table_name = str_replace('{db_prefix}', $real_prefix, $table_name);
299
	$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
300
301
	// God no - dropping one of these = bad.
302
	if (in_array(strtolower($table_name), $reservedTables))
303
		return false;
304
305
	// Does it exist?
306
	$tables = $smcFunc['db_list_tables']($database);
307
	if (in_array($full_table_name, $tables))
308
	{
309
		// We can then drop the table.
310
		$smcFunc['db_transaction']('begin');
311
312
		// the table
313
		$table_query = 'DROP TABLE ' . $short_table_name;
314
315
		// and the assosciated sequence, if any
316
		$sequence_query = 'DROP SEQUENCE IF EXISTS ' . $short_table_name . '_seq';
317
318
		// drop them
319
		$smcFunc['db_query']('',
320
			$table_query,
321
			array(
322
				'security_override' => true,
323
			)
324
		);
325
		$smcFunc['db_query']('',
326
			$sequence_query,
327
			array(
328
				'security_override' => true,
329
			)
330
		);
331
332
		$smcFunc['db_transaction']('commit');
333
334
		return true;
335
	}
336
337
	// Otherwise do 'nout.
338
	return false;
339
}
340
341
/**
342
 * This function adds a column.
343
 *
344
 * @param string $table_name The name of the table to add the column to
345
 * @param array $column_info An array of column info (see {@link smf_db_create_table()})
346
 * @param array $parameters Not used?
347
 * @param string $if_exists What to do if the column exists. If 'update', column is updated.
348
 * @param string $error
349
 * @return boolean Whether or not the operation was successful
350
 */
351
function smf_db_add_column($table_name, $column_info, $parameters = array(), $if_exists = 'update', $error = 'fatal')
352
{
353
	global $smcFunc, $db_package_log, $db_prefix;
354
355
	$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
356
357
	// Log that we will want to uninstall this!
358
	$db_package_log[] = array('remove_column', $short_table_name, $column_info['name']);
359
360
	// Does it exist - if so don't add it again!
361
	$columns = $smcFunc['db_list_columns']($table_name, false);
362
	foreach ($columns as $column)
363
		if ($column == $column_info['name'])
364
		{
365
			// If we're going to overwrite then use change column.
366
			if ($if_exists == 'update')
367
				return $smcFunc['db_change_column']($table_name, $column_info['name'], $column_info);
368
			else
369
				return false;
370
		}
371
372
	// Get the specifics...
373
	$column_info['size'] = isset($column_info['size']) && is_numeric($column_info['size']) ? $column_info['size'] : null;
374
	list ($type, $size) = $smcFunc['db_calculate_type']($column_info['type'], $column_info['size']);
375
	if ($size !== null)
376
		$type = $type . '(' . $size . ')';
377
378
	// Now add the thing!
379
	$query = '
380
		ALTER TABLE ' . $short_table_name . '
381
		ADD COLUMN ' . $column_info['name'] . ' ' . $type;
382
	$smcFunc['db_query']('', $query,
383
		array(
384
			'security_override' => true,
385
		)
386
	);
387
388
	// If there's more attributes they need to be done via a change on PostgreSQL.
389
	unset($column_info['type'], $column_info['size']);
390
391
	if (count($column_info) != 1)
392
		return $smcFunc['db_change_column']($table_name, $column_info['name'], $column_info);
393
	else
394
		return true;
395
}
396
397
/**
398
 * Removes a column.
399
 *
400
 * @param string $table_name The name of the table to drop the column from
401
 * @param string $column_name The name of the column to drop
402
 * @param array $parameters Not used?
403
 * @param string $error
404
 * @return boolean Whether or not the operation was successful
405
 */
406
function smf_db_remove_column($table_name, $column_name, $parameters = array(), $error = 'fatal')
407
{
408
	global $smcFunc, $db_prefix;
409
410
	$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
411
412
	// Does it exist?
413
	$columns = $smcFunc['db_list_columns']($table_name, true);
414
415
	foreach ($columns as $column)
416
		if (strtolower($column['name']) == strtolower($column_name))
417
		{
418
			// If there is an auto we need remove it!
419
			if ($column['auto'])
420
				$smcFunc['db_query']('', '
421
					DROP SEQUENCE IF EXISTS ' . $short_table_name . '_seq',
422
					array(
423
						'security_override' => true,
424
					)
425
				);
426
427
			$smcFunc['db_query']('', '
428
				ALTER TABLE ' . $short_table_name . '
429
				DROP COLUMN ' . $column_name,
430
				array(
431
					'security_override' => true,
432
				)
433
			);
434
435
			return true;
436
		}
437
438
	// If here we didn't have to work - joy!
439
	return false;
440
}
441
442
/**
443
 * Change a column.
444
 *
445
 * @param string $table_name The name of the table this column is in
446
 * @param string $old_column The name of the column we want to change
447
 * @param array $column_info An array of info about the "new" column definition (see {@link smf_db_create_table()})
448
 * @return bool
449
 */
450
function smf_db_change_column($table_name, $old_column, $column_info)
451
{
452
	global $smcFunc, $db_prefix;
453
454
	$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
455
456
	// backward compatibility
457
	if (isset($column_info['null']) && !isset($column_info['not_null']))
458
		$column_info['not_null'] = !$column_info['null'];
459
460
	// Check it does exist!
461
	$columns = $smcFunc['db_list_columns']($table_name, true);
462
	$old_info = null;
463
	foreach ($columns as $column)
464
		if ($column['name'] == $old_column)
465
			$old_info = $column;
466
467
	// Nothing?
468
	if ($old_info == null)
469
		return false;
470
471
	// Now we check each bit individually and ALTER as required.
472
	if (isset($column_info['name']) && $column_info['name'] != $old_column)
473
	{
474
		$smcFunc['db_query']('', '
475
			ALTER TABLE ' . $short_table_name . '
476
			RENAME COLUMN ' . $old_column . ' TO ' . $column_info['name'],
477
			array(
478
				'security_override' => true,
479
			)
480
		);
481
	}
482
	// Different default?
483
	if (array_key_exists('default', $column_info) && $column_info['default'] != $old_info['default'])
484
	{
485
		// Fix the default.
486
		$default = '';
487
		if (array_key_exists('default', $column_info) && is_null($column_info['default']))
488
			$default = 'NULL';
489
		elseif (isset($column_info['default']) && is_numeric($column_info['default']))
490
			$default = strpos($column_info['default'], '.') ? floatval($column_info['default']) : intval($column_info['default']);
491
		else
492
			$default = '\'' . $smcFunc['db_escape_string']($column_info['default']) . '\'';
493
494
		$action = $default === '' ? 'DROP DEFAULT' : 'SET DEFAULT ' . $default;
495
		$smcFunc['db_query']('', '
496
			ALTER TABLE ' . $short_table_name . '
497
			ALTER COLUMN ' . $column_info['name'] . ' ' . $action,
498
			array(
499
				'security_override' => true,
500
			)
501
		);
502
	}
503
504
	// Is it null - or otherwise?
505
	if (isset($column_info['not_null']) && $column_info['not_null'] != $old_info['not_null'])
506
	{
507
		$action = $column_info['not_null'] ? 'SET' : 'DROP';
508
		$smcFunc['db_transaction']('begin');
509
		if ($column_info['not_null'])
510
		{
511
			// We have to set it to something if we are making it NOT NULL. And we must comply with the current column format.
512
			$setTo = isset($column_info['default']) ? $column_info['default'] : (strpos($old_info['type'], 'int') !== false ? 0 : '');
513
			$smcFunc['db_query']('', '
514
				UPDATE ' . $short_table_name . '
515
				SET ' . $column_info['name'] . ' = \'' . $setTo . '\'
516
				WHERE ' . $column_info['name'] . ' IS NULL',
517
				array(
518
					'security_override' => true,
519
				)
520
			);
521
		}
522
		$smcFunc['db_query']('', '
523
			ALTER TABLE ' . $short_table_name . '
524
			ALTER COLUMN ' . $column_info['name'] . ' ' . $action . ' NOT NULL',
525
			array(
526
				'security_override' => true,
527
			)
528
		);
529
		$smcFunc['db_transaction']('commit');
530
	}
531
	// What about a change in type?
532
	if (isset($column_info['type']) && ($column_info['type'] != $old_info['type'] || (isset($column_info['size']) && $column_info['size'] != $old_info['size'])))
533
	{
534
		$column_info['size'] = isset($column_info['size']) && is_numeric($column_info['size']) ? $column_info['size'] : null;
535
		list ($type, $size) = $smcFunc['db_calculate_type']($column_info['type'], $column_info['size']);
536
		if ($size !== null)
537
			$type = $type . '(' . $size . ')';
538
539
		// The alter is a pain.
540
		$smcFunc['db_transaction']('begin');
541
		$smcFunc['db_query']('', '
542
			ALTER TABLE ' . $short_table_name . '
543
			ADD COLUMN ' . $column_info['name'] . '_tempxx ' . $type,
544
			array(
545
				'security_override' => true,
546
			)
547
		);
548
		$smcFunc['db_query']('', '
549
			UPDATE ' . $short_table_name . '
550
			SET ' . $column_info['name'] . '_tempxx = CAST(' . $column_info['name'] . ' AS ' . $type . ')',
551
			array(
552
				'security_override' => true,
553
			)
554
		);
555
		$smcFunc['db_query']('', '
556
			ALTER TABLE ' . $short_table_name . '
557
			DROP COLUMN ' . $column_info['name'],
558
			array(
559
				'security_override' => true,
560
			)
561
		);
562
		$smcFunc['db_query']('', '
563
			ALTER TABLE ' . $short_table_name . '
564
			RENAME COLUMN ' . $column_info['name'] . '_tempxx TO ' . $column_info['name'],
565
			array(
566
				'security_override' => true,
567
			)
568
		);
569
		$smcFunc['db_transaction']('commit');
570
	}
571
	// Finally - auto increment?!
572
	if (isset($column_info['auto']) && $column_info['auto'] != $old_info['auto'])
573
	{
574
		// Are we removing an old one?
575
		if ($old_info['auto'])
576
		{
577
			// Alter the table first - then drop the sequence.
578
			$smcFunc['db_query']('', '
579
				ALTER TABLE ' . $short_table_name . '
580
				ALTER COLUMN ' . $column_info['name'] . ' SET DEFAULT \'0\'',
581
				array(
582
					'security_override' => true,
583
				)
584
			);
585
			$smcFunc['db_query']('', '
586
				DROP SEQUENCE IF EXISTS ' . $short_table_name . '_seq',
587
				array(
588
					'security_override' => true,
589
				)
590
			);
591
		}
592
		// Otherwise add it!
593
		else
594
		{
595
			$smcFunc['db_query']('', '
596
				DROP SEQUENCE IF EXISTS ' . $short_table_name . '_seq',
597
				array(
598
					'security_override' => true,
599
				)
600
			);
601
602
			$smcFunc['db_query']('', '
603
				CREATE SEQUENCE ' . $short_table_name . '_seq',
604
				array(
605
					'security_override' => true,
606
				)
607
			);
608
			$smcFunc['db_query']('', '
609
				ALTER TABLE ' . $short_table_name . '
610
				ALTER COLUMN ' . $column_info['name'] . ' SET DEFAULT nextval(\'' . $short_table_name . '_seq\')',
611
				array(
612
					'security_override' => true,
613
				)
614
			);
615
		}
616
	}
617
618
	return true;
619
}
620
621
/**
622
 * Add an index.
623
 *
624
 * @param string $table_name The name of the table to add the index to
625
 * @param array $index_info An array of index info (see {@link smf_db_create_table()})
626
 * @param array $parameters Not used?
627
 * @param string $if_exists What to do if the index exists. If 'update', the definition will be updated.
628
 * @param string $error
629
 * @return boolean Whether or not the operation was successful
630
 */
631
function smf_db_add_index($table_name, $index_info, $parameters = array(), $if_exists = 'update', $error = 'fatal')
632
{
633
	global $smcFunc, $db_package_log, $db_prefix;
634
635
	$parsed_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
636
	$real_table_name = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $parsed_table_name, $match) === 1 ? $match[3] : $parsed_table_name;
637
638
	// No columns = no index.
639
	if (empty($index_info['columns']))
640
		return false;
641
642
	// MySQL you can do a "column_name (length)", postgresql does not allow this.  Strip it.
643
	foreach ($index_info['columns'] as &$c)
644
		$c = preg_replace('~\s+(\(\d+\))~', '', $c);
645
646
	$columns = implode(',', $index_info['columns']);
647
648
	// No name - make it up!
649
	if (empty($index_info['name']))
650
	{
651
		// No need for primary.
652
		if (isset($index_info['type']) && $index_info['type'] == 'primary')
653
			$index_info['name'] = '';
654
		else
655
			$index_info['name'] = trim(implode('_', preg_replace('~(\(\d+\))~', '', $index_info['columns'])));
656
	}
657
	else
658
		$index_info['name'] = $index_info['name'];
659
660
	// Log that we are going to want to remove this!
661
	$db_package_log[] = array('remove_index', $parsed_table_name, $index_info['name']);
662
663
	// Let's get all our indexes.
664
	$indexes = $smcFunc['db_list_indexes']($table_name, true);
665
666
	// Do we already have it?
667
	foreach ($indexes as $index)
668
	{
669
		if ($index['name'] == $index_info['name'] || ($index['type'] == 'primary' && isset($index_info['type']) && $index_info['type'] == 'primary'))
670
		{
671
			// If we want to overwrite simply remove the current one then continue.
672
			if ($if_exists != 'update' || $index['type'] == 'primary')
673
				return false;
674
			else
675
				$smcFunc['db_remove_index']($table_name, $index_info['name']);
676
		}
677
	}
678
679
	// If we're here we know we don't have the index - so just add it.
680
	if (!empty($index_info['type']) && $index_info['type'] == 'primary')
681
	{
682
		$smcFunc['db_query']('', '
683
			ALTER TABLE ' . $real_table_name . '
684
			ADD PRIMARY KEY (' . $columns . ')',
685
			array(
686
				'security_override' => true,
687
			)
688
		);
689
	}
690
	else
691
	{
692
		$smcFunc['db_query']('', '
693
			CREATE ' . (isset($index_info['type']) && $index_info['type'] == 'unique' ? 'UNIQUE' : '') . ' INDEX ' . $real_table_name . '_' . $index_info['name'] . ' ON ' . $real_table_name . ' (' . $columns . ')',
694
			array(
695
				'security_override' => true,
696
			)
697
		);
698
	}
699
}
700
701
/**
702
 * Remove an index.
703
 *
704
 * @param string $table_name The name of the table to remove the index from
705
 * @param string $index_name The name of the index to remove
706
 * @param array $parameters Not used?
707
 * @param string $error
708
 * @return boolean Whether or not the operation was successful
709
 */
710
function smf_db_remove_index($table_name, $index_name, $parameters = array(), $error = 'fatal')
711
{
712
	global $smcFunc, $db_prefix;
713
714
	$parsed_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
715
	$real_table_name = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $parsed_table_name, $match) === 1 ? $match[3] : $parsed_table_name;
716
717
	// Better exist!
718
	$indexes = $smcFunc['db_list_indexes']($table_name, true);
719
720
	// Do not add the table name to the index if it is arleady there.
721
	if ($index_name != 'primary' && strpos($index_name, $real_table_name) !== false)
722
		$index_name = str_replace($real_table_name . '_', '', $index_name);
723
724
	foreach ($indexes as $index)
725
	{
726
		// If the name is primary we want the primary key!
727
		if ($index['type'] == 'primary' && $index_name == 'primary')
728
		{
729
			// Dropping primary key is odd...
730
			$smcFunc['db_query']('', '
731
				ALTER TABLE ' . $real_table_name . '
732
				DROP CONSTRAINT ' . $index['name'],
733
				array(
734
					'security_override' => true,
735
				)
736
			);
737
738
			return true;
739
		}
740
741
		if ($index['name'] == $index_name)
742
		{
743
			// Drop the bugger...
744
			$smcFunc['db_query']('', '
745
				DROP INDEX ' . $real_table_name . '_' . $index_name,
746
				array(
747
					'security_override' => true,
748
				)
749
			);
750
751
			return true;
752
		}
753
	}
754
755
	// Not to be found ;(
756
	return false;
757
}
758
759
/**
760
 * Get the schema formatted name for a type.
761
 *
762
 * @param string $type_name The data type (int, varchar, smallint, etc.)
763
 * @param int $type_size The size (8, 255, etc.)
764
 * @param boolean $reverse If true, returns specific types for a generic type
765
 * @return array An array containing the appropriate type and size for this DB type
766
 */
767
function smf_db_calculate_type($type_name, $type_size = null, $reverse = false)
768
{
769
	// Let's be sure it's lowercase MySQL likes both, others no.
770
	$type_name = strtolower($type_name);
771
	// Generic => Specific.
772
	if (!$reverse)
773
	{
774
		$types = array(
775
			'varchar' => 'character varying',
776
			'char' => 'character',
777
			'mediumint' => 'int',
778
			'tinyint' => 'smallint',
779
			'tinytext' => 'character varying',
780
			'mediumtext' => 'text',
781
			'largetext' => 'text',
782
			'inet' => 'inet',
783
			'time' => 'time without time zone',
784
			'datetime' => 'timestamp without time zone',
785
			'timestamp' => 'timestamp without time zone',
786
		);
787
	}
788
	else
789
	{
790
		$types = array(
791
			'character varying' => 'varchar',
792
			'character' => 'char',
793
			'integer' => 'int',
794
			'inet' => 'inet',
795
			'time without time zone' => 'time',
796
			'timestamp without time zone' => 'datetime',
797
			'numeric' => 'decimal',
798
		);
799
	}
800
801
	// Got it? Change it!
802
	if (isset($types[$type_name]))
803
	{
804
		if ($type_name == 'tinytext')
805
			$type_size = 255;
806
		$type_name = $types[$type_name];
807
	}
808
809
	// Only char fields got size
810
	if (strpos($type_name, 'char') === false)
811
		$type_size = null;
812
813
	return array($type_name, $type_size);
814
}
815
816
/**
817
 * Get table structure.
818
 *
819
 * @param string $table_name The name of the table
820
 * @return array An array of table structure - the name, the column info from {@link smf_db_list_columns()} and the index info from {@link smf_db_list_indexes()}
821
 */
822
function smf_db_table_structure($table_name)
823
{
824
	global $smcFunc, $db_prefix;
825
826
	$parsed_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
827
	$real_table_name = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $parsed_table_name, $match) === 1 ? $match[3] : $parsed_table_name;
828
829
	return array(
830
		'name' => $real_table_name,
831
		'columns' => $smcFunc['db_list_columns']($table_name, true),
832
		'indexes' => $smcFunc['db_list_indexes']($table_name, true),
833
	);
834
}
835
836
/**
837
 * Return column information for a table.
838
 *
839
 * @param string $table_name The name of the table to get column info for
840
 * @param bool $detail Whether or not to return detailed info. If true, returns the column info. If false, just returns the column names.
841
 * @param array $parameters Not used?
842
 * @return array An array of column names or detailed column info, depending on $detail
843
 */
844
function smf_db_list_columns($table_name, $detail = false, $parameters = array())
845
{
846
	global $smcFunc, $db_prefix, $db_name;
847
848
	$parsed_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
849
	$real_table_name = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $parsed_table_name, $match) === 1 ? $match[3] : $parsed_table_name;
850
	$database = !empty($match[2]) ? $match[2] : $db_name;
0 ignored issues
show
Unused Code introduced by
The assignment to $database is dead and can be removed.
Loading history...
851
852
	$result = $smcFunc['db_query']('', '
853
		SELECT column_name, column_default, is_nullable, data_type, character_maximum_length
854
		FROM information_schema.columns
855
		WHERE table_schema = {string:schema_public}
856
			AND table_name = {string:table_name}
857
		ORDER BY ordinal_position',
858
		array(
859
			'schema_public' => 'public',
860
			'table_name' => $real_table_name,
861
		)
862
	);
863
	$columns = array();
864
	while ($row = $smcFunc['db_fetch_assoc']($result))
865
	{
866
		if (!$detail)
867
		{
868
			$columns[] = $row['column_name'];
869
		}
870
		else
871
		{
872
			$auto = false;
873
			// What is the default?
874
			if (preg_match('~nextval\(\'(.+?)\'(.+?)*\)~i', $row['column_default'], $matches) != 0)
875
			{
876
				$default = null;
877
				$auto = true;
878
			}
879
			elseif (trim($row['column_default']) != '')
880
				$default = trim(strpos($row['column_default'], '::') === false ? $row['column_default'] : substr($row['column_default'], 0, strpos($row['column_default'], '::')), '\'');
881
			else
882
				$default = null;
883
884
			// Make the type generic.
885
			list ($type, $size) = $smcFunc['db_calculate_type']($row['data_type'], $row['character_maximum_length'], true);
886
887
			$columns[$row['column_name']] = array(
888
				'name' => $row['column_name'],
889
				'not_null' => $row['is_nullable'] == 'YES' ? false : true,
890
				'null' => $row['is_nullable'] == 'YES' ? true : false,
891
				'default' => $default,
892
				'type' => $type,
893
				'size' => $size,
894
				'auto' => $auto,
895
			);
896
		}
897
	}
898
	$smcFunc['db_free_result']($result);
899
900
	return $columns;
901
}
902
903
/**
904
 * Get index information.
905
 *
906
 * @param string $table_name The name of the table to get indexes for
907
 * @param bool $detail Whether or not to return detailed info.
908
 * @param array $parameters Not used?
909
 * @return array An array of index names or a detailed array of index info, depending on $detail
910
 */
911
function smf_db_list_indexes($table_name, $detail = false, $parameters = array())
912
{
913
	global $smcFunc, $db_prefix, $db_name;
914
915
	$parsed_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
916
	$real_table_name = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $parsed_table_name, $match) === 1 ? $match[3] : $parsed_table_name;
917
	$database = !empty($match[2]) ? $match[2] : $db_name;
0 ignored issues
show
Unused Code introduced by
The assignment to $database is dead and can be removed.
Loading history...
918
919
	$result = $smcFunc['db_query']('', '
920
		SELECT CASE WHEN i.indisprimary THEN 1 ELSE 0 END AS is_primary,
921
			CASE WHEN i.indisunique THEN 1 ELSE 0 END AS is_unique,
922
			c2.relname AS name,
923
			pg_get_indexdef(i.indexrelid) AS inddef
924
		FROM pg_class AS c, pg_class AS c2, pg_index AS i
925
		WHERE c.relname = {string:table_name}
926
			AND c.oid = i.indrelid
927
			AND i.indexrelid = c2.oid',
928
		array(
929
			'table_name' => $real_table_name,
930
		)
931
	);
932
	$indexes = array();
933
	while ($row = $smcFunc['db_fetch_assoc']($result))
934
	{
935
		// Try get the columns that make it up.
936
		if (preg_match('~\(([^\)]+?)\)~i', $row['inddef'], $matches) == 0)
937
			continue;
938
939
		$columns = explode(',', $matches[1]);
940
941
		if (empty($columns))
942
			continue;
943
944
		foreach ($columns as $k => $v)
945
			$columns[$k] = trim($v);
946
947
		// Fix up the name to be consistent cross databases
948
		if (substr($row['name'], -5) == '_pkey' && $row['is_primary'] == 1)
949
			$row['name'] = 'PRIMARY';
950
		else
951
			$row['name'] = str_replace($real_table_name . '_', '', $row['name']);
952
953
		if (!$detail)
954
			$indexes[] = $row['name'];
955
		else
956
		{
957
			$indexes[$row['name']] = array(
958
				'name' => $row['name'],
959
				'type' => $row['is_primary'] ? 'primary' : ($row['is_unique'] ? 'unique' : 'index'),
960
				'columns' => $columns,
961
			);
962
		}
963
	}
964
	$smcFunc['db_free_result']($result);
965
966
	return $indexes;
967
}
968
969
?>