1
|
<?php
|
2
|
|
3
|
/**
|
4
|
* A class that implements the DB interface for Postgres
|
5
|
* Note: This class uses ADODB and returns RecordSets.
|
6
|
*
|
7
|
* $Id: Postgres74.php,v 1.50 2005/09/07 08:09:21 chriskl Exp $
|
8
|
*/
|
9
|
|
10
|
include_once('./classes/database/Postgres73.php');
|
11
|
|
12
|
class Postgres74 extends Postgres73 {
|
13
|
|
14
|
var $major_version = 7.4;
|
15
|
|
16
|
// Last oid assigned to a system object
|
17
|
var $_lastSystemOID = 17137;
|
18
|
|
19
|
// Max object name length
|
20
|
var $_maxNameLen = 63;
|
21
|
|
22
|
// How often to execute the trigger
|
23
|
var $triggerFrequency = array('ROW','STATEMENT');
|
24
|
|
25
|
/**
|
26
|
* Constructor
|
27
|
* @param $conn The database connection
|
28
|
*/
|
29
|
function Postgres74($conn) {
|
30
|
$this->Postgres73($conn);
|
31
|
}
|
32
|
|
33
|
// Help functions
|
34
|
|
35
|
function getHelpPages() {
|
36
|
include_once('./help/PostgresDoc74.php');
|
37
|
return $this->help_page;
|
38
|
}
|
39
|
|
40
|
// Database functions
|
41
|
|
42
|
/**
|
43
|
* Alters a database
|
44
|
* the multiple return vals are for postgres 8+ which support more functionality in alter database
|
45
|
* @param $dbName The name of the database
|
46
|
* @param $newName new name for the database
|
47
|
* @param $newOwner The new owner for the database
|
48
|
* @return 0 success
|
49
|
* @return -1 transaction error
|
50
|
* @return -2 owner error
|
51
|
* @return -3 rename error
|
52
|
*/
|
53
|
function alterDatabase($dbName, $newName, $newOwner = '')
|
54
|
{
|
55
|
//ignore $newowner, not supported pre 8.0
|
56
|
$this->clean($dbName);
|
57
|
$this->clean($newName);
|
58
|
|
59
|
$status = $this->alterDatabaseRename($dbName, $newName);
|
60
|
if ($status != 0) return -3;
|
61
|
else return 0;
|
62
|
}
|
63
|
|
64
|
/**
|
65
|
* Renames a database, note that this operation cannot be
|
66
|
* performed on a database that is currently being connected to
|
67
|
* @param string $oldName name of database to rename
|
68
|
* @param string $newName new name of database
|
69
|
* @return int 0 on success
|
70
|
*/
|
71
|
function alterDatabaseRename($oldName, $newName) {
|
72
|
$this->clean($oldName);
|
73
|
$this->clean($newName);
|
74
|
|
75
|
if ($oldName != $newName) {
|
76
|
$sql = "ALTER DATABASE \"{$oldName}\" RENAME TO \"{$newName}\"";
|
77
|
return $this->execute($sql);
|
78
|
}
|
79
|
else //just return success, we're not going to do anything
|
80
|
return 0;
|
81
|
}
|
82
|
|
83
|
// Table functions
|
84
|
|
85
|
/**
|
86
|
* Get the fields for uniquely identifying a row in a table
|
87
|
* @param $table The table for which to retrieve the identifier
|
88
|
* @return An array mapping attribute number to attribute name, empty for no identifiers
|
89
|
* @return -1 error
|
90
|
*/
|
91
|
function getRowIdentifier($table) {
|
92
|
$oldtable = $table;
|
93
|
$this->clean($table);
|
94
|
|
95
|
$status = $this->beginTransaction();
|
96
|
if ($status != 0) return -1;
|
97
|
|
98
|
// Get the first primary or unique index (sorting primary keys first) that
|
99
|
// is NOT a partial index.
|
100
|
$sql = "SELECT indrelid, indkey FROM pg_catalog.pg_index WHERE indisunique AND
|
101
|
indrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' AND
|
102
|
relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}'))
|
103
|
AND indpred IS NULL AND indexprs IS NULL ORDER BY indisprimary DESC LIMIT 1";
|
104
|
$rs = $this->selectSet($sql);
|
105
|
|
106
|
// If none, check for an OID column. Even though OIDs can be duplicated, the edit and delete row
|
107
|
// functions check that they're only modiying a single row. Otherwise, return empty array.
|
108
|
if ($rs->recordCount() == 0) {
|
109
|
// Check for OID column
|
110
|
$temp = array();
|
111
|
if ($this->hasObjectID($table)) {
|
112
|
$temp = array('oid');
|
113
|
}
|
114
|
$this->endTransaction();
|
115
|
return $temp;
|
116
|
}
|
117
|
// Otherwise find the names of the keys
|
118
|
else {
|
119
|
$attnames = $this->getAttributeNames($oldtable, explode(' ', $rs->f['indkey']));
|
120
|
if (!is_array($attnames)) {
|
121
|
$this->rollbackTransaction();
|
122
|
return -1;
|
123
|
}
|
124
|
else {
|
125
|
$this->endTransaction();
|
126
|
return $attnames;
|
127
|
}
|
128
|
}
|
129
|
}
|
130
|
|
131
|
/**
|
132
|
* Sets up the data object for a dump. eg. Starts the appropriate
|
133
|
* transaction, sets variables, etc.
|
134
|
* @return 0 success
|
135
|
*/
|
136
|
function beginDump() {
|
137
|
$status = parent::beginDump();
|
138
|
if ($status != 0) return $status;
|
139
|
|
140
|
// Set extra_float_digits to 2
|
141
|
$sql = "SET extra_float_digits TO 2";
|
142
|
$status = $this->execute($sql);
|
143
|
if ($status != 0) {
|
144
|
$this->rollbackTransaction();
|
145
|
return -1;
|
146
|
}
|
147
|
}
|
148
|
|
149
|
// Group functions
|
150
|
|
151
|
/**
|
152
|
* Return users in a specific group
|
153
|
* @param $groname The name of the group
|
154
|
* @return All users in the group
|
155
|
*/
|
156
|
function getGroup($groname) {
|
157
|
$this->clean($groname);
|
158
|
|
159
|
$sql = "SELECT s.usename FROM pg_catalog.pg_user s, pg_catalog.pg_group g
|
160
|
WHERE g.groname='{$groname}' AND s.usesysid = ANY (g.grolist)
|
161
|
ORDER BY s.usename";
|
162
|
|
163
|
return $this->selectSet($sql);
|
164
|
}
|
165
|
|
166
|
// Schema functions
|
167
|
|
168
|
/**
|
169
|
* Return all schemas in the current database. This differs from the version
|
170
|
* in 7.3 only in that it considers the information_schema to be a system schema.
|
171
|
* @return All schemas, sorted alphabetically
|
172
|
*/
|
173
|
function getSchemas() {
|
174
|
global $conf, $slony;
|
175
|
|
176
|
if (!$conf['show_system']) {
|
177
|
$where = "WHERE nspname NOT LIKE 'pg\\\\_%' AND nspname != 'information_schema'";
|
178
|
if (isset($slony) && $slony->isEnabled()) {
|
179
|
$temp = $slony->slony_schema;
|
180
|
$this->clean($temp);
|
181
|
$where .= " AND nspname != '{$temp}'";
|
182
|
}
|
183
|
|
184
|
}
|
185
|
else $where = "WHERE nspname !~ '^pg_t(emp_[0-9]+|oast)$'";
|
186
|
$sql = "SELECT pn.nspname, pu.usename AS nspowner, pg_catalog.obj_description(pn.oid, 'pg_namespace') AS nspcomment
|
187
|
FROM pg_catalog.pg_namespace pn LEFT JOIN pg_catalog.pg_user pu ON (pn.nspowner = pu.usesysid)
|
188
|
{$where} ORDER BY nspname";
|
189
|
|
190
|
return $this->selectSet($sql);
|
191
|
}
|
192
|
|
193
|
// Index functions
|
194
|
|
195
|
/**
|
196
|
* Grabs a list of indexes for a table
|
197
|
* @param $table The name of a table whose indexes to retrieve
|
198
|
* @param $unique Only get unique/pk indexes
|
199
|
* @return A recordset
|
200
|
*/
|
201
|
function getIndexes($table = '', $unique = false) {
|
202
|
$this->clean($table);
|
203
|
|
204
|
$sql = "SELECT c2.relname AS indname, i.indisprimary, i.indisunique, i.indisclustered,
|
205
|
pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS inddef
|
206
|
FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
|
207
|
WHERE c.relname = '{$table}' AND pg_catalog.pg_table_is_visible(c.oid)
|
208
|
AND c.oid = i.indrelid AND i.indexrelid = c2.oid
|
209
|
";
|
210
|
if ($unique) $sql .= " AND i.indisunique ";
|
211
|
$sql .= " ORDER BY c2.relname";
|
212
|
|
213
|
return $this->selectSet($sql);
|
214
|
}
|
215
|
|
216
|
// View functions
|
217
|
|
218
|
/**
|
219
|
* Returns all details for a particular view
|
220
|
* @param $view The name of the view to retrieve
|
221
|
* @return View info
|
222
|
*/
|
223
|
function getView($view) {
|
224
|
$this->clean($view);
|
225
|
|
226
|
$sql = "SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
|
227
|
pg_catalog.pg_get_viewdef(c.oid, true) AS vwdefinition, pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment
|
228
|
FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
|
229
|
WHERE (c.relname = '$view')
|
230
|
AND n.nspname='{$this->_schema}'";
|
231
|
|
232
|
return $this->selectSet($sql);
|
233
|
}
|
234
|
|
235
|
// Trigger functions
|
236
|
|
237
|
/**
|
238
|
* Grabs a list of triggers on a table
|
239
|
* @param $table The name of a table whose triggers to retrieve
|
240
|
* @return A recordset
|
241
|
*/
|
242
|
function getTriggers($table = '') {
|
243
|
$this->clean($table);
|
244
|
|
245
|
$sql = "SELECT t.tgname, pg_catalog.pg_get_triggerdef(t.oid) AS tgdef
|
246
|
FROM pg_catalog.pg_trigger t
|
247
|
WHERE t.tgrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
|
248
|
AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}'))
|
249
|
AND (NOT tgisconstraint OR NOT EXISTS
|
250
|
(SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
|
251
|
ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
|
252
|
WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))";
|
253
|
|
254
|
return $this->selectSet($sql);
|
255
|
}
|
256
|
|
257
|
// Constraint functions
|
258
|
|
259
|
/**
|
260
|
* Returns a list of all constraints on a table
|
261
|
* @param $table The table to find rules for
|
262
|
* @return A recordset
|
263
|
*/
|
264
|
function getConstraints($table) {
|
265
|
$this->clean($table);
|
266
|
|
267
|
// This SQL is greatly complicated by the need to retrieve
|
268
|
// index clustering information for primary and unique constraints
|
269
|
$sql = "SELECT
|
270
|
pc.conname,
|
271
|
pg_catalog.pg_get_constraintdef(pc.oid, true) AS consrc,
|
272
|
pc.contype,
|
273
|
CASE WHEN pc.contype='u' OR pc.contype='p' THEN (
|
274
|
SELECT
|
275
|
indisclustered
|
276
|
FROM
|
277
|
pg_catalog.pg_depend pd,
|
278
|
pg_catalog.pg_class pl,
|
279
|
pg_catalog.pg_index pi
|
280
|
WHERE
|
281
|
pd.refclassid=pc.tableoid
|
282
|
AND pd.refobjid=pc.oid
|
283
|
AND pd.objid=pl.oid
|
284
|
AND pl.oid=pi.indexrelid
|
285
|
) ELSE
|
286
|
NULL
|
287
|
END AS indisclustered
|
288
|
FROM
|
289
|
pg_catalog.pg_constraint pc
|
290
|
WHERE
|
291
|
pc.conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
|
292
|
AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
|
293
|
WHERE nspname='{$this->_schema}'))
|
294
|
ORDER BY
|
295
|
1
|
296
|
";
|
297
|
|
298
|
return $this->selectSet($sql);
|
299
|
}
|
300
|
|
301
|
// Administration functions
|
302
|
|
303
|
/**
|
304
|
* Recluster a table or all the tables in the current database
|
305
|
* @param $table (optional) The table to recluster
|
306
|
*/
|
307
|
function recluster($table = '') {
|
308
|
if ($table != '') {
|
309
|
$this->fieldClean($table);
|
310
|
$sql = "CLUSTER \"{$table}\"";
|
311
|
}
|
312
|
else
|
313
|
$sql = "CLUSTER";
|
314
|
|
315
|
return $this->execute($sql);
|
316
|
}
|
317
|
|
318
|
// Domain functions
|
319
|
|
320
|
/**
|
321
|
* Get domain constraints
|
322
|
* @param $domain The name of the domain whose constraints to fetch
|
323
|
* @return A recordset
|
324
|
*/
|
325
|
function getDomainConstraints($domain) {
|
326
|
$this->clean($domain);
|
327
|
|
328
|
$sql = "
|
329
|
SELECT
|
330
|
conname,
|
331
|
contype,
|
332
|
pg_catalog.pg_get_constraintdef(oid, true) AS consrc
|
333
|
FROM
|
334
|
pg_catalog.pg_constraint
|
335
|
WHERE
|
336
|
contypid = (SELECT oid FROM pg_catalog.pg_type
|
337
|
WHERE typname='{$domain}'
|
338
|
AND typnamespace = (SELECT oid FROM pg_catalog.pg_namespace
|
339
|
WHERE nspname = '{$this->_schema}'))
|
340
|
ORDER BY
|
341
|
conname";
|
342
|
|
343
|
return $this->selectSet($sql);
|
344
|
}
|
345
|
|
346
|
/**
|
347
|
* Drops a domain constraint
|
348
|
* @param $domain The domain from which to remove the constraint
|
349
|
* @param $constraint The constraint to remove
|
350
|
* @param $cascade True to cascade, false otherwise
|
351
|
* @return 0 success
|
352
|
*/
|
353
|
function dropDomainConstraint($domain, $constraint, $cascade) {
|
354
|
$this->fieldClean($domain);
|
355
|
$this->fieldClean($constraint);
|
356
|
|
357
|
$sql = "ALTER DOMAIN \"{$domain}\" DROP CONSTRAINT \"{$constraint}\"";
|
358
|
if ($cascade) $sql .= " CASCADE";
|
359
|
|
360
|
return $this->execute($sql);
|
361
|
}
|
362
|
|
363
|
/**
|
364
|
* Adds a check constraint to a domain
|
365
|
* @param $domain The domain to which to add the check
|
366
|
* @param $definition The definition of the check
|
367
|
* @param $name (optional) The name to give the check, otherwise default name is assigned
|
368
|
* @return 0 success
|
369
|
*/
|
370
|
function addDomainCheckConstraint($domain, $definition, $name = '') {
|
371
|
$this->fieldClean($domain);
|
372
|
$this->fieldClean($name);
|
373
|
|
374
|
$sql = "ALTER DOMAIN \"{$domain}\" ADD ";
|
375
|
if ($name != '') $sql .= "CONSTRAINT \"{$name}\" ";
|
376
|
$sql .= "CHECK ({$definition})";
|
377
|
|
378
|
return $this->execute($sql);
|
379
|
}
|
380
|
|
381
|
/**
|
382
|
* Alters a domain
|
383
|
* @param $domain The domain to alter
|
384
|
* @param $domdefault The domain default
|
385
|
* @param $domnotnull True for NOT NULL, false otherwise
|
386
|
* @param $domowner The domain owner
|
387
|
* @return 0 success
|
388
|
* @return -1 transaction error
|
389
|
* @return -2 default error
|
390
|
* @return -3 not null error
|
391
|
* @return -4 owner error
|
392
|
*/
|
393
|
function alterDomain($domain, $domdefault, $domnotnull, $domowner) {
|
394
|
$this->fieldClean($domain);
|
395
|
$this->fieldClean($domowner);
|
396
|
|
397
|
$status = $this->beginTransaction();
|
398
|
if ($status != 0) {
|
399
|
$this->rollbackTransaction();
|
400
|
return -1;
|
401
|
}
|
402
|
|
403
|
// Default
|
404
|
if ($domdefault == '')
|
405
|
$sql = "ALTER DOMAIN \"{$domain}\" DROP DEFAULT";
|
406
|
else
|
407
|
$sql = "ALTER DOMAIN \"{$domain}\" SET DEFAULT {$domdefault}";
|
408
|
|
409
|
$status = $this->execute($sql);
|
410
|
if ($status != 0) {
|
411
|
$this->rollbackTransaction();
|
412
|
return -2;
|
413
|
}
|
414
|
|
415
|
// NOT NULL
|
416
|
if ($domnotnull)
|
417
|
$sql = "ALTER DOMAIN \"{$domain}\" SET NOT NULL";
|
418
|
else
|
419
|
$sql = "ALTER DOMAIN \"{$domain}\" DROP NOT NULL";
|
420
|
|
421
|
$status = $this->execute($sql);
|
422
|
if ($status != 0) {
|
423
|
$this->rollbackTransaction();
|
424
|
return -3;
|
425
|
}
|
426
|
|
427
|
// Owner
|
428
|
$sql = "ALTER DOMAIN \"{$domain}\" OWNER TO \"{$domowner}\"";
|
429
|
|
430
|
$status = $this->execute($sql);
|
431
|
if ($status != 0) {
|
432
|
$this->rollbackTransaction();
|
433
|
return -4;
|
434
|
}
|
435
|
|
436
|
return $this->endTransaction();
|
437
|
}
|
438
|
|
439
|
// User functions
|
440
|
|
441
|
/**
|
442
|
* Renames a user
|
443
|
* @param $username The username of the user to rename
|
444
|
* @param $newname The new name of the user
|
445
|
* @return 0 success
|
446
|
*/
|
447
|
function renameUser($username, $newname){
|
448
|
$this->fieldClean($username);
|
449
|
$this->fieldClean($newname);
|
450
|
|
451
|
$sql = "ALTER USER \"{$username}\" RENAME TO \"{$newname}\"";
|
452
|
|
453
|
return $this->execute($sql);
|
454
|
}
|
455
|
|
456
|
/**
|
457
|
* Adjusts a user's info and renames the user
|
458
|
* @param $username The username of the user to modify
|
459
|
* @param $password A new password for the user
|
460
|
* @param $createdb boolean Whether or not the user can create databases
|
461
|
* @param $createuser boolean Whether or not the user can create other users
|
462
|
* @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire.
|
463
|
* @param $newname The new name of the user
|
464
|
* @return 0 success
|
465
|
* @return -1 transaction error
|
466
|
* @return -2 set user attributes error
|
467
|
* @return -3 rename error
|
468
|
*/
|
469
|
function setRenameUser($username, $password, $createdb, $createuser, $expiry, $newname) {
|
470
|
$status = $this->beginTransaction();
|
471
|
if ($status != 0) return -1;
|
472
|
|
473
|
$status = $this->setUser($username, $password, $createdb, $createuser, $expiry);
|
474
|
if ($status != 0) {
|
475
|
$this->rollbackTransaction();
|
476
|
return -2;
|
477
|
}
|
478
|
|
479
|
if ($username != $newname){
|
480
|
$status = $this->renameUser($username, $newname);
|
481
|
if ($status != 0) {
|
482
|
$this->rollbackTransaction();
|
483
|
return -3;
|
484
|
}
|
485
|
}
|
486
|
|
487
|
return $this->endTransaction();
|
488
|
}
|
489
|
|
490
|
// Function functions
|
491
|
|
492
|
/**
|
493
|
* Updates (replaces) a function.
|
494
|
* @param $function_oid The OID of the function
|
495
|
* @param $funcname The name of the function to create
|
496
|
* @param $newname The new name for the function
|
497
|
* @param $args The array of argument types
|
498
|
* @param $returns The return type
|
499
|
* @param $definition The definition for the new function
|
500
|
* @param $language The language the function is written for
|
501
|
* @param $flags An array of optional flags
|
502
|
* @param $setof True if returns a set, false otherwise
|
503
|
* @param $comment The comment on the function
|
504
|
* @return 0 success
|
505
|
* @return -1 transaction error
|
506
|
* @return -3 create function error
|
507
|
* @return -4 comment error
|
508
|
* @return -5 rename function error
|
509
|
*/
|
510
|
function setFunction($function_oid, $funcname, $newname, $args, $returns, $definition, $language, $flags, $setof, $comment) {
|
511
|
// Begin a transaction
|
512
|
$status = $this->beginTransaction();
|
513
|
if ($status != 0) {
|
514
|
$this->rollbackTransaction();
|
515
|
return -1;
|
516
|
}
|
517
|
|
518
|
// Replace the existing function
|
519
|
$status = $this->createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, true);
|
520
|
if ($status != 0) {
|
521
|
$this->rollbackTransaction();
|
522
|
return -3;
|
523
|
}
|
524
|
|
525
|
// Comment on the function
|
526
|
$this->fieldClean($funcname);
|
527
|
$this->clean($comment);
|
528
|
$status = $this->setComment('FUNCTION', "\"{$funcname}\"({$args})", null, $comment);
|
529
|
if ($status != 0) {
|
530
|
$this->rollbackTransaction();
|
531
|
return -4;
|
532
|
}
|
533
|
|
534
|
// Rename the function, if necessary
|
535
|
$this->fieldClean($newname);
|
536
|
if ($funcname != $newname) {
|
537
|
$sql = "ALTER FUNCTION \"{$funcname}\"({$args}) RENAME TO \"{$newname}\"";
|
538
|
$status = $this->execute($sql);
|
539
|
if ($status != 0) {
|
540
|
$this->rollbackTransaction();
|
541
|
return -5;
|
542
|
}
|
543
|
}
|
544
|
|
545
|
return $this->endTransaction();
|
546
|
}
|
547
|
|
548
|
// Capabilities
|
549
|
function hasAlterDatabaseRename() { return true; }
|
550
|
function hasGrantOption() { return true; }
|
551
|
function hasDomainConstraints() { return true; }
|
552
|
function hasUserRename() { return true; }
|
553
|
function hasRecluster() { return true; }
|
554
|
function hasReadOnlyQueries() { return true; }
|
555
|
}
|
556
|
|
557
|
?>
|