Completed
Pull Request — master (#3237)
by Emanuele
14:50
created

DbTable_PostgreSQL::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 25

Duplication

Lines 25
Ratio 100 %

Code Coverage

Tests 17
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 25
loc 25
rs 9.52
c 0
b 0
f 0
ccs 17
cts 17
cp 1
crap 2
1
<?php
2
3
/**
4
 * This class implements functionality related to table structure.
5
 * Intended in particular for addons to change it to suit their needs.
6
 *
7
 * @name      ElkArte Forum
8
 * @copyright ElkArte Forum contributors
9
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
10
 *
11
 * This file contains code covered by:
12
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
13
 * license:  	BSD, See included LICENSE.TXT for terms and conditions.
14
 *
15
 * @version 1.1
16
 *
17
 */
18
19
/**
20
 * Adds PostgreSQL table level functionality,
21
 * Table creation / dropping, column adding / removing
22
 * Most often used during install and Upgrades of the forum and addons
23
 */
24
class DbTable_PostgreSQL extends DbTable
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
25
{
26
	/**
27
	 * Holds this instance of the table interface
28
	 * @var DbTable_PostgreSQL
29
	 */
30
	private static $_tbl = null;
31
32
	/**
33
	 * DbTable_PostgreSQL::construct
34
	 *
35
	 * @param object $db - A Database_PostgreSQL object
36
	 */
37 1 View Code Duplication
	private function __construct($db)
38
	{
39 1
		global $db_prefix;
40
41
		// We won't do any remove on these
42 1
		$this->_reservedTables = array('admin_info_files', 'approval_queue', 'attachments', 'ban_groups', 'ban_items',
43 1
			'board_permissions', 'boards', 'calendar', 'calendar_holidays', 'categories', 'collapsed_categories',
44 1
			'custom_fields', 'group_moderators', 'log_actions', 'log_activity', 'log_banned', 'log_boards',
45 1
			'log_digest', 'log_errors', 'log_floodcontrol', 'log_group_requests', 'log_karma', 'log_mark_read',
46 1
			'log_notify', 'log_online', 'log_packages', 'log_polls', 'log_reported', 'log_reported_comments',
47 1
			'log_scheduled_tasks', 'log_search_messages', 'log_search_results', 'log_search_subjects',
48 1
			'log_search_topics', 'log_topics', 'mail_queue', 'membergroups', 'members', 'message_icons',
49 1
			'messages', 'moderators', 'package_servers', 'permission_profiles', 'permissions', 'personal_messages',
50 1
			'pm_recipients', 'poll_choices', 'polls', 'scheduled_tasks', 'sessions', 'settings', 'smileys',
51 1
			'themes', 'topics');
52
53 1
		foreach ($this->_reservedTables as $k => $table_name)
54 1
			$this->_reservedTables[$k] = strtolower($db_prefix . $table_name);
55
56
		// let's be sure.
57 1
		$this->_package_log = array();
58
59
		// This executes queries and things
60 1
		$this->_db = $db;
61 1
	}
62
63
	/**
64
	 * 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
	 *    - '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
	 *  - 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
	 *  - parameters: (None yet)
82
	 *  - if_exists values:
83
	 *    - 'ignore' will do nothing if the table exists. (And will return true)
84
	 *    - 'overwrite' will drop any existing table of the same name.
85
	 *    - 'error' will return false if the table already exists.
86
	 *
87
	 * @param string $table_name
88
	 * @param mixed[] $columns in the format specified.
89
	 * @param mixed[] $indexes default array(), in the format specified.
90
	 * @param mixed[] $parameters default array()
91
	 * @param string $if_exists default 'ignore'
92
	 * @param string $error default 'fatal'
93
	 */
94
	public function db_create_table($table_name, $columns, $indexes = array(), $parameters = array(), $if_exists = 'ignore', $error = 'fatal')
95
	{
96
		global $db_prefix;
97
98
		// Strip out the table name, we might not need it in some cases
99
		$real_prefix = preg_match('~^("?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix;
100
101
		// With or without the database name, the fullname looks like this.
102
		$full_table_name = str_replace('{db_prefix}', $real_prefix, $table_name);
103
		$table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
104
105
		// First - no way do we touch our tables.
106
		if (in_array(strtolower($table_name), $this->_reservedTables))
107
			return false;
108
109
		// Log that we'll want to remove this on uninstall.
110
		$this->_package_log[] = array('remove_table', $table_name);
111
112
		// This... my friends... is a function in a half - let's start by checking if the table exists!
113 View Code Duplication
		if ($this->table_exists($full_table_name))
114
		{
115
			// This is a sad day... drop the table? If not, return false (error) by default.
116
			if ($if_exists == 'overwrite')
117
				$this->db_drop_table($table_name);
118
			else
119
				return $if_exists == 'ignore';
120
		}
121
122
		// If we've got this far - good news - no table exists. We can build our own!
123
		$this->_db->db_transaction('begin');
124
		$table_query = 'CREATE TABLE ' . $table_name . "\n" . '(';
125
		foreach ($columns as $column)
126
		{
127
			// If we have an auto increment do it!
128
			if (!empty($column['auto']))
129
			{
130
				$this->_db->query('', '
131
					CREATE SEQUENCE ' . $table_name . '_seq',
132
					array(
133
						'security_override' => true,
134
					)
135
				);
136
				$default = 'default nextval(\'' . $table_name . '_seq\')';
137
			}
138 View Code Duplication
			elseif (isset($column['default']) && $column['default'] !== null)
139
				$default = 'default \'' . $this->_db->escape_string($column['default']) . '\'';
140
			else
141
				$default = '';
142
143
			// Sort out the size...
144
			$column['size'] = isset($column['size']) && is_numeric($column['size']) ? $column['size'] : null;
145
			list ($type, $size) = $this->db_calculate_type($column['type'], $column['size']);
146
			if ($size !== null)
147
				$type = $type . '(' . $size . ')';
148
149
			// Now just put it together!
150
			$table_query .= "\n\t\"" . $column['name'] . '" ' . $type . ' ' . (!empty($column['null']) ? '' : 'NOT NULL') . ' ' . $default . ',';
151
		}
152
153
		// Loop through the indexes a sec...
154
		$index_queries = array();
155
		foreach ($indexes as $index)
156
		{
157
			// MySQL supports a length argument, postgre no
158 View Code Duplication
			foreach ($index['columns'] as $id => $col)
159
				if (strpos($col, '(') !== false)
160
					$index['columns'][$id] = substr($col, 0, strpos($col, '('));
161
162
			$columns = implode(',', $index['columns']);
163
164
			// Primary goes in the table...
165
			if (isset($index['type']) && $index['type'] == 'primary')
166
				$table_query .= "\n\t" . 'PRIMARY KEY (' . $columns . '),';
167
			else
168
			{
169
				if (empty($index['name']))
170
					$index['name'] = implode('_', $index['columns']);
171
				$index_queries[] = 'CREATE ' . (isset($index['type']) && $index['type'] == 'unique' ? 'UNIQUE' : '') . ' INDEX ' . $table_name . '_' . $index['name'] . ' ON ' . $table_name . ' (' . $columns . ')';
172
			}
173
		}
174
175
		// No trailing commas!
176 View Code Duplication
		if (substr($table_query, -1) == ',')
177
			$table_query = substr($table_query, 0, -1);
178
179
		$table_query .= ')';
180
181
		// Create the table!
182
		$this->_db->query('', $table_query,
183
			array(
184
				'security_override' => true,
185
			)
186
		);
187
		// And the indexes...
188
		foreach ($index_queries as $query)
189
		{
190
			$this->_db->query('', $query,
191
				array(
192
					'security_override' => true,
193
				)
194
			);
195
		}
196
197
		// Go, go power rangers!
198
		$this->_db->db_transaction('commit');
199
200
		return true;
201
	}
202
203
	/**
204
	 * Drop a table.
205
	 *
206
	 * @param string $table_name
207
	 * @param mixed[] $parameters default array()
208
	 * @param string $error default 'fatal'
209
	 */
210
	public function db_drop_table($table_name, $parameters = array(), $error = 'fatal')
211
	{
212
		global $db_prefix;
213
214
		// After stripping away the database name, this is what's left.
215
		$real_prefix = preg_match('~^("?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix;
216
217
		// Get some aliases.
218
		$full_table_name = str_replace('{db_prefix}', $real_prefix, $table_name);
219
		$table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
220
221
		// God no - dropping one of these = bad.
222
		if (in_array(strtolower($table_name), $this->_reservedTables))
223
			return false;
224
225
		// Does it exist?
226
		if ($this->table_exists($full_table_name))
227
		{
228
			// We can then drop the table.
229
			$this->_db->db_transaction('begin');
230
231
			// the table
232
			$table_query = 'DROP TABLE ' . $table_name;
233
234
			// and the associated sequence, if any
235
			$sequence_query = 'DROP SEQUENCE IF EXISTS ' . $table_name . '_seq';
236
237
			// drop them
238
			$this->_db->query('',
239
				$table_query,
240
				array(
241
					'security_override' => true,
242
				)
243
			);
244
			$this->_db->query('',
245
				$sequence_query,
246
				array(
247
					'security_override' => true,
248
				)
249
			);
250
251
			$this->_db->db_transaction('commit');
252
253
			return true;
254
		}
255
256
		// Otherwise do 'nout.
257
		return false;
258
	}
259
260
	/**
261
	 * This function adds a column.
262
	 *
263
	 * @param string $table_name the name of the table
264
	 * @param mixed[] $column_info with column information
265
	 * @param mixed[] $parameters default array()
266
	 * @param string $if_exists default 'update'
267
	 * @param string $error default 'fatal'
268
	 */
269
	public function db_add_column($table_name, $column_info, $parameters = array(), $if_exists = 'update', $error = 'fatal')
270
	{
271
		global $db_prefix;
272
273
		$table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
274
275
		// Log that we will want to uninstall this!
276
		$this->_package_log[] = array('remove_column', $table_name, $column_info['name']);
277
278
		// Does it exist - if so don't add it again!
279 View Code Duplication
		if ($this->_get_column_info($table_name, $column_info['name']))
280
		{
281
			// If we're going to overwrite then use change column.
282
			if ($if_exists == 'update')
283
				return $this->db_change_column($table_name, $column_info['name'], $column_info);
284
			else
285
				return false;
286
		}
287
288
		// Get the specifics...
289
		$column_info['size'] = isset($column_info['size']) && is_numeric($column_info['size']) ? $column_info['size'] : null;
290
		list ($type, $size) = $this->db_calculate_type($column_info['type'], $column_info['size']);
291
		if ($size !== null)
292
			$type = $type . '(' . $size . ')';
293
294
		// Now add the thing!
295
		$this->_alter_table($table_name, '
296
			ADD COLUMN ' . $column_info['name'] . ' ' . $type);
297
298
		// If there's more attributes they need to be done via a change on PostgreSQL.
299
		unset($column_info['type'], $column_info['size']);
300
301 View Code Duplication
		if (count($column_info) != 1)
302
			return $this->db_change_column($table_name, $column_info['name'], $column_info);
303
		else
304
			return true;
305
	}
306
307
	/**
308
	 * Removes a column.
309
	 *
310
	 * @param string $table_name
311
	 * @param string $column_name
312
	 * @param mixed[] $parameters default array()
313
	 * @param string $error default 'fatal'
314
	 */
315
	public function db_remove_column($table_name, $column_name, $parameters = array(), $error = 'fatal')
316
	{
317
		global $db_prefix;
318
319
		$table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
320
321
		// Does it exist?
322
		$column = $this->_get_column_info($table_name, $column_name);
323
		if ($column !== false)
324
		{
325
			// If there is an auto we need remove it!
326
			if ($column['auto'])
327
				$this->_db->query('',
328
					'DROP SEQUENCE ' . $table_name . '_seq',
329
					array(
330
						'security_override' => true,
331
					)
332
				);
333
334
			$this->_alter_table($table_name, '
335
				DROP COLUMN ' . $column_name);
336
337
			return true;
338
		}
339
340
		// If here we didn't have to work - joy!
341
		return false;
342
	}
343
344
	/**
345
	 * Change a column.
346
	 *
347
	 * @param string $table_name
348
	 * @param string $old_column
349
	 * @param mixed[] $column_info
350
	 * @param mixed[] $parameters default array()
351
	 * @param string $error default 'fatal'
352
	 */
353
	public function db_change_column($table_name, $old_column, $column_info, $parameters = array(), $error = 'fatal')
354
	{
355
		global $db_prefix;
356
357
		$table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
358
359
		// Check it does exist!
360
		$old_info = $this->_get_column_info($table_name, $old_column);
361
362
		// Nothing?
363
		if ($old_info === false)
364
			return false;
365
366
		// Now we check each bit individually and ALTER as required.
367
		if (isset($column_info['name']) && $column_info['name'] != $old_column)
368
		{
369
			$this->_alter_table($table_name, '
370
				RENAME COLUMN ' . $old_column . ' TO ' . $column_info['name']);
371
		}
372
373
		// Different default?
374
		if (isset($column_info['default']) && $column_info['default'] != $old_info['default'])
375
		{
376
			$action = $column_info['default'] !== null ? 'SET DEFAULT \'' . $this->_db->escape_string($column_info['default']) . '\'' : 'DROP DEFAULT';
377
			$this->_alter_table($table_name, '
378
				ALTER COLUMN ' . $column_info['name'] . ' ' . $action);
379
		}
380
381
		// Is it null - or otherwise?
382
		if (isset($column_info['null']) && $column_info['null'] != $old_info['null'])
383
		{
384
			$action = $column_info['null'] ? 'DROP' : 'SET';
385
			$this->_db->db_transaction('begin');
386
			if (!$column_info['null'])
387
			{
388
				// We have to set it to something if we are making it NOT NULL. And we must comply with the current column format.
389
				$setTo = isset($column_info['default']) ? $column_info['default'] : (strpos($old_info['type'], 'int') !== false ? 0 : '');
390
				$this->_db->query('', '
391
					UPDATE ' . $table_name . '
392
					SET ' . $column_info['name'] . ' = \'' . $setTo . '\'
393
					WHERE ' . $column_info['name'] . ' IS NULL',
394
					array(
395
						'security_override' => true,
396
					)
397
				);
398
			}
399
			$this->_alter_table($table_name, '
400
				ALTER COLUMN ' . $column_info['name'] . ' ' . $action . ' NOT NULL');
401
			$this->_db->db_transaction('commit');
402
		}
403
404
		// What about a change in type?
405
		if (isset($column_info['type']) && ($column_info['type'] != $old_info['type'] || (isset($column_info['size']) && $column_info['size'] != $old_info['size'])))
406
		{
407
			$column_info['size'] = isset($column_info['size']) && is_numeric($column_info['size']) ? $column_info['size'] : null;
408
			list ($type, $size) = $this->db_calculate_type($column_info['type'], $column_info['size']);
409
			if ($size !== null)
410
				$type = $type . '(' . $size . ')';
411
412
			// The alter is a pain.
413
			$this->_db->db_transaction('begin');
414
			$this->_alter_table($table_name, '
415
				ADD COLUMN ' . $column_info['name'] . '_tempxx ' . $type);
416
			$this->_db->query('', '
417
				UPDATE ' . $table_name . '
418
				SET ' . $column_info['name'] . '_tempxx = CAST(' . $column_info['name'] . ' AS ' . $type . ')',
419
				array(
420
					'security_override' => true,
421
				)
422
			);
423
			$this->_alter_table($table_name, '
424
				DROP COLUMN ' . $column_info['name']);
425
			$this->_alter_table($table_name, '
426
				RENAME COLUMN ' . $column_info['name'] . '_tempxx TO ' . $column_info['name']);
427
			$this->_db->db_transaction('commit');
428
		}
429
430
		// Finally - auto increment?!
431
		if (isset($column_info['auto']) && $column_info['auto'] != $old_info['auto'])
432
		{
433
			// Are we removing an old one?
434
			if ($old_info['auto'])
435
			{
436
				// Alter the table first - then drop the sequence.
437
				$this->_alter_table($table_name, '
438
					ALTER COLUMN ' . $column_info['name'] . ' SET DEFAULT \'0\'');
439
				$this->_db->query('', '
440
					DROP SEQUENCE ' . $table_name . '_seq',
441
					array(
442
						'security_override' => true,
443
					)
444
				);
445
			}
446
			// Otherwise add it!
447
			else
448
			{
449
				$this->_db->query('', '
450
					CREATE SEQUENCE ' . $table_name . '_seq',
451
					array(
452
						'security_override' => true,
453
					)
454
				);
455
				$this->_alter_table($table_name, '
456
					ALTER COLUMN ' . $column_info['name'] . ' SET DEFAULT nextval(\'' . $table_name . '_seq\')');
457
			}
458
		}
459
	}
460
461
	/**
462
	 * Add an index.
463
	 *
464
	 * @param string $table_name
465
	 * @param mixed[] $index_info
466
	 * @param mixed[] $parameters default array()
467
	 * @param string $if_exists default 'update'
468
	 * @param string $error default 'fatal'
469
	 */
470
	public function db_add_index($table_name, $index_info, $parameters = array(), $if_exists = 'update', $error = 'fatal')
471
	{
472
		global $db_prefix;
473
474
		$table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
475
476
		// No columns = no index.
477
		if (empty($index_info['columns']))
478
			return false;
479
480
		// MySQL supports a length argument, postgre no
481 View Code Duplication
		foreach ($index_info['columns'] as $id => $col)
482
			if (strpos($col, '(') !== false)
483
				$index_info['columns'][$id] = substr($col, 0, strpos($col, '('));
484
485
		$columns = implode(',', $index_info['columns']);
486
487
		// No name - make it up!
488
		if (empty($index_info['name']))
489
		{
490
			// No need for primary.
491 View Code Duplication
			if (isset($index_info['type']) && $index_info['type'] == 'primary')
492
				$index_info['name'] = '';
493
			else
494
				$index_info['name'] = $table_name . implode('_', $index_info['columns']);
495
		}
496
		else
497
			$index_info['name'] = $table_name . $index_info['name'];
498
499
		// Log that we are going to want to remove this!
500
		$this->_package_log[] = array('remove_index', $table_name, $index_info['name']);
501
502
		// Let's get all our indexes.
503
		$indexes = $this->db_list_indexes($table_name, true);
504
505
		// Do we already have it?
506 View Code Duplication
		foreach ($indexes as $index)
507
		{
508
			if ($index['name'] == $index_info['name'] || ($index['type'] == 'primary' && isset($index_info['type']) && $index_info['type'] == 'primary'))
509
			{
510
				// If we want to overwrite simply remove the current one then continue.
511
				if ($if_exists != 'update' || $index['type'] == 'primary')
512
					return false;
513
				else
514
					$this->db_remove_index($table_name, $index_info['name']);
515
			}
516
		}
517
518
		// If we're here we know we don't have the index - so just add it.
519
		if (!empty($index_info['type']) && $index_info['type'] == 'primary')
520
		{
521
			$this->_alter_table($table_name, '
522
				ADD PRIMARY KEY (' . $columns . ')');
523
		}
524
		else
525
		{
526
			$this->_db->query('', '
527
				CREATE ' . (isset($index_info['type']) && $index_info['type'] == 'unique' ? 'UNIQUE' : '') . ' INDEX ' . $index_info['name'] . ' ON ' . $table_name . ' (' . $columns . ')',
528
				array(
529
					'security_override' => true,
530
				)
531
			);
532
		}
533
	}
534
535
	/**
536
	 * Remove an index.
537
	 *
538
	 * @param string $table_name
539
	 * @param string $index_name
540
	 * @param mixed[] $parameters default array()
541
	 * @param string $error default 'fatal'
542
	 */
543
	public function db_remove_index($table_name, $index_name, $parameters = array(), $error = 'fatal')
544
	{
545
		global $db_prefix;
546
547
		$table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
548
549
		// Better exist!
550
		$indexes = $this->db_list_indexes($table_name, true);
551
		if ($index_name != 'primary')
552
			$index_name = $table_name . '_' . $index_name;
553
554
		foreach ($indexes as $index)
555
		{
556
			// If the name is primary we want the primary key!
557 View Code Duplication
			if ($index['type'] == 'primary' && $index_name == 'primary')
558
			{
559
				// Dropping primary key is odd...
560
				$this->_alter_table($table_name, '
561
					DROP CONSTRAINT ' . $index['name']);
562
563
				return true;
564
			}
565
566
			if ($index['name'] == $index_name)
567
			{
568
				// Drop the bugger...
569
				$this->_db->query('', '
570
					DROP INDEX ' . $index_name,
571
					array(
572
						'security_override' => true,
573
					)
574
				);
575
576
				return true;
577
			}
578
		}
579
580
		// Not to be found ;(
581
		return false;
582
	}
583
584
	/**
585
	 * Get the schema formatted name for a type.
586
	 *
587
	 * @param string $type_name
588
	 * @param int|null $type_size
589
	 * @param boolean $reverse
590
	 */
591
	public function db_calculate_type($type_name, $type_size = null, $reverse = false)
592
	{
593
		// Let's be sure it's lowercase MySQL likes both, others no.
594
		$type_name = strtolower($type_name);
595
596
		// Generic => Specific.
597
		if (!$reverse)
598
		{
599
			$types = array(
600
				'varchar' => 'character varying',
601
				'char' => 'character',
602
				'mediumint' => 'int',
603
				'tinyint' => 'smallint',
604
				'tinytext' => 'character varying',
605
				'mediumtext' => 'text',
606
				'largetext' => 'text',
607
			);
608
		}
609
		else
610
		{
611
			$types = array(
612
				'character varying' => 'varchar',
613
				'character' => 'char',
614
				'integer' => 'int',
615
			);
616
		}
617
618
		// Got it? Change it!
619
		if (isset($types[$type_name]))
620
		{
621
			if ($type_name == 'tinytext')
622
				$type_size = 255;
623
			$type_name = $types[$type_name];
624
		}
625
626
		// Numbers don't have a size.
627
		if (strpos($type_name, 'int') !== false)
628
				$type_size = null;
629
630
		return array($type_name, $type_size);
631
	}
632
633
	/**
634
	 * Get table structure.
635
	 *
636
	 * @param string $table_name
637
	 * @param mixed[] $parameters default array()
638
	 */
639 View Code Duplication
	public function db_table_structure($table_name, $parameters = array())
640
	{
641
		global $db_prefix;
642
643
		$table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
644
645
		return array(
646
			'name' => $table_name,
647
			'columns' => $this->db_list_columns($table_name, true),
648
			'indexes' => $this->db_list_indexes($table_name, true),
649
		);
650
	}
651
652
	/**
653
	 * Return column information for a table.
654
	 *
655
	 * @param string $table_name
656
	 * @param bool $detail
657
	 * @param mixed[] $parameters default array()
658
	 * @return mixed
659
	 */
660
	public function db_list_columns($table_name, $detail = false, $parameters = array())
661
	{
662
		global $db_prefix;
663
664
		$table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
665
666
		$result = $this->_db->query('', '
667
			SELECT column_name, column_default, is_nullable, data_type, character_maximum_length
668
			FROM information_schema.columns
669
			WHERE table_name = \'' . $table_name . '\'
670
			ORDER BY ordinal_position',
671
			array(
672
				'security_override' => true,
673
			)
674
		);
675
		$columns = array();
676
		while ($row = $this->_db->fetch_assoc($result))
677
		{
678
			if (!$detail)
679
			{
680
				$columns[] = $row['column_name'];
681
			}
682
			else
683
			{
684
				$auto = false;
685
686
				// What is the default?
687
				if (preg_match('~nextval\(\'(.+?)\'(.+?)*\)~i', $row['column_default'], $matches) != 0)
688
				{
689
					$default = null;
690
					$auto = true;
691
				}
692
				elseif (trim($row['column_default']) != '')
693
					$default = strpos($row['column_default'], '::') === false ? $row['column_default'] : substr($row['column_default'], 0, strpos($row['column_default'], '::'));
694
				else
695
					$default = null;
696
697
				// Make the type generic.
698
				list ($type, $size) = $this->db_calculate_type($row['data_type'], $row['character_maximum_length'], true);
699
700
				$columns[$row['column_name']] = array(
701
					'name' => $row['column_name'],
702
					'null' => $row['is_nullable'] ? true : false,
703
					'default' => $default,
704
					'type' => $type,
705
					'size' => $size,
706
					'auto' => $auto,
707
				);
708
			}
709
		}
710
		$this->_db->free_result($result);
711
712
		return $columns;
713
	}
714
715
	/**
716
	 * Get index information.
717
	 *
718
	 * @param string $table_name
719
	 * @param bool $detail
720
	 * @param mixed[] $parameters
721
	 * @return mixed
722
	 */
723
	public function db_list_indexes($table_name, $detail = false, $parameters = array())
724
	{
725
		global $db_prefix;
726
727
		$table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
728
729
		$result = $this->_db->query('', '
730
			SELECT CASE WHEN i.indisprimary THEN 1 ELSE 0 END AS is_primary,
731
				CASE WHEN i.indisunique THEN 1 ELSE 0 END AS is_unique,
732
				c2.relname AS name,
733
				pg_get_indexdef(i.indexrelid) AS inddef
734
			FROM pg_class AS c, pg_class AS c2, pg_index AS i
735
			WHERE c.relname = \'' . $table_name . '\'
736
				AND c.oid = i.indrelid
737
				AND i.indexrelid = c2.oid',
738
			array(
739
				'security_override' => true,
740
			)
741
		);
742
		$indexes = array();
743
		while ($row = $this->_db->fetch_assoc($result))
744
		{
745
			// Try get the columns that make it up.
746
			if (preg_match('~\(([^\)]+?)\)~i', $row['inddef'], $matches) == 0)
747
				continue;
748
749
			$columns = explode(',', $matches[1]);
750
751
			if (empty($columns))
752
				continue;
753
754
			foreach ($columns as $k => $v)
755
				$columns[$k] = trim($v);
756
757
			// Fix up the name to be consistent cross databases
758
			if (substr($row['name'], -5) == '_pkey' && $row['is_primary'] == 1)
759
				$row['name'] = 'PRIMARY';
760
			else
761
				$row['name'] = str_replace($table_name . '_', '', $row['name']);
762
763
			if (!$detail)
764
				$indexes[] = $row['name'];
765
			else
766
			{
767
				$indexes[$row['name']] = array(
768
					'name' => $row['name'],
769
					'type' => $row['is_primary'] ? 'primary' : ($row['is_unique'] ? 'unique' : 'index'),
770
					'columns' => $columns,
771
				);
772
			}
773
		}
774
		$this->_db->free_result($result);
775
776
		return $indexes;
777
	}
778
779
	/**
780
	 * This function optimizes a table.
781
	 *
782
	 * @param string $table - the table to be optimized
783
	 *
784
	 * @return int how much it was gained
785
	 */
786 1
	public function optimize($table)
787
	{
788 1
		global $db_prefix;
789
790 1
		$table = str_replace('{db_prefix}', $db_prefix, $table);
791
792 1
		$request = $this->_db->query('', '
793 1
			VACUUM ANALYZE {raw:table}',
794
			array(
795 1
				'table' => $table,
796
			)
797 1
		);
798 1
		if (!$request)
799 1
			return -1;
800
801 1
		$row = $this->_db->fetch_assoc($request);
802 1
		$this->_db->free_result($request);
803
804 1
		if (isset($row['Data_free']))
805 1
			return $row['Data_free'] / 1024;
806
		else
807 1
			return 0;
808
	}
809
810
	/**
811
	 * Return a copy of this instance package log
812
	 */
813
	public function package_log()
814
	{
815
		return $this->_package_log;
816
	}
817
818
	/**
819
	 * Static method that allows to retrieve or create an instance of this class.
820
	 * @param object $db - A Database_PostgreSQL object
821
	 * @return DbTable_PostgreSQL - A DbTable_PostgreSQL object
822
	 */
823 1
	public static function db_table($db)
824
	{
825 1
		if (is_null(self::$_tbl))
826 1
			self::$_tbl = new DbTable_PostgreSQL($db);
827 1
		return self::$_tbl;
828
	}
829
}